diff --git a/CMakeLists.txt b/CMakeLists.txt index 538dbbe..be17258 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,65 @@ qt_standard_project_setup() add_subdirectory(third_party/KodoTerm) +if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/third_party/FreeRDP/CMakeLists.txt") + message(FATAL_ERROR "Vendored FreeRDP source is missing at third_party/FreeRDP") +endif() + +# Pin FreeRDP build to the vendored source tree so headers/libs are always from the same revision. +set(FREERDP_UNIFIED_BUILD ON CACHE BOOL "" FORCE) +set(WITH_MANPAGES OFF CACHE BOOL "" FORCE) +set(WITH_SAMPLE OFF CACHE BOOL "" FORCE) +set(BUILD_TESTING OFF CACHE BOOL "" FORCE) +set(BUILD_TESTING_INTERNAL OFF CACHE BOOL "" FORCE) +set(WITH_CLIENT_COMMON ON CACHE BOOL "" FORCE) +set(WITH_CLIENT OFF CACHE BOOL "" FORCE) +set(WITH_CLIENT_SDL OFF CACHE BOOL "" FORCE) +set(WITH_CLIENT_INTERFACE OFF CACHE BOOL "" FORCE) +set(WITH_SERVER OFF CACHE BOOL "" FORCE) +set(WITH_CHANNELS ON CACHE BOOL "" FORCE) +set(WITH_CLIENT_CHANNELS ON CACHE BOOL "" FORCE) +set(WITH_FUSE OFF CACHE BOOL "" FORCE) +set(CHANNEL_DRDYNVC ON CACHE BOOL "" FORCE) +set(CHANNEL_DRDYNVC_CLIENT ON CACHE BOOL "" FORCE) +set(CHANNEL_DISP ON CACHE BOOL "" FORCE) +set(CHANNEL_DISP_CLIENT ON CACHE BOOL "" FORCE) +set(CHANNEL_AINPUT OFF CACHE BOOL "" FORCE) +set(CHANNEL_AUDIN OFF CACHE BOOL "" FORCE) +set(CHANNEL_CLIPRDR OFF CACHE BOOL "" FORCE) +set(CHANNEL_DRIVE OFF CACHE BOOL "" FORCE) +set(CHANNEL_ECHO OFF CACHE BOOL "" FORCE) +set(CHANNEL_ENCOMSP OFF CACHE BOOL "" FORCE) +set(CHANNEL_GEOMETRY OFF CACHE BOOL "" FORCE) +set(CHANNEL_LOCATION OFF CACHE BOOL "" FORCE) +set(CHANNEL_PARALLEL OFF CACHE BOOL "" FORCE) +set(CHANNEL_PRINTER OFF CACHE BOOL "" FORCE) +set(CHANNEL_RAIL OFF CACHE BOOL "" FORCE) +set(CHANNEL_RDPDR OFF CACHE BOOL "" FORCE) +set(CHANNEL_RDPEAR OFF CACHE BOOL "" FORCE) +set(CHANNEL_RDPECAM OFF CACHE BOOL "" FORCE) +set(CHANNEL_RDPEI OFF CACHE BOOL "" FORCE) +set(CHANNEL_RDPEMSC OFF CACHE BOOL "" FORCE) +set(CHANNEL_RDPGFX OFF CACHE BOOL "" FORCE) +set(CHANNEL_RDPSND OFF CACHE BOOL "" FORCE) +set(CHANNEL_REMDESK OFF CACHE BOOL "" FORCE) +set(CHANNEL_SERIAL OFF CACHE BOOL "" FORCE) +set(CHANNEL_SMARTCARD OFF CACHE BOOL "" FORCE) +set(CHANNEL_SSHAGENT OFF CACHE BOOL "" FORCE) +set(CHANNEL_TELEMETRY OFF CACHE BOOL "" FORCE) +set(CHANNEL_URBDRC OFF CACHE BOOL "" FORCE) +set(CHANNEL_VIDEO OFF CACHE BOOL "" FORCE) +set(WITH_FFMPEG OFF CACHE BOOL "" FORCE) +set(WITH_DSP_FFMPEG OFF CACHE BOOL "" FORCE) +set(WITH_VIDEO_FFMPEG OFF CACHE BOOL "" FORCE) +set(WITH_CAIRO OFF CACHE BOOL "" FORCE) +set(WITH_SWSCALE OFF CACHE BOOL "" FORCE) +set(WITH_JPEG OFF CACHE BOOL "" FORCE) +set(WITH_KRB5 OFF CACHE BOOL "" FORCE) +set(WITH_UNICODE_BUILTIN ON CACHE BOOL "" FORCE) +set(WITH_WINPR_TOOLS OFF CACHE BOOL "" FORCE) + +add_subdirectory(third_party/FreeRDP EXCLUDE_FROM_ALL) + add_executable(orbithub src/main.cpp src/profile_dialog.cpp @@ -29,10 +88,14 @@ add_executable(orbithub src/session_backend_factory.h src/session_tab.cpp src/session_tab.h + src/rdp_display_widget.cpp + src/rdp_display_widget.h src/terminal_view.cpp src/terminal_view.h src/session_window.cpp src/session_window.h + src/rdp_session_backend.cpp + src/rdp_session_backend.h src/ssh_session_backend.cpp src/ssh_session_backend.h src/unsupported_session_backend.cpp @@ -41,5 +104,20 @@ add_executable(orbithub target_link_libraries(orbithub PRIVATE Qt6::Widgets Qt6::Sql) target_link_libraries(orbithub PRIVATE KodoTerm::KodoTerm) +if(TARGET freerdp AND TARGET winpr) + target_compile_definitions(orbithub PRIVATE ORBITHUB_HAS_FREERDP) + target_include_directories(orbithub PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/third_party/FreeRDP/include + ${CMAKE_CURRENT_SOURCE_DIR}/third_party/FreeRDP/winpr/include + ${CMAKE_CURRENT_BINARY_DIR}/third_party/FreeRDP/include + ${CMAKE_CURRENT_BINARY_DIR}/third_party/FreeRDP/winpr/include + ) + target_link_libraries(orbithub PRIVATE freerdp winpr) + if(TARGET freerdp-client) + target_link_libraries(orbithub PRIVATE freerdp-client) + endif() +else() + message(FATAL_ERROR "Vendored FreeRDP targets were not produced as expected.") +endif() install(TARGETS orbithub RUNTIME DESTINATION bin) diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index e720139..f2b87bb 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -81,14 +81,26 @@ Git: ## Milestone 5 - RDP Fully Working -Status: Planned +Status: Completed -Planned Scope: -- Replace current unsupported RDP path with complete RDP implementation -- Deliver usable in-app RDP session behavior aligned to SSH UX -- Implement RDP connect/disconnect/reconnect lifecycle handling -- Extend profile/session connect options needed by RDP -- Standardize event log and error mapping behavior with SSH +Delivered: +- Added `RdpSessionBackend` and wired protocol selection so `RDP` no longer routes to unsupported backend +- Pivoted RDP design to embedded-only integration (no external RDP process launches) +- Implemented embedded FreeRDP client thread with connect/disconnect lifecycle and event-loop handling +- Added in-window `RdpDisplayWidget` rendering surface with frame updates from FreeRDP GDI +- Wired direct keyboard/mouse input from the embedded RDP surface to the backend +- Added RDP connect-time password prompt flow and settings wiring (host/port/user/password, desktop size) +- Added explicit profile `Domain` support for RDP auth (with `DOMAIN\username` fallback parsing) +- Updated session tab/context-menu behavior so terminal-only actions are hidden on RDP tabs +- Implemented dynamic in-session RDP resolution renegotiation from viewport resize events +- Enabled minimal FreeRDP client-channel build (`drdynvc` + `disp`) and channel loading for runtime resize support +- Added RDP profile-level security mode and performance profile options, wired into FreeRDP connection settings +- Hardened RDP lifecycle handling for disconnect/reconnect/abort flows to avoid false failure states on user-initiated stops +- Expanded RDP error/disconnect diagnostics with richer FreeRDP code mapping and raw disconnect detail events +- Pulled FreeRDP source for integration planning and API review + +Git: +- Tag: pending (awaiting explicit approval before tagging/pushing) ## Milestone 6 - VNC Fully Working diff --git a/src/profile_dialog.cpp b/src/profile_dialog.cpp index 438020d..5704f51 100644 --- a/src/profile_dialog.cpp +++ b/src/profile_dialog.cpp @@ -32,11 +32,14 @@ ProfileDialog::ProfileDialog(QWidget* parent) m_hostInput(new QLineEdit(this)), m_portInput(new QSpinBox(this)), m_usernameInput(new QLineEdit(this)), + m_domainInput(new QLineEdit(this)), m_protocolInput(new QComboBox(this)), m_authModeInput(new QComboBox(this)), m_privateKeyPathInput(new QLineEdit(this)), m_browsePrivateKeyButton(new QPushButton(QStringLiteral("Browse"), this)), - m_knownHostsPolicyInput(new QComboBox(this)) + m_knownHostsPolicyInput(new QComboBox(this)), + m_rdpSecurityModeInput(new QComboBox(this)), + m_rdpPerformanceProfileInput(new QComboBox(this)) { resize(520, 340); @@ -48,11 +51,18 @@ ProfileDialog::ProfileDialog(QWidget* parent) m_portInput->setRange(1, 65535); m_portInput->setValue(22); m_usernameInput->setPlaceholderText(QStringLiteral("deploy")); + m_domainInput->setPlaceholderText(QStringLiteral("CONTOSO")); m_protocolInput->addItems({QStringLiteral("SSH"), QStringLiteral("RDP"), QStringLiteral("VNC")}); m_authModeInput->addItems({QStringLiteral("Password"), QStringLiteral("Private Key")}); m_knownHostsPolicyInput->addItems( {QStringLiteral("Ask"), QStringLiteral("Strict"), QStringLiteral("Accept New"), QStringLiteral("Ignore")}); + m_rdpSecurityModeInput->addItems( + {QStringLiteral("Negotiate"), QStringLiteral("NLA"), QStringLiteral("TLS"), QStringLiteral("RDP")}); + m_rdpPerformanceProfileInput->addItems({QStringLiteral("Balanced"), + QStringLiteral("Best Quality"), + QStringLiteral("Best Performance"), + QStringLiteral("Auto Detect")}); m_privateKeyPathInput->setPlaceholderText(QStringLiteral("/home/user/.ssh/id_ed25519")); @@ -80,6 +90,10 @@ ProfileDialog::ProfileDialog(QWidget* parent) this, [this](const QString& protocol) { m_portInput->setValue(standardPortForProtocol(protocol)); + if (protocol != QStringLiteral("SSH")) { + const QSignalBlocker blocker(m_authModeInput); + m_authModeInput->setCurrentText(QStringLiteral("Password")); + } refreshAuthFields(); }); @@ -92,10 +106,13 @@ ProfileDialog::ProfileDialog(QWidget* parent) form->addRow(QStringLiteral("Host"), m_hostInput); form->addRow(QStringLiteral("Port"), m_portInput); form->addRow(QStringLiteral("Username"), m_usernameInput); + form->addRow(QStringLiteral("Domain"), m_domainInput); form->addRow(QStringLiteral("Protocol"), m_protocolInput); form->addRow(QStringLiteral("Auth Mode"), m_authModeInput); form->addRow(QStringLiteral("Private Key"), privateKeyRow); form->addRow(QStringLiteral("Known Hosts"), m_knownHostsPolicyInput); + form->addRow(QStringLiteral("RDP Security"), m_rdpSecurityModeInput); + form->addRow(QStringLiteral("RDP Performance"), m_rdpPerformanceProfileInput); auto* note = new QLabel( QStringLiteral("Passwords are requested at connect time and are not stored."), @@ -124,6 +141,7 @@ void ProfileDialog::setProfile(const Profile& profile) m_hostInput->setText(profile.host); m_portInput->setValue(profile.port > 0 ? profile.port : 22); m_usernameInput->setText(profile.username); + m_domainInput->setText(profile.domain); m_privateKeyPathInput->setText(profile.privateKeyPath); const int protocolIndex = m_protocolInput->findText(profile.protocol); @@ -138,6 +156,12 @@ void ProfileDialog::setProfile(const Profile& profile) const int knownHostsIndex = m_knownHostsPolicyInput->findText(profile.knownHostsPolicy); m_knownHostsPolicyInput->setCurrentIndex(knownHostsIndex >= 0 ? knownHostsIndex : 0); + const int securityModeIndex = m_rdpSecurityModeInput->findText(profile.rdpSecurityMode); + m_rdpSecurityModeInput->setCurrentIndex(securityModeIndex >= 0 ? securityModeIndex : 0); + const int performanceProfileIndex = + m_rdpPerformanceProfileInput->findText(profile.rdpPerformanceProfile); + m_rdpPerformanceProfileInput->setCurrentIndex(performanceProfileIndex >= 0 ? performanceProfileIndex + : 0); refreshAuthFields(); } @@ -150,10 +174,13 @@ Profile ProfileDialog::profile() const profile.host = m_hostInput->text().trimmed(); profile.port = m_portInput->value(); profile.username = m_usernameInput->text().trimmed(); + profile.domain = m_domainInput->text().trimmed(); profile.protocol = m_protocolInput->currentText(); profile.authMode = m_authModeInput->currentText(); profile.privateKeyPath = m_privateKeyPathInput->text().trimmed(); profile.knownHostsPolicy = m_knownHostsPolicyInput->currentText(); + profile.rdpSecurityMode = m_rdpSecurityModeInput->currentText(); + profile.rdpPerformanceProfile = m_rdpPerformanceProfileInput->currentText(); return profile; } @@ -173,11 +200,12 @@ void ProfileDialog::accept() return; } - if (m_protocolInput->currentText() == QStringLiteral("SSH") + const QString protocol = m_protocolInput->currentText(); + if ((protocol == QStringLiteral("SSH") || protocol == QStringLiteral("RDP")) && m_usernameInput->text().trimmed().isEmpty()) { QMessageBox::warning(this, QStringLiteral("Validation Error"), - QStringLiteral("Username is required for SSH profiles.")); + QStringLiteral("Username is required for %1 profiles.").arg(protocol)); return; } @@ -187,10 +215,14 @@ void ProfileDialog::accept() void ProfileDialog::refreshAuthFields() { const bool isSsh = m_protocolInput->currentText() == QStringLiteral("SSH"); + const bool isRdp = m_protocolInput->currentText() == QStringLiteral("RDP"); const bool isPrivateKey = m_authModeInput->currentText() == QStringLiteral("Private Key"); m_authModeInput->setEnabled(isSsh); m_privateKeyPathInput->setEnabled(isSsh && isPrivateKey); m_browsePrivateKeyButton->setEnabled(isSsh && isPrivateKey); m_knownHostsPolicyInput->setEnabled(isSsh); + m_domainInput->setEnabled(isRdp); + m_rdpSecurityModeInput->setEnabled(isRdp); + m_rdpPerformanceProfileInput->setEnabled(isRdp); } diff --git a/src/profile_dialog.h b/src/profile_dialog.h index 1496ace..dfcd669 100644 --- a/src/profile_dialog.h +++ b/src/profile_dialog.h @@ -29,11 +29,14 @@ private: QLineEdit* m_hostInput; QSpinBox* m_portInput; QLineEdit* m_usernameInput; + QLineEdit* m_domainInput; QComboBox* m_protocolInput; QComboBox* m_authModeInput; QLineEdit* m_privateKeyPathInput; QPushButton* m_browsePrivateKeyButton; QComboBox* m_knownHostsPolicyInput; + QComboBox* m_rdpSecurityModeInput; + QComboBox* m_rdpPerformanceProfileInput; void refreshAuthFields(); }; diff --git a/src/profile_repository.cpp b/src/profile_repository.cpp index 6680541..c936a9b 100644 --- a/src/profile_repository.cpp +++ b/src/profile_repository.cpp @@ -22,18 +22,51 @@ QString buildDatabasePath() return dataDir.filePath(QStringLiteral("orbithub_profiles.sqlite")); } +QString normalizedRdpSecurityMode(const QString& value) +{ + const QString mode = value.trimmed(); + if (mode.compare(QStringLiteral("NLA"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("NLA"); + } + if (mode.compare(QStringLiteral("TLS"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("TLS"); + } + if (mode.compare(QStringLiteral("RDP"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("RDP"); + } + return QStringLiteral("Negotiate"); +} + +QString normalizedRdpPerformanceProfile(const QString& value) +{ + const QString profile = value.trimmed(); + if (profile.compare(QStringLiteral("Best Quality"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("Best Quality"); + } + if (profile.compare(QStringLiteral("Best Performance"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("Best Performance"); + } + if (profile.compare(QStringLiteral("Auto Detect"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("Auto Detect"); + } + return QStringLiteral("Balanced"); +} + void bindProfileFields(QSqlQuery& query, const Profile& profile) { query.addBindValue(profile.name.trimmed()); query.addBindValue(profile.host.trimmed()); query.addBindValue(profile.port); query.addBindValue(profile.username.trimmed()); + query.addBindValue(profile.domain.trimmed()); query.addBindValue(profile.protocol.trimmed()); query.addBindValue(profile.authMode.trimmed()); query.addBindValue(profile.privateKeyPath.trimmed()); query.addBindValue(profile.knownHostsPolicy.trimmed().isEmpty() ? QStringLiteral("Ask") : profile.knownHostsPolicy.trimmed()); + query.addBindValue(normalizedRdpSecurityMode(profile.rdpSecurityMode)); + query.addBindValue(normalizedRdpPerformanceProfile(profile.rdpPerformanceProfile)); } Profile profileFromQuery(const QSqlQuery& query) @@ -44,13 +77,16 @@ Profile profileFromQuery(const QSqlQuery& query) profile.host = query.value(2).toString(); profile.port = query.value(3).toInt(); profile.username = query.value(4).toString(); - profile.protocol = query.value(5).toString(); - profile.authMode = query.value(6).toString(); - profile.privateKeyPath = query.value(7).toString(); - profile.knownHostsPolicy = query.value(8).toString(); + profile.domain = query.value(5).toString(); + profile.protocol = query.value(6).toString(); + profile.authMode = query.value(7).toString(); + profile.privateKeyPath = query.value(8).toString(); + profile.knownHostsPolicy = query.value(9).toString(); if (profile.knownHostsPolicy.isEmpty()) { profile.knownHostsPolicy = QStringLiteral("Ask"); } + profile.rdpSecurityMode = normalizedRdpSecurityMode(query.value(10).toString()); + profile.rdpPerformanceProfile = normalizedRdpPerformanceProfile(query.value(11).toString()); return profile; } @@ -102,12 +138,12 @@ std::vector ProfileRepository::listProfiles(const QString& searchQuery) QSqlQuery query(QSqlDatabase::database(m_connectionName)); if (searchQuery.trimmed().isEmpty()) { query.prepare(QStringLiteral( - "SELECT id, name, host, port, username, protocol, auth_mode, private_key_path, known_hosts_policy " + "SELECT id, name, host, port, username, domain, protocol, auth_mode, private_key_path, known_hosts_policy, rdp_security_mode, rdp_performance_profile " "FROM profiles " "ORDER BY lower(name) ASC, id ASC")); } else { query.prepare(QStringLiteral( - "SELECT id, name, host, port, username, protocol, auth_mode, private_key_path, known_hosts_policy " + "SELECT id, name, host, port, username, domain, protocol, auth_mode, private_key_path, known_hosts_policy, rdp_security_mode, rdp_performance_profile " "FROM profiles " "WHERE lower(name) LIKE lower(?) OR lower(host) LIKE lower(?) " "ORDER BY lower(name) ASC, id ASC")); @@ -138,7 +174,7 @@ std::optional ProfileRepository::getProfile(qint64 id) const QSqlQuery query(QSqlDatabase::database(m_connectionName)); query.prepare(QStringLiteral( - "SELECT id, name, host, port, username, protocol, auth_mode, private_key_path, known_hosts_policy " + "SELECT id, name, host, port, username, domain, protocol, auth_mode, private_key_path, known_hosts_policy, rdp_security_mode, rdp_performance_profile " "FROM profiles WHERE id = ?")); query.addBindValue(id); @@ -169,8 +205,8 @@ std::optional ProfileRepository::createProfile(const Profile& profile) QSqlQuery query(QSqlDatabase::database(m_connectionName)); query.prepare(QStringLiteral( - "INSERT INTO profiles(name, host, port, username, protocol, auth_mode, private_key_path, known_hosts_policy) " - "VALUES (?, ?, ?, ?, ?, ?, ?, ?)")); + "INSERT INTO profiles(name, host, port, username, domain, protocol, auth_mode, private_key_path, known_hosts_policy, rdp_security_mode, rdp_performance_profile) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")); bindProfileFields(query, profile); if (!query.exec()) { @@ -199,7 +235,7 @@ bool ProfileRepository::updateProfile(const Profile& profile) const QSqlQuery query(QSqlDatabase::database(m_connectionName)); query.prepare(QStringLiteral( "UPDATE profiles " - "SET name = ?, host = ?, port = ?, username = ?, protocol = ?, auth_mode = ?, private_key_path = ?, known_hosts_policy = ? " + "SET name = ?, host = ?, port = ?, username = ?, domain = ?, protocol = ?, auth_mode = ?, private_key_path = ?, known_hosts_policy = ?, rdp_security_mode = ?, rdp_performance_profile = ? " "WHERE id = ?")); bindProfileFields(query, profile); query.addBindValue(profile.id); @@ -250,10 +286,13 @@ bool ProfileRepository::initializeDatabase() "host TEXT NOT NULL DEFAULT ''," "port INTEGER NOT NULL DEFAULT 22," "username TEXT NOT NULL DEFAULT ''," + "domain TEXT NOT NULL DEFAULT ''," "protocol TEXT NOT NULL DEFAULT 'SSH'," "auth_mode TEXT NOT NULL DEFAULT 'Password'," "private_key_path TEXT NOT NULL DEFAULT ''," - "known_hosts_policy TEXT NOT NULL DEFAULT 'Ask'" + "known_hosts_policy TEXT NOT NULL DEFAULT 'Ask'," + "rdp_security_mode TEXT NOT NULL DEFAULT 'Negotiate'," + "rdp_performance_profile TEXT NOT NULL DEFAULT 'Balanced'" ")")); if (!created) { @@ -296,10 +335,13 @@ bool ProfileRepository::ensureProfileSchema() const {QStringLiteral("host"), QStringLiteral("ALTER TABLE profiles ADD COLUMN host TEXT NOT NULL DEFAULT ''")}, {QStringLiteral("port"), QStringLiteral("ALTER TABLE profiles ADD COLUMN port INTEGER NOT NULL DEFAULT 22")}, {QStringLiteral("username"), QStringLiteral("ALTER TABLE profiles ADD COLUMN username TEXT NOT NULL DEFAULT ''")}, + {QStringLiteral("domain"), QStringLiteral("ALTER TABLE profiles ADD COLUMN domain TEXT NOT NULL DEFAULT ''")}, {QStringLiteral("protocol"), QStringLiteral("ALTER TABLE profiles ADD COLUMN protocol TEXT NOT NULL DEFAULT 'SSH'")}, {QStringLiteral("auth_mode"), QStringLiteral("ALTER TABLE profiles ADD COLUMN auth_mode TEXT NOT NULL DEFAULT 'Password'")}, {QStringLiteral("private_key_path"), QStringLiteral("ALTER TABLE profiles ADD COLUMN private_key_path TEXT NOT NULL DEFAULT ''")}, - {QStringLiteral("known_hosts_policy"), QStringLiteral("ALTER TABLE profiles ADD COLUMN known_hosts_policy TEXT NOT NULL DEFAULT 'Ask'")}}; + {QStringLiteral("known_hosts_policy"), QStringLiteral("ALTER TABLE profiles ADD COLUMN known_hosts_policy TEXT NOT NULL DEFAULT 'Ask'")}, + {QStringLiteral("rdp_security_mode"), QStringLiteral("ALTER TABLE profiles ADD COLUMN rdp_security_mode TEXT NOT NULL DEFAULT 'Negotiate'")}, + {QStringLiteral("rdp_performance_profile"), QStringLiteral("ALTER TABLE profiles ADD COLUMN rdp_performance_profile TEXT NOT NULL DEFAULT 'Balanced'")}}; for (const ColumnDef& column : required) { if (columns.contains(column.name)) { diff --git a/src/profile_repository.h b/src/profile_repository.h index e1566e8..403f5e7 100644 --- a/src/profile_repository.h +++ b/src/profile_repository.h @@ -14,10 +14,13 @@ struct Profile QString host; int port = 22; QString username; + QString domain; QString protocol = QStringLiteral("SSH"); QString authMode = QStringLiteral("Password"); QString privateKeyPath; QString knownHostsPolicy = QStringLiteral("Ask"); + QString rdpSecurityMode = QStringLiteral("Negotiate"); + QString rdpPerformanceProfile = QStringLiteral("Balanced"); }; class ProfileRepository diff --git a/src/profiles_window.cpp b/src/profiles_window.cpp index a7bf4ef..85a4903 100644 --- a/src/profiles_window.cpp +++ b/src/profiles_window.cpp @@ -4,8 +4,6 @@ #include "profile_repository.h" #include "session_window.h" -#include - #include #include #include @@ -119,13 +117,29 @@ void ProfilesWindow::loadProfiles(const QString& query) for (const Profile& profile : profiles) { auto* item = new QListWidgetItem(formatProfileListItem(profile), m_profilesList); item->setData(Qt::UserRole, QVariant::fromValue(profile.id)); - item->setToolTip(QStringLiteral("%1://%2@%3:%4\nAuth: %5\nKnown Hosts: %6") - .arg(profile.protocol, - profile.username.isEmpty() ? QStringLiteral("") : profile.username, - profile.host, - QString::number(profile.port), - profile.authMode, - profile.knownHostsPolicy)); + const QString identity = [&profile]() { + if (profile.protocol.compare(QStringLiteral("RDP"), Qt::CaseInsensitive) == 0 + && !profile.domain.trimmed().isEmpty()) { + return QStringLiteral("%1\\%2").arg(profile.domain.trimmed(), + profile.username.trimmed().isEmpty() + ? QStringLiteral("") + : profile.username.trimmed()); + } + return profile.username.isEmpty() ? QStringLiteral("") : profile.username; + }(); + QString tooltip = QStringLiteral("%1://%2@%3:%4\nAuth: %5") + .arg(profile.protocol, + identity, + profile.host, + QString::number(profile.port), + profile.authMode); + if (profile.protocol.compare(QStringLiteral("SSH"), Qt::CaseInsensitive) == 0) { + tooltip += QStringLiteral("\nKnown Hosts: %1").arg(profile.knownHostsPolicy); + } else if (profile.protocol.compare(QStringLiteral("RDP"), Qt::CaseInsensitive) == 0) { + tooltip += QStringLiteral("\nRDP Security: %1\nRDP Performance: %2") + .arg(profile.rdpSecurityMode, profile.rdpPerformanceProfile); + } + item->setToolTip(tooltip); m_profileCache.insert_or_assign(profile.id, profile); } } @@ -264,22 +278,16 @@ void ProfilesWindow::openSessionForItem(QListWidgetItem* item) return; } - auto* session = new SessionWindow(profile.value()); - session->setAttribute(Qt::WA_DeleteOnClose); + if (m_sessionWindow.isNull()) { + m_sessionWindow = new SessionWindow(profile.value()); + m_sessionWindow->setAttribute(Qt::WA_DeleteOnClose); + connect(m_sessionWindow, &QObject::destroyed, this, [this]() { m_sessionWindow = nullptr; }); + } else { + m_sessionWindow->openProfile(profile.value()); + } - m_sessionWindows.emplace_back(session); - connect(session, - &QObject::destroyed, - this, - [this](QObject* object) { - m_sessionWindows.erase( - std::remove_if(m_sessionWindows.begin(), - m_sessionWindows.end(), - [object](const QPointer& candidate) { - return candidate.isNull() || candidate.data() == object; - }), - m_sessionWindows.end()); - }); - - session->show(); + m_sessionWindow->setWindowState(m_sessionWindow->windowState() & ~Qt::WindowMinimized); + m_sessionWindow->show(); + m_sessionWindow->raise(); + m_sessionWindow->activateWindow(); } diff --git a/src/profiles_window.h b/src/profiles_window.h index b1c6e1c..33c548b 100644 --- a/src/profiles_window.h +++ b/src/profiles_window.h @@ -11,7 +11,6 @@ #include #include #include -#include class QListWidget; class QListWidgetItem; @@ -33,7 +32,7 @@ private: QPushButton* m_newButton; QPushButton* m_editButton; QPushButton* m_deleteButton; - std::vector> m_sessionWindows; + QPointer m_sessionWindow; std::unique_ptr m_repository; std::unordered_map m_profileCache; diff --git a/src/rdp_display_widget.cpp b/src/rdp_display_widget.cpp new file mode 100644 index 0000000..1d9c62f --- /dev/null +++ b/src/rdp_display_widget.cpp @@ -0,0 +1,207 @@ +#include "rdp_display_widget.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace { +QSize sanitizeSize(const QSize& size) +{ + return QSize(qMax(1, size.width()), qMax(1, size.height())); +} +} + +RdpDisplayWidget::RdpDisplayWidget(QWidget* parent) + : QWidget(parent), m_remoteSize(1280, 720) +{ + setFocusPolicy(Qt::StrongFocus); + setMouseTracking(true); + setAutoFillBackground(false); + setMinimumSize(320, 200); + + QTimer::singleShot(0, this, [this]() { + const QSize size = sanitizeSize(this->size()); + emit viewportSizeChanged(size.width(), size.height()); + }); +} + +void RdpDisplayWidget::setFrame(const QImage& frame) +{ + if (frame.isNull()) { + return; + } + + m_frame = frame; + m_remoteSize = sanitizeSize(frame.size()); + update(); +} + +void RdpDisplayWidget::setRemoteDesktopSize(int width, int height) +{ + if (width < 1 || height < 1) { + return; + } + + const QSize nextSize(width, height); + if (m_remoteSize == nextSize) { + return; + } + + m_remoteSize = nextSize; + update(); +} + +void RdpDisplayWidget::clearFrame() +{ + m_frame = QImage(); + update(); +} + +void RdpDisplayWidget::paintEvent(QPaintEvent* event) +{ + Q_UNUSED(event); + + QPainter painter(this); + painter.fillRect(rect(), QColor(QStringLiteral("#101214"))); + + const QRectF target = renderRect(); + if (!m_frame.isNull()) { + painter.drawImage(target, m_frame); + } else { + painter.setPen(QColor(QStringLiteral("#b0bec5"))); + painter.drawText(rect(), + Qt::AlignCenter, + QStringLiteral("Waiting for remote desktop frame...")); + } +} + +void RdpDisplayWidget::resizeEvent(QResizeEvent* event) +{ + QWidget::resizeEvent(event); + const QSize size = sanitizeSize(event->size()); + emit viewportSizeChanged(size.width(), size.height()); +} + +void RdpDisplayWidget::keyPressEvent(QKeyEvent* event) +{ + if (event == nullptr || event->isAutoRepeat()) { + return; + } + + emit keyInput(event->key(), + event->nativeScanCode(), + event->text(), + true, + static_cast(event->modifiers())); + event->accept(); +} + +void RdpDisplayWidget::keyReleaseEvent(QKeyEvent* event) +{ + if (event == nullptr || event->isAutoRepeat()) { + return; + } + + emit keyInput(event->key(), + event->nativeScanCode(), + event->text(), + false, + static_cast(event->modifiers())); + event->accept(); +} + +void RdpDisplayWidget::mousePressEvent(QMouseEvent* event) +{ + if (event == nullptr) { + return; + } + + setFocus(Qt::MouseFocusReason); + const QPoint mapped = mapToRemote(event->position()); + emit mouseButtonInput(mapped.x(), mapped.y(), static_cast(event->button()), true); + event->accept(); +} + +void RdpDisplayWidget::mouseReleaseEvent(QMouseEvent* event) +{ + if (event == nullptr) { + return; + } + + const QPoint mapped = mapToRemote(event->position()); + emit mouseButtonInput(mapped.x(), mapped.y(), static_cast(event->button()), false); + event->accept(); +} + +void RdpDisplayWidget::mouseMoveEvent(QMouseEvent* event) +{ + if (event == nullptr) { + return; + } + + const QPoint mapped = mapToRemote(event->position()); + emit mouseMoveInput(mapped.x(), mapped.y()); + event->accept(); +} + +void RdpDisplayWidget::wheelEvent(QWheelEvent* event) +{ + if (event == nullptr) { + return; + } + + const QPoint mapped = mapToRemote(event->position()); + const QPoint angle = event->angleDelta(); + emit mouseWheelInput(mapped.x(), mapped.y(), angle.x(), angle.y()); + event->accept(); +} + +QRectF RdpDisplayWidget::renderRect() const +{ + const QSize remote = effectiveRemoteSize(); + const QRectF area = rect(); + if (area.isEmpty()) { + return QRectF(); + } + + const qreal scale = qMin(area.width() / remote.width(), area.height() / remote.height()); + const qreal drawWidth = remote.width() * scale; + const qreal drawHeight = remote.height() * scale; + const qreal x = area.x() + ((area.width() - drawWidth) * 0.5); + const qreal y = area.y() + ((area.height() - drawHeight) * 0.5); + return QRectF(x, y, drawWidth, drawHeight); +} + +QPoint RdpDisplayWidget::mapToRemote(const QPointF& pos) const +{ + const QSize remote = effectiveRemoteSize(); + const QRectF target = renderRect(); + if (target.isEmpty()) { + return QPoint(0, 0); + } + + const qreal clampedX = qBound(target.left(), pos.x(), target.right()); + const qreal clampedY = qBound(target.top(), pos.y(), target.bottom()); + + const qreal normalizedX = (clampedX - target.left()) / qMax(1.0, target.width()); + const qreal normalizedY = (clampedY - target.top()) / qMax(1.0, target.height()); + + const int remoteX = qBound(0, static_cast(normalizedX * remote.width()), remote.width() - 1); + const int remoteY = qBound(0, static_cast(normalizedY * remote.height()), remote.height() - 1); + return QPoint(remoteX, remoteY); +} + +QSize RdpDisplayWidget::effectiveRemoteSize() const +{ + if (m_remoteSize.width() > 0 && m_remoteSize.height() > 0) { + return m_remoteSize; + } + if (!m_frame.isNull()) { + return sanitizeSize(m_frame.size()); + } + return QSize(1280, 720); +} diff --git a/src/rdp_display_widget.h b/src/rdp_display_widget.h new file mode 100644 index 0000000..b7372f4 --- /dev/null +++ b/src/rdp_display_widget.h @@ -0,0 +1,50 @@ +#ifndef ORBITHUB_RDP_DISPLAY_WIDGET_H +#define ORBITHUB_RDP_DISPLAY_WIDGET_H + +#include +#include + +class QKeyEvent; +class QMouseEvent; +class QPaintEvent; +class QResizeEvent; +class QWheelEvent; + +class RdpDisplayWidget : public QWidget +{ + Q_OBJECT + +public: + explicit RdpDisplayWidget(QWidget* parent = nullptr); + + void setFrame(const QImage& frame); + void setRemoteDesktopSize(int width, int height); + void clearFrame(); + +signals: + void keyInput(int key, quint32 nativeScanCode, const QString& text, bool pressed, int modifiers); + void mouseMoveInput(int x, int y); + void mouseButtonInput(int x, int y, int button, bool pressed); + void mouseWheelInput(int x, int y, int deltaX, int deltaY); + void viewportSizeChanged(int width, int height); + +protected: + void paintEvent(QPaintEvent* event) override; + void resizeEvent(QResizeEvent* event) override; + void keyPressEvent(QKeyEvent* event) override; + void keyReleaseEvent(QKeyEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + void wheelEvent(QWheelEvent* event) override; + +private: + QImage m_frame; + QSize m_remoteSize; + + QRectF renderRect() const; + QPoint mapToRemote(const QPointF& pos) const; + QSize effectiveRemoteSize() const; +}; + +#endif diff --git a/src/rdp_session_backend.cpp b/src/rdp_session_backend.cpp new file mode 100644 index 0000000..2118894 --- /dev/null +++ b/src/rdp_session_backend.cpp @@ -0,0 +1,1809 @@ +#include "rdp_session_backend.h" + +#include +#include +#include +#include + +#include +#include +#include + +#ifdef ORBITHUB_HAS_FREERDP +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +namespace { +constexpr int kDefaultDesktopWidth = 1280; +constexpr int kDefaultDesktopHeight = 720; +constexpr int kMinDesktopWidth = 640; +constexpr int kMinDesktopHeight = 360; +constexpr int kMaxDesktopWidth = 8192; +constexpr int kMaxDesktopHeight = 4320; + +constexpr DWORD kWaitTimeoutMs = 25; +constexpr DWORD kMaxEventHandles = 64; +constexpr UINT16 kWheelStep = 0x78; +constexpr double kDefaultDpi = 96.0; +constexpr double kMillimetersPerInch = 25.4; + +#ifdef ORBITHUB_HAS_FREERDP +QString normalizedRdpSecurityMode(const QString& value) +{ + const QString mode = value.trimmed(); + if (mode.compare(QStringLiteral("NLA"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("NLA"); + } + if (mode.compare(QStringLiteral("TLS"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("TLS"); + } + if (mode.compare(QStringLiteral("RDP"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("RDP"); + } + return QStringLiteral("Negotiate"); +} + +QString normalizedRdpPerformanceProfile(const QString& value) +{ + const QString profile = value.trimmed(); + if (profile.compare(QStringLiteral("Best Quality"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("Best Quality"); + } + if (profile.compare(QStringLiteral("Best Performance"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("Best Performance"); + } + if (profile.compare(QStringLiteral("Auto Detect"), Qt::CaseInsensitive) == 0) { + return QStringLiteral("Auto Detect"); + } + return QStringLiteral("Balanced"); +} + +bool applyRdpSecurityMode(rdpSettings* settings, const QString& mode) +{ + if (settings == nullptr) { + return false; + } + + const QString normalized = normalizedRdpSecurityMode(mode); + + BOOL rdp = FALSE; + BOOL tls = FALSE; + BOOL nla = FALSE; + BOOL useRdpLayer = FALSE; + if (normalized == QStringLiteral("NLA")) { + nla = TRUE; + } else if (normalized == QStringLiteral("TLS")) { + tls = TRUE; + } else if (normalized == QStringLiteral("RDP")) { + rdp = TRUE; + useRdpLayer = TRUE; + } else { + rdp = TRUE; + tls = TRUE; + nla = TRUE; + } + + return freerdp_settings_set_bool(settings, FreeRDP_AadSecurity, FALSE) + && freerdp_settings_set_bool(settings, FreeRDP_ExtSecurity, FALSE) + && freerdp_settings_set_bool(settings, FreeRDP_NegotiateSecurityLayer, TRUE) + && freerdp_settings_set_bool(settings, FreeRDP_RdpSecurity, rdp) + && freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, tls) + && freerdp_settings_set_bool(settings, FreeRDP_NlaSecurity, nla) + && freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, useRdpLayer); +} + +bool applyRdpPerformanceProfile(rdpSettings* settings, const QString& profile) +{ + if (settings == nullptr) { + return false; + } + + const QString normalized = normalizedRdpPerformanceProfile(profile); + UINT32 connectionType = CONNECTION_TYPE_BROADBAND_HIGH; + BOOL networkAutoDetect = FALSE; + if (normalized == QStringLiteral("Best Quality")) { + connectionType = CONNECTION_TYPE_LAN; + } else if (normalized == QStringLiteral("Best Performance")) { + connectionType = CONNECTION_TYPE_MODEM; + } else if (normalized == QStringLiteral("Auto Detect")) { + connectionType = CONNECTION_TYPE_AUTODETECT; + networkAutoDetect = TRUE; + } + + return freerdp_set_connection_type(settings, connectionType) + && freerdp_settings_set_bool(settings, FreeRDP_NetworkAutoDetect, networkAutoDetect) + && freerdp_settings_set_uint32(settings, FreeRDP_ConnectionType, connectionType) + && (freerdp_performance_flags_make(settings), true); +} + +struct OrbitRdpContext +{ + rdpContext context; + RdpSessionBackend* backend; +}; + +thread_local RdpSessionBackend* g_contextBackend = nullptr; + +void orbitOnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e); +void orbitOnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e); +UINT orbitDisplayControlCaps(DispClientContext* context, + UINT32 maxNumMonitors, + UINT32 maxMonitorAreaFactorA, + UINT32 maxMonitorAreaFactorB); +BOOL orbitLoadChannels(freerdp* instance); +bool orbitLoadStaticChannel(rdpChannels* channels, + rdpSettings* settings, + const char* name, + void* data); +bool orbitEnsureStaticChannelProvider(RdpSessionBackend* backend); + +RdpSessionBackend* backendFromContext(rdpContext* context) +{ + if (context == nullptr) { + return nullptr; + } + + auto* orbit = reinterpret_cast(context); + return orbit->backend; +} + +BOOL orbitContextNew(freerdp*, rdpContext* context) +{ + auto* orbit = reinterpret_cast(context); + orbit->backend = g_contextBackend; + return orbit->backend != nullptr; +} + +void orbitContextFree(freerdp*, rdpContext* context) +{ + if (context == nullptr) { + return; + } + + auto* orbit = reinterpret_cast(context); + orbit->backend = nullptr; +} + +BOOL orbitBeginPaint(rdpContext* context) +{ + if (context == nullptr || context->gdi == nullptr || context->gdi->primary == nullptr + || context->gdi->primary->hdc == nullptr || context->gdi->primary->hdc->hwnd == nullptr + || context->gdi->primary->hdc->hwnd->invalid == nullptr) { + return TRUE; + } + + context->gdi->primary->hdc->hwnd->invalid->null = TRUE; + return TRUE; +} + +BOOL orbitEndPaint(rdpContext* context) +{ + if (context == nullptr || context->gdi == nullptr || context->gdi->primary == nullptr + || context->gdi->primary->hdc == nullptr || context->gdi->primary->hdc->hwnd == nullptr + || context->gdi->primary->hdc->hwnd->invalid == nullptr) { + return TRUE; + } + + HGDI_WND window = context->gdi->primary->hdc->hwnd; + if (window->invalid->null) { + return TRUE; + } + + RdpSessionBackend* backend = backendFromContext(context); + if (backend == nullptr) { + return TRUE; + } + + rdpGdi* gdi = context->gdi; + if (gdi->primary_buffer == nullptr || gdi->width <= 0 || gdi->height <= 0 || gdi->stride == 0) { + return TRUE; + } + + const QImage wrapped(gdi->primary_buffer, + gdi->width, + gdi->height, + static_cast(gdi->stride), + QImage::Format_ARGB32); + if (wrapped.isNull()) { + return TRUE; + } + + emit backend->frameUpdated(wrapped.copy()); + return TRUE; +} + +BOOL orbitDesktopResize(rdpContext* context) +{ + if (context == nullptr || context->settings == nullptr || context->gdi == nullptr) { + return FALSE; + } + + const UINT32 width = freerdp_settings_get_uint32(context->settings, FreeRDP_DesktopWidth); + const UINT32 height = freerdp_settings_get_uint32(context->settings, FreeRDP_DesktopHeight); + if (!gdi_resize(context->gdi, width, height)) { + return FALSE; + } + + if (RdpSessionBackend* backend = backendFromContext(context)) { + emit backend->remoteDesktopSizeChanged(static_cast(width), static_cast(height)); + } + return TRUE; +} + +BOOL orbitPreConnect(freerdp* instance) +{ + if (instance == nullptr || instance->context == nullptr || instance->context->settings == nullptr) { + return FALSE; + } + + rdpSettings* settings = instance->context->settings; + freerdp_settings_set_bool(settings, FreeRDP_CertificateCallbackPreferPEM, TRUE); + freerdp_settings_set_bool(settings, FreeRDP_DesktopResize, TRUE); + freerdp_settings_set_bool(settings, FreeRDP_IgnoreCertificate, TRUE); + return TRUE; +} + +BOOL orbitPostConnect(freerdp* instance) +{ + if (instance == nullptr || instance->context == nullptr || instance->context->settings == nullptr) { + return FALSE; + } + + if (!gdi_init(instance, PIXEL_FORMAT_BGRA32)) { + return FALSE; + } + + rdpContext* context = instance->context; + context->update->BeginPaint = orbitBeginPaint; + context->update->EndPaint = orbitEndPaint; + context->update->DesktopResize = orbitDesktopResize; + + if (RdpSessionBackend* backend = backendFromContext(context)) { + const int width = static_cast( + freerdp_settings_get_uint32(context->settings, FreeRDP_DesktopWidth)); + const int height = static_cast( + freerdp_settings_get_uint32(context->settings, FreeRDP_DesktopHeight)); + emit backend->remoteDesktopSizeChanged(width, height); + } + + return TRUE; +} + +void orbitPostDisconnect(freerdp* instance) +{ + if (instance != nullptr) { + if (instance->context != nullptr) { + if (RdpSessionBackend* backend = backendFromContext(instance->context)) { + backend->onChannelDisconnectedEvent(DISP_DVC_CHANNEL_NAME, nullptr); + } + PubSub_UnsubscribeChannelConnected(instance->context->pubSub, + orbitOnChannelConnectedEventHandler); + PubSub_UnsubscribeChannelDisconnected(instance->context->pubSub, + orbitOnChannelDisconnectedEventHandler); + } + gdi_free(instance); + } +} + +void orbitOnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e) +{ + if (context == nullptr || e == nullptr) { + return; + } + + if (RdpSessionBackend* backend = backendFromContext(reinterpret_cast(context))) { + backend->onChannelConnectedEvent(e->name, e->pInterface); + } +} + +void orbitOnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e) +{ + if (context == nullptr || e == nullptr) { + return; + } + + if (RdpSessionBackend* backend = backendFromContext(reinterpret_cast(context))) { + backend->onChannelDisconnectedEvent(e->name, e->pInterface); + } +} + +UINT orbitDisplayControlCaps(DispClientContext* context, + UINT32 maxNumMonitors, + UINT32 maxMonitorAreaFactorA, + UINT32 maxMonitorAreaFactorB) +{ + if (context == nullptr || context->custom == nullptr) { + return CHANNEL_RC_OK; + } + + auto* backend = reinterpret_cast(context->custom); + backend->onDisplayControlCaps(maxNumMonitors, maxMonitorAreaFactorA, maxMonitorAreaFactorB); + return CHANNEL_RC_OK; +} + +BOOL orbitLoadChannels(freerdp* instance) +{ + if (instance == nullptr || instance->context == nullptr || instance->context->settings == nullptr + || instance->context->channels == nullptr) { + return TRUE; + } + + rdpSettings* settings = instance->context->settings; + rdpChannels* channels = instance->context->channels; + RdpSessionBackend* backend = backendFromContext(instance->context); + + if (!orbitEnsureStaticChannelProvider(backend)) { + if (backend != nullptr) { + emit backend->eventLogged(QStringLiteral( + "RDP warning: static channel provider unavailable; dynamic resize may not work.")); + } + return TRUE; + } + + const char* const dynamicChannels[] = {DISP_CHANNEL_NAME}; + if (!freerdp_client_add_dynamic_channel(settings, 1, dynamicChannels)) { + if (backend != nullptr) { + emit backend->eventLogged( + QStringLiteral("RDP warning: failed to register dynamic '%1' channel.") + .arg(QString::fromUtf8(DISP_CHANNEL_NAME))); + } + return TRUE; + } + + if (!freerdp_settings_set_bool(settings, FreeRDP_SupportDynamicChannels, TRUE)) { + if (backend != nullptr) { + emit backend->eventLogged( + QStringLiteral("RDP warning: failed to enable dynamic channels in settings.")); + } + return TRUE; + } + + if (!orbitLoadStaticChannel(channels, settings, DRDYNVC_SVC_CHANNEL_NAME, settings)) { + if (backend != nullptr) { + emit backend->eventLogged( + QStringLiteral("RDP warning: failed to load static '%1' channel.") + .arg(QString::fromUtf8(DRDYNVC_SVC_CHANNEL_NAME))); + } + return TRUE; + } + + if (backend != nullptr) { + emit backend->eventLogged(QStringLiteral("RDP channel loader: display-control channels initialized.")); + } + return TRUE; +} + +bool orbitLoadStaticChannel(rdpChannels* channels, + rdpSettings* settings, + const char* name, + void* data) +{ + if (channels == nullptr || settings == nullptr || name == nullptr) { + return false; + } + + PVIRTUALCHANNELENTRY channelEntry = freerdp_load_channel_addin_entry( + name, nullptr, nullptr, FREERDP_ADDIN_CHANNEL_STATIC | FREERDP_ADDIN_CHANNEL_ENTRYEX); + PVIRTUALCHANNELENTRYEX channelEntryEx = + WINPR_FUNC_PTR_CAST(channelEntry, PVIRTUALCHANNELENTRYEX); + + if (channelEntryEx != nullptr) { + return freerdp_channels_client_load_ex(channels, settings, channelEntryEx, data) == 0; + } + + channelEntry = + freerdp_load_channel_addin_entry(name, nullptr, nullptr, FREERDP_ADDIN_CHANNEL_STATIC); + if (channelEntry != nullptr) { + return freerdp_channels_client_load(channels, settings, channelEntry, data) == 0; + } + + return false; +} + +bool orbitEnsureStaticChannelProvider(RdpSessionBackend* backend) +{ + FREERDP_LOAD_CHANNEL_ADDIN_ENTRY_FN provider = freerdp_get_current_addin_provider(); + if (provider == nullptr) { + const int rc = freerdp_register_addin_provider(freerdp_channels_load_static_addin_entry, 0); + if (rc != CHANNEL_RC_OK) { + if (backend != nullptr) { + emit backend->eventLogged( + QStringLiteral("RDP warning: failed to register static channel provider (rc=%1).") + .arg(rc)); + } + return false; + } + provider = freerdp_get_current_addin_provider(); + } + + if (provider == nullptr) { + if (backend != nullptr) { + emit backend->eventLogged( + QStringLiteral("RDP warning: static channel provider registration did not persist.")); + } + return false; + } + + return true; +} + +DWORD orbitVerifyCertificateEx(freerdp* instance, + const char* host, + UINT16 port, + const char*, + const char*, + const char*, + const char*, + DWORD) +{ + if (instance != nullptr && instance->context != nullptr) { + if (RdpSessionBackend* backend = backendFromContext(instance->context)) { + emit backend->eventLogged( + QStringLiteral("Accepting server certificate for %1:%2.") + .arg(QString::fromUtf8(host == nullptr ? "" : host)) + .arg(port)); + } + } + return 1; +} + +DWORD orbitVerifyChangedCertificateEx(freerdp* instance, + const char* host, + UINT16 port, + const char*, + const char*, + const char*, + const char*, + const char*, + const char*, + const char*, + DWORD) +{ + if (instance != nullptr && instance->context != nullptr) { + if (RdpSessionBackend* backend = backendFromContext(instance->context)) { + emit backend->eventLogged( + QStringLiteral("Accepting changed server certificate for %1:%2.") + .arg(QString::fromUtf8(host == nullptr ? "" : host)) + .arg(port)); + } + } + return 1; +} + +const char* authReasonName(rdp_auth_reason reason) +{ + switch (reason) { + case AUTH_NLA: + return "NLA"; + case AUTH_TLS: + return "TLS"; + case AUTH_RDP: + return "RDP"; + case GW_AUTH_HTTP: + return "GW_HTTP"; + case GW_AUTH_RDG: + return "GW_RDG"; + case GW_AUTH_RPC: + return "GW_RPC"; + case AUTH_SMARTCARD_PIN: + return "SMARTCARD_PIN"; + case AUTH_RDSTLS: + return "RDSTLS"; + default: + return "UNKNOWN"; + } +} + +BOOL orbitAuthenticateEx(freerdp* instance, + char** username, + char** password, + char** domain, + rdp_auth_reason reason) +{ + if (instance == nullptr || instance->context == nullptr || instance->context->settings == nullptr + || username == nullptr || password == nullptr || domain == nullptr) { + return FALSE; + } + + rdpSettings* settings = instance->context->settings; + const char* configuredUsername = freerdp_settings_get_string(settings, FreeRDP_Username); + const char* configuredPassword = freerdp_settings_get_string(settings, FreeRDP_Password); + const char* configuredDomain = freerdp_settings_get_string(settings, FreeRDP_Domain); + + if (configuredUsername == nullptr || configuredUsername[0] == '\0') { + if (RdpSessionBackend* backend = backendFromContext(instance->context)) { + emit backend->eventLogged( + QStringLiteral("Authentication failed: missing username for %1.") + .arg(QString::fromUtf8(authReasonName(reason)))); + } + return FALSE; + } + + if (configuredPassword == nullptr || configuredPassword[0] == '\0') { + if (RdpSessionBackend* backend = backendFromContext(instance->context)) { + emit backend->eventLogged( + QStringLiteral("Authentication failed: missing password for %1.") + .arg(QString::fromUtf8(authReasonName(reason)))); + } + return FALSE; + } + + char* newUsername = _strdup(configuredUsername); + char* newPassword = _strdup(configuredPassword); + char* newDomain = _strdup(configuredDomain == nullptr ? "" : configuredDomain); + if (newUsername == nullptr || newPassword == nullptr || newDomain == nullptr) { + free(newUsername); + free(newPassword); + free(newDomain); + return FALSE; + } + + free(*username); + free(*password); + free(*domain); + *username = newUsername; + *password = newPassword; + *domain = newDomain; + + if (RdpSessionBackend* backend = backendFromContext(instance->context)) { + emit backend->eventLogged( + QStringLiteral("Authentication credentials supplied for %1.") + .arg(QString::fromUtf8(authReasonName(reason)))); + } + + return TRUE; +} + +UINT32 scancodeFromNativeScanCode(quint32 nativeScanCode) +{ + if (nativeScanCode == 0) { + return RDP_SCANCODE_UNKNOWN; + } + + quint32 code = 0; + bool extended = false; + if ((nativeScanCode & 0xFF000000u) == 0xE0000000u) { + code = (nativeScanCode >> 16) & 0xFFu; + extended = true; + } else if ((nativeScanCode & 0x00FF0000u) == 0x00E00000u) { + code = (nativeScanCode >> 8) & 0xFFu; + extended = true; + } else if ((nativeScanCode & 0xFF00u) == 0xE000u || (nativeScanCode & 0xFF00u) == 0x0100u) { + code = nativeScanCode & 0xFFu; + extended = true; + } else if (nativeScanCode <= 0xFFu) { + code = nativeScanCode; + } else { + code = nativeScanCode & 0xFFu; + const quint32 prefix = (nativeScanCode >> 8) & 0xFFu; + extended = (prefix == 0xE0u) || (prefix == 0x01u); + } + + if (code == 0) { + return RDP_SCANCODE_UNKNOWN; + } + + UINT32 scancode = MAKE_RDP_SCANCODE(code, extended ? TRUE : FALSE); + if (scancode == RDP_SCANCODE_NUMLOCK_EXTENDED) { + scancode = RDP_SCANCODE_NUMLOCK; + } else if (scancode == RDP_SCANCODE_RSHIFT_EXTENDED) { + scancode = RDP_SCANCODE_RSHIFT; + } + + return scancode; +} + +UINT32 scancodeForQtKey(int key, Qt::KeyboardModifiers modifiers, quint32 nativeScanCode) +{ + const bool keypad = modifiers.testFlag(Qt::KeypadModifier); + const UINT32 nativeScancode = scancodeFromNativeScanCode(nativeScanCode); + + switch (key) { + case Qt::Key_Escape: + return RDP_SCANCODE_ESCAPE; + case Qt::Key_Backtab: + case Qt::Key_Tab: + return RDP_SCANCODE_TAB; + case Qt::Key_Backspace: + return RDP_SCANCODE_BACKSPACE; + case Qt::Key_Return: + if (nativeScancode == RDP_SCANCODE_RETURN_KP || keypad) { + return RDP_SCANCODE_RETURN_KP; + } + return RDP_SCANCODE_RETURN; + case Qt::Key_Enter: + return RDP_SCANCODE_RETURN_KP; + case Qt::Key_Insert: + return keypad ? RDP_SCANCODE_NUMPAD0 : RDP_SCANCODE_INSERT; + case Qt::Key_Delete: + return keypad ? RDP_SCANCODE_DECIMAL : RDP_SCANCODE_DELETE; + case Qt::Key_Home: + return keypad ? RDP_SCANCODE_NUMPAD7 : RDP_SCANCODE_HOME; + case Qt::Key_End: + return keypad ? RDP_SCANCODE_NUMPAD1 : RDP_SCANCODE_END; + case Qt::Key_PageUp: + return keypad ? RDP_SCANCODE_NUMPAD9 : RDP_SCANCODE_PRIOR; + case Qt::Key_PageDown: + return keypad ? RDP_SCANCODE_NUMPAD3 : RDP_SCANCODE_NEXT; + case Qt::Key_Left: + return keypad ? RDP_SCANCODE_NUMPAD4 : RDP_SCANCODE_LEFT; + case Qt::Key_Right: + return keypad ? RDP_SCANCODE_NUMPAD6 : RDP_SCANCODE_RIGHT; + case Qt::Key_Up: + return keypad ? RDP_SCANCODE_NUMPAD8 : RDP_SCANCODE_UP; + case Qt::Key_Down: + return keypad ? RDP_SCANCODE_NUMPAD2 : RDP_SCANCODE_DOWN; + case Qt::Key_Space: + return RDP_SCANCODE_SPACE; + case Qt::Key_Control: + if (nativeScancode == RDP_SCANCODE_RCONTROL || nativeScancode == RDP_SCANCODE_LCONTROL) { + return nativeScancode; + } + return RDP_SCANCODE_LCONTROL; + case Qt::Key_Shift: + if (nativeScancode == RDP_SCANCODE_RSHIFT || nativeScancode == RDP_SCANCODE_LSHIFT) { + return nativeScancode; + } + return RDP_SCANCODE_LSHIFT; + case Qt::Key_AltGr: + return RDP_SCANCODE_RMENU; + case Qt::Key_Alt: + if (nativeScancode == RDP_SCANCODE_RMENU || nativeScancode == RDP_SCANCODE_LMENU) { + return nativeScancode; + } + return RDP_SCANCODE_LMENU; + case Qt::Key_Meta: + if (nativeScancode == RDP_SCANCODE_LWIN || nativeScancode == RDP_SCANCODE_RWIN) { + return nativeScancode; + } + return RDP_SCANCODE_LWIN; + case Qt::Key_Menu: + return RDP_SCANCODE_APPS; + case Qt::Key_Print: + return RDP_SCANCODE_PRINTSCREEN; + case Qt::Key_Pause: + return RDP_SCANCODE_PAUSE; + case Qt::Key_CapsLock: + return RDP_SCANCODE_CAPSLOCK; + case Qt::Key_NumLock: + return RDP_SCANCODE_NUMLOCK; + case Qt::Key_ScrollLock: + return RDP_SCANCODE_SCROLLLOCK; + case Qt::Key_Plus: + return keypad ? RDP_SCANCODE_ADD : RDP_SCANCODE_OEM_PLUS; + case Qt::Key_Minus: + return keypad ? RDP_SCANCODE_SUBTRACT : RDP_SCANCODE_OEM_MINUS; + case Qt::Key_Equal: + return RDP_SCANCODE_OEM_PLUS; + case Qt::Key_BracketLeft: + return RDP_SCANCODE_OEM_4; + case Qt::Key_BracketRight: + return RDP_SCANCODE_OEM_6; + case Qt::Key_Backslash: + return RDP_SCANCODE_OEM_5; + case Qt::Key_Semicolon: + return RDP_SCANCODE_OEM_1; + case Qt::Key_Apostrophe: + return RDP_SCANCODE_OEM_7; + case Qt::Key_QuoteLeft: + case Qt::Key_AsciiTilde: + return RDP_SCANCODE_OEM_3; + case Qt::Key_Comma: + return RDP_SCANCODE_OEM_COMMA; + case Qt::Key_Period: + return keypad ? RDP_SCANCODE_DECIMAL : RDP_SCANCODE_OEM_PERIOD; + case Qt::Key_Slash: + return keypad ? RDP_SCANCODE_DIVIDE : RDP_SCANCODE_OEM_2; + case Qt::Key_Asterisk: + return keypad ? RDP_SCANCODE_MULTIPLY : RDP_SCANCODE_UNKNOWN; + case Qt::Key_F1: + return RDP_SCANCODE_F1; + case Qt::Key_F2: + return RDP_SCANCODE_F2; + case Qt::Key_F3: + return RDP_SCANCODE_F3; + case Qt::Key_F4: + return RDP_SCANCODE_F4; + case Qt::Key_F5: + return RDP_SCANCODE_F5; + case Qt::Key_F6: + return RDP_SCANCODE_F6; + case Qt::Key_F7: + return RDP_SCANCODE_F7; + case Qt::Key_F8: + return RDP_SCANCODE_F8; + case Qt::Key_F9: + return RDP_SCANCODE_F9; + case Qt::Key_F10: + return RDP_SCANCODE_F10; + case Qt::Key_F11: + return RDP_SCANCODE_F11; + case Qt::Key_F12: + return RDP_SCANCODE_F12; + case Qt::Key_F13: + return RDP_SCANCODE_F13; + case Qt::Key_F14: + return RDP_SCANCODE_F14; + case Qt::Key_F15: + return RDP_SCANCODE_F15; + case Qt::Key_F16: + return RDP_SCANCODE_F16; + case Qt::Key_F17: + return RDP_SCANCODE_F17; + case Qt::Key_F18: + return RDP_SCANCODE_F18; + case Qt::Key_F19: + return RDP_SCANCODE_F19; + case Qt::Key_F20: + return RDP_SCANCODE_F20; + case Qt::Key_F21: + return RDP_SCANCODE_F21; + case Qt::Key_F22: + return RDP_SCANCODE_F22; + case Qt::Key_F23: + return RDP_SCANCODE_F23; + case Qt::Key_F24: + return RDP_SCANCODE_F24; + case Qt::Key_0: + return keypad ? RDP_SCANCODE_NUMPAD0 : RDP_SCANCODE_KEY_0; + case Qt::Key_1: + return keypad ? RDP_SCANCODE_NUMPAD1 : RDP_SCANCODE_KEY_1; + case Qt::Key_2: + return keypad ? RDP_SCANCODE_NUMPAD2 : RDP_SCANCODE_KEY_2; + case Qt::Key_3: + return keypad ? RDP_SCANCODE_NUMPAD3 : RDP_SCANCODE_KEY_3; + case Qt::Key_4: + return keypad ? RDP_SCANCODE_NUMPAD4 : RDP_SCANCODE_KEY_4; + case Qt::Key_5: + return keypad ? RDP_SCANCODE_NUMPAD5 : RDP_SCANCODE_KEY_5; + case Qt::Key_6: + return keypad ? RDP_SCANCODE_NUMPAD6 : RDP_SCANCODE_KEY_6; + case Qt::Key_7: + return keypad ? RDP_SCANCODE_NUMPAD7 : RDP_SCANCODE_KEY_7; + case Qt::Key_8: + return keypad ? RDP_SCANCODE_NUMPAD8 : RDP_SCANCODE_KEY_8; + case Qt::Key_9: + return keypad ? RDP_SCANCODE_NUMPAD9 : RDP_SCANCODE_KEY_9; + case Qt::Key_A: + return RDP_SCANCODE_KEY_A; + case Qt::Key_B: + return RDP_SCANCODE_KEY_B; + case Qt::Key_C: + return RDP_SCANCODE_KEY_C; + case Qt::Key_D: + return RDP_SCANCODE_KEY_D; + case Qt::Key_E: + return RDP_SCANCODE_KEY_E; + case Qt::Key_F: + return RDP_SCANCODE_KEY_F; + case Qt::Key_G: + return RDP_SCANCODE_KEY_G; + case Qt::Key_H: + return RDP_SCANCODE_KEY_H; + case Qt::Key_I: + return RDP_SCANCODE_KEY_I; + case Qt::Key_J: + return RDP_SCANCODE_KEY_J; + case Qt::Key_K: + return RDP_SCANCODE_KEY_K; + case Qt::Key_L: + return RDP_SCANCODE_KEY_L; + case Qt::Key_M: + return RDP_SCANCODE_KEY_M; + case Qt::Key_N: + return RDP_SCANCODE_KEY_N; + case Qt::Key_O: + return RDP_SCANCODE_KEY_O; + case Qt::Key_P: + return RDP_SCANCODE_KEY_P; + case Qt::Key_Q: + return RDP_SCANCODE_KEY_Q; + case Qt::Key_R: + return RDP_SCANCODE_KEY_R; + case Qt::Key_S: + return RDP_SCANCODE_KEY_S; + case Qt::Key_T: + return RDP_SCANCODE_KEY_T; + case Qt::Key_U: + return RDP_SCANCODE_KEY_U; + case Qt::Key_V: + return RDP_SCANCODE_KEY_V; + case Qt::Key_W: + return RDP_SCANCODE_KEY_W; + case Qt::Key_X: + return RDP_SCANCODE_KEY_X; + case Qt::Key_Y: + return RDP_SCANCODE_KEY_Y; + case Qt::Key_Z: + return RDP_SCANCODE_KEY_Z; + default: + return RDP_SCANCODE_UNKNOWN; + } +} + +QString mapRdpError(UINT32 code) +{ + switch (code) { + case FREERDP_ERROR_CONNECT_LOGON_FAILURE: + case FREERDP_ERROR_CONNECT_WRONG_PASSWORD: + case FREERDP_ERROR_CONNECT_NO_OR_MISSING_CREDENTIALS: + case FREERDP_ERROR_AUTHENTICATION_FAILED: + case FREERDP_ERROR_CONNECT_ACCESS_DENIED: + case FREERDP_ERROR_CONNECT_ACCOUNT_RESTRICTION: + case FREERDP_ERROR_CONNECT_LOGON_TYPE_NOT_GRANTED: + return QStringLiteral("Authentication failed. Check username and password."); + case FREERDP_ERROR_CONNECT_ACCOUNT_DISABLED: + return QStringLiteral("Account is disabled."); + case FREERDP_ERROR_CONNECT_ACCOUNT_LOCKED_OUT: + return QStringLiteral("Account is locked out."); + case FREERDP_ERROR_CONNECT_ACCOUNT_EXPIRED: + return QStringLiteral("Account has expired."); + case FREERDP_ERROR_CONNECT_PASSWORD_EXPIRED: + case FREERDP_ERROR_CONNECT_PASSWORD_CERTAINLY_EXPIRED: + return QStringLiteral("Password has expired."); + case FREERDP_ERROR_CONNECT_PASSWORD_MUST_CHANGE: + return QStringLiteral("Password must be changed before sign-in."); + case FREERDP_ERROR_INSUFFICIENT_PRIVILEGES: + return QStringLiteral("The account does not have sufficient privileges for RDP sign-in."); + case FREERDP_ERROR_DNS_NAME_NOT_FOUND: + case FREERDP_ERROR_DNS_ERROR: + return QStringLiteral("Host could not be resolved."); + case FREERDP_ERROR_CONNECT_KDC_UNREACHABLE: + return QStringLiteral("Kerberos domain controller is unreachable."); + case FREERDP_ERROR_CONNECT_TRANSPORT_FAILED: + return QStringLiteral("Network transport failed while connecting."); + case FREERDP_ERROR_TLS_CONNECT_FAILED: + return QStringLiteral("TLS negotiation failed while connecting."); + case FREERDP_ERROR_SECURITY_NEGO_CONNECT_FAILED: + return QStringLiteral("RDP security negotiation failed. Try a different RDP security mode."); + case FREERDP_ERROR_CONNECT_FAILED: + return QStringLiteral("Could not establish an RDP session. Check host, port, and server RDP settings."); + case FREERDP_ERROR_PRE_CONNECT_FAILED: + case FREERDP_ERROR_POST_CONNECT_FAILED: + return QStringLiteral("A local RDP client configuration error prevented the connection."); + case FREERDP_ERROR_CONNECT_TARGET_BOOTING: + return QStringLiteral("The remote host is still booting. Try again shortly."); + case FREERDP_ERROR_CONNECT_ACTIVATION_TIMEOUT: + return QStringLiteral("Connection timed out."); + case FREERDP_ERROR_CONNECT_CANCELLED: + return QStringLiteral("Connection cancelled."); + default: + break; + } + + const char* message = freerdp_get_last_error_string(code); + if (message != nullptr && message[0] != '\0') { + return QString::fromUtf8(message); + } + + const char* name = freerdp_get_last_error_name(code); + if (name != nullptr && name[0] != '\0') { + return QString::fromUtf8(name); + } + + return QStringLiteral("RDP connection failed (0x%1).").arg(code, 8, 16, QChar('0')); +} + +bool isExpectedDisconnectCode(UINT32 code) +{ + switch (code) { + case FREERDP_ERROR_SUCCESS: + case FREERDP_ERROR_NONE: + case MAKE_FREERDP_ERROR(ERRINFO, ERRINFO_PEER_DISCONNECTED): + case FREERDP_ERROR_IDLE_TIMEOUT: + case FREERDP_ERROR_LOGON_TIMEOUT: + case FREERDP_ERROR_RPC_INITIATED_DISCONNECT: + case FREERDP_ERROR_RPC_INITIATED_LOGOFF: + case FREERDP_ERROR_RPC_INITIATED_DISCONNECT_BY_USER: + case FREERDP_ERROR_LOGOFF_BY_USER: + case FREERDP_ERROR_DISCONNECTED_BY_OTHER_CONNECTION: + case FREERDP_ERROR_CONNECT_CANCELLED: + case FREERDP_ERROR_CONNECT_TRANSPORT_FAILED: + case FREERDP_ERROR_TLS_CONNECT_FAILED: + case FREERDP_ERROR_MCS_CONNECT_INITIAL_ERROR: + case FREERDP_ERROR_CONNECT_FAILED: + case FREERDP_ERROR_CLOSE_STACK_ON_DRIVER_NOT_READY: + case FREERDP_ERROR_CLOSE_STACK_ON_DRIVER_FAILURE: + case FREERDP_ERROR_CLOSE_STACK_ON_DRIVER_IFACE_FAILURE: + case FREERDP_ERROR_SERVER_DWM_CRASH: + case FREERDP_ERROR_SERVER_WINLOGON_CRASH: + case FREERDP_ERROR_SERVER_CSRSS_CRASH: + return true; + default: + return false; + } +} + +bool isExpectedConnectAbortCode(UINT32 code) +{ + switch (code) { + case FREERDP_ERROR_SUCCESS: + case FREERDP_ERROR_NONE: + case FREERDP_ERROR_CONNECT_CANCELLED: + return true; + default: + return false; + } +} + +QString disconnectMessageForCode(UINT32 code) +{ + switch (code) { + case FREERDP_ERROR_IDLE_TIMEOUT: + return QStringLiteral("RDP session disconnected due to idle timeout."); + case FREERDP_ERROR_LOGON_TIMEOUT: + return QStringLiteral("RDP session disconnected due to logon timeout."); + case FREERDP_ERROR_CONNECT_CANCELLED: + return QStringLiteral("Connection cancelled."); + case FREERDP_ERROR_RPC_INITIATED_LOGOFF: + case FREERDP_ERROR_LOGOFF_BY_USER: + return QStringLiteral("RDP session signed out."); + case FREERDP_ERROR_RPC_INITIATED_DISCONNECT: + case FREERDP_ERROR_RPC_INITIATED_DISCONNECT_BY_USER: + return QStringLiteral("RDP session disconnected by remote host."); + case FREERDP_ERROR_DISCONNECTED_BY_OTHER_CONNECTION: + return QStringLiteral("RDP session disconnected by another connection."); + case FREERDP_ERROR_CLOSE_STACK_ON_DRIVER_NOT_READY: + case FREERDP_ERROR_CLOSE_STACK_ON_DRIVER_FAILURE: + case FREERDP_ERROR_CLOSE_STACK_ON_DRIVER_IFACE_FAILURE: + return QStringLiteral("RDP session ended while the remote display subsystem restarted."); + case FREERDP_ERROR_SERVER_DWM_CRASH: + case FREERDP_ERROR_SERVER_WINLOGON_CRASH: + case FREERDP_ERROR_SERVER_CSRSS_CRASH: + return QStringLiteral("RDP session ended because the remote Windows session restarted."); + case MAKE_FREERDP_ERROR(ERRINFO, ERRINFO_PEER_DISCONNECTED): + case FREERDP_ERROR_CONNECT_TRANSPORT_FAILED: + case FREERDP_ERROR_TLS_CONNECT_FAILED: + case FREERDP_ERROR_MCS_CONNECT_INITIAL_ERROR: + case FREERDP_ERROR_CONNECT_FAILED: + return QStringLiteral("RDP session disconnected."); + default: + return QStringLiteral("RDP session ended."); + } +} + +QString rdpErrorRaw(UINT32 code) +{ + const char* name = freerdp_get_last_error_name(code); + const QString text = (name != nullptr && name[0] != '\0') ? QString::fromUtf8(name) + : QStringLiteral("UNKNOWN"); + return QStringLiteral("%1 (0x%2)").arg(text).arg(code, 8, 16, QChar('0')); +} +#endif +} + +RdpSessionBackend::RdpSessionBackend(const Profile& profile, QObject* parent) + : SessionBackend(profile, parent), + m_state(SessionState::Disconnected), + m_userInitiatedDisconnect(false), + m_requestedDesktopWidth(kDefaultDesktopWidth), + m_requestedDesktopHeight(kDefaultDesktopHeight), + m_workerRunning(false), + m_stopRequested(false), + m_instance(nullptr), + m_displayControlContext(nullptr), + m_displayControlReady(false), + m_resizeFailureLogged(false), + m_lastResizeWidth(0), + m_lastResizeHeight(0) +{ +} + +RdpSessionBackend::~RdpSessionBackend() +{ + stopWorker(true); +} + +void RdpSessionBackend::connectSession(const SessionConnectOptions& options) +{ + if (m_state == SessionState::Connecting || m_state == SessionState::Connected + || m_workerRunning.load()) { + emit eventLogged(QStringLiteral("Connect skipped: session is already active.")); + return; + } + + QString validationMessage; + if (!validateProfile(validationMessage)) { + setState(SessionState::Failed, validationMessage); + emit connectionError(validationMessage, validationMessage); + return; + } + + m_activeOptions = options; + m_userInitiatedDisconnect.store(false); + m_stopRequested.store(false); + m_resizeFailureLogged = false; + m_lastResizeWidth = 0; + m_lastResizeHeight = 0; + { + std::lock_guard guard(m_displayControlMutex); + m_displayControlContext = nullptr; + m_displayControlReady = false; + } + { + std::lock_guard guard(m_inputMutex); + m_inputEvents.clear(); + } + + setState(SessionState::Connecting, QStringLiteral("Starting embedded RDP session...")); + startWorker(); +} + +void RdpSessionBackend::disconnectSession() +{ + if (m_state == SessionState::Disconnected && !m_workerRunning.load()) { + return; + } + + emit eventLogged(QStringLiteral("Disconnect requested.")); + stopWorker(true); + setState(SessionState::Disconnected, QStringLiteral("Session disconnected.")); +} + +void RdpSessionBackend::reconnectSession(const SessionConnectOptions& options) +{ + emit eventLogged(QStringLiteral("Reconnect requested.")); + stopWorker(true); + m_activeOptions = options; + setState(SessionState::Disconnected, QStringLiteral("Reconnecting...")); + connectSession(options); +} + +void RdpSessionBackend::sendInput(const QString&) +{ + emit eventLogged(QStringLiteral("Input ignored: RDP backend uses direct keyboard/mouse events.")); +} + +void RdpSessionBackend::confirmHostKey(bool) +{ +} + +void RdpSessionBackend::updateTerminalSize(int columns, int rows) +{ + const int width = sanitizeDesktopWidth(columns); + const int height = sanitizeDesktopHeight(rows); + m_requestedDesktopWidth.store(width); + m_requestedDesktopHeight.store(height); + + if (!m_workerRunning.load()) { + return; + } + + InputEvent event; + event.type = InputEventType::Resize; + event.width = width; + event.height = height; + enqueueInputEvent(event); +} + +void RdpSessionBackend::sendKeyEvent(int key, + quint32 nativeScanCode, + const QString& text, + bool pressed, + int modifiers) +{ + if (!m_workerRunning.load()) { + return; + } + + InputEvent event; + event.type = InputEventType::Key; + event.key = key; + event.nativeScanCode = nativeScanCode; + event.text = text; + event.pressed = pressed; + event.modifiers = modifiers; + enqueueInputEvent(event); +} + +void RdpSessionBackend::sendMouseMoveEvent(int x, int y) +{ + if (!m_workerRunning.load()) { + return; + } + + InputEvent event; + event.type = InputEventType::MouseMove; + event.x = x; + event.y = y; + enqueueInputEvent(event); +} + +void RdpSessionBackend::sendMouseButtonEvent(int x, int y, int button, bool pressed) +{ + if (!m_workerRunning.load()) { + return; + } + + InputEvent event; + event.type = InputEventType::MouseButton; + event.x = x; + event.y = y; + event.button = button; + event.pressed = pressed; + enqueueInputEvent(event); +} + +void RdpSessionBackend::sendMouseWheelEvent(int x, int y, int deltaX, int deltaY) +{ + if (!m_workerRunning.load()) { + return; + } + + InputEvent event; + event.type = InputEventType::MouseWheel; + event.x = x; + event.y = y; + event.deltaX = deltaX; + event.deltaY = deltaY; + enqueueInputEvent(event); +} + +void RdpSessionBackend::setState(SessionState state, const QString& message) +{ + m_state = state; + emit stateChanged(state, message); + emit eventLogged(message); +} + +bool RdpSessionBackend::validateProfile(QString& message) const +{ + const Profile& p = profile(); + if (p.host.trimmed().isEmpty()) { + message = QStringLiteral("Host is required for RDP connections."); + return false; + } + if (p.port < 1 || p.port > 65535) { + message = QStringLiteral("Port must be between 1 and 65535."); + return false; + } + message.clear(); + return true; +} + +void RdpSessionBackend::startWorker() +{ +#ifndef ORBITHUB_HAS_FREERDP + const QString message = QStringLiteral( + "Embedded RDP engine is not available in this build. External RDP tools are intentionally disabled."); + setState(SessionState::Failed, message); + emit connectionError(message, message); + return; +#else + if (m_worker.joinable()) { + m_worker.join(); + } + + m_workerRunning.store(true); + m_worker = std::thread([this]() { workerMain(); }); +#endif +} + +void RdpSessionBackend::stopWorker(bool userInitiated) +{ + m_userInitiatedDisconnect.store(userInitiated); + m_stopRequested.store(true); + +#ifdef ORBITHUB_HAS_FREERDP + { + std::lock_guard guard(m_instanceMutex); + if (m_instance != nullptr && m_instance->context != nullptr) { + freerdp_abort_connect_context(m_instance->context); + } + } +#endif + + if (m_worker.joinable()) { + m_worker.join(); + } + m_workerRunning.store(false); +} + +void RdpSessionBackend::workerMain() +{ +#ifndef ORBITHUB_HAS_FREERDP + m_workerRunning.store(false); + return; +#else + freerdp* instance = freerdp_new(); + if (instance == nullptr) { + emitConnectionFailureAsync(QStringLiteral("Failed to initialize FreeRDP runtime."), + QStringLiteral("freerdp_new returned null.")); + emitStateAsync(SessionState::Failed, QStringLiteral("Failed to initialize RDP runtime.")); + m_workerRunning.store(false); + return; + } + + if (!orbitEnsureStaticChannelProvider(this)) { + emit eventLogged(QStringLiteral( + "RDP warning: failed to initialize static channel provider; resize may be limited.")); + } + + instance->ContextSize = sizeof(OrbitRdpContext); + instance->ContextNew = orbitContextNew; + instance->ContextFree = orbitContextFree; + instance->PreConnect = orbitPreConnect; + instance->PostConnect = orbitPostConnect; + instance->PostDisconnect = orbitPostDisconnect; + instance->VerifyCertificateEx = orbitVerifyCertificateEx; + instance->VerifyChangedCertificateEx = orbitVerifyChangedCertificateEx; + instance->AuthenticateEx = orbitAuthenticateEx; + instance->LoadChannels = orbitLoadChannels; + + g_contextBackend = this; + const BOOL contextReady = freerdp_context_new(instance); + g_contextBackend = nullptr; + if (!contextReady || instance->context == nullptr || instance->context->settings == nullptr) { + emitConnectionFailureAsync(QStringLiteral("Failed to initialize RDP context."), + QStringLiteral("freerdp_context_new failed.")); + emitStateAsync(SessionState::Failed, QStringLiteral("Failed to initialize RDP context.")); + freerdp_free(instance); + m_workerRunning.store(false); + return; + } + + { + std::lock_guard guard(m_instanceMutex); + m_instance = instance; + } + + rdpSettings* settings = instance->context->settings; + const Profile& p = profile(); + + const QString host = p.host.trimmed(); + QString username = p.username.trimmed(); + QString domain = p.domain.trimmed(); + if (domain.isEmpty()) { + const int domainSeparator = username.indexOf(QLatin1Char('\\')); + if (domainSeparator > 0) { + domain = username.left(domainSeparator); + username = username.mid(domainSeparator + 1); + } else { + const int upnSeparator = username.lastIndexOf(QLatin1Char('@')); + if (upnSeparator > 0 && upnSeparator < username.size() - 1) { + domain = username.mid(upnSeparator + 1); + username = username.left(upnSeparator); + } + } + } + + const QByteArray hostUtf8 = host.toUtf8(); + const QByteArray userUtf8 = username.toUtf8(); + const QByteArray domainUtf8 = domain.toUtf8(); + const QByteArray passwordUtf8 = m_activeOptions.password.toUtf8(); + + freerdp_settings_set_string(settings, FreeRDP_ServerHostname, hostUtf8.constData()); + freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, static_cast(p.port)); + if (!userUtf8.isEmpty()) { + freerdp_settings_set_string(settings, FreeRDP_Username, userUtf8.constData()); + } + if (!domainUtf8.isEmpty()) { + freerdp_settings_set_string(settings, FreeRDP_Domain, domainUtf8.constData()); + } + if (!passwordUtf8.isEmpty()) { + freerdp_settings_set_string(settings, FreeRDP_Password, passwordUtf8.constData()); + } + + const int desktopWidth = sanitizeDesktopWidth(m_requestedDesktopWidth.load()); + const int desktopHeight = sanitizeDesktopHeight(m_requestedDesktopHeight.load()); + const QString securityMode = normalizedRdpSecurityMode(p.rdpSecurityMode); + const QString performanceProfile = normalizedRdpPerformanceProfile(p.rdpPerformanceProfile); + freerdp_settings_set_bool(settings, FreeRDP_SupportDisplayControl, TRUE); + freerdp_settings_set_bool(settings, FreeRDP_DynamicResolutionUpdate, TRUE); + freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, static_cast(desktopWidth)); + freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, static_cast(desktopHeight)); + freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, 32); + freerdp_settings_set_bool(settings, FreeRDP_AuthenticationOnly, FALSE); + freerdp_settings_set_bool(settings, FreeRDP_AutoLogonEnabled, TRUE); + if (!applyRdpSecurityMode(settings, securityMode)) { + emitConnectionFailureAsync(QStringLiteral("Invalid RDP security configuration."), + QStringLiteral("Failed to apply RDP security mode settings.")); + emitStateAsync(SessionState::Failed, QStringLiteral("Failed to configure RDP security.")); + freerdp_context_free(instance); + freerdp_free(instance); + { + std::lock_guard guard(m_instanceMutex); + m_instance = nullptr; + } + m_workerRunning.store(false); + return; + } + if (!applyRdpPerformanceProfile(settings, performanceProfile)) { + emitConnectionFailureAsync( + QStringLiteral("Invalid RDP performance configuration."), + QStringLiteral("Failed to apply RDP performance profile settings.")); + emitStateAsync(SessionState::Failed, QStringLiteral("Failed to configure RDP performance.")); + freerdp_context_free(instance); + freerdp_free(instance); + { + std::lock_guard guard(m_instanceMutex); + m_instance = nullptr; + } + m_workerRunning.store(false); + return; + } + + emit eventLogged( + QStringLiteral("Connecting RDP to %1:%2 as %3.") + .arg(host, + QString::number(p.port), + domain.isEmpty() ? username : QStringLiteral("%1\\%2").arg(domain, username))); + emit eventLogged(QStringLiteral("RDP security mode: %1.").arg(securityMode)); + emit eventLogged(QStringLiteral("RDP performance profile: %1.").arg(performanceProfile)); + + if (!freerdp_settings_are_valid(settings)) { + emitConnectionFailureAsync(QStringLiteral("RDP settings are invalid."), + QStringLiteral("freerdp_settings_are_valid returned false.")); + emitStateAsync(SessionState::Failed, QStringLiteral("Invalid RDP settings.")); + freerdp_context_free(instance); + freerdp_free(instance); + { + std::lock_guard guard(m_instanceMutex); + m_instance = nullptr; + } + m_workerRunning.store(false); + return; + } + + if (PubSub_SubscribeChannelConnected(instance->context->pubSub, + orbitOnChannelConnectedEventHandler) < 0) { + emit eventLogged(QStringLiteral("RDP warning: failed to subscribe to channel-connected events.")); + } else if (PubSub_SubscribeChannelDisconnected(instance->context->pubSub, + orbitOnChannelDisconnectedEventHandler) + < 0) { + PubSub_UnsubscribeChannelConnected(instance->context->pubSub, + orbitOnChannelConnectedEventHandler); + emit eventLogged(QStringLiteral( + "RDP warning: failed to subscribe to channel-disconnected events.")); + } + + if (!freerdp_connect(instance)) { + const UINT32 code = freerdp_get_last_error(instance->context); + const QString raw = rdpErrorRaw(code); + if (isExpectedConnectAbortCode(code) || m_stopRequested.load()) { + emit eventLogged(QStringLiteral("RDP connect aborted: %1").arg(raw)); + emitStateAsync(SessionState::Disconnected, disconnectMessageForCode(code)); + } else { + const QString mapped = mapRdpError(code); + emit eventLogged(QStringLiteral("RDP connect failure detail: %1").arg(raw)); + emitConnectionFailureAsync(mapped, raw); + emitStateAsync(SessionState::Failed, mapped); + } + freerdp_context_free(instance); + freerdp_free(instance); + { + std::lock_guard guard(m_instanceMutex); + m_instance = nullptr; + } + m_workerRunning.store(false); + return; + } + + emitStateAsync(SessionState::Connected, QStringLiteral("RDP session established.")); + processInputEvents(instance); + + HANDLE handles[kMaxEventHandles] = {0}; + bool hasFailure = false; + QString failureMessage; + QString failureRaw; + + while (!m_stopRequested.load() && !freerdp_shall_disconnect_context(instance->context)) { + processInputEvents(instance); + + const DWORD count = + freerdp_get_event_handles(instance->context, handles, static_cast(kMaxEventHandles)); + if (count == 0) { + hasFailure = true; + failureMessage = QStringLiteral("RDP event loop failed to acquire event handles."); + failureRaw = QStringLiteral("freerdp_get_event_handles returned 0."); + break; + } + + const DWORD status = WaitForMultipleObjects(count, handles, FALSE, kWaitTimeoutMs); + if (status == WAIT_TIMEOUT) { + continue; + } + if (status == WAIT_FAILED) { + hasFailure = true; + failureMessage = QStringLiteral("RDP event loop wait failed."); + failureRaw = QStringLiteral("WaitForMultipleObjects returned WAIT_FAILED."); + break; + } + + if (!freerdp_check_event_handles(instance->context)) { + if (m_stopRequested.load()) { + break; + } + const UINT32 code = freerdp_get_last_error(instance->context); + hasFailure = !isExpectedDisconnectCode(code); + if (hasFailure) { + failureMessage = mapRdpError(code); + failureRaw = rdpErrorRaw(code); + } + break; + } + } + + processInputEvents(instance); + freerdp_disconnect(instance); + const UINT32 finalCode = freerdp_get_last_error(instance->context); + const QString finalRaw = rdpErrorRaw(finalCode); + + if (hasFailure) { + if (!m_stopRequested.load() && !m_userInitiatedDisconnect.load()) { + emit eventLogged(QStringLiteral("RDP failure detail: %1").arg(failureRaw)); + emitConnectionFailureAsync(failureMessage, failureRaw); + emitStateAsync(SessionState::Failed, failureMessage); + } else { + emit eventLogged(QStringLiteral("RDP session ended during disconnect: %1").arg(finalRaw)); + emitStateAsync(SessionState::Disconnected, QStringLiteral("Session disconnected.")); + } + } else if (m_stopRequested.load() || m_userInitiatedDisconnect.load()) { + emit eventLogged(QStringLiteral("RDP disconnect detail: %1").arg(finalRaw)); + emitStateAsync(SessionState::Disconnected, QStringLiteral("Session disconnected.")); + } else { + if (!isExpectedDisconnectCode(finalCode)) { + const QString mapped = mapRdpError(finalCode); + emit eventLogged(QStringLiteral("RDP disconnect failure detail: %1").arg(finalRaw)); + emitConnectionFailureAsync(mapped, finalRaw); + emitStateAsync(SessionState::Failed, mapped); + } else { + emit eventLogged(QStringLiteral("RDP disconnect detail: %1").arg(finalRaw)); + emitStateAsync(SessionState::Disconnected, disconnectMessageForCode(finalCode)); + } + } + + freerdp_context_free(instance); + freerdp_free(instance); + { + std::lock_guard guard(m_instanceMutex); + m_instance = nullptr; + } + m_workerRunning.store(false); +#endif +} + +void RdpSessionBackend::enqueueInputEvent(const InputEvent& event) +{ + std::lock_guard guard(m_inputMutex); + m_inputEvents.push_back(event); +} + +bool RdpSessionBackend::sendDisplayResize(rdp_freerdp* instance, int width, int height) +{ +#ifndef ORBITHUB_HAS_FREERDP + Q_UNUSED(instance); + Q_UNUSED(width); + Q_UNUSED(height); + return false; +#else + if (instance == nullptr || instance->context == nullptr || instance->context->settings == nullptr + || width <= 0 || height <= 0) { + return false; + } + + rdpSettings* settings = instance->context->settings; + freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, static_cast(width)); + freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, static_cast(height)); + + DispClientContext* dispContext = nullptr; + bool displayControlReady = false; + { + std::lock_guard guard(m_displayControlMutex); + dispContext = reinterpret_cast(m_displayControlContext); + displayControlReady = m_displayControlReady; + } + + if (dispContext != nullptr && displayControlReady && dispContext->SendMonitorLayout != nullptr) { + DISPLAY_CONTROL_MONITOR_LAYOUT layout = {0}; + layout.Flags = DISPLAY_CONTROL_MONITOR_PRIMARY; + layout.Left = 0; + layout.Top = 0; + layout.Width = static_cast(width); + layout.Height = static_cast(height); + layout.Orientation = ORIENTATION_LANDSCAPE; + layout.DesktopScaleFactor = freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor); + layout.DeviceScaleFactor = freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor); + layout.PhysicalWidth = static_cast( + std::lround((static_cast(width) / kDefaultDpi) * kMillimetersPerInch)); + layout.PhysicalHeight = static_cast( + std::lround((static_cast(height) / kDefaultDpi) * kMillimetersPerInch)); + + const UINT rc = dispContext->SendMonitorLayout(dispContext, 1, &layout); + if (rc == CHANNEL_RC_OK) { + m_lastResizeWidth = width; + m_lastResizeHeight = height; + return true; + } + } + + return false; +#endif +} + +void RdpSessionBackend::processInputEvents(rdp_freerdp* instance) +{ +#ifndef ORBITHUB_HAS_FREERDP + Q_UNUSED(instance); +#else + if (instance == nullptr || instance->context == nullptr || instance->context->input == nullptr) { + return; + } + rdpInput* input = instance->context->input; + + std::deque pending; + { + std::lock_guard guard(m_inputMutex); + pending.swap(m_inputEvents); + } + + bool hasResize = false; + int resizeWidth = 0; + int resizeHeight = 0; + + for (const InputEvent& event : pending) { + switch (event.type) { + case InputEventType::Resize: + hasResize = true; + resizeWidth = event.width; + resizeHeight = event.height; + break; + case InputEventType::MouseMove: + freerdp_input_send_mouse_event( + input, PTR_FLAGS_MOVE, static_cast(event.x), static_cast(event.y)); + break; + case InputEventType::MouseButton: { + UINT16 flags = 0; + switch (event.button) { + case Qt::LeftButton: + flags = PTR_FLAGS_BUTTON1; + break; + case Qt::RightButton: + flags = PTR_FLAGS_BUTTON2; + break; + case Qt::MiddleButton: + flags = PTR_FLAGS_BUTTON3; + break; + default: + break; + } + + if (flags == 0) { + break; + } + + if (event.pressed) { + flags |= PTR_FLAGS_DOWN; + } + freerdp_input_send_mouse_event(input, + flags, + static_cast(event.x), + static_cast(event.y)); + break; + } + case InputEventType::MouseWheel: { + if (event.deltaY != 0) { + const int direction = event.deltaY > 0 ? 1 : -1; + const int steps = qMax(1, qAbs(event.deltaY) / 120); + for (int i = 0; i < steps; ++i) { + UINT16 flags = PTR_FLAGS_WHEEL | kWheelStep; + if (direction < 0) { + flags |= PTR_FLAGS_WHEEL_NEGATIVE; + } + freerdp_input_send_mouse_event(input, + flags, + static_cast(event.x), + static_cast(event.y)); + } + } + + if (event.deltaX != 0) { + const int direction = event.deltaX > 0 ? 1 : -1; + const int steps = qMax(1, qAbs(event.deltaX) / 120); + for (int i = 0; i < steps; ++i) { + UINT16 flags = PTR_FLAGS_HWHEEL | kWheelStep; + if (direction < 0) { + flags |= PTR_FLAGS_WHEEL_NEGATIVE; + } + freerdp_input_send_mouse_event(input, + flags, + static_cast(event.x), + static_cast(event.y)); + } + } + break; + } + case InputEventType::Key: { + const Qt::KeyboardModifiers modifiers = Qt::KeyboardModifiers(event.modifiers); + UINT32 scancode = scancodeForQtKey(event.key, modifiers, event.nativeScanCode); + if (scancode == RDP_SCANCODE_UNKNOWN) { + scancode = scancodeFromNativeScanCode(event.nativeScanCode); + } + + if (scancode != RDP_SCANCODE_UNKNOWN) { + if (scancode == RDP_SCANCODE_PAUSE) { + if (event.pressed) { + freerdp_input_send_keyboard_pause_event(input); + } + break; + } + freerdp_input_send_keyboard_event_ex(input, event.pressed, FALSE, scancode); + break; + } + + const bool hasShortcutModifier = modifiers.testFlag(Qt::ControlModifier) + || modifiers.testFlag(Qt::AltModifier) + || modifiers.testFlag(Qt::MetaModifier); + if (event.pressed && !event.text.isEmpty() && !hasShortcutModifier) { + for (QChar ch : event.text) { + const UINT16 unicode = ch.unicode(); + freerdp_input_send_unicode_keyboard_event(input, 0, unicode); + freerdp_input_send_unicode_keyboard_event(input, + KBD_FLAGS_RELEASE, + unicode); + } + } + break; + } + } + } + + if (hasResize) { + const int width = sanitizeDesktopWidth(resizeWidth); + const int height = sanitizeDesktopHeight(resizeHeight); + if (width != m_lastResizeWidth || height != m_lastResizeHeight) { + if (sendDisplayResize(instance, width, height)) { + if (m_resizeFailureLogged) { + emit eventLogged(QStringLiteral("Dynamic RDP resize recovered.")); + } + m_resizeFailureLogged = false; + } else if (!m_resizeFailureLogged) { + m_resizeFailureLogged = true; + emit eventLogged(QStringLiteral( + "Dynamic RDP resize unavailable (display-control channel not ready).")); + } + } + } +#endif +} + +void RdpSessionBackend::onChannelConnectedEvent(const char* name, void* channelInterface) +{ +#ifdef ORBITHUB_HAS_FREERDP + if (name == nullptr || std::strcmp(name, DISP_DVC_CHANNEL_NAME) != 0) { + return; + } + + auto* dispContext = reinterpret_cast(channelInterface); + if (dispContext == nullptr) { + return; + } + + dispContext->custom = this; + dispContext->DisplayControlCaps = orbitDisplayControlCaps; + + { + std::lock_guard guard(m_displayControlMutex); + m_displayControlContext = dispContext; + m_displayControlReady = false; + } + emit eventLogged(QStringLiteral("RDP display-control channel connected.")); +#else + Q_UNUSED(name); + Q_UNUSED(channelInterface); +#endif +} + +void RdpSessionBackend::onChannelDisconnectedEvent(const char* name, void* channelInterface) +{ +#ifdef ORBITHUB_HAS_FREERDP + if (name != nullptr && std::strcmp(name, DISP_DVC_CHANNEL_NAME) != 0) { + return; + } + + bool cleared = false; + bool hadDisplayControl = false; + { + std::lock_guard guard(m_displayControlMutex); + hadDisplayControl = (m_displayControlContext != nullptr) || m_displayControlReady; + if (channelInterface == nullptr || m_displayControlContext == channelInterface) { + m_displayControlContext = nullptr; + m_displayControlReady = false; + cleared = true; + } + } + + if (cleared && hadDisplayControl) { + emit eventLogged(QStringLiteral("RDP display-control channel disconnected.")); + } +#else + Q_UNUSED(name); + Q_UNUSED(channelInterface); +#endif +} + +void RdpSessionBackend::onDisplayControlCaps(uint32_t maxNumMonitors, + uint32_t maxMonitorAreaFactorA, + uint32_t maxMonitorAreaFactorB) +{ +#ifdef ORBITHUB_HAS_FREERDP + { + std::lock_guard guard(m_displayControlMutex); + if (m_displayControlContext == nullptr) { + return; + } + m_displayControlReady = true; + } + + emit eventLogged( + QStringLiteral("RDP dynamic resize ready (maxMonitors=%1, maxArea=%2x%3).") + .arg(maxNumMonitors) + .arg(maxMonitorAreaFactorA) + .arg(maxMonitorAreaFactorB)); + + InputEvent event; + event.type = InputEventType::Resize; + event.width = m_requestedDesktopWidth.load(); + event.height = m_requestedDesktopHeight.load(); + enqueueInputEvent(event); +#else + Q_UNUSED(maxNumMonitors); + Q_UNUSED(maxMonitorAreaFactorA); + Q_UNUSED(maxMonitorAreaFactorB); +#endif +} + +void RdpSessionBackend::emitStateAsync(SessionState state, const QString& message) +{ + QMetaObject::invokeMethod( + this, + [this, state, message]() { + if (state == SessionState::Disconnected) { + m_stopRequested.store(false); + m_userInitiatedDisconnect.store(false); + } + setState(state, message); + }, + Qt::QueuedConnection); +} + +void RdpSessionBackend::emitConnectionFailureAsync(const QString& displayMessage, + const QString& rawMessage) +{ + QMetaObject::invokeMethod( + this, + [this, displayMessage, rawMessage]() { emit connectionError(displayMessage, rawMessage); }, + Qt::QueuedConnection); +} + +int RdpSessionBackend::sanitizeDesktopWidth(int width) const +{ + if (width <= 0) { + return kDefaultDesktopWidth; + } + return qBound(kMinDesktopWidth, width, kMaxDesktopWidth); +} + +int RdpSessionBackend::sanitizeDesktopHeight(int height) const +{ + if (height <= 0) { + return kDefaultDesktopHeight; + } + return qBound(kMinDesktopHeight, height, kMaxDesktopHeight); +} diff --git a/src/rdp_session_backend.h b/src/rdp_session_backend.h new file mode 100644 index 0000000..1484205 --- /dev/null +++ b/src/rdp_session_backend.h @@ -0,0 +1,108 @@ +#ifndef ORBITHUB_RDP_SESSION_BACKEND_H +#define ORBITHUB_RDP_SESSION_BACKEND_H + +#include "session_backend.h" + +#include +#include +#include +#include +#include + +struct rdp_freerdp; + +class RdpSessionBackend : public SessionBackend +{ + Q_OBJECT + +public: + explicit RdpSessionBackend(const Profile& profile, QObject* parent = nullptr); + ~RdpSessionBackend() override; + +public slots: + void connectSession(const SessionConnectOptions& options) override; + void disconnectSession() override; + void reconnectSession(const SessionConnectOptions& options) override; + void sendInput(const QString& input) override; + void confirmHostKey(bool trustHost) override; + void updateTerminalSize(int columns, int rows) override; + void sendKeyEvent(int key, + quint32 nativeScanCode, + const QString& text, + bool pressed, + int modifiers) override; + void sendMouseMoveEvent(int x, int y) override; + void sendMouseButtonEvent(int x, int y, int button, bool pressed) override; + void sendMouseWheelEvent(int x, int y, int deltaX, int deltaY) override; + +private: + enum class InputEventType { + Key, + MouseMove, + MouseButton, + MouseWheel, + Resize, + }; + + struct InputEvent { + InputEventType type = InputEventType::MouseMove; + int key = 0; + quint32 nativeScanCode = 0; + QString text; + bool pressed = false; + int modifiers = 0; + int x = 0; + int y = 0; + int button = 0; + int deltaX = 0; + int deltaY = 0; + int width = 0; + int height = 0; + }; + + SessionState m_state; + SessionConnectOptions m_activeOptions; + std::atomic_bool m_userInitiatedDisconnect; + + std::atomic_int m_requestedDesktopWidth; + std::atomic_int m_requestedDesktopHeight; + + std::thread m_worker; + std::atomic_bool m_workerRunning; + std::atomic_bool m_stopRequested; + + std::mutex m_instanceMutex; + rdp_freerdp* m_instance; + + std::mutex m_inputMutex; + std::deque m_inputEvents; + + std::mutex m_displayControlMutex; + void* m_displayControlContext; + bool m_displayControlReady; + bool m_resizeFailureLogged; + int m_lastResizeWidth; + int m_lastResizeHeight; + + void setState(SessionState state, const QString& message); + bool validateProfile(QString& message) const; + void startWorker(); + void stopWorker(bool userInitiated); + void workerMain(); + void enqueueInputEvent(const InputEvent& event); + void processInputEvents(rdp_freerdp* instance); + bool sendDisplayResize(rdp_freerdp* instance, int width, int height); +public: + void onChannelConnectedEvent(const char* name, void* channelInterface); + void onChannelDisconnectedEvent(const char* name, void* channelInterface); + void onDisplayControlCaps(uint32_t maxNumMonitors, + uint32_t maxMonitorAreaFactorA, + uint32_t maxMonitorAreaFactorB); +private: + void emitStateAsync(SessionState state, const QString& message); + void emitConnectionFailureAsync(const QString& displayMessage, const QString& rawMessage); + int sanitizeDesktopWidth(int width) const; + int sanitizeDesktopHeight(int height) const; +}; + +#endif diff --git a/src/session_backend.h b/src/session_backend.h index 2e61ad7..779be4c 100644 --- a/src/session_backend.h +++ b/src/session_backend.h @@ -3,8 +3,10 @@ #include "profile_repository.h" +#include #include #include +#include class SessionConnectOptions { @@ -44,6 +46,37 @@ public slots: virtual void sendInput(const QString& input) = 0; virtual void confirmHostKey(bool trustHost) = 0; virtual void updateTerminalSize(int columns, int rows) = 0; + virtual void sendKeyEvent(int key, + quint32 nativeScanCode, + const QString& text, + bool pressed, + int modifiers) + { + Q_UNUSED(key); + Q_UNUSED(nativeScanCode); + Q_UNUSED(text); + Q_UNUSED(pressed); + Q_UNUSED(modifiers); + } + virtual void sendMouseMoveEvent(int x, int y) + { + Q_UNUSED(x); + Q_UNUSED(y); + } + virtual void sendMouseButtonEvent(int x, int y, int button, bool pressed) + { + Q_UNUSED(x); + Q_UNUSED(y); + Q_UNUSED(button); + Q_UNUSED(pressed); + } + virtual void sendMouseWheelEvent(int x, int y, int deltaX, int deltaY) + { + Q_UNUSED(x); + Q_UNUSED(y); + Q_UNUSED(deltaX); + Q_UNUSED(deltaY); + } signals: void stateChanged(SessionState state, const QString& message); @@ -51,6 +84,8 @@ signals: void connectionError(const QString& displayMessage, const QString& rawMessage); void outputReceived(const QString& text); void hostKeyConfirmationRequested(const QString& prompt); + void frameUpdated(const QImage& frame); + void remoteDesktopSizeChanged(int width, int height); private: Profile m_profile; diff --git a/src/session_backend_factory.cpp b/src/session_backend_factory.cpp index f3deb2b..b6c5097 100644 --- a/src/session_backend_factory.cpp +++ b/src/session_backend_factory.cpp @@ -1,5 +1,6 @@ #include "session_backend_factory.h" +#include "rdp_session_backend.h" #include "session_backend.h" #include "ssh_session_backend.h" #include "unsupported_session_backend.h" @@ -9,6 +10,9 @@ std::unique_ptr createSessionBackend(const Profile& profile) if (profile.protocol.compare(QStringLiteral("SSH"), Qt::CaseInsensitive) == 0) { return std::make_unique(profile); } + if (profile.protocol.compare(QStringLiteral("RDP"), Qt::CaseInsensitive) == 0) { + return std::make_unique(profile); + } return std::make_unique(profile); } diff --git a/src/session_tab.cpp b/src/session_tab.cpp index 1b4e5b3..fa2088e 100644 --- a/src/session_tab.cpp +++ b/src/session_tab.cpp @@ -1,5 +1,6 @@ #include "session_tab.h" +#include "rdp_display_widget.h" #include "session_backend_factory.h" #include "terminal_view.h" @@ -62,6 +63,7 @@ SessionTab::SessionTab(const Profile& profile, QWidget* parent) m_state(SessionState::Disconnected), m_terminalThemeName(QStringLiteral("Dark")), m_sshTerminal(nullptr), + m_rdpDisplay(nullptr), m_terminalOutput(nullptr), m_eventLog(nullptr), m_toggleEventsButton(nullptr), @@ -148,6 +150,26 @@ SessionTab::SessionTab(const Profile& profile, QWidget* parent) m_backend, &SessionBackend::updateTerminalSize, Qt::QueuedConnection); + connect(this, + &SessionTab::requestKeyEvent, + m_backend, + &SessionBackend::sendKeyEvent, + Qt::QueuedConnection); + connect(this, + &SessionTab::requestMouseMoveEvent, + m_backend, + &SessionBackend::sendMouseMoveEvent, + Qt::QueuedConnection); + connect(this, + &SessionTab::requestMouseButtonEvent, + m_backend, + &SessionBackend::sendMouseButtonEvent, + Qt::QueuedConnection); + connect(this, + &SessionTab::requestMouseWheelEvent, + m_backend, + &SessionBackend::sendMouseWheelEvent, + Qt::QueuedConnection); connect(m_backend, &SessionBackend::stateChanged, @@ -174,6 +196,24 @@ SessionTab::SessionTab(const Profile& profile, QWidget* parent) this, &SessionTab::onBackendHostKeyConfirmationRequested, Qt::QueuedConnection); + connect(m_backend, + &SessionBackend::frameUpdated, + this, + [this](const QImage& frame) { + if (m_rdpDisplay != nullptr) { + m_rdpDisplay->setFrame(frame); + } + }, + Qt::QueuedConnection); + connect(m_backend, + &SessionBackend::remoteDesktopSizeChanged, + this, + [this](int width, int height) { + if (m_rdpDisplay != nullptr) { + m_rdpDisplay->setRemoteDesktopSize(width, height); + } + }, + Qt::QueuedConnection); m_backendThread->start(); } @@ -284,6 +324,12 @@ void SessionTab::clearTerminal() emit requestInput(QStringLiteral("\x0c")); } m_terminalOutput->setFocus(); + return; + } + + if (m_rdpDisplay != nullptr) { + m_rdpDisplay->clearFrame(); + m_rdpDisplay->setFocus(); } } @@ -308,6 +354,16 @@ QString SessionTab::terminalThemeName() const return m_terminalThemeName; } +bool SessionTab::supportsThemeSelection() const +{ + return m_useKodoTermForSsh || m_terminalOutput != nullptr; +} + +bool SessionTab::supportsClearAction() const +{ + return m_useKodoTermForSsh || m_terminalOutput != nullptr; +} + void SessionTab::onBackendStateChanged(SessionState state, const QString& message) { setState(state, message); @@ -322,6 +378,9 @@ void SessionTab::onBackendConnectionError(const QString& displayMessage, const Q { m_lastError = rawMessage.isEmpty() ? displayMessage : rawMessage; appendEvent(QStringLiteral("Error: %1").arg(displayMessage)); + if (!rawMessage.trimmed().isEmpty() && rawMessage.trimmed() != displayMessage.trimmed()) { + appendEvent(QStringLiteral("Raw Error: %1").arg(rawMessage.trimmed())); + } } void SessionTab::onBackendOutputReceived(const QString& text) @@ -363,12 +422,21 @@ void SessionTab::setupUi() config.maxScrollback = 12000; m_sshTerminal->setConfig(config); rootLayout->addWidget(m_sshTerminal, 1); + } else if (m_profile.protocol.compare(QStringLiteral("RDP"), Qt::CaseInsensitive) == 0) { + m_rdpDisplay = new RdpDisplayWidget(this); + rootLayout->addWidget(m_rdpDisplay, 1); } else { m_terminalOutput = new TerminalView(this); m_terminalOutput->setFont(defaultTerminalFont()); m_terminalOutput->setMinimumHeight(260); - m_terminalOutput->setPlaceholderText( - QStringLiteral("Session is connecting. Type directly here once connected.")); + m_terminalOutput->setReadOnly(true); + if (m_profile.protocol.compare(QStringLiteral("VNC"), Qt::CaseInsensitive) == 0) { + m_terminalOutput->setPlaceholderText( + QStringLiteral("Embedded VNC session output appears here when the backend is available.")); + } else { + m_terminalOutput->setPlaceholderText( + QStringLiteral("Session output appears here.")); + } rootLayout->addWidget(m_terminalOutput, 1); } @@ -415,6 +483,33 @@ void SessionTab::setupUi() &TerminalView::terminalSizeChanged, this, [this](int columns, int rows) { emit requestTerminalSize(columns, rows); }); + } else if (m_rdpDisplay != nullptr) { + connect(m_rdpDisplay, + &RdpDisplayWidget::viewportSizeChanged, + this, + [this](int width, int height) { emit requestTerminalSize(width, height); }); + connect(m_rdpDisplay, + &RdpDisplayWidget::keyInput, + this, + [this](int key, quint32 nativeScanCode, const QString& text, bool pressed, int modifiers) { + emit requestKeyEvent(key, nativeScanCode, text, pressed, modifiers); + }); + connect(m_rdpDisplay, + &RdpDisplayWidget::mouseMoveInput, + this, + [this](int x, int y) { emit requestMouseMoveEvent(x, y); }); + connect(m_rdpDisplay, + &RdpDisplayWidget::mouseButtonInput, + this, + [this](int x, int y, int button, bool pressed) { + emit requestMouseButtonEvent(x, y, button, pressed); + }); + connect(m_rdpDisplay, + &RdpDisplayWidget::mouseWheelInput, + this, + [this](int x, int y, int deltaX, int deltaY) { + emit requestMouseWheelEvent(x, y, deltaX, deltaY); + }); } } @@ -423,7 +518,41 @@ std::optional SessionTab::buildConnectOptions() SessionConnectOptions options; options.knownHostsPolicy = m_profile.knownHostsPolicy; - if (m_profile.protocol.compare(QStringLiteral("SSH"), Qt::CaseInsensitive) != 0) { + const bool isSsh = m_profile.protocol.compare(QStringLiteral("SSH"), Qt::CaseInsensitive) == 0; + const bool isRdp = m_profile.protocol.compare(QStringLiteral("RDP"), Qt::CaseInsensitive) == 0; + + if (!isSsh && !isRdp) { + return options; + } + + if (isRdp) { + if (m_profile.authMode.compare(QStringLiteral("Password"), Qt::CaseInsensitive) != 0) { + return options; + } + + bool accepted = false; + const QString password = QInputDialog::getText( + this, + QStringLiteral("RDP Password"), + QStringLiteral("Password for %1:") + .arg(m_profile.username.trimmed().isEmpty() + ? m_profile.host + : QStringLiteral("%1@%2").arg(m_profile.username, m_profile.host)), + QLineEdit::Password, + QString(), + &accepted); + if (!accepted) { + return std::nullopt; + } + + if (password.isEmpty()) { + QMessageBox::warning(this, + QStringLiteral("Connect"), + QStringLiteral("Password is required for password authentication.")); + return std::nullopt; + } + + options.password = password; return options; } @@ -481,28 +610,26 @@ std::optional SessionTab::buildConnectOptions() bool SessionTab::validateProfileForConnect() { - if (m_profile.protocol.compare(QStringLiteral("SSH"), Qt::CaseInsensitive) != 0) { - return true; - } - if (m_profile.host.trimmed().isEmpty()) { QMessageBox::warning(this, QStringLiteral("Connect"), - QStringLiteral("SSH host is required.")); - return false; - } - - if (m_profile.username.trimmed().isEmpty()) { - QMessageBox::warning(this, - QStringLiteral("Connect"), - QStringLiteral("SSH username is required.")); + QStringLiteral("%1 host is required.").arg(m_profile.protocol)); return false; } if (m_profile.port < 1 || m_profile.port > 65535) { QMessageBox::warning(this, QStringLiteral("Connect"), - QStringLiteral("SSH port must be between 1 and 65535.")); + QStringLiteral("Port must be between 1 and 65535.")); + return false; + } + + if ((m_profile.protocol.compare(QStringLiteral("SSH"), Qt::CaseInsensitive) == 0 + || m_profile.protocol.compare(QStringLiteral("RDP"), Qt::CaseInsensitive) == 0) + && m_profile.username.trimmed().isEmpty()) { + QMessageBox::warning(this, + QStringLiteral("Connect"), + QStringLiteral("%1 username is required.").arg(m_profile.protocol)); return false; } @@ -554,6 +681,14 @@ void SessionTab::refreshActionButtons() if (m_terminalOutput != nullptr) { m_terminalOutput->setEnabled(isConnected); m_terminalOutput->setFocus(); + return; + } + + if (m_rdpDisplay != nullptr) { + m_rdpDisplay->setEnabled(isConnected); + if (isConnected) { + m_rdpDisplay->setFocus(); + } } } diff --git a/src/session_tab.h b/src/session_tab.h index 0ac4f40..4fe899f 100644 --- a/src/session_tab.h +++ b/src/session_tab.h @@ -5,6 +5,7 @@ #include "session_backend.h" #include +#include #include @@ -12,6 +13,7 @@ class QPlainTextEdit; class QThread; class SessionBackend; class TerminalView; +class RdpDisplayWidget; class QToolButton; class KodoTerm; @@ -30,6 +32,8 @@ public: void clearTerminal(); void setTerminalThemeName(const QString& themeName); QString terminalThemeName() const; + bool supportsThemeSelection() const; + bool supportsClearAction() const; signals: void tabTitleChanged(const QString& title); @@ -40,6 +44,14 @@ signals: void requestInput(const QString& input); void requestHostKeyConfirmation(bool trustHost); void requestTerminalSize(int columns, int rows); + void requestKeyEvent(int key, + quint32 nativeScanCode, + const QString& text, + bool pressed, + int modifiers); + void requestMouseMoveEvent(int x, int y); + void requestMouseButtonEvent(int x, int y, int button, bool pressed); + void requestMouseWheelEvent(int x, int y, int deltaX, int deltaY); private slots: void onBackendStateChanged(SessionState state, const QString& message); @@ -59,6 +71,7 @@ private: QString m_terminalThemeName; KodoTerm* m_sshTerminal; + RdpDisplayWidget* m_rdpDisplay; TerminalView* m_terminalOutput; QPlainTextEdit* m_eventLog; QToolButton* m_toggleEventsButton; diff --git a/src/session_window.cpp b/src/session_window.cpp index 9b99ae0..0db761f 100644 --- a/src/session_window.cpp +++ b/src/session_window.cpp @@ -72,24 +72,32 @@ SessionWindow::SessionWindow(const Profile& profile, QWidget* parent) QMenu menu(this); QAction* disconnectAction = menu.addAction(QStringLiteral("Disconnect")); QAction* reconnectAction = menu.addAction(QStringLiteral("Reconnect")); - menu.addSeparator(); - QMenu* themeMenu = menu.addMenu(QStringLiteral("Theme")); QList themeActions; - const QString currentTheme = tab->terminalThemeName(); - for (const QString& themeName : terminalThemeNames()) { - QAction* themeAction = themeMenu->addAction(themeName); - themeAction->setCheckable(true); - themeAction->setChecked( - themeName.compare(currentTheme, Qt::CaseInsensitive) == 0); - themeActions.append(themeAction); + + if (tab->supportsThemeSelection()) { + menu.addSeparator(); + QMenu* themeMenu = menu.addMenu(QStringLiteral("Theme")); + const QString currentTheme = tab->terminalThemeName(); + for (const QString& themeName : terminalThemeNames()) { + QAction* themeAction = themeMenu->addAction(themeName); + themeAction->setCheckable(true); + themeAction->setChecked( + themeName.compare(currentTheme, Qt::CaseInsensitive) == 0); + themeActions.append(themeAction); + } } - QAction* clearAction = menu.addAction(QStringLiteral("Clear")); + + QAction* clearAction = nullptr; + if (tab->supportsClearAction()) { + clearAction = menu.addAction(QStringLiteral("Clear")); + } + QAction* chosen = menu.exec(m_tabs->tabBar()->mapToGlobal(pos)); if (chosen == disconnectAction) { tab->disconnectSession(); } else if (chosen == reconnectAction) { tab->reconnectSession(); - } else if (chosen == clearAction) { + } else if (clearAction != nullptr && chosen == clearAction) { tab->clearTerminal(); } else { for (QAction* themeAction : themeActions) { @@ -105,11 +113,19 @@ SessionWindow::SessionWindow(const Profile& profile, QWidget* parent) addSessionTab(profile); } +void SessionWindow::openProfile(const Profile& profile) +{ + addSessionTab(profile); +} + void SessionWindow::addSessionTab(const Profile& profile) { auto* tab = new SessionTab(profile, this); const int index = m_tabs->addTab(tab, tab->tabTitle()); m_tabs->setCurrentIndex(index); + if (m_tabs->count() > 1) { + setWindowTitle(QStringLiteral("OrbitHub Sessions")); + } m_tabs->tabBar()->setTabTextColor( index, tabColorForState(SessionState::Disconnected, m_tabs->palette())); diff --git a/src/session_window.h b/src/session_window.h index abfbcac..408449a 100644 --- a/src/session_window.h +++ b/src/session_window.h @@ -14,6 +14,7 @@ class SessionWindow : public QMainWindow public: explicit SessionWindow(const Profile& profile, QWidget* parent = nullptr); + void openProfile(const Profile& profile); private: QTabWidget* m_tabs; diff --git a/third_party/FreeRDP/.clang-format b/third_party/FreeRDP/.clang-format new file mode 100644 index 0000000..6791025 --- /dev/null +++ b/third_party/FreeRDP/.clang-format @@ -0,0 +1,124 @@ +--- +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: true + BeforeElse: true + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Allman +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakStringLiterals: true +ColumnLimit: 100 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: true +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Left +ReflowComments: true +SortIncludes: false +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +TabWidth: 4 +UseTab: ForIndentation +... +Language: Cpp +Standard: Auto +NamespaceIndentation: All +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +... +Language: ObjC +PointerBindsToType: false +SortIncludes: false +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +... +Language: Java +BreakAfterJavaFieldAnnotations: false +... +Language: JavaScript +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +... +Language: Proto +... +Language: TableGen +... +Language: TextProto +... diff --git a/third_party/FreeRDP/.clang-tidy b/third_party/FreeRDP/.clang-tidy new file mode 100644 index 0000000..d298d76 --- /dev/null +++ b/third_party/FreeRDP/.clang-tidy @@ -0,0 +1,138 @@ +--- +Checks: > + -*, + abseil-*, + altera-*, + bugprone-*, + cert-*, + clang-analyzer*, + concurrency-*, + cppcoreguidelines*, + google-*, + hicpp-*, + llvm-*, + modernize-*, + objc-*, + openmp-*, + performance-*, + portability-*, + readability-*, + -altera-id-dependent-backward-branch, + -altera-struct-pack-align, + -altera-unroll-loops, + -cppcoreguidelines-interfaces-global-init, + -bugprone-easily-swappable-parameters, + -bugprone-assignment-in-if-condition, + -bugprone-branch-clone, + -bugprone-macro-parentheses, + -cert-dcl16-c, + -cert-env33-c, + -cert-dcl50-cpp, + -clang-analyzer-webkit.NoUncountedMemberChecker, + -clang-analyzer-optin.performance.Padding, + -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling, + -clang-analyzer-security.VAList, + -clang-analyzer-valist.Uninitialized, + -cppcoreguidelines-pro-bounds-array-to-pointer-decay, + -cppcoreguidelines-owning-memory, + -cppcoreguidelines-avoid-c-arrays, + -cppcoreguidelines-avoid-do-while, + -cppcoreguidelines-avoid-magic-numbers, + -cppcoreguidelines-avoid-non-const-global-variables, + -cppcoreguidelines-macro-to-enum, + -cppcoreguidelines-macro-usage, + -cppcoreguidelines-pro-type-vararg, + -cppcoreguidelines-pro-type-reinterpret-cast, + -cppcoreguidelines-pro-bounds-pointer-arithmetic, + -cppcoreguidelines-no-malloc, + -cppcoreguidelines-use-enum-class, + -google-readability-braces-around-statements, + -google-readability-todo, + -hicpp-avoid-c-arrays, + -hicpp-braces-around-statements, + -hicpp-no-array-decay, + -hicpp-no-assembler, + -hicpp-multiway-paths-covered, + -hicpp-signed-bitwise, + -hicpp-uppercase-literal-suffix, + -hicpp-vararg, + -hicpp-no-malloc, + -llvm-use-ranges, + -llvm-header-guard, + -llvm-include-order, + -llvm-qualified-auto, + -llvm-else-after-return, + -readability-else-after-return, + -readability-avoid-nested-conditional-operator, + -modernize-use-using, + -modernize-avoid-variadic-functions, + -modernize-use-trailing-return-type, + -modernize-return-braced-init-list, + -modernize-macro-to-enum, + -modernize-pass-by-value, + -modernize-avoid-c-arrays, + -readability-use-anyofallof, + -readability-braces-around-statements, + -readability-convert-member-functions-to-static, + -readability-function-cognitive-complexity, + -readability-identifier-length, + -readability-implicit-bool-conversion, + -readability-magic-numbers, + -readability-math-missing-parentheses, + -readability-misleading-indentation, + -readability-qualified-auto, + -readability-redundant-parentheses, + -readability-suspicious-call-argument, + -readability-string-compare, + -readability-uppercase-literal-suffix, + -readability-use-concise-preprocessor-directives, + -performance-no-int-to-ptr, + -performance-enum-size, + -performance-avoid-endl, + -portability-avoid-pragma-once +WarningsAsErrors: '' +HeaderFilterRegex: '' +FormatStyle: file +User: nin +CheckOptions: + - key: readability-implicit-bool-conversion.AllowIntegerConditions + value: 'true' + - key: llvm-else-after-return.WarnOnConditionVariables + value: 'false' + - key: modernize-loop-convert.MinConfidence + value: reasonable + - key: modernize-replace-auto-ptr.IncludeStyle + value: llvm + - key: cert-str34-c.DiagnoseSignedUnsignedCharComparisons + value: 'false' + - key: google-readability-namespace-comments.ShortNamespaceLines + value: '10' + - key: cert-err33-c.CheckedFunctions + value: '::aligned_alloc;::asctime_s;::at_quick_exit;::atexit;::bsearch;::bsearch_s;::btowc;::c16rtomb;::c32rtomb;::calloc;::clock;::cnd_broadcast;::cnd_init;::cnd_signal;::cnd_timedwait;::cnd_wait;::ctime_s;::fclose;::fflush;::fgetc;::fgetpos;::fgets;::fgetwc;::fopen;::fopen_s;::fprintf;::fprintf_s;::fputc;::fputs;::fputwc;::fputws;::fread;::freopen;::freopen_s;::fscanf;::fscanf_s;::fseek;::fsetpos;::ftell;::fwprintf;::fwprintf_s;::fwrite;::fwscanf;::fwscanf_s;::getc;::getchar;::getenv;::getenv_s;::gets_s;::getwc;::getwchar;::gmtime;::gmtime_s;::localtime;::localtime_s;::malloc;::mbrtoc16;::mbrtoc32;::mbsrtowcs;::mbsrtowcs_s;::mbstowcs;::mbstowcs_s;::memchr;::mktime;::mtx_init;::mtx_lock;::mtx_timedlock;::mtx_trylock;::mtx_unlock;::printf_s;::putc;::putwc;::raise;::realloc;::remove;::rename;::scanf;::scanf_s;::setlocale;::setvbuf;::signal;::snprintf;::snprintf_s;::sprintf;::sprintf_s;::sscanf;::sscanf_s;::strchr;::strerror_s;::strftime;::strpbrk;::strrchr;::strstr;::strtod;::strtof;::strtoimax;::strtok;::strtok_s;::strtol;::strtold;::strtoll;::strtoul;::strtoull;::strtoumax;::strxfrm;::swprintf;::swprintf_s;::swscanf;::swscanf_s;::thrd_create;::thrd_detach;::thrd_join;::thrd_sleep;::time;::timespec_get;::tmpfile;::tmpfile_s;::tmpnam;::tmpnam_s;::tss_create;::tss_get;::tss_set;::ungetc;::ungetwc;::vfprintf;::vfprintf_s;::vfscanf;::vfscanf_s;::vfwprintf;::vfwprintf_s;::vfwscanf;::vfwscanf_s;::vprintf_s;::vscanf;::vscanf_s;::vsnprintf;::vsnprintf_s;::vsprintf;::vsprintf_s;::vsscanf;::vsscanf_s;::vswprintf;::vswprintf_s;::vswscanf;::vswscanf_s;::vwprintf_s;::vwscanf;::vwscanf_s;::wcrtomb;::wcschr;::wcsftime;::wcspbrk;::wcsrchr;::wcsrtombs;::wcsrtombs_s;::wcsstr;::wcstod;::wcstof;::wcstoimax;::wcstok;::wcstok_s;::wcstol;::wcstold;::wcstoll;::wcstombs;::wcstombs_s;::wcstoul;::wcstoull;::wcstoumax;::wcsxfrm;::wctob;::wctrans;::wctype;::wmemchr;::wprintf_s;::wscanf;::wscanf_s;' + - key: cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField + value: 'false' + - key: cert-dcl16-c.NewSuffixes + value: 'L;LL;LU;LLU' + - key: google-readability-braces-around-statements.ShortStatementLines + value: '1' + - key: cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic + value: 'true' + - key: google-readability-namespace-comments.SpacesBeforeComments + value: '2' + - key: modernize-loop-convert.MaxCopySize + value: '16' + - key: modernize-pass-by-value.IncludeStyle + value: llvm + - key: modernize-use-nullptr.NullMacros + value: 'NULL' + - key: llvm-qualified-auto.AddConstToQualified + value: 'false' + - key: modernize-loop-convert.NamingStyle + value: CamelCase + - key: llvm-else-after-return.WarnOnUnfixable + value: 'false' + - key: google-readability-function-size.StatementThreshold + value: '800' +... + + diff --git a/third_party/FreeRDP/.gitattributes b/third_party/FreeRDP/.gitattributes new file mode 100644 index 0000000..02b13c8 --- /dev/null +++ b/third_party/FreeRDP/.gitattributes @@ -0,0 +1,3 @@ +.gitattributes export-ignore +.gitignore export-ignore +.github export-ignore diff --git a/third_party/FreeRDP/.github/ISSUE_TEMPLATE.md b/third_party/FreeRDP/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..6ed5fee --- /dev/null +++ b/third_party/FreeRDP/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,28 @@ +## Found a bug? - We would like to help you and smash the bug away. +1. __Please don't "report" questions as bugs.__ + * We are reachable via + * Matrix room : #FreeRDP:matrix.org (main) + * XMPP channel: #FreeRDP#matrix.org@matrix.org (bridged) + * IRC channel : #freerdp @ irc.oftc.net (bridged) + * We are reachable via mailing list + * Try our mailing list for discussions/questions +1. Before reporting a bug have a look into our issue tracker to see if the bug was already reported and you can add some additional information. +1. If it's a __new__ bug - create a new issue. +1. For more details see https://github.com/FreeRDP/FreeRDP/wiki/BugReporting + +## To save time and help us identify the issue a bug report should at least contain the following: + * a useful description of the bug - "It's not working" isn't good enough - you must try harder ;) + * the steps to reproduce the bug + * command line you have used + * to what system did you connect to? (win8, 2008, ..) + * what did you expect to happen? + * what actually happened? + * freerdp version (e.g. xfreerdp --version) or package version or git commit + * freerdp configuration (e.g. xfreerdp --buildconfig) + * operating System, architecture, distribution e.g. linux, amd64, debian + * if you built it yourself add some notes which branch you have used, also your cmake parameters can help + * extra information helping us to find the bug + +## Please remove this text before submitting your issue! + +_Thank you for reporting a bug!_ diff --git a/third_party/FreeRDP/.github/ISSUE_TEMPLATE/backport.md b/third_party/FreeRDP/.github/ISSUE_TEMPLATE/backport.md new file mode 100644 index 0000000..a2cff52 --- /dev/null +++ b/third_party/FreeRDP/.github/ISSUE_TEMPLATE/backport.md @@ -0,0 +1,7 @@ +--- +name: Backport +about: Create a issue to request/track a backport + +--- + +Related pull request for master: diff --git a/third_party/FreeRDP/.github/ISSUE_TEMPLATE/bug_report.md b/third_party/FreeRDP/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..bb88f0b --- /dev/null +++ b/third_party/FreeRDP/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,55 @@ +--- +name: Bug report +about: Create a report to help us improve + +--- + +**Found a bug? - We would like to help you and smash the bug away.** +1. __Please don't "report" questions as bugs. For these (questions/build instructions/...) please use one of the following means of contact:__ + * We are reachable via: + * Matrix room : #FreeRDP:matrix.org (main) + * XMPP channel: #FreeRDP#matrix.org@matrix.org (bridged) + * IRC channel : #freerdp @ irc.oftc.net (bridged) + * We are reachable via mailing list + * Try our mailing list for discussions/questions +1. Before reporting a bug have a look into our issue tracker to see if the bug was already reported and you can add some additional information. +1. If it's a __new__ bug - create a new issue. +1. For more details see https://github.com/FreeRDP/FreeRDP/wiki/BugReporting + + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Application details** +* FreeRDP version (`xfreerdp /version`) +* Command line used +* Output of `xfreerdp /buildconfig` +* OS version connecting to (server side) +* If available the log output from a run with `/log-level:trace 2>&1 | tee log.txt` +* If you built it yourself add some notes which tag/commit/branch you have used, also your cmake parameters and + compiler can help + +**Environment (please complete the following information):** + - OS: [e.g. Linux/Windows/Android/..] + - Version/Distribution: [e.g. Debian 10, Windows 2008, Android 10] + - Architecture: [amd64, arm]: + +**Additional context** +Add any other context about the problem here. + +** Please remove this text before submitting your issue! + +_Thank you for reporting a bug!_ diff --git a/third_party/FreeRDP/.github/ISSUE_TEMPLATE/feature_request.md b/third_party/FreeRDP/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..066b2d9 --- /dev/null +++ b/third_party/FreeRDP/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea for this project + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/third_party/FreeRDP/.github/PULL_REQUEST_TEMPLATE.md b/third_party/FreeRDP/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..c7c6791 --- /dev/null +++ b/third_party/FreeRDP/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,29 @@ +## This is how are pull requests handled by FreeRDP +1. Every new pull request needs to build and pass the unit tests at https://ci.freerdp.com +1. At least 1 (better two) people need to review and test a pull request and agree to accept + +## Preparations before creating a pull +* Rebase your branch to current master, no merges allowed! +* Try to clean up your commit history, group changes to commits +* Check your formatting! A _clang-format_ script can be found at ```.clang-format``` + * The cmake target ```clangformat``` reformats the whole codebase +* Optional (but higly recommended) + * Run a clang scanbuild before and after your changes to avoid introducing new bugs + * Run your compiler at pedantic level to check for new warnings + +## To ease accepting your contribution +* Give the pull request a proper name so people looking at it have an basic idea what it is for +* Add at least a brief description what it does (or should do :) and what it's good for +* Give instructions on how to test your changes +* Ideally add unit tests if adding new features + +## What you should be prepared for +* fix issues found during the review phase +* Joining our chat to talk to other developers or help them test your pull might accelerate acceptance + * Matrix room : #FreeRDP:matrix.org (main) + * XMPP channel: #FreeRDP#matrix.org@matrix.org (bridged) + * IRC channel : #freerdp @ irc.oftc.net (bridged) +* Joining our mailing list may be helpful too. +* Check the pull request builder at https://ci.freerdp.com/job/code-quality-checker/ and fix all warnings affecting your code + +## Please remove this text before submitting your pull! diff --git a/third_party/FreeRDP/.github/workflows/abi-checker.yml b/third_party/FreeRDP/.github/workflows/abi-checker.yml new file mode 100644 index 0000000..b3e721b --- /dev/null +++ b/third_party/FreeRDP/.github/workflows/abi-checker.yml @@ -0,0 +1,57 @@ +name: abi-checker +on: + workflow_dispatch: + branches: [ master, stable* ] + inputs: + API_BASE_REF: + description: 'Base revision for ABI compatibility check' + required: true + default: '3.6.0' + pull_request: + branches: [ master, stable* ] + schedule: + - cron: '30 4 * * SUN' + +jobs: + build: + runs-on: ubuntu-latest + name: "Run ABI checker on ubuntu-latest" + steps: + - name: "Check out pull request" + if: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event_name == 'pull_request' }} + uses: suzuki-shunsuke/get-pr-action@v0.1.0 + id: pr + + - name: "Check out source" + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{steps.pr.outputs.merge_commit_sha}} + + - name: "Prepare environment" + run: | + sudo apt-get update -q -y + sudo apt-get --fix-broken install -q -y + sudo apt-get install -q -y devscripts equivs abigail-tools \ + clang \ + pylint \ + curl + ./packaging/scripts/prepare_deb_freerdp-nightly.sh + sudo mk-build-deps -i + + - name: "Prepare configuration" + run: | + mkdir -p abi-checker + cp scripts/abi-diff.sh abi-checker/ + echo "GITHUB_BASE_REF=$GITHUB_BASE_REF" + echo "GITHUB_HEAD_REF=$GITHUB_HEAD_REF" + echo "API_BASE_REF=${{ inputs.API_BASE_REF || '3.6.0' }}" + echo "HEAD=$(git rev-parse HEAD)" + echo "remotes=$(git remote -v)" + + - name: "Run ABI check..." + env: + BASE_REF: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event_name == 'workflow_dispatch' && inputs.API_BASE_REF || '3.6.0' }} + run: | + echo "BASE_REF=$BASE_REF" + ./abi-checker/abi-diff.sh $BASE_REF diff --git a/third_party/FreeRDP/.github/workflows/alt-architectures.yml b/third_party/FreeRDP/.github/workflows/alt-architectures.yml new file mode 100644 index 0000000..ce45279 --- /dev/null +++ b/third_party/FreeRDP/.github/workflows/alt-architectures.yml @@ -0,0 +1,56 @@ +name: '[arm,ppc,ricsv] architecture builds' +on: + workflow_dispatch: + branches: [ master, stable* ] + schedule: + - cron: '30 5 * * SUN' + +jobs: + build_job: + runs-on: ubuntu-latest + name: "Test on ${{ matrix.distro }}/${{ matrix.arch }}" + strategy: + fail-fast: false + matrix: + include: + - arch: armv7 + distro: bookworm + - arch: aarch64 + distro: bookworm + - arch: s390x + distro: bookworm + - arch: ppc64le + distro: bookworm + - arch: riscv64 + distro: ubuntu24.04 + steps: + - uses: actions/checkout@v4 + - uses: uraimo/run-on-arch-action@v3.0.1 + name: "Run tests" + id: build + with: + arch: ${{ matrix.arch }} + distro: ${{ matrix.distro }} + githubToken: ${{ github.token }} + env: | + CTEST_OUTPUT_ON_FAILURE: 1 + WLOG_LEVEL: 'trace' + install: | + echo "whoami: $(whoami)" + echo "working directory: $(pwd)" + apt-get update -q -y + apt-get install -q -y devscripts clang ninja-build ccache equivs + + run: | + echo "whoami: $(whoami)" + echo "working directory: $(pwd)" + find . -name control -exec mk-build-deps -i -t "apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends -y" {} \; + cmake -GNinja \ + -C ci/cmake-preloads/config-linux-alt-arch.txt \ + -B ci-build \ + -S . \ + -DCMAKE_INSTALL_PREFIX=/tmp/ci-test \ + -DCMAKE_C_COMPILER=/usr/bin/clang \ + -DCMAKE_CXX_COMPILER=/usr/bin/clang++ + cmake --build ci-build --parallel $(nproc) --target install + cmake --build ci-build --parallel $(nproc) --target test diff --git a/third_party/FreeRDP/.github/workflows/bash-format.yml b/third_party/FreeRDP/.github/workflows/bash-format.yml new file mode 100644 index 0000000..6a204e6 --- /dev/null +++ b/third_party/FreeRDP/.github/workflows/bash-format.yml @@ -0,0 +1,26 @@ +name: bash-format +on: + workflow_dispatch: + branches: [ master, stable* ] + pull_request: + branches: [ master, stable* ] + schedule: + - cron: '30 4 * * SUN' + +jobs: + build: + runs-on: ubuntu-latest + name: "bash-format" + steps: + - name: "Check out source" + uses: actions/checkout@v4 + + - name: "Prepare environment" + run: | + sudo apt-get update -q -y + sudo apt-get install -q -y \ + shfmt + + - name: "Run shfmt..." + run: | + ./scripts/bash-format.sh diff --git a/third_party/FreeRDP/.github/workflows/clang-tidy-post.yml b/third_party/FreeRDP/.github/workflows/clang-tidy-post.yml new file mode 100644 index 0000000..492e442 --- /dev/null +++ b/third_party/FreeRDP/.github/workflows/clang-tidy-post.yml @@ -0,0 +1,24 @@ +name: Post clang-tidy review comments + +on: + workflow_run: + workflows: ["clang-tidy-review"] + types: + - completed + +permissions: + pull-requests: write + issues: write + checks: write + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: akallabeth/clang-tidy-review/post@master + # lgtm_comment_body, max_comments, and annotations need to be set on the posting workflow in a split setup + with: + token: ${{ secrets.GITHUB_TOKEN }} + annotations: false + max_comments: 10 diff --git a/third_party/FreeRDP/.github/workflows/clang-tidy.yml b/third_party/FreeRDP/.github/workflows/clang-tidy.yml new file mode 100644 index 0000000..5086b55 --- /dev/null +++ b/third_party/FreeRDP/.github/workflows/clang-tidy.yml @@ -0,0 +1,28 @@ +name: clang-tidy-review +on: + pull_request: + branches: [ master, stable* ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + # Run clang-tidy + - uses: akallabeth/clang-tidy-review@master + id: review + with: + split_workflow: true + clang_tidy_checks: '' + apt_packages: devscripts,equivs + install_commands: 'ln -s packaging/deb/freerdp-nightly debian; mk-build-deps -i -t "apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends -y"' + + # CMake command to run in order to generate compile_commands.json + build_dir: tidy + cmake_command: cmake -Btidy -S. -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -C ci/cmake-preloads/config-qa.cmake + + # Uploads an artefact containing clang_fixes.json + - uses: akallabeth/clang-tidy-review/upload@master + id: upload-review diff --git a/third_party/FreeRDP/.github/workflows/cmake-format.yml b/third_party/FreeRDP/.github/workflows/cmake-format.yml new file mode 100644 index 0000000..85f2a4d --- /dev/null +++ b/third_party/FreeRDP/.github/workflows/cmake-format.yml @@ -0,0 +1,26 @@ +name: cmake-format +on: + workflow_dispatch: + branches: [ master, stable* ] + pull_request: + branches: [ master, stable* ] + schedule: + - cron: '30 4 * * SUN' + +jobs: + build: + runs-on: ubuntu-latest + name: "cmake-format" + steps: + - name: "Check out source" + uses: actions/checkout@v4 + + - name: "Prepare environment" + run: | + sudo apt-get update -q -y + sudo apt-get install -q -y \ + cmake-format + + - name: "Run cmake-format..." + run: | + ./scripts/cmake-format.sh diff --git a/third_party/FreeRDP/.github/workflows/codeql-analysis.yml b/third_party/FreeRDP/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..f369f18 --- /dev/null +++ b/third_party/FreeRDP/.github/workflows/codeql-analysis.yml @@ -0,0 +1,93 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + workflow_dispatch: + branches: [ master, stable* ] + schedule: + - cron: '41 2 * * 2' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} + permissions: + # required for all workflows + security-events: write + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: c-cpp + build-mode: manual + # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - if: matrix.build-mode == 'manual' + run: | + sudo apt-get update -q -y + sudo apt-get install -q -y devscripts clang ccache ninja-build equivs + ./packaging/scripts/prepare_deb_freerdp-nightly.sh + sudo mk-build-deps -i + mkdir ci-build + cd ci-build + export CC=/usr/bin/clang + export CXX=/usr/bin/clang++ + export CFLAGS="-Weverything" + export CXXFLAGS="-Weverything" + cmake -GNinja ../ci/cmake-preloads/config-linux-all.txt .. + cmake --build . + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/third_party/FreeRDP/.github/workflows/codespell.yml b/third_party/FreeRDP/.github/workflows/codespell.yml new file mode 100644 index 0000000..15fd13f --- /dev/null +++ b/third_party/FreeRDP/.github/workflows/codespell.yml @@ -0,0 +1,26 @@ +name: codespell +on: + workflow_dispatch: + branches: [ master, stable* ] + pull_request: + branches: [ master, stable* ] + schedule: + - cron: '30 4 * * SUN' + +jobs: + build: + runs-on: ubuntu-latest + name: "codespell" + steps: + - name: "Check out source" + uses: actions/checkout@v4 + + - name: "Prepare environment" + run: | + sudo apt-get update -q -y + sudo apt-get install -q -y \ + codespell + + - name: "Run codespell..." + run: | + ./scripts/codespell.sh diff --git a/third_party/FreeRDP/.github/workflows/coverity.yml b/third_party/FreeRDP/.github/workflows/coverity.yml new file mode 100644 index 0000000..bee2206 --- /dev/null +++ b/third_party/FreeRDP/.github/workflows/coverity.yml @@ -0,0 +1,59 @@ + +name: Coverity + +on: + schedule: + - cron: "0 0 * * *" + workflow_dispatch: + branches: [ master, stable* ] + +permissions: + contents: read + +jobs: + scan: + runs-on: ubuntu-latest + if: ${{ github.repository_owner == 'FreeRDP' }} + steps: + - uses: actions/checkout@v4 + - name: Install apt dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + devscripts \ + ninja-build \ + equivs \ + ccache \ + clang + sudo mk-build-deps --install packaging/deb/freerdp-nightly/control + + - name: Download Coverity build tool + run: | + wget -c -N https://scan.coverity.com/download/linux64 --post-data "token=${{ secrets.COVERITY_SCAN_TOKEN }}&project=FreeRDP" -O coverity_tool.tar.gz + mkdir coverity_tool + tar xzf coverity_tool.tar.gz --strip 1 -C coverity_tool + + - name: Build with Coverity build tool + run: | + export PATH=`pwd`/coverity_tool/bin:$PATH + export CC=/usr/bin/clang + export CXX=/usr/bin/clang++ + cov-configure --template --compiler clang --comptype clangcc + # in source build is used to help coverity to determine relative file path + cmake \ + -GNinja \ + -C ci/cmake-preloads/config-coverity.txt \ + -DCOVERITY_BUILD=ON \ + -Bcov-build \ + -S. + cov-build --dir cov-int cmake --build cov-build + + - name: Submit build result to Coverity Scan + run: | + tar czvf cov.tar.gz cov-int + curl --form token=${{ secrets.COVERITY_SCAN_TOKEN }} \ + --form email=team+coverity@freerdp.com \ + --form file=@cov.tar.gz \ + --form version="Commit $GITHUB_SHA" \ + --form description="Build submitted via CI" \ + https://scan.coverity.com/builds?project=FreeRDP diff --git a/third_party/FreeRDP/.github/workflows/freebsd.yml b/third_party/FreeRDP/.github/workflows/freebsd.yml new file mode 100644 index 0000000..ac1d2ac --- /dev/null +++ b/third_party/FreeRDP/.github/workflows/freebsd.yml @@ -0,0 +1,71 @@ +name: '[freebsd] architecture builds' +on: + workflow_dispatch: + branches: [ master, stable* ] + schedule: + - cron: '30 5 * * SAT' + +jobs: + freebsd_job: + runs-on: ubuntu-latest + name: Build on FreeBSD + steps: + - uses: actions/checkout@v4 + - name: Test in FreeBSD + id: test + uses: vmactions/freebsd-vm@v1 + with: + usesh: true + copyback: false + prepare: | + pkg install -y \ + cmake \ + ninja \ + krb5-devel \ + json-c \ + libcjson \ + fdk-aac \ + libsoxr \ + sdl2 \ + sdl3 \ + sdl2_ttf \ + sdl2_image \ + opus \ + png \ + webp \ + openjpeg \ + libjpeg-turbo \ + opensc \ + v4l_compat \ + libv4l \ + uriparser \ + ffmpeg \ + pulseaudio \ + pcsc-lite \ + cups \ + opencl \ + openssl34 \ + gsm \ + influxpkg-config \ + icu \ + fusefs-libs3 \ + ccache \ + opencl-clang-llvm15 \ + faac \ + faad2 \ + opus-tools \ + openh264 \ + alsa-lib \ + cairo \ + ocl-icd + + run: | + export LD_LIBRARY_PATH=/usr/lib/clang/18/lib/freebsd + export CTEST_OUTPUT_ON_FAILURE=1 + cmake -GNinja \ + -C ci/cmake-preloads/config-freebsd.txt \ + -B ci-build \ + -S . \ + -DCMAKE_INSTALL_PREFIX=/tmp/ci-test + cmake --build ci-build --parallel $(nproc) --target install + cmake --build ci-build --parallel $(nproc) --target test diff --git a/third_party/FreeRDP/.github/workflows/fuzzing.yml b/third_party/FreeRDP/.github/workflows/fuzzing.yml new file mode 100644 index 0000000..04b5029 --- /dev/null +++ b/third_party/FreeRDP/.github/workflows/fuzzing.yml @@ -0,0 +1,43 @@ +name: Fuzzing testing + +on: + workflow_dispatch: + branches: [ master, stable* ] + schedule: + - cron: "0 3 21 * *" + +jobs: + fuzzing: + if: github.repository == 'FreeRDP/FreeRDP' + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + sanitizer: [address, undefined] + + steps: + - uses: actions/checkout@v4 + + - name: Build fuzzers (${{ matrix.sanitizer }}) + id: build + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: 'freerdp' + dry-run: false + sanitizer: ${{ matrix.sanitizer }} + - name: Run fuzzers (${{ matrix.sanitizer }}) + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + oss-fuzz-project-name: 'freerdp' + fuzz-seconds: 600 + dry-run: false + sanitizer: ${{ matrix.sanitizer }} + - name: Upload crash + uses: actions/upload-artifact@v4.3.6 + if: failure() && steps.build.outcome == 'success' + with: + name: ${{ matrix.sanitizer }}-artifacts + retention-days: 21 + path: ./out/artifacts diff --git a/third_party/FreeRDP/.github/workflows/issue-autoclose.yml b/third_party/FreeRDP/.github/workflows/issue-autoclose.yml new file mode 100644 index 0000000..6034144 --- /dev/null +++ b/third_party/FreeRDP/.github/workflows/issue-autoclose.yml @@ -0,0 +1,29 @@ +name: Close inactive issues +on: + workflow_dispatch: + + schedule: + - cron: "33 3 * * *" + +jobs: + close-issues: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/stale@v5 + with: + days-before-stale: 30 + days-before-close: 30 + operations-per-run: 90 + exempt-all-milestones: true + exempt-assignees: true + exempt-issue-labels: "wip,pinned,help-wanted,blocker,feature" + exempt-pr-labels: "wip,pinned,help-wanted,blocker,feature" + stale-issue-label: "stale" + stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." + close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." + days-before-pr-stale: -1 + days-before-pr-close: -1 + repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/third_party/FreeRDP/.github/workflows/macos.yml b/third_party/FreeRDP/.github/workflows/macos.yml new file mode 100644 index 0000000..0283ed5 --- /dev/null +++ b/third_party/FreeRDP/.github/workflows/macos.yml @@ -0,0 +1,22 @@ +name: macos-builder +on: + workflow_dispatch: + branches: [ master, stable* ] + schedule: + - cron: '30 5 * * SUN' + +jobs: + build: + runs-on: macos-latest + name: "Run macos build on mac-latest" + steps: + - name: "Check out source" + uses: actions/checkout@v4 + + - name: "Prepare environment" + run: | + brew install autoconf automake git libtool meson + + - name: "Run mac os build..." + run: | + ./scripts/bundle-mac-os.sh diff --git a/third_party/FreeRDP/.github/workflows/mingw.yml b/third_party/FreeRDP/.github/workflows/mingw.yml new file mode 100644 index 0000000..39798e3 --- /dev/null +++ b/third_party/FreeRDP/.github/workflows/mingw.yml @@ -0,0 +1,35 @@ +name: mingw-builder +on: + workflow_dispatch: + branches: [ master, stable* ] + schedule: + - cron: '30 5 * * SUN' + +jobs: + build: + runs-on: ubuntu-latest + name: "Run mingw build on ubuntu-latest" + steps: + - name: "Check out source" + uses: actions/checkout@v4 + + - name: "Prepare environment" + run: | + sudo apt-get update -q -y + sudo apt-get install -q -y \ + git \ + nasm \ + meson \ + cmake \ + ninja-build \ + mingw-w64 \ + mingw-w64-tools \ + binutils-mingw-w64 + + - name: "Run mingw [shared] build..." + run: | + ./scripts/mingw.sh + + - name: "Run mingw [static] build..." + run: | + ./scripts/mingw.sh -c -s --clean-first diff --git a/third_party/FreeRDP/.github/workflows/timezone-update.yml b/third_party/FreeRDP/.github/workflows/timezone-update.yml new file mode 100644 index 0000000..8be7e4d --- /dev/null +++ b/third_party/FreeRDP/.github/workflows/timezone-update.yml @@ -0,0 +1,61 @@ +# This workflow will build a .NET project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net + +name: timezone-update + +on: + workflow_dispatch: + branches: [ master, stable* ] + schedule: + - cron: "0 5 11 * *" + +jobs: + build: + runs-on: windows-latest + + permissions: + contents: write + pull-requests: write + + steps: + - uses: actions/checkout@v4 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + - name: Configure CMake + run: cmake -G"Visual Studio 17 2022" -Bbuild -Swinpr\libwinpr\timezone\utils + - name: Restore dependencies + run: dotnet restore build\tzextract.sln + - name: Build & Install CMake + run: cmake --build build --config Release + - name: Update timezones + run: build\Release\tzextract.exe winpr\libwinpr\timezone + - name: Format code + run: | + clang-format -i --style=file:.clang-format winpr/libwinpr/timezone/WindowsZones.c + clang-format -i --style=file:.clang-format winpr/libwinpr/timezone/TimeZoneNameMap.c + clang-format -i --style=file:.clang-format winpr/libwinpr/timezone/TimeZoneNameMap_static.h + clang-format -i --style=file:.clang-format winpr/libwinpr/timezone/TimeZoneNameMap.json + - name: Create Pull Request + id: cpr + uses: peter-evans/create-pull-request@v6 + with: + commit-message: Update timezone definitions + committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> + author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com> + signoff: false + branch: timezone-patches + branch-suffix: timestamp + delete-branch: true + title: '[timezones] Update definitions' + body: | + Timezone update + - Auto-generated by [create-pull-request][1] + + [1]: https://github.com/peter-evans/create-pull-request + labels: | + automated pr + assignees: akallabeth + reviewers: akallabeth + draft: false diff --git a/third_party/FreeRDP/.gitignore b/third_party/FreeRDP/.gitignore new file mode 100644 index 0000000..0726b4c --- /dev/null +++ b/third_party/FreeRDP/.gitignore @@ -0,0 +1,5 @@ +**/CMakeCache.txt +**/CMakeFiles +build +checker +abi-checker diff --git a/third_party/FreeRDP/CMakeCPack.cmake b/third_party/FreeRDP/CMakeCPack.cmake new file mode 100644 index 0000000..d28454f --- /dev/null +++ b/third_party/FreeRDP/CMakeCPack.cmake @@ -0,0 +1,98 @@ +# Generate .txt license file for CPack (PackageMaker requires a file extension) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/LICENSE ${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt @ONLY) + +# Workaround to remove c++ compiler macros and defines for Eclipse. +# If c++ macros/defines are set __cplusplus is also set which causes +# problems when compiling freerdp/jni. To prevent this problem we set the macros to "". + +if(ANDROID AND CMAKE_EXTRA_GENERATOR STREQUAL "Eclipse CDT4") + set(CMAKE_EXTRA_GENERATOR_CXX_SYSTEM_DEFINED_MACROS "") + message(STATUS "Disabled CXX system defines for eclipse (workaround).") +endif() + +set(CPACK_SOURCE_IGNORE_FILES "/\\\\.git/;/\\\\.gitignore;/CMakeCache.txt") + +if(NOT WIN32) + if(APPLE AND (NOT IOS)) + + if(WITH_SERVER) + set(CPACK_PACKAGE_EXECUTABLES ${CPACK_PACKAGE_EXECUTABLES} "mfreerdp-server") + endif() + endif() + + if(WITH_X11) + set(CPACK_PACKAGE_EXECUTABLES "xfreerdp") + + if(WITH_SERVER) + set(CPACK_PACKAGE_EXECUTABLES ${CPACK_PACKAGE_EXECUTABLES} "xfreerdp-server") + endif() + endif() +endif() + +set(CPACK_SYSTEM_NAME "${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}") +set(CPACK_TOPLEVEL_TAG "${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}") + +string(TOLOWER ${CMAKE_PROJECT_NAME} CMAKE_PROJECT_NAME_lower) +set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME_lower}-${FREERDP_VERSION_FULL}-${CPACK_SYSTEM_NAME}") +set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME_lower}-${FREERDP_VERSION_FULL}-${CPACK_SYSTEM_NAME}") + +set(CPACK_PACKAGE_NAME "FreeRDP") +set(CPACK_PACKAGE_VENDOR "FreeRDP") +set(CPACK_PACKAGE_VERSION ${FREERDP_VERSION_FULL}) +set(CPACK_PACKAGE_VERSION_MAJOR ${FREERDP_VERSION_MAJOR}) +set(CPACK_PACKAGE_VERSION_MINOR ${FREERDP_VERSION_MINOR}) +set(CPACK_PACKAGE_VERSION_PATCH ${FREERDP_VERSION_REVISION}) +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "FreeRDP: A Remote Desktop Protocol Implementation") + +set(CPACK_PACKAGE_CONTACT "Marc-Andre Moreau") +set(CPACK_DEBIAN_PACKAGE_MAINTAINER "marcandre.moreau@gmail.com") +set(CPACK_DEBIAN_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR}) + +set(CPACK_PACKAGE_INSTALL_DIRECTORY "FreeRDP") +set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt") +set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt") + +set(CPACK_NSIS_MODIFY_PATH ON) +set(CPACK_PACKAGE_ICON "${PROJECT_SOURCE_DIR}/resources\\\\FreeRDP_Install.bmp") +set(CPACK_NSIS_MUI_ICON "${PROJECT_SOURCE_DIR}/resources\\\\FreeRDP_Icon_96px.ico") +set(CPACK_NSIS_MUI_UNICON "${PROJECT_SOURCE_DIR}/resource\\\\FreeRDP_Icon_96px.ico") + +set(CPACK_COMPONENTS_ALL client server libraries headers symbols tools) + +if(MSVC) + string(FIND ${CMAKE_MSVC_RUNTIME_LIBRARY} "DLL" IS_SHARED) + + if(NOT IS_SHARED STREQUAL "-1") + set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE) + include(InstallRequiredSystemLibraries) + + install(PROGRAMS ${CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT libraries) + endif() +endif() + +set(CPACK_COMPONENT_CLIENT_DISPLAY_NAME "Client") +set(CPACK_COMPONENT_CLIENT_GROUP "Applications") + +set(CPACK_COMPONENT_SERVER_DISPLAY_NAME "Server") +set(CPACK_COMPONENT_SERVER_GROUP "Applications") + +set(CPACK_COMPONENT_LIBRARIES_DISPLAY_NAME "Libraries") +set(CPACK_COMPONENT_LIBRARIES_GROUP "Runtime") + +set(CPACK_COMPONENT_HEADERS_DISPLAY_NAME "Headers") +set(CPACK_COMPONENT_HEADERS_GROUP "Development") + +set(CPACK_COMPONENT_SYMBOLS_DISPLAY_NAME "Symbols") +set(CPACK_COMPONENT_SYMBOLS_GROUP "Development") + +set(CPACK_COMPONENT_TOOLS_DISPLAY_NAME "Tools") +set(CPACK_COMPONENT_TOOLS_GROUP "Applications") + +set(CPACK_COMPONENT_GROUP_RUNTIME_DESCRIPTION "Runtime") +set(CPACK_COMPONENT_GROUP_APPLICATIONS_DESCRIPTION "Applications") +set(CPACK_COMPONENT_GROUP_DEVELOPMENT_DESCRIPTION "Development") + +configure_file("${PROJECT_SOURCE_DIR}/CMakeCPackOptions.cmake.in" "${PROJECT_BINARY_DIR}/CMakeCPackOptions.cmake" @ONLY) +set(CPACK_PROJECT_CONFIG_FILE "${PROJECT_BINARY_DIR}/CMakeCPackOptions.cmake") + +include(CPack) diff --git a/third_party/FreeRDP/CMakeCPackOptions.cmake.in b/third_party/FreeRDP/CMakeCPackOptions.cmake.in new file mode 100644 index 0000000..826eaa1 --- /dev/null +++ b/third_party/FreeRDP/CMakeCPackOptions.cmake.in @@ -0,0 +1,10 @@ +# This file is configured at cmake time, and loaded at cpack time. +# To pass variables to cpack from cmake, they must be configured in this file. + +if("${CPACK_GENERATOR}" STREQUAL "PackageMaker") + if(CMAKE_PACKAGE_QTGUI) + set(CPACK_PACKAGE_DEFAULT_LOCATION "/Applications") + else() + set(CPACK_PACKAGE_DEFAULT_LOCATION "/usr") + endif() +endif() diff --git a/third_party/FreeRDP/CMakeLists.txt b/third_party/FreeRDP/CMakeLists.txt new file mode 100644 index 0000000..a7fe20c --- /dev/null +++ b/third_party/FreeRDP/CMakeLists.txt @@ -0,0 +1,603 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2011 O.S. Systems Software Ltda. +# Copyright 2011 Otavio Salvador +# Copyright 2011 Marc-Andre Moreau +# Copyright 2012 HP Development Company, LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 3.13) + +if(POLICY CMP0091) + cmake_policy(SET CMP0091 NEW) +endif() +project(FreeRDP LANGUAGES C) + +add_custom_target(fuzzers COMMENT "Build fuzzers") + +if(NOT PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR) + # Git auto-ignore out-of-source build directory + file(GENERATE OUTPUT .gitignore CONTENT "*") +endif() + +if(NOT DEFINED VENDOR) + set(VENDOR "FreeRDP" CACHE STRING "FreeRDP package vendor") +endif() + +if(NOT DEFINED PRODUCT) + set(PRODUCT "FreeRDP" CACHE STRING "FreeRDP package name") +endif() + +if(NOT DEFINED PROJECT_URL) + set(PROJECT_URL "https://freerdp.com" CACHE STRING "FreeRDP package url") +endif() + +if(NOT DEFINED FREERDP_VENDOR) + set(FREERDP_VENDOR 1) +endif() + +if(NOT WIN32 AND NOT ANDROID) + if(APPLE) + set(OPT_DEFAULT_VAL OFF) + else() + set(OPT_DEFAULT_VAL ON) + endif() + option(WITH_X11 "build X11 client/server" ${OPT_DEFAULT_VAL}) +endif() + +# Enable coverity related pragma definitions +if(COVERITY_BUILD) + add_compile_definitions(COVERITY_BUILD) +endif() + +# Include our extra modules +list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/) + +include(ProjectCStandard) +include(PkgConfigHelpers) + +# Check for cmake compatibility (enable/disable features) +include(CheckCmakeCompat) + +# Include cmake modules +if(WITH_CLANG_FORMAT) + include(ClangFormat) +endif() + +include(CompilerFlags) +include(CheckIncludeFiles) +include(CheckLibraryExists) +include(CheckSymbolExists) +include(CheckStructHasMember) +include(TestBigEndian) +include(CompilerDetect) + +include(FindFeature) +include(ShowCMakeVars) +include(ConfigOptions) +include(FeatureSummary) +include(CheckCCompilerFlag) +include(CMakePackageConfigHelpers) +include(InstallFreeRDPMan) +include(GetGitRevisionDescription) +include(SetFreeRDPCMakeInstallDir) +include(Doxygen) +include(GetSysconfDir) + +# FreeRDP internal builds should always check results +add_compile_definitions(WINPR_DEFINE_ATTR_NODISCARD) + +# Soname versioning +set(BUILD_NUMBER 0) +if($ENV{BUILD_NUMBER}) + set(BUILD_NUMBER $ENV{BUILD_NUMBER}) +endif() + +include(GetProjectVersion) +get_project_version( + FREERDP_VERSION_MAJOR FREERDP_VERSION_MINOR FREERDP_VERSION_REVISION FREERDP_VERSION_SUFFIX GIT_REVISION +) + +set(FREERDP_API_VERSION "${FREERDP_VERSION_MAJOR}") +set(FREERDP_VERSION "${FREERDP_VERSION_MAJOR}.${FREERDP_VERSION_MINOR}.${FREERDP_VERSION_REVISION}") +if(FREERDP_VERSION_SUFFIX) + set(FREERDP_VERSION_FULL "${FREERDP_VERSION}-${FREERDP_VERSION_SUFFIX}") +else() + set(FREERDP_VERSION_FULL "${FREERDP_VERSION}") +endif() +message("FREERDP_VERSION=${FREERDP_VERSION_FULL}") + +message(STATUS "Git Revision ${GIT_REVISION}") + +# MSVC compatibility with system headers +add_compile_definitions(NONAMELESSUNION) + +# Make the detected version available as default version for all subprojects +set(FREERDP_DEFAULT_PROJECT_VERSION ${FREERDP_VERSION} CACHE STRING INTERNAL) + +set(FREERDP_MAJOR_DIR "freerdp${FREERDP_VERSION_MAJOR}") +set(FREERDP_INCLUDE_DIR "include/${FREERDP_MAJOR_DIR}/") + +option(WITH_SMARTCARD_EMULATE "Emulate smartcards instead of redirecting readers" ON) +if(WITH_SMARTCARD_EMULATE) + add_compile_definitions(WITH_SMARTCARD_EMULATE) + find_package(ZLIB REQUIRED) +endif() + +option(WITH_FREERDP_DEPRECATED "Build FreeRDP deprecated symbols" OFF) +if(WITH_FREERDP_DEPRECATED) + add_compile_definitions(WITH_FREERDP_DEPRECATED) +endif() + +option(WITHOUT_FREERDP_3x_DEPRECATED "Build FreeRDP 3x deprecated symbols" OFF) +if(WITH_FREERDP_3x_DEPRECATED) + message(WARNING "WITH_FREERDP_3x_DEPRECATED has been replaced with WITHOUT_FREERDP_3x_DEPRECATED") + set(WITHOUT_FREERDP_3x_DEPRECATED OFF) +endif() + +option(WITH_FREERDP_DEPRECATED_COMMANDLINE "Build FreeRDP deprecated command line options" OFF) +if(WITH_FREERDP_DEPRECATED_COMMANDLINE) + add_compile_definitions(WITH_FREERDP_DEPRECATED_COMMANDLINE) +endif() + +# Make paths absolute +if(CMAKE_INSTALL_PREFIX) + get_filename_component(CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" ABSOLUTE) +endif() +if(FREERDP_EXTERNAL_PATH) + get_filename_component(FREERDP_EXTERNAL_PATH "${FREERDP_EXTERNAL_PATH}" ABSOLUTE) +endif() + +# Allow to search the host machine for git/ccache +if(CMAKE_CROSSCOMPILING) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) +endif(CMAKE_CROSSCOMPILING) + +find_program(CCACHE ccache) +if(CCACHE AND WITH_CCACHE) + if(NOT DEFINED CMAKE_C_COMPILER_LAUNCHER) + set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE}) + endif(NOT DEFINED CMAKE_C_COMPILER_LAUNCHER) +endif(CCACHE AND WITH_CCACHE) + +if(CMAKE_CROSSCOMPILING) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY) +endif(CMAKE_CROSSCOMPILING) +# /Allow to search the host machine for git/ccache + +# Turn on solution folders (2.8.4+) +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +option(CTEST_OUTPUT_ON_FAILURE ON "show verbose output on CTest failures") +if(BUILD_TESTING_INTERNAL) + set(EXPORT_ALL_SYMBOLS ON CACHE BOOL "testing default" FORCE) + add_compile_definitions(BUILD_TESTING_INTERNAL) +elseif(BUILD_TESTING) + set(EXPORT_ALL_SYMBOLS OFF CACHE BOOL "testing default" FORCE) +endif() + +include(ExportAllSymbols) + +set(THREAD_PREFER_PTHREAD_FLAG TRUE) + +if(NOT IOS) + find_package(Threads REQUIRED) +endif() + +if(WIN32) + add_compile_definitions(UNICODE _UNICODE) + add_compile_definitions(_CRT_SECURE_NO_WARNINGS) + add_compile_definitions(WIN32_LEAN_AND_MEAN) + add_compile_definitions(_WINSOCK_DEPRECATED_NO_WARNINGS) + + set(CMAKE_DL_LIBS "") + set(CMAKE_USE_RELATIVE_PATH ON) + string(TIMESTAMP RC_VERSION_YEAR "%Y") + + if(NOT DEFINED CMAKE_WINDOWS_VERSION) + set(CMAKE_WINDOWS_VERSION "WIN7") + endif() + + if(CMAKE_WINDOWS_VERSION STREQUAL "WINXP") + add_compile_definitions(WINVER=0x0501 _WIN32_WINNT=0x0501) + elseif(CMAKE_WINDOWS_VERSION STREQUAL "WIN7") + add_compile_definitions(WINVER=0x0601 _WIN32_WINNT=0x0601) + elseif(CMAKE_WINDOWS_VERSION STREQUAL "WIN8") + add_compile_definitions(WINVER=0x0602 _WIN32_WINNT=0x0602) + elseif(CMAKE_WINDOWS_VERSION STREQUAL "WIN10") + add_compile_definitions(WINVER=0x0A00 _WIN32_WINNT=0x0A00) + endif() + + # Set product and vendor for dll and exe version information. + set(RC_VERSION_VENDOR ${VENDOR}) + set(RC_VERSION_PRODUCT ${PRODUCT}) + set(RC_VERSION_PATCH ${BUILD_NUMBER}) + set(RC_VERSION_DESCRIPTION + "${FREERDP_VERSION_FULL} ${GIT_REVISION} ${CMAKE_WINDOWS_VERSION} ${CMAKE_SYSTEM_PROCESSOR}" + ) + + if(FREERDP_EXTERNAL_SSL_PATH) + set(OPENSSL_ROOT_DIR ${FREERDP_EXTERNAL_SSL_PATH}) + endif() +endif() + +add_compile_definitions(FREERDP_EXPORTS) + +# Mac OS X +if(APPLE) + if(IOS) + if(NOT FREERDP_IOS_EXTERNAL_SSL_PATH) + message( + STATUS + "FREERDP_IOS_EXTERNAL_SSL_PATH not set! Required if openssl is not found in the iOS SDK (which usually isn't" + ) + endif() + set(CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH} ${FREERDP_IOS_EXTERNAL_SSL_PATH}) + set_property(GLOBAL PROPERTY XCODE_ATTRIBUTE_SKIP_INSTALL YES) + endif(IOS) + + # Temporarily disabled, causes the cmake script to be reexecuted, causing the compilation to fail. + # Workaround: specify the parameter in the command-line + # if(WITH_CLANG) + # set(CMAKE_C_COMPILER "clang") + # endif() + + if(WITH_VERBOSE) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -v") + endif() +endif(APPLE) + +# Android +if(ANDROID) + set_property(GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS ${ANDROID_LIBRARY_USE_LIB64_PATHS}) + + if(ANDROID_ABI STREQUAL arm64-v8a) + include(CheckCCompilerFlag) + check_c_compiler_flag("-mfloat-abi=softfp" ABI_SOFTFP_SUPPORTED) + + if(ABI_SOFTFP_SUPPORTED) + # https://github.com/android/ndk/issues/910 + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mfloat-abi=softfp") + endif() + endif() + + add_compile_definitions("$<$:NDK_DEBUG=1>") + + # NOTE: Manually add -gdwarf-3, as newer toolchains default to -gdwarf-4, + # which is not supported by the gdbserver binary shipped with + # the android NDK (tested with r9b) + add_compile_options("$<$:-gdwarf-3>") + add_link_options(-llog) + + # CMAKE_PREFIX_PATH detection is broken in most Android toolchain files + # Append it to CMAKE_FIND_ROOT_PATH and avoid potential duplicates + list(APPEND CMAKE_FIND_ROOT_PATH ${CMAKE_PREFIX_PATH}) + list(REMOVE_DUPLICATES CMAKE_FIND_ROOT_PATH) + + if(NOT FREERDP_EXTERNAL_PATH) + if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/external/") + set(FREERDP_EXTERNAL_PATH "${CMAKE_CURRENT_SOURCE_DIR}/external/") + else() + message(STATUS "FREERDP_EXTERNAL_PATH not set!") + endif() + endif() + + list(APPEND CMAKE_INCLUDE_PATH ${FREERDP_EXTERNAL_PATH}/${ANDROID_ABI}/include) + list(APPEND CMAKE_LIBRARY_PATH ${FREERDP_EXTERNAL_PATH}/${ANDROID_ABI}/) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH) + + if(WITH_GPROF) + configure_file( + ${PROJECT_SOURCE_DIR}/scripts/gprof_generate.sh.in ${PROJECT_BINARY_DIR}/scripts/gprof_generate.sh @ONLY + ) + endif(WITH_GPROF) +endif() + +if(UNIX OR CYGWIN) + set(WAYLAND_FEATURE_TYPE "RECOMMENDED") +else() + set(WAYLAND_FEATURE_TYPE "DISABLED") +endif() + +if(WITH_PCSC_WINPR) + find_package(PCSCWinPR) +endif() + +set(WAYLAND_FEATURE_PURPOSE "Wayland") +set(WAYLAND_FEATURE_DESCRIPTION "Wayland client") + +set(OPENSSL_FEATURE_TYPE "REQUIRED") +set(OPENSSL_FEATURE_PURPOSE "cryptography") +set(OPENSSL_FEATURE_DESCRIPTION "encryption, certificate validation, hashing functions") + +set(MBEDTLS_FEATURE_TYPE "OPTIONAL") +set(MBEDTLS_FEATURE_PURPOSE "cryptography") +set(MBEDTLS_FEATURE_DESCRIPTION "[experimental] encryption, certificate validation, hashing functions") + +set(PCSC_FEATURE_TYPE "RECOMMENDED") +set(PCSC_FEATURE_PURPOSE "smart card") +set(PCSC_FEATURE_DESCRIPTION "smart card device redirection") + +set(OPENH264_FEATURE_TYPE "OPTIONAL") +set(OPENH264_FEATURE_PURPOSE "codec") +set(OPENH264_FEATURE_DESCRIPTION "use OpenH264 library") + +set(GSM_FEATURE_TYPE "OPTIONAL") +set(GSM_FEATURE_PURPOSE "codec") +set(GSM_FEATURE_DESCRIPTION "GSM audio codec library") + +set(LAME_FEATURE_TYPE "OPTIONAL") +set(LAME_FEATURE_PURPOSE "codec") +set(LAME_FEATURE_DESCRIPTION "lame MP3 audio codec library") + +set(FAAD2_FEATURE_TYPE "OPTIONAL") +set(FAAD2_FEATURE_PURPOSE "codec") +set(FAAD2_FEATURE_DESCRIPTION "FAAD2 AAC audio codec library") + +set(FAAC_FEATURE_TYPE "OPTIONAL") +set(FAAC_FEATURE_PURPOSE "codec") +set(FAAC_FEATURE_DESCRIPTION "FAAC AAC audio codec library") + +set(SOXR_FEATURE_TYPE "OPTIONAL") +set(SOXR_FEATURE_PURPOSE "codec") +set(SOXR_FEATURE_DESCRIPTION "SOX audio resample library") + +if(WIN32) + set(WAYLAND_FEATURE_TYPE "DISABLED") + set(PCSC_FEATURE_TYPE "DISABLED") +endif() + +if(APPLE) + set(WAYLAND_FEATURE_TYPE "DISABLED") + if(IOS) + set(PCSC_FEATURE_TYPE "DISABLED") + endif() +endif() + +if(ANDROID) + set(WAYLAND_FEATURE_TYPE "DISABLED") + set(PCSC_FEATURE_TYPE "DISABLED") +endif() + +if(NOT WITHOUT_FREERDP_3x_DEPRECATED) + find_feature(Wayland ${WAYLAND_FEATURE_TYPE} ${WAYLAND_FEATURE_PURPOSE} ${WAYLAND_FEATURE_DESCRIPTION}) +endif() + +option(WITH_LIBRESSL "[experimental] build with LibreSSL" OFF) +if(WITH_LIBRESSL) + message(WARNING "-DWITH_LIBRESSL=ON: LibreSSL enabled. Expect incompatibilities.") + message(WARNING "Only OpenSSL is tested on each release, other implementations depend on external contributions") + + find_package(LibreSSL REQUIRED) + include_directories(SYSTEM ${LibreSSL_INCLUDE_DIRS}) + set(OPENSSL_INCLUDE_DIR ${LIBRESSL_INCLUDE_DIR}) + set(OPENSSL_LIBRARIES ${LIBRESSL_LIBRARIES}) + set(OPENSSL_CRYPTO_LIBRARIES ${LIBRESSL_LIBRARIES}) + set(WITH_OPENSSL ON) + set(OPENSSL_FOUND ON) + add_compile_definitions("WITH_LIBRESSL") + add_compile_definitions("WITH_OPENSSL") +else() + find_feature(OpenSSL ${OPENSSL_FEATURE_TYPE} ${OPENSSL_FEATURE_PURPOSE} ${OPENSSL_FEATURE_DESCRIPTION}) + find_feature(MbedTLS ${MBEDTLS_FEATURE_TYPE} ${MBEDTLS_FEATURE_PURPOSE} ${MBEDTLS_FEATURE_DESCRIPTION}) +endif() + +find_feature(PCSC ${PCSC_FEATURE_TYPE} ${PCSC_FEATURE_PURPOSE} ${PCSC_FEATURE_DESCRIPTION}) + +if(WITH_DSP_FFMPEG OR WITH_VIDEO_FFMPEG OR WITH_FFMPEG) + find_package(FFmpeg REQUIRED COMPONENTS AVUTIL AVCODEC) +endif() + +find_feature(OpenH264 ${OPENH264_FEATURE_TYPE} ${OPENH264_FEATURE_PURPOSE} ${OPENH264_FEATURE_DESCRIPTION}) +find_feature(GSM ${GSM_FEATURE_TYPE} ${GSM_FEATURE_PURPOSE} ${GSM_FEATURE_DESCRIPTION}) +find_feature(LAME ${LAME_FEATURE_TYPE} ${LAME_FEATURE_PURPOSE} ${LAME_FEATURE_DESCRIPTION}) +find_feature(FAAD2 ${FAAD2_FEATURE_TYPE} ${FAAD2_FEATURE_PURPOSE} ${FAAD2_FEATURE_DESCRIPTION}) +find_feature(FAAC ${FAAC_FEATURE_TYPE} ${FAAC_FEATURE_PURPOSE} ${FAAC_FEATURE_DESCRIPTION}) +find_feature(soxr ${SOXR_FEATURE_TYPE} ${SOXR_FEATURE_PURPOSE} ${SOXR_FEATURE_DESCRIPTION}) + +option(WITH_OPENCL "[experimental] enable OpenCL support for primitives" OFF) +if(WITH_OPENCL) + find_package(OpenCL REQUIRED) +endif() + +if(WITH_OPENH264 AND NOT WITH_OPENH264_LOADING) + option(WITH_OPENH264_LOADING "Use LoadLibrary to load openh264 at runtime" OFF) +endif(WITH_OPENH264 AND NOT WITH_OPENH264_LOADING) + +# Version check, if we have detected FFMPEG but the version is too old +# deactivate it as sound backend. +if(WITH_DSP_FFMPEG) + if(AVCODEC_VERSION VERSION_LESS "57.48.101") + message( + WARNING + "FFmpeg version detected (${AVCODEC_VERSION}) is too old. (Require at least 57.48.101 for sound). Deactivating" + ) + set(WITH_DSP_FFMPEG OFF) + endif() +endif(WITH_DSP_FFMPEG) + +if(WITH_OPENH264 AND NOT OPENH264_FOUND) + message(FATAL_ERROR "OpenH264 support requested but not detected") +endif() +set(WITH_OPENH264 ${OPENH264_FOUND}) + +if(OPENSSL_FOUND) + add_compile_definitions("WITH_OPENSSL") + message(STATUS "Using OpenSSL Version: ${OPENSSL_VERSION}") + include_directories(SYSTEM ${OPENSSL_INCLUDE_DIR}) +endif() + +if(MBEDTLS_FOUND) + message(WARNING "-DWITH_MBEDTLS=ON: Mbed-TLS enabled. Expect incompatibilities") + message(WARNING "Only OpenSSL is tested on each release, other implementations depend on external contributions") + add_compile_definitions("WITH_MBEDTLS") +endif() + +if(WITH_OPENH264 OR WITH_MEDIA_FOUNDATION OR WITH_VIDEO_FFMPEG OR WITH_MEDIACODEC) + set(WITH_GFX_H264 ON) +else() + set(WITH_GFX_H264 OFF) +endif() + +# Android expects all libraries to be loadable +# without paths. +if(ANDROID OR WIN32 OR MAC_BUNDLE) + set(PLUGIN_ABS_PATHS_DEFAULT OFF) +else() + set(PLUGIN_ABS_PATHS_DEFAULT ON) +endif() +option(WITH_ABSOLUTE_PLUGIN_LOAD_PATHS "Load plugins with absolute paths" ${PLUGIN_ABS_PATHS_DEFAULT}) + +if(NOT WITH_ABSOLUTE_PLUGIN_LOAD_PATHS) + set(FREERDP_DATA_PATH "share") + if(NOT FREERDP_INSTALL_PREFIX) + set(FREERDP_INSTALL_PREFIX ".") + endif() + set(FREERDP_LIBRARY_PATH ".") + set(FREERDP_PLUGIN_PATH ".") +else() + set(FREERDP_DATA_PATH "${CMAKE_INSTALL_PREFIX}/share/${FREERDP_MAJOR_DIR}") + if(NOT FREERDP_INSTALL_PREFIX) + set(FREERDP_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") + endif() + set(FREERDP_LIBRARY_PATH "${CMAKE_INSTALL_LIBDIR}") + if(WIN32) + set(FREERDP_PLUGIN_PATH "${CMAKE_INSTALL_BINDIR}/${FREERDP_MAJOR_DIR}") + else() + set(FREERDP_PLUGIN_PATH "${CMAKE_INSTALL_LIBDIR}/${FREERDP_MAJOR_DIR}") + endif() +endif() +set(FREERDP_ADDIN_PATH "${FREERDP_PLUGIN_PATH}") + +# Path to put extensions +set(FREERDP_EXTENSION_POSTFIX "${FREERDP_MAJOR_DIR}/extensions") +set(FREERDP_EXTENSION_REL_PATH "${CMAKE_INSTALL_LIBDIR}/${FREERDP_EXTENSION_POSTFIX}") +set(FREERDP_EXTENSION_PATH "${CMAKE_INSTALL_FULL_LIBDIR}/${FREERDP_EXTENSION_POSTFIX}") + +# Proxy plugins path +if(NOT DEFINED PROXY_PLUGINDIR) + message("using default plugins location") + set(FREERDP_PROXY_PLUGINDIR "${FREERDP_PLUGIN_PATH}/proxy/") +else() + set(FREERDP_PROXY_PLUGINDIR "${PROXY_PLUGINDIR}") +endif() + +# Unit Tests + +include(CTest) + +if(BUILD_TESTING_INTERNAL OR BUILD_TESTING) + enable_testing() + + if(MSVC) + set(TESTING_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}") + else() + set(TESTING_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/Testing") + endif() +endif() + +include(CommonConfigOptions) + +if(FREERDP_UNIFIED_BUILD) + add_subdirectory(winpr) + if(NOT WITHOUT_FREERDP_3x_DEPRECATED) + if(WITH_WAYLAND) + add_subdirectory(uwac) + endif() + endif() + + if(WITH_SERVER) + option(WITH_RDTK "build rdtk toolkit" OFF) + if(WITH_RDTK) + add_subdirectory(rdtk) + endif() + endif() + + include_directories(${PROJECT_SOURCE_DIR}/winpr/include) + include_directories(${PROJECT_BINARY_DIR}/winpr/include) +else() + find_package(WinPR 3 REQUIRED) + include_directories(SYSTEM ${WinPR_INCLUDE_DIR}) +endif() + +option(WITH_AAD "Compile with support for Azure AD authentication" ${WITH_WINPR_JSON}) + +# Include directories +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) + +# Sub-directories + +if(WITH_THIRD_PARTY) + add_subdirectory(third-party) + if(NOT "${THIRD_PARTY_INCLUDES}" STREQUAL "") + include_directories(SYSTEM ${THIRD_PARTY_INCLUDES}) + endif() +endif() + +# used in libfreerdp subfolder as well +setfreerdpcmakeinstalldir(FREERDP_CMAKE_INSTALL_DIR "FreeRDP${FREERDP_VERSION_MAJOR}") + +add_subdirectory(libfreerdp) + +if(WITH_CHANNELS) + add_subdirectory(channels) +endif() + +if(WITH_CLIENT_COMMON OR WITH_CLIENT) + add_subdirectory(client) +endif() + +if(WITH_SERVER) + add_subdirectory(server) +endif() + +# must be after all targets have been added in libfreerdp and channel +install(EXPORT FreeRDPTargets DESTINATION ${FREERDP_CMAKE_INSTALL_DIR}) + +# Packaging + +set(CMAKE_CPACK_INCLUDE_FILE "CMakeCPack.cmake") + +if(NOT (VENDOR MATCHES "FreeRDP")) + if(DEFINED CLIENT_VENDOR_PATH) + if(EXISTS "${PROJECT_SOURCE_DIR}/${CLIENT_VENDOR_PATH}/CMakeCPack.cmake") + set(CMAKE_CPACK_INCLUDE_FILE "${CLIENT_VENDOR_PATH}/CMakeCPack.cmake") + endif() + endif() +endif() + +#message("VENDOR: ${VENDOR} CLIENT_VENDOR_PATH: ${CLIENT_VENDOR_PATH} CMAKE_CPACK_INCLUDE_FILE: ${CMAKE_CPACK_INCLUDE_FILE}") + +set(FREERDP_BUILD_CONFIG_LIST "") +get_cmake_property(res VARIABLES) +foreach(var ${res}) + if(var MATCHES "^WITH_*|^BUILD_TESTING*|^WINPR_HAVE_*") + list(APPEND FREERDP_BUILD_CONFIG_LIST "${var}=${${var}}") + endif() +endforeach() +string(REPLACE ";" " " FREERDP_BUILD_CONFIG "${FREERDP_BUILD_CONFIG_LIST}") + +add_subdirectory(include) + +include(${CMAKE_CPACK_INCLUDE_FILE}) + +message(STATUS "Intrinsic path configuration:") +#ShowCMakeVars("^CMAKE_INSTALL_PREFIX") +#ShowCMakeVars("^CMAKE_INSTALL_LIBDIR") +showcmakevars("^FREERDP_INSTALL_PREFIX|^FREERDP_LIBRARY_PATH|^FREERDP_PLUGIN_PATH") +showcmakevars("^FREERDP_ADDIN_PATH|^FREERDP_EXTENSION_PATH|^FREERDP_PROXY_PLUGINDIR") diff --git a/third_party/FreeRDP/ChangeLog b/third_party/FreeRDP/ChangeLog new file mode 100644 index 0000000..e81baa3 --- /dev/null +++ b/third_party/FreeRDP/ChangeLog @@ -0,0 +1,1481 @@ +# 2026-02-25 Version 3.23.0 + +A new release and again a lot of changes: +* We've received in depth analysis of FreeRDP client code and have addressed shortcomings uncovered by these. +CVE-2026-26965 +CVE-2026-26955 +CVE-2026-26271 +CVE-2026-25997 +CVE-2026-25959 +CVE-2026-25955 +CVE-2026-25954 +CVE-2026-25953 +CVE-2026-25952 +CVE-2026-25942 +CVE-2026-25941 + +Another weakness was reported, see https://github.com/FreeRDP/FreeRDP/security/advisories/GHSA-qcfc-ghxr-h927 + +* Configuration isolation was added. 3rd party client/server applications should check + the new API freerdp_setApplicationDetails and winpr_setApplicationDetails which allows + using a custom namespace for configuration files and runtime data per application +* For developers, we've marked most of the API with [[nodiscard]] now so compilers + might start complaining about unchecked return values now. This is intentional and should + give some incentive to clean up code. Functions where the return is optional have been + omitted. For the time being these checks are automatically applied for FreeRDP builds, external + projects can opt in by defining WINPR_DEFINE_ATTR_NODISCARD in their build system. +* For developers: Please start testing your applications against FreeRDP builds with + `-DWITHOUT_FREERDP_3x_DEPRECATED=ON` to ensure you're not using some soon to be removed API. +* SDL client did get a huge update, multimonitor and high DPI modes are now much improved +* We got a contribution for smartcard channel adding support for new attributes, so more + applications might work now. + +## What's Changed +* Sdl cleanup (#12202) +* [client,sdl] do not apply window offset (#12205) +* [client,sdl] add SDL_Error to exceptions (#12214) +* Rdp monitor log (#12215) +* [winpr,smartcard] implement some attributes (#12213) +* [client,windows] Fix return value checks for mouse event functions (#12279) +* [channels,rdpecam] fix sws context checks (#12272) +* [client,windows] Enhance error handling and context validation (#12264) +* [client,windows] Add window handle validation in RDP_EVENT_TYPE_WINDOW_NEW (#12261) +* [client,sdl] fix multimon/fullscreen on wayland (#12248) +* Vendor by app (#12207) +* [core,gateway] relax TSG parsing (#12283) +* [winpr,smartcard] simplify PCSC_ReadDeviceSystemName (#12273) +* [client,windows] Implement complete keyboard indicator synchronization (#12268) +* Fixes more more more (#12286) +* Use application details for names (#12285) +* warning cleanups (#12289) +* Warning cleanup (#12291) +* [client,windows] Enhance memory safety with NULL checks and resource protection (#12271) +* [client,x11] apply /size:xx% only once (#12293) +* Freerdp config test (#12295) +* [winpr,smartcard] fix returned attribute length (#12296) +* [client,SDL3] Fix properly handle smart-sizing with fullscreen (#12298) +* [core,test] fix use after free (#12299) +* Sign warnings (#12300) +* [cmake,compiler] disable -Wjump-misses-init (#12301) +* [codec,color] fix input length checks (#12302) +* [client,sdl] improve cursor updates, fix surface sizes (#12303) +* Sdl fullscreen (#12217) +* [client,sdl] fix move constructor of SdlWindow (#12305) +* [utils,smartcard] check stream length on padding (#12306) +* [android] Fix invert scrolling default value mismatch (#12309) +* Clear fix bounds checks (#12310) +* Winpr attr nodiscard fkt ptr (#12311) +* [codec,planar] fix missing destination bounds checks (#12312) +* [codec,clear] fix destination checks (#12315) +* NSC Codec fixes (#12317) +* Freerdp api nodiscard (#12313) +* [allocations] fix growth of preallocated buffers (#12319) +* Rdpdr simplify (#12320) +* Resource fix (#12323) +* [winpr,utils] ensure message queue capacity (#12322) +* [server,shadow] fix return and parameter checks (#12330) +* Shadow fixes (#12331) +* [rdtk,nodiscard] mark rdtk API nodiscard (#12329) +* [client,x11] fix XGetWindowProperty return handling (#12334) +* Win32 signal (#12335) +* [channel,usb] fix message parsing and creation (#12336) +* [cmake] Define WINPR_DEFINE_ATTR_NODISCARD (#12338) +* Proxy config fix (#12345) +* [codec,progressive] refine progressive decoding (#12347) +* [client,sdl] fix sdl_Pointer_New (#12350) +* [core,gateway] parse [MS-TSGU] 2.2.10.5 HTTP_CHANNEL_RESPONSE_OPTIONAL (#12353) +* X11 kbd sym (#12354) +* Windows compile warning fixes (#12357,#12358,#12359) + +## New Contributors +* @tsz8899 made their first contribution in (#12279) +* @morgan9e made their first contribution in (#12298) +* @Wladefant made their first contribution in (#12309) + +For a complete and detailed change log since the last release run: +git log 3.23.0...3.22.0 + +# 2026-01-28 Version 3.22.0 + +Major bugfix release: +* Complete overhaul of SDL client +* Introduction of new WINPR_ATTR_NODISCARD macro wrapping compiler or C language + version specific [[nodiscard]] attributes +* Addition of WINPR_ATTR_NODISCARD to (some) public API functions so usage errors + are producing warnings now +* Add some more stringify functions for logging +* We've received CVE reports, check + https://github.com/FreeRDP/FreeRDP/security/advisories for more details! + * @Keryer reported an issue affecting client and proxy: + * CVE-2026-23948 + * @ehdgks0627 did some more fuzzying and found quite a number of client side bugs. + * CVE-2026-24682 + * CVE-2026-24683 + * CVE-2026-24676 + * CVE-2026-24677 + * CVE-2026-24678 + * CVE-2026-24684 + * CVE-2026-24679 + * CVE-2026-24681 + * CVE-2026-24675 + * CVE-2026-24491 + * CVE-2026-24680 + +## What's Changed +* [core,info] fix missing NULL check (#12157) +* [gateway,tsg] fix TSG_PACKET_RESPONSE parsing (#12161) +* Allow querying auth identity with kerberos when running as a server (#12162) +* Sspi krb heimdal (#12163) +* Tsg fix idleTimeout parsing (#12167) +* [channels,smartcard] revert 649f7deee4e32ecedf0dcdfe571e54134b5be81e (#12166) +* [crypto] deprecate er and der modules (#12170) +* [channels,rdpei] lock full update, not only parts (#12175) +* [winpr,platform] add WINPR_ATTR_NODISCARD macro (#12178) +* Wlog cleanup (#12179) +* new stringify functions & touch API defines (#12180) +* Add support for querying SECPKG_ATTR_PACKAGE_INFO to NTLM and Kerberos (#12171) +* [channels,video] measure times in ns (#12184) +* [utils] Nodiscard (#12187) +* Error handling fixes (#12186) +* [channels,drdynvc] check pointer before reset (#12189) +* Winpr api def (#12190) +* [winpr,platform] drop C23 [[nodiscard]] (#12192) +* [gdi] add additional checks for a valid rdpGdi (#12194) +* Sdl3 high dpiv2 (#12173) +* peer: Disconnect if Logon() returned FALSE (#12196) +* [channels,rdpecam] fix PROPERTY_DESCRIPTION parsing (#12197) +* [channel,rdpsnd] only clean up thread before free (#12199) +* [channels,rdpei] add RDPINPUT_CONTACT_FLAG_UP (#12195) + + +For a complete and detailed change log since the last release run: +git log 3.22.0...3.21.0 + +# 2026-01-19 Version 3.21.0 + +Bugfix release with a few new API functions addressing shortcomings with +regard to input data validation. +Thanks to @ehdgks0627 we have fixed the following additional (medium) +client side vulnerabilities: +* CVE-2026-23530 +* CVE-2026-23531 +* CVE-2026-23532 +* CVE-2026-23533 +* CVE-2026-23534 +* CVE-2026-23732 +* CVE-2026-23883 +* CVE-2026-23884 + +## What's Changed +* [client,sdl] fix monitor resolution (#12142) +* [codec,progressive] fix progressive_rfx_upgrade_block (#12143) +* Krb cache fix (#12145) +* Rdpdr improved checks (#12141) +* Codec advanced length checks (#12146) +* Glyph fix length checks (#12151) +* Wlog printf format string checks (#12150) +* [warnings,format] fix format string warnings (#12152) +* Double free fixes (#12153) +* [clang-tidy] clean up code warnings (#12154) + +For a complete and detailed change log since the last release run: +git log 3.21.0...3.20.2 + +# 2026-01-14 Version 3.20.2 + +Patch release fixing a regression with gateway connections introduced with 3.20.1 + +## What's Changed +* Warnings and missing enumeration types (#12137) + +For a complete and detailed change log since the last release run: +git log 3.20.2...3.20.1 + +# 2026-01-14 Version 3.20.1 + +New years cleanup release. Fixes some issues reported and does a cleaning sweep +to bring down warnings. +Thanks to @ehdgks0627 doing some code review/testing we've uncovered the following +(medium) vulnerabilities: +* CVE-2026-22851 +* CVE-2026-22852 +* CVE-2026-22853 +* CVE-2026-22854 +* CVE-2026-22855 +* CVE-2026-22856 +* CVE-2026-22857 +* CVE-2026-22858 +* CVE-2026-22859 + +These affect FreeRDP based clients only, with the exception of CVE-2026-22858 +also affecting FreeRDP proxy. FreeRDP based servers are not affected. + +## What's Changed +* [ci,abi] use abigail-tools from repo (#12079) +* [ci,abi] fix missing ABI suppressions (#12080) +* [ci,abi] add missing functions to suppression list (#12081) +* [core,gateway] fix http response (#12095) +* [ci,mac] build openh264 from master branch (#12104) +* [client,sdl] lock primary while used (#12103) +* [client,sdl] show file selection dialog (#12083) +* Proxy fixes (#12106) +* [core,freerdp] fix race in freerdp_abort_connect_context (#12107) +* [server,proxy] make peer_list access thread-safe and fix leaks (#12108) +* Clang warning fixes (#12109) +* Tidy nsc (#12110) +* Clang warn fixes (#12105) +* Tcp refactor (#12113) +* [enum,cast] fix implicit enum casts (#12111) +* [client,common] fix /remoteGuard (#12115) +* Coverity warning fixes (#12116) +* [channels,rdpei] properly clean up server channel (#12119) +* [core,gateway] ignore unknown http headers (#12120) +* Asan fixes (#12121, #12124, #12124) +* [crypto,base64] do proper length checks (#12122) +* [core,gcc] fix integer promotion issue (#12126) +* [core,orders] fix brush update decoding (#12130) +* [client,sdl] fix +workarea (#12131) +* [channels,rdpear] add checks for itemSize (#12127) +* Fix dead lock in smartcard when using smartcard logon with emulated smartcard (#12132) + +For a complete and detailed change log since the last release run: +git log 3.20.1...3.20.0 + +# 2025-12-17 Version 3.20.0 + +## What's Changed +* Mingw fixes (#12070) +* [crypto,certificate_data] add some hostname sanitation (#12072) +* [client,common]: Fix loading of rdpsnd channel (#12074) +* [client,sdl] set touch and pen hints (#12076) + +For a complete and detailed change log since the last release run: +git log 3.20.0...3.19.1 + +# 2025-12-12 Version 3.19.1 + +## What's Changed +* [core,transport] improve SSL error logging (#12045) +* [utils,helpers] fix freerdp_settings_get_legacy_config_path (#12052) +* From stdin and sdl-creds improve (#12050) +* [crypto,certificate] sanitize hostnames (#12055) +* [channels,drdynvc] propagate error in dynamic channel (#12057) +* [CMake] make Mbed-TLS and LibreSSL experimental (#12058) +* Json fix (#12060) +* rdpecam: send sample only if it's available (#12061) +* [channels,rdpecam] allow MJPEG frame skip and direct passthrough (#12059) +* [winpr,utils] explicit NULL checks in jansson WINPR_JSON_ParseWithLength (#12064) +* [packaging,flatpak] remove xprop (#12065) + +For a complete and detailed change log since the last release run: +git log 3.19.1...3.19.0 + +# 2025-12-05 Version 3.19.0 + +Release addressing a regression (gateway transport failing) and some bugfixes + +## What's Changed +* [ci] add git-archive ignore list (#11994) +* [client,common] fix retry counter (#11996) +* [cmake] fix aarch64 neon detection (#11998) +* Fix response body existence check when using RDP Gateway (#12002) +* fix line clipping issue (#12005) +* Clip coord fix (#12006) +* [core,input] Add debug log to keyboard state sync (#12008) +* Update command line usage for gateway option (#12011) +* [codec,ffmpeg] 8.0 dropped AV_PROFILE_AAC_MAIN (#12012) +* [channels,audin] fix pulse memory leak (#12013) +* [channels,drive] Small performance improvements in drive channel (#12014) +* [winpr,utils] fix command line error logging (#12021) +* [common,test] Adjust AVC and H264 expectations. (#12020) +* drdynvc: implement compressed packet (#12028) +* [channels,rdpecam] improve log messages (#12029) +* Fix remote credential guard channel loading (#12031) +* Fix inverted ifdef (#12032) +* [core,nego] disable all enabled modes except the one requested (#12035) +* rdpear: handle basic NTLM commands and fix server-side (#12039) +* [smartcardlogon] Fix off-by-one error in `smartcard_hw_enumerateCerts` (#12042) +* rdpecam: fix camera sample grabbing (#12041) + +## New Contributors +* @kov-serg made their first contribution in (#12005) +* @alexiri made their first contribution in (#12011) +* @nteodosio made their first contribution in (#12020) + +For a complete and detailed change log since the last release run: +git log 3.19.0...3.18.0 + +# 2025-11-12 Version 3.18.0 + +Minor improvements and bugfix release. +Some user visible changes: +* Fix a regression reading passwords from stdin +* Fix a timer regression (µs instead of ms) +* Improved multitouch support +* Fix a bug with PLANAR codec (used with /bpp:32 or sometimes with /gfx) +* Better error handling for ARM transport (Entra) +* Fix audio encoder lag (microphone/AAC) with FFMPEG +* Support for janssen JSON library + +## What's Changed +* [core,arm] extract redirected username (#11873) +* [winpr,path] fix endianness issues (#11875) +* [cmake,pkg-config] properly set requires fields (#11876) +* [codec,planar] make test output verbose (#11877) +* [codec,planar] more test output (#11878) +* Planar fix sign (#11880) +* Entra fixes (#11881, #11882) +* Warn fixes cast (#11884) +* wst error handling (#11885) +* [winpr,json] add jansson support (#11886) +* [client,sdl] set metadata after command line parsing (#11890) +* [core,arm] add TARGET_BOOTING error code (#11889) +* [core] fix const correctness (#11891) +* [c,standard] use C99 inline (#11879) +* [winpr,pool] limit minimum threadpool size (#11897) +* Azure domain (#11892) +* [core,arm] fix TargetNetAddress size and checks (#11899) +* [winpr,json] fix a memory leak with jansson (#11901) +* Jansson fix (#11902) +* Bitmap fixes and unit tests (#11903) +* [channels,rdpecam] fix a memory leak (#11907) +* [common,settings] fix resize of TargetNetAddressess (#11905) +* Jansson ref count (#11908) +* [winpr,json] fix WINPR_JSON_AddItemToArray (#11909) +* [client,common] improve retry handling (#11910) +* Janssen version limit (#11911) +* Rdstls error code mapping (#11913) +* dsp_ffmpeg: fix latency buildup during resampling (#11912) +* [core,rdstls] improve logging (#11914) +* [client,common] fix parsing of enablerdsaadauth (#11915) +* Codec stringify (#11918) +* [core,tcp] fix a regression (#11919) +* [core,timer] fix reschedule interval (#11921) +* [winpr,timezone] update dotnet version for tzextract (#11927) +* [timezones] Update definitions by @github-actions[bot] in (#11928) +* [winpr,synch] Yield after a poll timeout in emscripten (#11929) +* [channels,audin] fix a leak in pulse backend (#11933) +* [crypto,x509] add missing OpenSSL include for d2i_RSA_PSS_PARAMS (#11942) +* [client,android] fix wrong type of variable (#11945) +* Revert smart sizing (#11946) +* Align width and height for AVC444 decoding to 32 (#11930) +* [crypto,tls] make cert warning more accurate (#11947) +* [core,timer] ensure all scheduled timers are handled (#11948) +* Fix build and run with optional channels (#11941) +* [channels,rdpei] fix not sending essential touch events (#11955) +* [CMake] mark WITH_VAAPI experimental (#11956) +* Config extension (#11961) +* [winpr,synch] Fix starvation in pollset_poll caused by emscripten_sleep (#11962) +* [utils] fix from-stdin (#11965) +* [client,x11] log mouse event types and call stack (#11966) +* libfreerdp: remove SIGUSR1 and SIGUSR2 from fatal signals (#11968) +* [input, virtualkey] Add Korean keys in XKB_KEYNAME_TABLE (#11977) +* [cache,glyph] overallocate to compensate for off by one (#11980) +* [client,common] improve multitouch mouse emulation (#11970) +* [core,gateway] improve response cookie handling (#11971) +* Revert "[core,gateway] improve arm transport" (#11983) +* Http request improvements (#11984) +* Log improve (#11985) +* [client,sdl] sdl2 dialog auth: remove std::move (#11986) + +## New Contributors +* @FriederHannenheim made their first contribution in (#11912) +* @ploosin made their first contribution in (#11955) + +For a complete and detailed change log since the last release run: +git log 3.18.0...3.17.2 + +# 2025-09-19 Version 3.17.2 + +Minor improvements and bugfix release. +Most notably resource usage (file handles) has been greatly reduced and +static build pkg-config have been fixed. +For users of xfreerdp RAILS/RemoteApp mode the switch to DesktopSession +mode has been fixed (working UAC screen) + +## What's Changed +* Findfirst fix (#11833) +* [channels,drive] tolerate drive_file_set_disposition_information (#11834) +* endianness fixes (#11835) +* fix(winpr): ncrypt_pkcs11: set correct PIV certificate labels (#11837) +* [cmake] fix versioning regression (#11832) +* Limit threadpool (#11840) +* [winpr,path] fix missing length check (#11841) +* [proxy,channels] better NULL checks (#11842) +* [codec,yuv] wrap step calculation (#11843) +* [winpr,sspi] log mechanisms not valid (#11844) +* settings: remove duplicate setting of GatewayAvdScope (#11845) +* [client,sdl] improve clipboard logging (#11849) +* rdpecam: add some new callbacks to the HAL (#11851) +* [proxy,modules] generate pkg-config files for modules (#11848) +* [cmake] static build: populate private (#11852) +* [proxy,modules] extend dynamic module loader (#11854) +* [winpr,threadpool] default minimum thread count (#11855) +* [core,tcp] unify setting of TCP_NODELAY (#11856) +* Planar fix (#11857) +* Fix quote parsing (#11858) +* Sdl mod: disable hotkeys (#11862) +* Aad auth fail (#11863) +* [clients] add checks from #11804 to all clients (#11865) +* [client,x11] fix rails/desktop switch (#11866) +* [client,x11] disable output during rail/desktop switch (#11867) +* [core,gateway] automatically accept ARM redirection (#11870) +* Update android deps (#11871) + +## New Contributors +* @TheBestTvarynka made their first contribution in #11837) + +For a complete and detailed change log since the last release run: +git log 3.17.2...3.17.1 + +# 2025-09-01 Version 3.17.1 + +Minor improvements and bugfix release. + +* most notably a memory leak was addressed +* fixed header files missing C++ guards +* xfreerdp as well as the SDL clients now support a system wide configuration file +* Heimdal kerberos support was improved +* builds with [MS-RDPEAR] now properly abort at configure if Heimdal is used + (this configuration was never supported, so ensure nobody compiles it that way) + +## What's Changed +* [client,sdl] always set sdl->windows_created (#11807) +* [winpr,synch] increase timeout for TestSynchCritical (#11808) +* Enable RDPECAM client in flatpak release (#11809) +* [proxy,channels] refactor dynamic channel (#11812) +* [core,settings] fix ReceivedCapabilities reset (#11814) +* Freebsd build fixes (#11815) +* [client,sdl] disable connection dialog (#11820) +* audin_oss: do not reset mic volume on capture start (#11822) +* add-x11-config-file (#11823) +* [client,sdl] fix global config evaluation (#11825) +* [sspi,negotiate] improve /auth-pkg-list parsing (#11826) +* Geometry channel fixes (#)11828) +* core/redirection: Ensure stream has enough space for all parameters (#11830) + +## New Contributors +* @omatasas made their first contribution in #11787 +* @sharkcz made their first contribution in #11808 +* @cvpcs made their first contribution in #11809 +* @Defenso-QTH made their first contribution in #11822 + +For a complete and detailed change log since the last release run: +git log 3.17.1...3.17.0 + +# 2025-08-22 Version 3.17.0 + +Bugfix release with (lots) of format string issues along with a few minor parser +issues fixed. Most notable (user visible) change is full X509 chain support for +client/server. + +## What's Changed +* [client,sdl2] fix build with webview (#11685) +* [core,nla] use wcslen for password length (#11687) +* Clear channel error prior to call channel init event proc (#11688) +* Warn args (#11689) +* [client,common] fix -mouse-motion (#11690) +* [core,proxy] fix IPv4 and IPv6 length (#11692) +* Regression fix2 (#11696) +* Log fixes (#11693) +* [common,settings] fix int casts (#11699) +* [core,connection] fix log level of several messages (#11697) +* [client,sdl] print current video driver (#11701) +* [crypto,tls] print big warning for /cert:ignore (#11704) +* [client,desktop] fix StartupWMClass setting (#11708) +* [cmake] unify version creation (#11711) +* [common,settings] force reallocation on caps copy (#11715) +* [manpages] Add example of keyboard remapping (#11718) +* Some fixes in Negotiate and NLA (#11722) +* [client,x11] fix clipboard issues (#11724) +* kerberos: do various tries for TGT retrieval in u2u (#11723) +* Cmdline escape strings (#11735) +* [winpr,utils] do not log command line arguments (#11736) +* [api,doc] Add stylesheed for doxygen (#11738) +* [core,proxy] fix BIO read methods (#11739) +* [client,common] fix sso_mib_get_access_token return value in error case (#11741) +* [crypto,tls] do not use context->settings->instance (#11749) +* winpr: re-introduce the credentials module (#11734) +* [winpr,timezone] ensure thread-safe initialization (#11754) +* core/redirection: Ensure stream has enough space for the certificate (#11762) +* [client,common] do not log success (#11766) +* Clean up bugs exposed on systems with high core counts (#11761) +* [cmake] add installWithRPATH (#11747) +* [clang-tidy] fix various warnings (#11769) +* Wlog improve type checks (#11774) +* [client,common] fix tenantid command line parsing (#11779) +* Proxy module static and shared linking support (#11768) +* LoadLibrary Null fix (#11786) +* [client,common] add freerdp_client_populate_settings_from_rdp_file_un… (#11780) +* Fullchain support (#11787) +* [client,x11] ignore floatbar events (#11771) +* [winpr,credentials] prefer utf-8 over utf-16-LE #11790 +* [proxy,modules] ignore bitmap-filter skip remaining #11789 + +## New Contributors +* @steelman made their first contribution in #11718 +* @pvachon made their first contribution in #11761 + +For a complete and detailed change log since the last release run: +git log 3.17.0...3.16.0 + +# 2025-06-16 Version 3.16.0 + +Bugfix release with (again) much improved SDL3 and X11 client + +## What's Changed +* Lots of improvements for the SDL3 client (#11502,#11504,#11516,#11546,#11552, + #11553,#11556,#11560,#11568,#11587,#11613,#11643,#11635,#11648,#11653,#11654, + #11661) +* Various X11 client improvements (#11619,#11612,#11620,#11624,#11625,#11660) +* Various Ci build fixes (#11543,#11554,#11570,#11571,#11575,#11577,#11579, + #11580,#11581,#11582,#11583,#11584,#11585,#11586) +* [utils,smartcard] Better logging and handling of output buffer too small + (#11503,#11565,#11636) +* Add a timer implementation (#11578,#11592,#11615) +* Various bugfixed for drive channel (#11569,#11601,#11637,#11647,#11659) +* add login through MS identity broker via sso-mib interface (#11600,#11608) +* Update flatpak build script in repo (#11609,#11610,#11621,#11670) +* Various AAD/Azure/Entra improvements (#11606,#11607,#11371,#11518) +* YUV420 primitives fixes (#11673,#11539) +* GCC Fixes (#11538) +* [core,settings] fix freerdp_device_collection_add (#11533) +* [core,proxy] detect address type (#11534) +* [core,test] refactor TestSettings (#11558) +* [core,test] improve settings test log (#11559) +* [core,activation] skip sending PDU_TYPE_DEACTIVATE_ALL (#11603) +* [core,transport] only free userContext if userContextSize > 0 (#11642) +* [core,info] Allow INFO_HIDEF_RAIL_SUPPORTED with RDP version RDP_VERS… (#11652) +* [core,gcc] use dynamic logger from rdpMcs (#11669) +* [core,settings] default MonitorIds size to MonitorDefArray size (#11671) +* Rdp security fixes (#11506) +* rdpei/server: Fix incorrect PDU length read (#11510) +* [winpr] Put '\0' when converting empty string to wstr (#11511) +* [common,settings] new settings (de)serialization API (#11508) +* [cache,glyph] fix GLYPH_FRAGMENT_USE (#11517) +* [winpr,sysinfo] use a single clock to provide System and Local time (#11520) +* [common,settings] fix add_string_or_null (#11522) +* Compiler warning fixes (#11523) +* fix [resources]: remove MimeType from desktop file (#11525) +* gcc: fix server-side connection with multiple monitor (#11527) +* [rdpsnd/client] add parameters to pulse snd device plugin (#11530) +* [crypto,key] do not deprecate new_from* (#11535) +* [winpr,file] Fix assert fail always when removing flags (#11540) +* FF_PROFILE Depreciation (#11542) +* [cmake] Fix finding ffmpeg under nonstandard prefixes (#11548) +* [client,android] update (#11555) +* Support 'Restrict Credential Delegation' mode (#11547) +* Support NLA in shadow server when running behind a Hyper-V proxy (#11549) +* [winpr,file] Add implementation of FileFlushFileBuffers (#11566) +* [winpr,file] add TestFileWriteFile testcase (#11567) +* [channels,rdpdr] expose device add/remove for clients (#11564) +* Deb & RPM update (#11572) +* Transport fix (#11573) +* [winpr,sspi] add kerberos string len checks (#11590) +* [winpr,sspi] assert kerberos principal (#11591) +* [channels,video] fix NULL dereference (#11597) +* Reconnect strict (#11599) +* [rdpdr,hotplug] fix passing of device::Id back to caller (#11617) +* [client,common] lock clipboard on update (#11618) +* [client,cliprdr] refactor file clipboard (#11627) +* [winpr,wtypes] align BOOL typedef with objc.h header (#11632) +* [stream] reset pool array size after clearing (#11631) +* fix compile errors: xfc not defined even if with WITH_XCURSOR=ON (#11629) +* [utils,helpers] add missing WINPR_ATTR_MALLOC (#11633) +* JSON configuration helpers (#11634) +* [client,common] (re)initialize fuse root in cliprdr_file_context_init (#11646) +* [WaitForXXObject] use infinite timeout where possible (#11651) +* [channels,printer] fix missing include (#11663) +* [winpr,file] fix definition of winpr_CreateFile (#11664) + +## New Contributors +* @lazy5f made their first contribution in #11511 +* @EndlessEden made their first contribution in #11542 +* @thestr4ng3r made their first contribution in #11548 +* @ljaeh0121 made their first contribution in #11566 +* @rupran made their first contribution in #11600 +* @asterwyx made their first contribution in #11631 +* @ligangcc made their first contribution in #11629 +* @motor-dev made their first contribution in #11635 + +For a complete and detailed change log since the last release run: +git log 3.16.0...3.15.0 + +# 2025-04-14 Version 3.15.0 + +Bugfix release with much improved SDL3 client and relative mouse input support + +## What's Changed +* [client,sdl] fix crash on suppress output (#11421) +* Refactor checks (#11425) +* Clean warn, sdl dynamic sizes (#11426) +* [channels,remdesk] fix possible memory leak (#11428) +* [client,x11] map exit code success (#11432) +* nla: send user and domain hints with smartcard logon (#11435) +* [client,windows] ignore clipboard failures (#11436) +* Hidef rail checks and deprecation fixes (#11439) +* Fix child session hanging issue. (#11442) +* [channels,rdpdr] relax state checks for PAKID_CORE_CLIENTID_CONFIRM (#11433) +* Standard rdp security network issues (#11446) +* Various fixes related to smartcard logon server-side (#11443) +* [core,rdp] fix check for SEC_FLAGSHI_VALID (#11449) +* [scripts,mac] limit make -j to number of processors (#11450) +* [readme] deprecate xmpp bridge (#11451) +* [readme] explicitly link FAQ (#11452) +* [readme] put links on one line each (#11453) +* [core,tls] enable SNI when building with libreSSL (#11454) +* [channels,client] log server format list (#11455) +* [client,mac] prefer unicode from clipboard (#11456) +* [cmake] drop legacy and unused cmake_policy (#11457) +* Sdl suppress output fix (#11458) +* [client,sdl] unify all gdi_suppress_output calls (#11460) +* [client,sdl] fix multimonitor fullscreen (#11462) +* [client,sdl] fix unused result warning (#11463) +* [client,sdl] quit on window close (#11464) +* [core,gateway] log tsg timeout (#11465) +* [core,settings] enforce OrderSupportFlags (#11468) +* [core,caps] fix rdp_apply_order_capability_set (#11469) +* Sdl elminiate sdl and rdp thread dependency (#11473) +* [client,sdl] wrap connection dialog (#11475) +* [core,proxy] align no_proxy to curl (#11479) +* [winpr,smartcard] fix SCARD_ATTR_VENDOR_NAME length (#11481) +* [core,gateway] fix string reading for TSG (#11485) +* [rdpei/server] fix build and channel init (#11484) +* [client,sdl] refactor display update (#11472) +* [client,sdl] fix clipboard updates (#11486) +* [client,sdl] fix orientation update (#11487) +* Sample fix (#11488) +* [timezones] Update definitions (#)11489) +* Rel mouse change (#11384) +* [winpr,utils] ignore _Unwind_Backtrace return (#11491) +* Warn log (#11493) +* [cmake] simplify v4l detection (#11495) +* [client,sdl] use a GUID to identify the clipboard (#11496) +* [utils,smartcard] assert and improve log (#11498) +* rdpei/server: Add optional threaded handling of messages (#11499) + +## New Contributors +* @poasungoh made their first contribution in https://github.com/FreeRDP/FreeRDP/pull/11442 +* @TolchiIsland made their first contribution in https://github.com/FreeRDP/FreeRDP/pull/11446 +* @mnauw made their first contribution in https://github.com/FreeRDP/FreeRDP/pull/11484 + +For a complete and detailed change log since the last release run: +git log 3.15.0...3.14.1 + + +# 2025-03-24 Version 3.14.1 + +Bugfix and papercut release. +Some small improvements in RDP file parsing, logging, +clipboard support, gateway detection and many more. + +## What's Changed +* [core,gateway] add rts parser checks (#11340) +* [core,gateway] additional RTS checks (#11341) +* [ci,workflow] use mk-build-deps to install deps (#11343) +* [ci,workflow] add equivs dependency (#11344) +* [clipboard] improve logging, fix image conversions (#11342) +* core: Set instance pointer after channel reload (#11346) +* [ci,alt-arch] request sudo for package installation (#11345, #11347, #11348, + #11349, #11350, #11351, #11352, #11353, #11355) +* [channels,printer] Ignore printer settings (#11354) +* [ci,alt-arch] fix gsm, simplify config (#11356) +* [primitives] fix detection and refactor yuv420 to RGB (#11358) +* [client,sdl3] fix clipboard format detection (#11366) +* [cmake] add explicit instructions to turn off unmaintained modules (#11362) +* client: Fix population of string settings in rdp file (#11370) +* [client,common] fix rdp parser (#11372) +* [core] use dynamic logger where possible (#11360) +* [client,x11] add ++d shortcut, log detected shortcuts (#11363) +* [client] add image as HTML clipboard format, fix bitmap conversions (#11369) +* [core,gateway] improve RPC fallback detection and logging (#11375) +* [core,transport] fix transport statistics (#11377) +* code cleanups and abi-checker improvements (#11378, #11381) +* refactor GetStdHandle (use global destructor), fix possible rdp2tcp leaks (#11383, #11386) +* fix a few missing checks in xfreerdp and keyboard remapping for sdl-freerdp (#11406) +* fix deprecation warnings on macos (#11390) +* fix capslock and hotkey keyboard state sync (#11410, #11415) + +For a complete and detailed change log since the last release run: +git log 3.14.1...3.14.0 + + +# 2025-03-13 Version 3.14.0 + +Bugfix and cleanup release. +Due to some new API functions the minor version has been increased. + +So, what has been changed: +* Fix spelling of 'dont' (#11297) +* missing ConnectFlags variable in license_read_platform_challenge_packet breaks in WITH_DEBUG_LICENSE builds (#11301) +* [locale] add freerdp_detect_keyboard_layout_from_locale (#11298) +* Invert 3x deprecated (#11296) +* [primitives,copy] remove alignment check (#11302) +* sdl-common (#11303) +* [client,sdl3] fix bitmap clipboard copy (#11304) +* [channels,ubdrc] add some more failure logging (#11306) +* [client] Fix writing incorrect type for integer values in RDP file (#11307) +* Urbdrc cleanups (#11308) +* [winpr,nt] Fix incorrect name in FILE_INFORMATION_CLASS (#11311) +* [core,gateway] improve rts_read_auth_verifier_with_stub (#11314) +* [cmake] Fix DLL install directory (#11316) +* Enable dynamic resolution setting (#11317) +* [client,x11] add apple keyboard fallback (#11315) +* [client,sdl] #include (#11318) +* [windows] fix deprecation and int warnings (#11319) +* Mingw build improvements (#11321) +* Urbdrc leak fix (#11322) +* [warnings] fixed integer casts (#11325) +* [core,gateway] unify TSG_PACKET_MSG_RESPONSE (#11327) +* [channels,drive] Prefer using handle from IRP_CREATE when possible (#11338) + +New Contributors +* @eduar-hte made their first contribution in (#11301) + +For a complete and detailed change log since the last release run: +git log 3.14.0...3.13.0 + +# 2025-03-06 Version 3.13.0 + +Another bugfix and cleanup release. +Due to some new functions and fields being introduced the minor version +has been increased. + +New for application developers: +A new CMake Variable WITH_FREERDP_3x_DEPRECATED (ON by default) allows +disabling all symbols that have been marked deprecated during the 3.x +release cycle. Such a build can be used to test compatibility with future +versions that might drop these symbols entirely. + +So, what has been done: +* Friends of old hardware rejoice, serial port redirection got an update + (not kidding you) +* Android builds have been updated to be usable again +* Mingw builds now periodically do a shared and static build +* Fixed some bugs and regressions along the way and improved test coverage as + well + +Noteworthy Changes: +* Cmake fix symbol visibility (#11185) +* Sanity checks (#11186) +* [locale,keyboard] fix loading from file (#11188) +* [client,x11] only filter input on floatbar lock (#11190) +* [core,gcc] improve consistency checks (#11191) +* [channel,urbdrc] fix urbdrc_udevman_register_devices (#11194) +* [client,sdl] fix keyboard grab (#11195) +* Nightly deb sdl3 optional (#11197) +* Alt arch update (#11199) +* [ci,alt-arch] split config (#11200) +* [core,freerdp] send MCS Disconnect Provider Ultimatum PDU (#11202) +* [macro] fix use of WINPR_DEPRECATED (#11203) +* [channel,rdpecam] UVC H.264 fix for c922 camera (#11207) +* [channel,rdpdr] support general caps V1 (#11209) +* [cmake] fix missing include (#11213) +* [client,sdl] mark SDL2 as deprecated. (#11223) +* Cursor test and fixes (#11220) +* [build,android] add workaround for OpenSSL tag naming (#11224) +* [core,credssp_auth] Fix faulty string length check in `credssp_auth_client_init_cred_attributes` (#11226) +* [codec,test] fix type mismatch (#11229) +* [codec,dsp] ignore encoder errors (#11225) +* Android fixes (#11230) +* [channels,rdpsnd] fix android build warnings (#11232) +* [client,common] improve parsing of TLS options (#11235) +* [client,x11] reduce verbosity of actionscript log (#11238) +* CMake: generate a .gitignore file for the build directory (#11241) +* [winpr,wlog] simplify WLog_* macros (#11237) +* [client,cmdline] fix port parsing for gateway (#11243) +* Mingw update (#11242, #11244, #11245) +* [ci,abi] suppress gdi_graphics_pipeline_init_ex (#11246) +* [cmake] Enable CMAKE_EXPORT_COMPILE_COMMANDS (#11252) +* [packaging,flatpak] remove .orig file (#11254) +* [utils,smartcard] check output buffer length (#11255) +* [client,x11] improve action script logging (#11257) +* [warnings] fix -Wunused-macro (#11258) +* [warnings] fix -Wunused-function (#11260) +* Redirection && StreamPool usage fixes (#11262) +* Serial term fixes (#11253) +* [server,shadow] multi rect BitmapUpdate support (#11268) +* Redirection && StreamPool usage fixes (#11262) +* [warnings] eliminate dead code (#11275) +* Implement stuff (#11277) +* [dead code] remove some unused code (#11280) +* [channels,rdpecam] fix libusb include path (#11282) +* Rdpear test fix (#11284) +* client: Move buffer pointer after writing RDP settings (#11287) +* [warnings] eliminate dead code (#11283) +* [client,x11] implement keyboard mapping (#11273) +* Serial term fixes (#11253) +* [core,gateway] add tsg checks (#11288) + +New Contributors: +* @yegorich made their first contribution in (#11241) +* @THS-on made their first contribution in (#11243) + +For a complete and detailed change log since the last release run: +git log 3.13.0...3.12.0 + +# 2025-02-14 Version 3.12.0 + +A bugfix and cleanup release. +Due to a new function and a new macro the minor version was incremented. +* Multimonitor backward compatibility fixes +* Smartcard compatibility +* Improve the [MS-RDPECAM] support +* Improve smartcard redirection support +* Refactor SSE optimizations: Split headers, unify load/store, require SSE3 for + all optimized functions +* Refactors the CMake build to better support configuration based builders +* Fix a few regressions from last release (USB redirection and graphical glitches) + +Noteworthy Changes: +* Fix all unused warnings (#11167) +* [common,settings] fix backward compatibility for LocalMonitorOffset (#11175) +* Warning cleanups (#11172, #11173, #11167) +* CMake configurable C/C++ standard, WINPR_ATTR_UNUSED (#11171) +* [utils,smartcard] fix return checks for SCardListReaders (#11170) +* [primitives,sse] split headers (#11163) +* X11 keymap reload fix (#11162) +* [core,freerdp] New API freerdp_presist_credentials (#/11160) +* [client,common] Avoid use of reserved types by @fossdd (#11144) +* [core,orders] fix update_read_delta by @akallabeth (#11145) +* [build,android] only enable required codecs for ffmpeg by @akallabeth (#11147) +* [iOS] Update OpenSSL library location and build script by @beersheba (#11148) +* Warn fixes, code cleanups by @akallabeth (#11140) +* [server] fix compilation errors after adding NONAMELESSUNION. by @llyzs (#11149) +* [channel,rdpecam] support Logitech UVC H.264 stream mux payload by @oleg0421 (#11132) +* [winpr,sysinfo] limit GetComputerNameA to 31 chars by @akallabeth (#11150) +* Warn fixes42 by @akallabeth (#11151) +* [utils,smartcard] return proper list for smartcard listing by @akallabeth (#11152) +* [channel,rdpecam] uvc_h264 xu_descriptor pointer fix by @oleg0421 (#11154) +* [channel,urbdrc] fix libusb return code checks by @akallabeth (#11156) +* Function size refactor by @akallabeth (#11157) +* Cmake multiconfig2 by @akallabeth (#10853) + +New Contributors: +* @fossdd made their first contribution in https://github.com/FreeRDP/FreeRDP/pull/11144 + +For a complete and detailed change log since the last release run: +git log 3.12.0...3.11.1 + +# 2025-02-07 Version 3.11.1 + +A bugfix release addressing two regressions reported against 3.11.0 + +Noteworthy changes: + * Fix a segfault when passing /pth (#11138) + * Fix a regression in planar codec (#11136) + +For a complete and detailed change log since the last release run: +git log 3.11.1...3.11.0 + +# 2025-02-06 Version 3.11.0 + +A new release with bugfixes and code cleanups as well as a few nifty little +features that will improve your meetings. + +Noteworthy changes: + * Updated android client to more recent gradle (#11105, #11110) + * Fix cmake clean target (#109 + * SDL3 bugfixes and API updates (#11092, #11093, #11128) + * Fix keyboard mapping, add working japanese and korean types, deprecate + obsolete functions (#10989, #11035, #11011, #11074, #11037) + * Fix timezone mapping and iteration (#11077, #11079, #11080, #11083) + * Fix YUV reverse filter for AVC444 modes (#11045, #11063, #11066, #11081, #11086, + #11087) + * Fix H.264 encoder wrapper issues (#11117, #11121, #11078) + * MS-RDPECAM: Support for H.264 encoding with VA-API (#10887) + * Fix various CMake, build script and github workflow issues (#10992, #10996, + #11020, #11031, #11030, #11062, #11064, #11069, #11073, #11123, #11109, + #11120, #11053, #11089) + * [codec,planar] fix decoder regression (#11033) + * [client,cmdline] fix vmconnect checks (#11051) + * Fix multi-monitor related checks (#11095) + * Fix various compiler and clang-tidy warnings (#10953, #11003, #11004, + #11007, #11016, #11018, #11019, #11021, #11017, #11000, #11023, #11024, + #11026, #11002, #11028, #11001, #11029, #10999, #11006, #11034, #10998, + #11044, #11050, #11052, #11057, #11059, #11065, #11067, #11068, #11060, + #11071, #11085, #11088, #11099, #11102, #11108, #11124, #11126, #11129, + #11130) + +New Contributors + * @chewi made their first contribution in https://github.com/FreeRDP/FreeRDP/pull/11004 + * @gpotter2 made their first contribution in https://github.com/FreeRDP/FreeRDP/pull/11016 + * @vmpn made their first contribution in https://github.com/FreeRDP/FreeRDP/pull/11092 + +For a complete and detailed change log since the last release run: +git log 3.11.0...3.10.3 + + +# 2024-12-17 Version 3.10.3 + +Follow up release to 3.10.2, as we've discovered a few bugs after release. + +Noteworthy changes: + * Fix usage of GetComputerNameExA (#10988) + * Fix cmake clean target (#10990) + +For a complete and detailed change log since the last release run: +git log 3.10.3...3.10.2 + +# 2024-12-16 Version 3.10.2 + +Follow up release to 3.10.1, as we've discovered a few bugs during release +tests. + +Noteworthy changes: + * Fix initializing ComputerName setting (#10985) + * Fix some warnings and possible leaks (#10985) + +For a complete and detailed change log since the last release run: +git log 3.10.2...3.10.1 + +# 2024-12-16 Version 3.10.1 + +We're happy to present a new release of FreeRDP. +This release contains a few fixes for bugs revealed by checks introduced +with 3.10.0 + +Noteworthy Changes: + * Add FreeBSD as architecture build to our ci (#10980 and others) + * Fix empty include directory creation (#10981) + * fix SIMD detection (#10968) + * improve settings unit test coverage (#10966) + * fix sending server redirection PDU (#10963) + * fix return and use of GetComputerNameA (#10962) + +For a complete and detailed change log since the last release run: +git log 3.10.1...3.10.0 + +# 2024-12-12 Version 3.10.0 + +We're happy to present a new release of FreeRDP. +This one contains some more code cleanups (we've addressed lots of clang-tidy +warnings) as well as some highly anticipated new features and bugfixes. + +So, what is new: +* Enforce use of a supported build type (#10777) +* Enable FDK-AAC support for nightly packages (#10875, #10781) +* Better AAD/AVD support (#10796) +* Build system updates (#10844) +* Enforce spell checking (#10881) +* Split unit tests so a subset can be run during package build (#10776) +* We're shipping a .desktop file now (#10465) +* Build scripts for nightly packages (#10835, #10783) + +Noteworthy changes: +* Fix wStream API bugs (#10885) +* Autoreconnect fixes (#10915) +* Fix monitor layout checks (#10905) +* Enforce code formatting for CMake files (#10895) +* Enable SIMD optimizations by default (#10894) +* WinPR types not based on stdint.h et al (#10754) +* Improve code assertions (#10768) +* Code cleanups (#10763, #10914) + +For a complete and detailed change log since the last release run: +git log 3.10.0...3.9.0 + + +# 2024-10-21 Version 3.9.0 + +We're proud to present the newest release of FreeRDP. +This one brings some major code cleanup (we've addressed lots of clang-tidy +warnings) as well as some highly anticipated new features. +We also did update the API documentation quite a bit (still incomplete though, +help always welcome ;)) + +So, what is new: +* Support for RDPEAR (remote credential guard) /remoteGuard option for non windows clients +* Global configuration file support, allowing to configure certificate + accept/ignore/... default settings for all users +* Simplified manpage generation, eliminates docbook and xmlto dependencies + speeding up builds +* New API for client channels to run tasks on RDP thread +* New extended transport layer API +* RDPECAM MJPEG support +* the first updates of timezone definitions from our automated ci + +Noteworthy changes: +* Fix bugs in SSE4.1/AVX2 image copy (#10720) +* Add warnings for invalid monitor settings during connect (#10672) +* Fix ALSA microphone support (#10664) +* Fix modal windows in RAILS mode (#10629) +* Update experimental SDL3 client (SDL3 API should now have been stabilized, + various pull requests) +* Fix keyboard layouts, the external JSON did miss a few (#10639) + +For a complete and detailed change log since the last release run: +git log 3.9.0...3.8.0 + +# 2024-08-30 Version 3.8.0 + +This is a bugfix release. Due to additional exports required by a bugfix the minor version was incremented + +Noteworthy changes: +* Reduce number of warnings on CI build (make dependency includes SYSTEM) (#10509) +* Fix possible crashes with P11 certificate parsing (#10462, #10463) +* Various clipboard related fixes (#10472, #10476, #10477, #10480, #10484) +* Fix a race condition on DesktopResize (xfreerdp) (#10488) +* Improve certificate warnings (#10489) +* Try all possible resolved IP addresses for a DNS name on connect (#10491) +* Fix an issue with GFX SolidFill alpha data (#10498) +* Various fixes for SDL clients (#10504, #10492, #10471) +* Fix serial and parallel redirection crashes (#10510) +* Fix android build issues with NDK 27 (#10529) +* Improve performance of some WinPR primitives (#10528) +* Fix an issue with autoreconnect (#10523) +* Support ssh-askpass like password reading (#10516) +* Lots of code cleanups to reduce clang-tidy warnings (#10531, #10525, #10521, #10520, #10519, #10518) + +For a complete and detailed change log since the last release run: +git log 3.8.0...3.7.0 + +# 2024-08-08 Version 3.7.0 + +This release has accumulated quite a number of changes. Along bugfixes for 3.6.3 it also +contains a number of improvements for distributors: + +* Support for FDK-AAC for sound and microphone redirection (activate with -DWITH_FDK_AAC=ON build option) + This allows enabling the AAC compression that do not ship faad2 and/or faac +* Support keyboard layouts as JSON resources (activate with -DWITH_KEYBOARD_LAYOUT_FROM_FILE=ON build option, + also requires JSON support) + This allows editing keyboard layouts for existing releases should the need arise +* Support timezones as JSON resources (activate with -DWITH_TIMEZONE_FROM_FILE=ON -DWITH_TIMEZONE_COMPILED=OFF build options, + also requires JSON support) + Allows reading the mapping between IANA and windows timezones from a JSON file, allowing easier updates without recompile +* Improve shadow server compatibility with windows 11 24H2 RDP client + Windows 7 RFX and bitmap updates with multiple rectangles have been deactivated, so adjust shadow to not send such. + +Noteworthy changes: +* Fix ActionScript parameter (#10423) +* Support keyboard layouts as JSON resource (#10394) +* Support timezones as JSON resource and command line argument (#10428 #10393 #10391) +* Deactivate AsyncUpdate (#10402) +* Compatibility fixes for shadow with windows 11 24H2 (#10455 #10422 #10420 #10416) +* Fix SDL client autoreconnect (#10390) +* Fix xfreerdp clipboard locking (#10385) +* Improve logging (#10426 #10441) +* Improve warnings and code checks (#10381 #10401 #10403 #10405 #10406 #10410 #10421 #10454) +* Support FDK-AAC (#10372) +* Fix drive redirection state transitions (#10367 #10374) +* Support mth:// routing token (#10366) +* Ignore unsupported SetThreadPriority (#10363) +* Fix reported documentation and code typos (#10365 #10368 #10370 #10369 #10431 #10446) + +For a complete and detailed change log since the last release run: +git log 3.7.0...3.6.3 + +# 2024-07-08 Version 3.6.3 + +Bugfix release for 3.6.2 issues reported + +Noteworthy changes: +* fix a graphics regression (#10352) +* workaround for a protocol bug of older FreeRDP based servers (#10358) +* fix possible NULL dereference in command line parser (#10348) +* fix intrinsics detection (#10346, #10350) + +For a complete and detailed change log since the last release run: +git log 3.6.3...3.6.2 + + +# 2024-07-04 Version 3.6.2 + +Bugfix release for 3.6.1 issues detected during release tests + +Noteworthy changes: +* Fix xfreerdp and sdl-freerdp manpage names (accidentally changed name) +* Fix crash of wfreerdp + +For a complete and detailed change log since the last release run: +git log 3.6.2...3.6.1 + + +# 2024-07-04 Version 3.6.1 + +Bugfix release for 3.6.0 + +Noteworthy changes: +* Fix missing dependency for ci abi-checker +* Fix build WITH_SSE2/WITH_NEON: only enable support if the compiler + also defines symbols that suggest support. +* Fix incomplete changelog for 3.6.0: + * Improved image copy (#10208) + * Experimental [MS-RDPECAM] support by @oleg0421 (#10258) + * Improved primitives (#10304) + * Connection timeout for HTTP gateway transport (#10288) + +For a complete and detailed change log since the last release run: +git log 3.6.1...3.6.0 + + +# 2024-07-03 Version 3.6.0 + +With this release we did improve decoder speed so you should notice a significant +speed improvement with progressive and other gfx codecs. +We've also eliminated a couple of issues along the way, so an update +is highly recommended. + +Noteworthy changes: +* Improved command line failure logging (#10333) +* p11-kit support (#10081) +* json-c support (#10183) +* (experimental) SDL3 port SDL client (#10195) +* New option '/gfx:frame-ack:off' for connection delay testing (#10214) +* improved decoder speed (#10222, #10235) +* xfreerdp floatbar hide bug (#10237) +* winpr-makecert month bug (#10236) +* kerberos kdcUrl check fixes (#10238) +* timezone updates (#10120, #10144, #10170) +* fixed a capability protocol violation bug (#10132) +* fix SDL client dialog bug terminating on credential dialog (#10134) +* some more oss-fuzz issues (#10126, #10141, #10148, #10161, #10239) +* rails popup window fixes (#10160) + + +For a complete and detailed change log since the last release run: +git log 3.6.0...3.5.1 + + +# 2024-04-22 Version 3.5.1 + +This release eliminates a bunch of issues detected during oss-fuzz runs. +The test coverage was increased and detected issues eliminates, so an update +is highly recommended. + +Noteworthy changes: +* Lots of fixes for oss-fuzz reports +* Timezone detection fixes (#10106) +* SDL key remapping support (#10103) +* Improved help (#10099) +* FreeBSD epoll detection fix (#10097) + +For a complete and detailed change log since the last release run: +git log 3.5.1...3.5.0 + +# 2024-04-16 Version 3.5.0 + +This release focus is on squashing bugs. +The improved test coverage and ci builds revealed a number of previously +unnoticed issues we have addressed and we also got a report from +Evgeny Legerov of Kaspersky Lab identifying a number of out of bound reads +in decoder components and one very nasty out of bound write. + +CVE: +CVE-2024-32041 [Low[ OutOfBound Read in zgfx_decompress_segment +CVE-2024-32039 [Moderate] Integer overflow & OutOfBound Write in clear_decompress_residual_data +CVE-2024-32040 [Low] integer underflow in nsc_rle_decode +CVE-2024-32458 [Low] OutOfBound Read in planar_skip_plane_rle +CVE-2024-32459 [Low] OutOfBound Read in ncrush_decompress +CVE-2024-32460 [Low] OutOfBound Read in interleaved_decompress + +Noteworthy changes: +* location channel support #9981, #9984, #10065 +* bugfixes for report from Evgeny Legerov of Kaspersky Lab #10077 +* fuzzer tests from Evgeny Legerov of Kaspersky Lab #10078 +* bugfixes for coverty scanner #10066, #10068, #10069, #10070, #10075 +* clipboard and generic locking fixes #10076 +* split autoreconnect support from enabling it #10063 +* various nightly and workflow fixes #10064, #10058, #10062 +* always set wm-class to app_id #10051 +* refactored and simplified CMake #10046, #10047 +* fix relative mouse event sending #10010 +* improve and unify check for APIs used (POSIX, win32, mac, ...) #9995 +* fix termination for gateway connections #9985 +* fix drivestoredirect RDP file setting, ignore invalid #9989 +* drop IPP support #10038 + +For a complete and detailed change log since the last release run: +git log 3.5.0...3.4.0 + +# 2024-03-14 Version 3.4.0 + +This release concentrates on improving test coverage and ci builds. +Some usability issues and inconvenient API functions were fixed on the way. + +New features have been introduced (stub for location channel) + +Noteworthy changes: +* fix a bug in RAIL mode not activating window focus (#9973) +* improve logging (#9969, #9943) +* OpenSSL <= 1.1.1 build fixes (#9897) +* improved help (#9899, #9905) +* improved MINGW support (#9914, #9915, #9919, #9964, #9965, #9920) +* fix right control ungrab for xfreerdp (#9960) +* fix RPATH option settings (#9963) +* fix SDL client screen updates (#9962, #9954) +* fix issues with childSession under windows (#9961, #9956, #9922) +* fix xfreerdp crash with +auth-only (#9947) +* fix windows printer channel (#9934) +* add support to enforce gateway policy (#9942) +* improve big endian support (#9927) +* ignore empty proxy environment variables (#9929) +* improve quoting support for command line (#9912) + +For a complete and detailed change log since the last release run: +git log 3.4.0...3.3.0 + + +# 2024-02-22 Version 3.3.0 + +This release concentrates on code cleanup and overall quality improvements. +Some usability issues and inconvenient API functions were fixed on the way. + +New features have been introduced (better image clipboard) but that stays +deactivated by default as we´re in a stable series. + +Check the new CMake options: +* PLUGIN_ABS_PATHS_DEFAULT disables loading of external channels from all + but a specified absolute plugin directory defined by FREERDP_PLUGIN_PATH +* WINPR_UTILS_IMAGE_PNG enables PNG support with libpng in winpr image/clipboard +* WITH_LODEPNG enables PNG support with lodepng library in winpr image/clipboard +* WINPR_UTILS_IMAGE_WEBP enables WEBP support in winpr image/clipboard +* WINPR_UTILS_IMAGE_JPEG enables JPEG support in winpr image/clipboard +* USE_EXECINFO enables or disables backtrace support with execinfo +* WITH_WEBVIEW now defaults to OFF on windows, apple and android (not implemented) + +Noteworthy changes: +* Improved image clipboard (xfreerdp, wlfreerdp) (#9873, #9826) +* Improved SDL client (#9875, #9887, #9883, #9878, #9792) +* Allow plugin loader to only use absolute paths (#9809) +* Improved TLS channel binding (#9838) +* Add GCC/clang attribute malloc wrapper WINPR_ATTR_MALLOC (#9863) +* Major clang-tidy code cleanups and bugfixes (#9799, #9834) +* Provide some defaults for wObject functions (#9799) +* Fix a bug in shadow with GFX breaking mstsc (#9818) +* Improved manpages and help (#9813, #9804) +* Blocking mode via transport IO interface (#9793) + +For a complete and detailed change log since the last release run: +git log 3.3.0...3.2.0 + +# 2024-01-19 Version 3.2.0 + +This release mostly addresses issues reported since the last release. +Fixing some usablity and build issues as well as adding API functions +that are needed from external projects + +Noteworthy changes: +* Fix proxy module load check (#9777) +* Improve kerberos error logging (#9771) +* Improve mac client keyboard handling (#9767) +* Add option to run client dynamic channel synchronous (#9764) +* Move huge struct to heap (#9763) +* Improved failure logging of license module (#9759) +* Improve server side gfx logging (#9757) +* Print shadow server help with printf instead of WLog (#9756) +* Fix SDL client timer initialization (#9754) +* Fix server peer message parsing (#9751) +* Enable NEON instructions if __ARM_NEON is defined (#9748) +* Add new proxy config file option TlsSecLevel (#9741) +* Improve android and mac os build scripts (#9735) +* Do not disable wayland support on BSD (#9730) +* Fix issues with assistance file parsing (#9727, #9728) +* Keyboard handling fixes for wayland client (#9725) +* Fix relative pkg-config file paths (#9720) +* Add new transport IO callback GetPublicKey (#9719) +* Fix wayland client scaling (#9715) + +For a complete and detailed change log since the last release run: +git log 3.2.0...3.1.0 + +# 2023-12-22 Version 3.1.0 + +A new 3.1.0 minor release for the new 3.0.0 series. +This contains bugfixes, adds (better) support for libressl and mbedtls and +brings a bunch of improvements for the SDL client. + +This comes with a price though, we now (optionally) require SDL_image if you +want to build the sdl-client + +Since there are multiple new features, some new files (man pages) and new +optional dependencies we´ve directly incremented the minor version. + +New CMake options: +* SDL_USE_COMPILED_RESOURCES (default ON) builds fonts and images into SDL + client. Set to OFF to install these resources as files. (was already part of + 3.0.0, but worth mentioning here) +* WITH_SDL_IMAGE_DIALOGS (default OFF) Show some nice icons for SDL client + connection dialogs. Requires SDL_image for build. +* WITH_BINARY_VERSIONING (default OFF) Similar as for libraries the binaries, + manpages and resource locations created by FreeRDP project are postfixed + with the API version. Recommended if packagers want to install the package + alongside FreeRDP 2 without conflicts. +* RDTK_FORCE_STATIC_BUILD (default OFF) Build and link RDTK statically into + shadow server. Recommended for packagers as this library is not really used + outside of FreeRDP-shadow. +* UWAC_FORCE_STATIC_BUILD (default OFF) Build and link UWAC statically into + wlfreerdp. Recommended for packagers as this library is not really used + outside of wlfreerdp. + +Noteworthy changes: +* Fix a nasty bug with relative mouse movement (#9677) +* LibreSSL support enhancements (#9691, #9670) +* mbedTLS support enhancements (#9662) +* Improve building on mac OS (#9641) +* New and improved manpages (#9690, #9650) +* Unify CMake common options, add (optional) binary versioning and allow + building rdtk and uwac as static dependencies (#9695) +* SDL client improvements (#9693, #9657, #9659, #9683, #9680, #9657, #9664, + #9656) + +For a complete and detailed change log since the last release run: +git log 3.1.0...3.0.0 + +# 2023-12-12 Version 3.0.0 + +Final 3.0.0 release just a little over two weeks after the last 3.0.0-rc0. +This contains bugfixes, drops some legacy code, implements a small feature +request and adds some improvements to the build system. + +Most notably is the new PreventInSourceBuilds.cmake which does exactly what +the name implies, it aborts builds where source equals build directory. +If you can not use out of source tree builds for some reason, you can +circumvent this measure with the CMake setting -DALLOW_IN_SOURCE_BUILD=ON + +Noteworthy changes: +* add support for AF_VSOCK #9561 +* xfreerdp drop X11 GDI implementation #9492 +* fixed connection freeze with childSession #9594 +* fixed relative mouse input issues #9608 +* fixed issues with drive redirection #9610 +* simplified mac build #9601 +* fixed TSMF to build again #9603 +* fixed command line /gfx parsing bug #9598 +* prevent in source tree build #9550 +* fixed various issues with settings #9595, #9596 +* add E2K cpu support in WinPR #9599 +* fixed wfreerdp DPI settings when used as embedded window #9593 +* android add mouse hover support #9495 + +For a complete and detailed change log since the last release run: +git log 3.0.0..3.0.0-rc0 + +# 2023-11-27 Version 3.0.0-rc0 + +Nearly 2 months of testing, bugfixing and API refinements later we´re +happy to announce the first release candidate for FreeRDP 3.0 +The API should now be considered stable and only minor changes (if at all) +will happen from this point on, so every project using FreeRDP can check +compatibility with upcoming 3.0 + +Noteworthy changes: +* Updated rdpSettings API #9465: + * getter/setter now use enum types for keys (generates compiler warnings for mismatch) + * Refined functions (added missing, dropped problematic ones) + * prepared opaque settings (direct struct access now deprecated) +* Server side [MS-RDPEL] channel #9471 +* Relative mouse movement support #9459 +* relocatable pkg-config files (enable with -DPKG_CONFIG_RELOCATABLE=ON, #9453) +* cliprdr dropped support for fuse2 (#9453) +* added support for uriparser for clipboard file:// parsing (#9455) +* aFreeRDP translation for traditional chinese (zh-rTW) added (#9450) +* fixed sdl-freerdp crash on credential dialog (#9455) +* fixed sdl-freerdp alt+tab in fullscreen (#9442) +* added /connect-child-session option (WIN32 only, #9427) +* fix rfx-image codec setup (#9425) +* added missing cmake configuration for winpr-tools (#9453) +* cleaned up cmake configuration files, dropped no longer required ones (#9455) +* fixed x11 keyboard layout detection (#9433) +* add missing API calls for server implementation (tested against ogon, #9453) +* keep dynamic channels in a hash table instead of a list (#9448) +* keep TSCredentials in server peer instance (#9430) +* fix FFMPEG/AAC encoding (#9576) +* support remote credential guard (#9574) +* fix printing on mac os 14 (#9569) +* improve RPC gateway support (#9508) +* add opus audio support for gnome-remote-desktop (#9575) +* server side handling of mouse cursor channel [MS-RDPEMSC] (#9513) + +For a complete and detailed change log since the last release run: +git log 3.0.0-rc0..3.0.0-beta4 + +# 2023-09-31 Version 3.0.0-beta4 + +Noteworthy changes: +* Improved and fixed AVD authentication, now allows retries for + machines just starting up +* Improve RDP file parser, prepare new fields used by AVD +* Fixed and improved pen support in multitouch implementation (xfreerdp) +* Lots of smaller code and leak cleanups + +For a complete and detailed change log since the last release run: +git log 3.0.0-beta4..3.0.0-beta3 + +# 2023-08-31 Version 3.0.0-beta3 + +Noteworthy changes: +* fix xfreerdp keyboard on mac os +* Various crashes and input check fixes +* Improved logging of autodetect, redirection and fastpath failures +* Smartcard emulation now selectable at runtime +* Allow certificates without a subject to pass client checks +* Fix FindFirstFile issues on android +* Add FREERDP_ENTRY_POINT to silence -Wmissing-prototypes warnings for + library entry points +* Add WINPR_RESTRICT to enable restrict (C99) or __restring (MSVC) + keywords for compiler +* Fix support for older OpenSSL versions + +For a complete and detailed change log since the last release run: +git log 3.0.0-beta3..3.0.0-beta2 + +# 2023-08-03 Version 3.0.0-beta2 + +Noteworthy changes: +* Update CMake defaults, now all features are enabled by default with a platform + independent option if multiple are available. +* SDL client: (basic) multimonitor support +* SDL client: fix dialog cleanup order (crash fix) +* clipboard: fix FUSE shutdown crash +* fixed drive redirection: FindNextFile did miss some files/directories +* improved AAD support: honor rdp file options +* improved (gateway) http failure logging +* improved shadow server error handling +* improved CMake configuration (using find_dependency) +* updated timezone definitions +* mbedTLS build fixed +* improved MINGW build support + +For a complete and detailed change log since the last release run: +git log 3.0.0-beta2..3.0.0-beta1 + +# 2023-07-21 Version 3.0.0-beta1 + +We are pleased to announce the first beta release for the next stable 3.0 +series of FreeRDP. It has been a huge endeavour to implement all the new +shiny bells and whistles as well as clean up the code base and we´re still +ironing out some smaller glitches. +This is the first API breaking change since the 2.0 series and there are +some adjustments to be made for existing applications. +See https://github.com/FreeRDP/FreeRDP/wiki/FreeRDP3-migration-notes for +help (still incomplete) + +Noteworthy changes: +* Support for AAD/AVD authentication +* Support for websocket transport +* Support smartcard authentication (TLS and NLA) +* Full smartcard emulation support (login with certificate + key) +* Rewritten proxy, new module API +* New reference client based on SDL2 (work in progress) +* Rewritten logging, now parsing issues are all writing to the log so + that issues with protocol incompatibilities can be easier analyzed + by just turning on logging +* Full OpenSSL 3 support +* Internal implementations for RC4, MD4 and MD5 (required for non critical + parts in RDP but not part of more recent SSL libraries) +* Updated RDP protocol support +* Improved xfreerdp remote app support +* Reworked internal state machine for both client and server implementations +* Server implementations can now make use of connect-time network autodetection +* Improved clipboard handling, now also support server-to-client file transfer + (currently xfreerdp only) +* EnhancedRemoteApp support: Utilizing the more modern standard allows remote + apps with less glitches and window shadows +* Added client- and server-side handling for RDSTLS +* Support for the graphics redirection channel + +For a complete and detailed change log since the last release run: +git log 3.0.0-beta1..2.10.0 + + diff --git a/third_party/FreeRDP/LICENSE b/third_party/FreeRDP/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/third_party/FreeRDP/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/third_party/FreeRDP/README.md b/third_party/FreeRDP/README.md new file mode 100644 index 0000000..07be1b3 --- /dev/null +++ b/third_party/FreeRDP/README.md @@ -0,0 +1,54 @@ +# FreeRDP: A Remote Desktop Protocol Implementation + +FreeRDP is a free implementation of the Remote Desktop Protocol (RDP), released under the Apache license. +Enjoy the freedom of using your software wherever you want, the way you want it, in a world where +interoperability can finally liberate your computing experience. + +## Code Quality Status + +[![abi-checker](https://github.com/FreeRDP/FreeRDP/actions/workflows/abi-checker.yml/badge.svg)](https://github.com/FreeRDP/FreeRDP/actions/workflows/abi-checker.yml) +[![clang-tidy-review](https://github.com/FreeRDP/FreeRDP/actions/workflows/clang-tidy.yml/badge.svg?event=pull_request_target)](https://github.com/FreeRDP/FreeRDP/actions/workflows/clang-tidy.yml) +[![CodeQL](https://github.com/FreeRDP/FreeRDP/actions/workflows/codeql-analysis.yml/badge.svg?branch=master)](https://github.com/FreeRDP/FreeRDP/actions/workflows/codeql-analysis.yml) +[![mingw-builder](https://github.com/FreeRDP/FreeRDP/actions/workflows/mingw.yml/badge.svg)](https://github.com/FreeRDP/FreeRDP/actions/workflows/mingw.yml) +[![macos-builder](https://github.com/FreeRDP/FreeRDP/actions/workflows/macos.yml/badge.svg)](https://github.com/FreeRDP/FreeRDP/actions/workflows/macos.yml) +[![[arm,ppc,ricsv] architecture builds](https://github.com/FreeRDP/FreeRDP/actions/workflows/alt-architectures.yml/badge.svg)](https://github.com/FreeRDP/FreeRDP/actions/workflows/alt-architectures.yml) +[![[freebsd] architecture builds](https://github.com/FreeRDP/FreeRDP/actions/workflows/freebsd.yml/badge.svg)](https://github.com/FreeRDP/FreeRDP/actions/workflows/freebsd.yml) +[![coverity](https://scan.coverity.com/projects/616/badge.svg)](https://scan.coverity.com/projects/freerdp) + +## Resources + +Project website: https://www.freerdp.com/ + +Issue tracker: https://github.com/FreeRDP/FreeRDP/issues + +Sources: https://github.com/FreeRDP/FreeRDP/ + +Downloads: https://pub.freerdp.com/releases/ + +Wiki: https://github.com/FreeRDP/FreeRDP/wiki + +API documentation: https://pub.freerdp.com/api/ + +Security policy: https://github.com/FreeRDP/FreeRDP/security/policy + +FAQ: https://github.com/FreeRDP/FreeRDP/wiki/FAQ + +### Contact + +* Matrix room : `#FreeRDP:matrix.org` (main) + * ~~XMPP channel: `#FreeRDP#matrix.org@matrix.org` (bridged)~~ no longer available + * IRC channel : `#freerdp @ irc.oftc.net` (bridged) +* Mailing list: https://lists.sourceforge.net/lists/listinfo/freerdp-devel + +## Microsoft Open Specifications + +Information regarding the Microsoft Open Specifications can be found at: +https://www.microsoft.com/openspecifications/ + +A list of reference documentation is maintained here: +https://github.com/FreeRDP/FreeRDP/wiki/Reference-Documentation + +## Compilation + +Instructions on how to get started compiling FreeRDP can be found on the wiki: +https://github.com/FreeRDP/FreeRDP/wiki/Compilation diff --git a/third_party/FreeRDP/SECURITY.md b/third_party/FreeRDP/SECURITY.md new file mode 100644 index 0000000..246248b --- /dev/null +++ b/third_party/FreeRDP/SECURITY.md @@ -0,0 +1,114 @@ +# FreeRDP Security Policies and Procedures + +This document describes the security policy and procedures for the [FreeRDP Project](https://github.com/FreeRDP/FreeRDP). +The following topics are covered: + + * [Supported Versions](#supported-versions) + * [Reporting a Vulnerability](#reporting-a-vulnerability) + * [Disclosure Procedure](#disclosure-procedure) + + +## Supported versions + +Security is very important for us therefore we try to provide security updates and support for +the latest stable version as well as for the development branch. +Since our development branch is, like the protocol itself, a moving target we won't request CVEs for issues that are *only* found on the development branch. + +The following table shows the currently supported versions: + +| Version | Branch | Supported | +| ------- |--------------| ------------------ | +| < 2.0.0 | stable-1.x | :x: | +| 2.x.x | stable-2.0 | :x: | +| 3.x.x | stable-3.0 | :white_check_mark: | +| - | master | :white_check_mark: | + + +## Reporting a vulnerability + +**IMPORTANT**: Please, do not file security vulnerabilities as public issues on GitHub + +In advance: **Thank you** for reporting a security vulnerability and making FreeRDP more stable! We really appreciate your effort. +Please let us know who we should give the credit or attributions to. + + +If you have found a security vulnerability in FreeRDP you can either directly open an [Advisory on GitHub](https://github.com/FreeRDP/FreeRDP/security/advisories/new)[^1] or send us an email to mailto:security@freerdp.com + +In case of an email you can use the [FreeRDP security team GPG key](#reporting-gpg-key) for encrypted communication. + +Once we receive a report we will review it and respond as soon as possible. + +### + + +## Disclosure procedure + +When the FreeRDP team receives a report one of the team members will be assigned as primary contact. +The primary contact will do all further communications and coordinate the fix and release process. + +How your report will be handled: + +* When a report is received we will acknowledge the reception and review the reported issue(s) as soon as possible. +* Once confirmed we will determine the affected versions. If not reported via GitHub a [security advisory draft on GitHub](https://github.com/FreeRDP/FreeRDP/security/advisories) will be created for any issue. If it applies we will request a CVE. +* On a private branch we will fix the issue and check the code for any potential similar problem. +* After the fix is validated we will create and publish a new release for all supported versions and publish the advisories. + +## Reporting GPG key + +FreeRDP's security reporting public gpg key https://pub.freerdp.com/FreeRDP-security-team.pub.asc + +``` +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBGBz+jsBEADaIM94hYfn/xDzncQwXl7/q6+06+ssqO3iUGqFr+0EPS+HxRjD +BeKjVRSkuo0+QLQoZgCwkoltEj1xRWNqCTDMA+oZkZH8L82eqCnUQqgCOyNWAVMH +6u6ValiZH3ruYxergBBHhyR4Ot2ia0xWN8MKTp+emLpzQ7goimGMo0mxR5FiDAdb +QKz1q5bgs3bb2pLpERNF+z13OS10Mzk1zdr++1pov5PWOTBRKmvBtPJKswmDpb0y +jQGeeqBFZwKzx0n6BTzDZtkqzTwvGhbm9Sb+qO0IO66IV8zQhPG/JUfDkByd6mX9 +Ykke0gxoRx54XqoRwZGNydOxMN6g3Oj1+ioWisltYLs/SzW20f3AMCoTeYyfjKtf +01refrA3aRfhDctvW5/s2LP0OEG2P/yQYXiGhK6uVxShz3Oa5dhFwiS8G63omZRH +AEqSk46EhAbbT4xfZ/Np209rhis4KW40cMMpI0F+XpyfT05ZQD6ytHTPgWTxv/OF +G9zy2ysT0kq+t+Hb+1RWQUq/2Dz9Lf6xLZPgqtyzg8xiFxZ4i1kf/VDWa3M76zn3 +qMcj3SPOxKY//wW70jCxf44yD38NvSa1M2Sz/K/RJKWkRWP/jhV1UHYusbzCmsvm +M9JkknNMJvGIjBDjHEVy6dlTaHQoHDY+Me9gsrEX0ZS9xXgAiB2IupabEwARAQAB +tEJGcmVlUkRQIFNlY3VyaXR5IFRlYW0gKGh0dHBzOi8vZnJlZXJkcC5jb20pIDxz +ZWN1cml0eUBmcmVlcmRwLmNvbT6JAk4EEwEKADgWIQRvuAE0sDt7JnxXu0o3Ibww +YbfjNAUCYHP6OwIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRA3IbwwYbfj +NPviD/43NLg7YfjAlvj5GipSmgelLwlIA+L/qbrf4NAB+NZ9oqp3bBdj4e5gZmiI +zd6bkANCqk21YiOE31medUfy+nfBQFVvj0oUg1X16C6RaIX5qA3Dwt5qBwKmDkT5 +j7JlxUS6Eluiau67ePiDYu2Wbp0qYuAmNUNL+Y2NCO9UJiy0Oq6YVXS971D5lC+a +SX0x9pizmFV3zro+l6/3kHTVbPednfX99yz9SZge64aWXo3MXVN8JD0lR3+92l99 +XsFDc+lGeR4azLFIqXC4Cr5Lbk34Hw/VwUC32xxFUaJ2ZmV3pA8bhCtBSxSmxnHS +H3hoBaD1WpuApbW8Psx6qsgoaSUdWsjluA4eQ5afJBf9O2NlT1mim5MAINY4PbWP +o4zq3p1ABVTzuB8tsGA9o6DeYVUUrj7lCv9STdGRhm0472BDkp/gvKMBoPgg3Qez +kvGKK7iVy8R/BOPjh9wP1art5JLVsralXGHA/5Ceid4ojKFzGIC9g3lnAPh+T/eM +duyY9XH4un1r73r6DRqUoczSfHYbxhKxWt0cRNdIadcXXusMPV/w4J4j55WcLrBE +5nopp/prJ5bYegUvRRrwVSFwLDxkE2dh68Zvlh5VWXIPFge0RPEAijYWR5qR2z+/ +VHgPYmliOnWFJN1rzekmWjKFtg5A57FkZyk3cp5x0/2xAX+TIbkCDQRgc/o7ARAA +vw53CoVkMzBlisSEETNdEKQMaiQ8BtbC438v/b1mOOeoE0YCfSW7RyflA/TXHOah +db0s3v/Kk2xmbjeMS9IJXlWviKKnOVMrMZvtJdQ4EKfqc5EpxNx7OiEofA/7n7Xs +1YEt6KjYaM/vgANl9HA2UXzqSFiRhkWjj1WA7vhqCWUArpAMGeCDYab2BBfp6Z4f +W9178N2vHH+Hh/uBwGUDnShU38GH8Nstkdcyw5puiJqNQBfZ1Fz9luzutp6zAgHz +WzobeRPZCCXs7CfxcvpkFS0ctOteQtIRIfP+jbDnldMmClQ87UVcKv0pCCJkMLNk +YUCMAb2UC2boCIf0omeeque4+FOphcO4+R/8jc6cYlQpgwUg2/IwBEEnCqtvo3qu +k6uzONhfWZPtUdJd158MGKGTogXVXGzoGzxIrKkZ4W1VuuMiEmhIQZO8e7/4Iz4a +Zp4qQXI8rsmNJN3lB5a7MWgrZ8mjllYRdfiTEvfQ+PiQqnG6PEHZ82om9kp555gs +15UqhjHAqRRtfXzQvZko0ngAxxZNVFPwK8LnxkyEPClRBC5eV3ljI8cvCfnWD01q +rCzSlSafFHCEUEQOhOrf/bBbXPkYTJw2KlumH5w9R6xQWgqneiD/+Qmqdclzdn36 +Pgbhyu6uSNZehbx5ptt/EM66JSAW7Q7W6Qnz5PNnHgEAEQEAAYkCNgQYAQoAIBYh +BG+4ATSwO3smfFe7SjchvDBht+M0BQJgc/o7AhsMAAoJEDchvDBht+M0JYUQALlV +dwmk6ZFq5dq0utWgutysL47b30BhYwNMVe0/6UW4h4TYaW6B3f58X7ik7EdYciyR +68eYfwKGhuv/y90QaGXJMU13XHpoInSaHQRhn5M/GkN16DBXdBok70Fh9Gx89Zhs +VKF3qwIVx5AO5CwrVA6F/iOiUEW31xiT7VFkbW1Cfl5H+M6nVXSR1bOdmxTObTz7 +CEeJMOVrZs36hVLMWLqZF0igVebO2AsDOY63fy/9MLn8ynCHhnAMvsm9ULWuFzGj +OsJezChduaHqPkopgwihe7jthUn4qWjABbbzKkS6HLBpGAfCzUun+lMpvIEUf+EJ +bpk7gj9xDEP6y96tV/dCeWb4p8N8webR8nVgsRxoEnfIdCkoB80iZGOzKfYYnvdz +ngs8MIL6dC4Nc1/t9ECV4O/w4uwIH65nC1ay0YOK/O/j2SEfnVHQmAuOsgTz+pBn +u6DIA2HsBzFdOCljtf3m4AeAaTbL7MBSDceApqg0lcrhjclqHJo1aJh3M6aVm3gq +yUt7y26Hkh/vYEJwW4gqRho4gb7BvjTZh5LUbrjmRtexFQ1eWM82u23yYS2L+y2Y +ejSKIKmJhXHqsgCVGYw5woZEEMzgpkoIWYG/Eoy+oVuU02QITh/Uc5VRsA9DuwSV +Vw2F8gu/fHiadawxWIhUH+plFVQZc1KwgPcIMW3S +=O0kP +-----END PGP PUBLIC KEY BLOCK----- +``` +[^1]: https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability diff --git a/third_party/FreeRDP/channels/CMakeLists.txt b/third_party/FreeRDP/channels/CMakeLists.txt new file mode 100644 index 0000000..5f135d4 --- /dev/null +++ b/third_party/FreeRDP/channels/CMakeLists.txt @@ -0,0 +1,292 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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(CMakeParseArguments) +include(CMakeDependentOption) + +macro(define_channel_options) + set(PREFIX "CHANNEL") + + cmake_parse_arguments( + ${PREFIX} "" "NAME;TYPE;DESCRIPTION;SPECIFICATIONS;DEFAULT;CLIENT_DEFAULT;SERVER_DEFAULT" "" ${ARGN} + ) + + string(TOUPPER "CHANNEL_${CHANNEL_NAME}" CHANNEL_OPTION) + string(TOUPPER "CHANNEL_${CHANNEL_NAME}_CLIENT" CHANNEL_CLIENT_OPTION) + string(TOUPPER "CHANNEL_${CHANNEL_NAME}_SERVER" CHANNEL_SERVER_OPTION) + string(TOUPPER "${CHANNEL_TYPE}" CHANNEL_TYPE) + + if(CHANNEL_DEFAULT) + set(OPTION_DEFAULT ${CHANNEL_DEFAULT}) + elseif(CHANNEL_CLIENT_OPTION OR CHANNEL_SERVER_OPTION) + set(OPTION_DEFAULT "ON") + endif() + + set(CHANNEL_OPTION_DOC "Build ${CHANNEL_NAME} ${CHANNEL_TYPE} channel") + set(CHANNEL_CLIENT_OPTION_DOC "Build ${CHANNEL_NAME} ${CHANNEL_TYPE} channel client") + set(CHANNEL_SERVER_OPTION_DOC "Build ${CHANNEL_NAME} ${CHANNEL_TYPE} channel server") + + if("${CHANNEL_TYPE}" STREQUAL "DYNAMIC") + cmake_dependent_option(${CHANNEL_OPTION} "${CHANNEL_OPTION_DOC}" ${CHANNEL_DEFAULT} "CHANNEL_DRDYNVC" OFF) + else() + option(${CHANNEL_OPTION} "${CHANNEL_OPTION_DOC}" ${CHANNEL_DEFAULT}) + endif() + + # If the channel was enabled before the client/server options will stay ensure + # they are deleted if the channel is gone. + if(NOT ${CHANNEL_OPTION}) + unset(${CHANNEL_CLIENT_OPTION} CACHE) + unset(${CHANNEL_SERVER_OPTION} CACHE) + endif() + + cmake_dependent_option( + ${CHANNEL_CLIENT_OPTION} "${CHANNEL_CLIENT_OPTION_DOC}" ${CHANNEL_CLIENT_DEFAULT} "${CHANNEL_OPTION}" OFF + ) + + cmake_dependent_option( + ${CHANNEL_SERVER_OPTION} "${CHANNEL_SERVER_OPTION_DOC}" ${CHANNEL_SERVER_DEFAULT} "${CHANNEL_OPTION}" OFF + ) +endmacro(define_channel_options) + +macro(define_channel _channel_name) + set(CHANNEL_NAME ${_channel_name}) + set(MODULE_NAME ${CHANNEL_NAME}) + string(TOUPPER "CHANNEL_${CHANNEL_NAME}" MODULE_PREFIX) +endmacro(define_channel) + +macro(define_channel_client _channel_name) + set(CHANNEL_NAME ${_channel_name}) + set(MODULE_NAME "${CHANNEL_NAME}-client") + string(TOUPPER "CHANNEL_${CHANNEL_NAME}_CLIENT" MODULE_PREFIX) +endmacro(define_channel_client) + +macro(define_channel_server _channel_name) + set(CHANNEL_NAME ${_channel_name}) + set(MODULE_NAME "${CHANNEL_NAME}-server") + string(TOUPPER "CHANNEL_${CHANNEL_NAME}_SERVER" MODULE_PREFIX) +endmacro(define_channel_server) + +macro(define_channel_client_subsystem _channel_name _subsystem _type) + set(CHANNEL_NAME ${_channel_name}) + set(CHANNEL_SUBSYSTEM ${_subsystem}) + string(LENGTH "${_type}" _type_length) + string(TOUPPER "CHANNEL_${CHANNEL_NAME}_CLIENT" CHANNEL_PREFIX) + if(_type_length GREATER 0) + set(SUBSYSTEM_TYPE ${_type}) + set(MODULE_NAME "${CHANNEL_NAME}-client-${CHANNEL_SUBSYSTEM}-${SUBSYSTEM_TYPE}") + string(TOUPPER "CHANNEL_${CHANNEL_NAME}_CLIENT_${CHANNEL_SUBSYSTEM}_${SUBSYSTEM_TYPE}" MODULE_PREFIX) + else() + set(MODULE_NAME "${CHANNEL_NAME}-client-${CHANNEL_SUBSYSTEM}") + string(TOUPPER "CHANNEL_${CHANNEL_NAME}_CLIENT_${CHANNEL_SUBSYSTEM}" MODULE_PREFIX) + endif() +endmacro(define_channel_client_subsystem) + +macro(define_channel_server_subsystem _channel_name _subsystem _type) + set(CHANNEL_NAME ${_channel_name}) + set(CHANNEL_SUBSYSTEM ${_subsystem}) + set(MODULE_NAME "${CHANNEL_NAME}-server-${CHANNEL_SUBSYSTEM}") + string(TOUPPER "CHANNEL_${CHANNEL_NAME}_server_${CHANNEL_SUBSYSTEM}" MODULE_PREFIX) +endmacro(define_channel_server_subsystem) + +macro(add_channel_client _channel_prefix _channel_name) + if(${_channel_prefix}_CLIENT) + add_subdirectory(client) + if(${${_channel_prefix}_CLIENT_STATIC}) + set(CHANNEL_STATIC_CLIENT_MODULES ${CHANNEL_STATIC_CLIENT_MODULES} ${_channel_prefix} PARENT_SCOPE) + set(${_channel_prefix}_CLIENT_NAME ${${_channel_prefix}_CLIENT_NAME} PARENT_SCOPE) + set(${_channel_prefix}_CLIENT_CHANNEL ${${_channel_prefix}_CLIENT_CHANNEL} PARENT_SCOPE) + set(${_channel_prefix}_CLIENT_ENTRY ${${_channel_prefix}_CLIENT_ENTRY} PARENT_SCOPE) + set(CHANNEL_STATIC_CLIENT_ENTRIES ${CHANNEL_STATIC_CLIENT_ENTRIES} ${${_channel_prefix}_CLIENT_ENTRY} + PARENT_SCOPE + ) + endif() + endif() +endmacro(add_channel_client) + +macro(add_channel_server _channel_prefix _channel_name) + if(${_channel_prefix}_SERVER) + add_subdirectory(server) + if(${${_channel_prefix}_SERVER_STATIC}) + set(CHANNEL_STATIC_SERVER_MODULES ${CHANNEL_STATIC_SERVER_MODULES} ${_channel_prefix} PARENT_SCOPE) + set(${_channel_prefix}_SERVER_NAME ${${_channel_prefix}_SERVER_NAME} PARENT_SCOPE) + set(${_channel_prefix}_SERVER_CHANNEL ${${_channel_prefix}_SERVER_CHANNEL} PARENT_SCOPE) + set(${_channel_prefix}_SERVER_ENTRY ${${_channel_prefix}_SERVER_ENTRY} PARENT_SCOPE) + set(CHANNEL_STATIC_SERVER_ENTRIES ${CHANNEL_STATIC_SERVER_ENTRIES} ${${_channel_prefix}_SERVER_ENTRY} + PARENT_SCOPE + ) + endif() + endif() +endmacro(add_channel_server) + +macro(add_channel_client_subsystem _channel_prefix _channel_name _subsystem _type) + add_subdirectory(${_subsystem}) + set(_channel_module_name "${_channel_name}-client") + string(LENGTH "${_type}" _type_length) + if(_type_length GREATER 0) + string(TOUPPER "CHANNEL_${_channel_name}_CLIENT_${_subsystem}_${_type}" _subsystem_prefix) + else() + string(TOUPPER "CHANNEL_${_channel_name}_CLIENT_${_subsystem}" _subsystem_prefix) + endif() + if(${${_subsystem_prefix}_STATIC}) + get_target_property(CHANNEL_SUBSYSTEMS ${_channel_module_name} SUBSYSTEMS) + if(_type_length GREATER 0) + set(SUBSYSTEMS ${SUBSYSTEMS} "${_subsystem}-${_type}") + else() + set(SUBSYSTEMS ${SUBSYSTEMS} ${_subsystem}) + endif() + set_target_properties(${_channel_module_name} PROPERTIES SUBSYSTEMS "${SUBSYSTEMS}") + endif() +endmacro(add_channel_client_subsystem) + +macro(channel_install _targets _destination _export_target) + if(NOT BUILD_SHARED_LIBS) + foreach(_target_name IN ITEMS ${_targets}) + target_include_directories(${_target_name} INTERFACE $) + endforeach() + installwithrpath(TARGETS ${_targets} DESTINATION ${_destination} EXPORT ${_export_target}) + endif() +endmacro(channel_install) + +macro(server_channel_install _targets _destination) + channel_install(${_targets} ${_destination} "FreeRDP-ServerTargets") +endmacro(server_channel_install) + +macro(client_channel_install _targets _destination) + channel_install(${_targets} ${_destination} "FreeRDP-ClientTargets") +endmacro(client_channel_install) + +macro(add_channel_client_library _module_prefix _module_name _channel_name _dynamic _entry) + set(_lnk_dir ${${_module_prefix}_LINK_DIRS}) + if(NOT "${_lnk_dir}" STREQUAL "") + link_directories(${_lnk_dir}) + endif() + + set(${_module_prefix}_STATIC ON PARENT_SCOPE) + set(${_module_prefix}_NAME ${_module_name} PARENT_SCOPE) + set(${_module_prefix}_CHANNEL ${_channel_name} PARENT_SCOPE) + set(${_module_prefix}_ENTRY ${_entry} PARENT_SCOPE) + + add_library(${_module_name} OBJECT ${${_module_prefix}_SRCS}) + set_property(TARGET ${_module_name} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client") + + if(${_module_prefix}_LIBS) + target_link_libraries(${_module_name} PUBLIC ${${_module_prefix}_LIBS}) + endif() + client_channel_install(${_module_name} ${FREERDP_ADDIN_PATH}) +endmacro(add_channel_client_library) + +macro( + add_channel_client_subsystem_library + _module_prefix + _module_name + _channel_name + _type + _dynamic + _entry +) + set(_lnk_dir ${${_module_prefix}_LINK_DIRS}) + if(NOT "${_lnk_dir}" STREQUAL "") + link_directories(${_lnk_dir}) + endif() + + set(${_module_prefix}_STATIC ON PARENT_SCOPE) + set(${_module_prefix}_NAME ${_module_name} PARENT_SCOPE) + set(${_module_prefix}_TYPE ${_type} PARENT_SCOPE) + + add_library(${_module_name} OBJECT ${${_module_prefix}_SRCS}) + set_property(TARGET ${_module_name} PROPERTY FOLDER "Channels/${_channel_name}/Client/Subsystem/${CHANNEL_SUBSYSTEM}") + + if(${_module_prefix}_LIBS) + target_link_libraries(${_module_name} PUBLIC ${${_module_prefix}_LIBS}) + endif() + client_channel_install(${_module_name} ${FREERDP_ADDIN_PATH}) +endmacro(add_channel_client_subsystem_library) + +macro(add_channel_server_library _module_prefix _module_name _channel_name _dynamic _entry) + set(_lnk_dir ${${_module_prefix}_LINK_DIRS}) + if(NOT "${_lnk_dir}" STREQUAL "") + link_directories(${_lnk_dir}) + endif() + + set(${_module_prefix}_STATIC ON PARENT_SCOPE) + set(${_module_prefix}_NAME ${_module_name} PARENT_SCOPE) + set(${_module_prefix}_CHANNEL ${_channel_name} PARENT_SCOPE) + set(${_module_prefix}_ENTRY ${_entry} PARENT_SCOPE) + + add_library(${_module_name} OBJECT ${${_module_prefix}_SRCS}) + set_property(TARGET ${_module_name} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Server") + + if(${_module_prefix}_LIBS) + target_link_libraries(${_module_name} PUBLIC ${${_module_prefix}_LIBS}) + endif() + server_channel_install(${_module_name} ${FREERDP_ADDIN_PATH}) +endmacro(add_channel_server_library) + +set(FILENAME "ChannelOptions.cmake") +file(GLOB FILEPATHS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*/${FILENAME}") + +# We need special treatment for drdynvc: +# It needs to be the first entry so that every +# dynamic channel has the dependent options available. +set(DRDYNVC_MATCH "") + +foreach(FILEPATH ${FILEPATHS}) + if(${FILEPATH} MATCHES "^([^/]*)drdynvc/+${FILENAME}") + set(DRDYNVC_MATCH ${FILEPATH}) + endif() +endforeach() + +if(NOT "${DRDYNVC_MATCH}" STREQUAL "") + list(REMOVE_ITEM FILEPATHS ${DRDYNVC_MATCH}) + list(APPEND FILEPATHS ${DRDYNVC_MATCH}) + list(REVERSE FILEPATHS) # list PREPEND is not available on old CMake3 +endif() + +foreach(FILEPATH ${FILEPATHS}) + if(${FILEPATH} MATCHES "^([^/]*)/+${FILENAME}") + string(REGEX REPLACE "^([^/]*)/+${FILENAME}" "\\1" DIR ${FILEPATH}) + set(CHANNEL_OPTION) + include(${FILEPATH}) + if(${CHANNEL_OPTION}) + set(CHANNEL_MESSAGE "Adding ${CHANNEL_TYPE} channel") + if(${CHANNEL_CLIENT_OPTION}) + set(CHANNEL_MESSAGE "${CHANNEL_MESSAGE} client") + endif() + if(${CHANNEL_SERVER_OPTION}) + set(CHANNEL_MESSAGE "${CHANNEL_MESSAGE} server") + endif() + set(CHANNEL_MESSAGE "${CHANNEL_MESSAGE} \"${CHANNEL_NAME}\"") + set(CHANNEL_MESSAGE "${CHANNEL_MESSAGE}: ${CHANNEL_DESCRIPTION}") + message(STATUS "${CHANNEL_MESSAGE}") + add_subdirectory(${DIR}) + endif() + endif() +endforeach(FILEPATH) + +if(WITH_CHANNELS) + if(WITH_CLIENT_CHANNELS) + add_subdirectory(client) + set(FREERDP_CHANNELS_CLIENT_SRCS ${FREERDP_CHANNELS_CLIENT_SRCS} PARENT_SCOPE) + set(FREERDP_CHANNELS_CLIENT_LIBS ${FREERDP_CHANNELS_CLIENT_LIBS} PARENT_SCOPE) + endif() + + if(WITH_SERVER_CHANNELS) + add_subdirectory(server) + set(FREERDP_CHANNELS_SERVER_SRCS ${FREERDP_CHANNELS_SERVER_SRCS} PARENT_SCOPE) + set(FREERDP_CHANNELS_SERVER_LIBS ${FREERDP_CHANNELS_SERVER_LIBS} PARENT_SCOPE) + endif() +endif() diff --git a/third_party/FreeRDP/channels/ainput/CMakeLists.txt b/third_party/FreeRDP/channels/ainput/CMakeLists.txt new file mode 100644 index 0000000..0e332eb --- /dev/null +++ b/third_party/FreeRDP/channels/ainput/CMakeLists.txt @@ -0,0 +1,27 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2022 Armin Novak +# Copyright 2022 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. + +define_channel("ainput") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/third_party/FreeRDP/channels/ainput/ChannelOptions.cmake b/third_party/FreeRDP/channels/ainput/ChannelOptions.cmake new file mode 100644 index 0000000..5bab1c8 --- /dev/null +++ b/third_party/FreeRDP/channels/ainput/ChannelOptions.cmake @@ -0,0 +1,20 @@ +set(OPTION_DEFAULT ON) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options( + NAME + "ainput" + TYPE + "dynamic" + DESCRIPTION + "Advanced Input Virtual Channel Extension" + SPECIFICATIONS + "[XXXXX]" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/ainput/client/CMakeLists.txt b/third_party/FreeRDP/channels/ainput/client/CMakeLists.txt new file mode 100644 index 0000000..0bd9abf --- /dev/null +++ b/third_party/FreeRDP/channels/ainput/client/CMakeLists.txt @@ -0,0 +1,27 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2022 Armin Novak +# Copyright 2022 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. + +define_channel_client("ainput") + +set(${MODULE_PREFIX}_SRCS ainput_main.c ainput_main.h) + +set(${MODULE_PREFIX}_LIBS winpr) + +include_directories(..) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") diff --git a/third_party/FreeRDP/channels/ainput/client/ainput_main.c b/third_party/FreeRDP/channels/ainput/client/ainput_main.c new file mode 100644 index 0000000..f930040 --- /dev/null +++ b/third_party/FreeRDP/channels/ainput/client/ainput_main.c @@ -0,0 +1,200 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Advanced Input Virtual Channel Extension + * + * Copyright 2022 Armin Novak + * Copyright 2022 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 + +#include +#include + +#include +#include +#include +#include + +#include "ainput_main.h" +#include +#include +#include +#include + +#include "../common/ainput_common.h" + +#define TAG CHANNELS_TAG("ainput.client") + +typedef struct AINPUT_PLUGIN_ AINPUT_PLUGIN; +struct AINPUT_PLUGIN_ +{ + GENERIC_DYNVC_PLUGIN base; + AInputClientContext* context; + UINT32 MajorVersion; + UINT32 MinorVersion; + CRITICAL_SECTION lock; +}; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ainput_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data) +{ + UINT16 type = 0; + AINPUT_PLUGIN* ainput = nullptr; + GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + + WINPR_ASSERT(callback); + WINPR_ASSERT(data); + + ainput = (AINPUT_PLUGIN*)callback->plugin; + WINPR_ASSERT(ainput); + + if (!Stream_CheckAndLogRequiredLength(TAG, data, 2)) + return ERROR_NO_DATA; + Stream_Read_UINT16(data, type); + switch (type) + { + case MSG_AINPUT_VERSION: + if (!Stream_CheckAndLogRequiredLength(TAG, data, 8)) + return ERROR_NO_DATA; + Stream_Read_UINT32(data, ainput->MajorVersion); + Stream_Read_UINT32(data, ainput->MinorVersion); + break; + default: + WLog_WARN(TAG, "Received unsupported message type 0x%04" PRIx16, type); + break; + } + + return CHANNEL_RC_OK; +} + +static UINT ainput_send_input_event(AInputClientContext* context, UINT64 flags, INT32 x, INT32 y) +{ + BYTE buffer[32] = WINPR_C_ARRAY_INIT; + wStream sbuffer = WINPR_C_ARRAY_INIT; + wStream* s = Stream_StaticInit(&sbuffer, buffer, sizeof(buffer)); + + WINPR_ASSERT(s); + WINPR_ASSERT(context); + + const UINT64 time = GetTickCount64(); + AINPUT_PLUGIN* ainput = (AINPUT_PLUGIN*)context->handle; + WINPR_ASSERT(ainput); + + if (ainput->MajorVersion != AINPUT_VERSION_MAJOR) + { + WLog_WARN(TAG, "Unsupported channel version %" PRIu32 ".%" PRIu32 ", aborting.", + ainput->MajorVersion, ainput->MinorVersion); + return CHANNEL_RC_UNSUPPORTED_VERSION; + } + + { + char ebuffer[128] = WINPR_C_ARRAY_INIT; + WLog_VRB(TAG, "sending timestamp=0x%08" PRIx64 ", flags=%s, %" PRId32 "x%" PRId32, time, + ainput_flags_to_string(flags, ebuffer, sizeof(ebuffer)), x, y); + } + + /* Message type */ + Stream_Write_UINT16(s, MSG_AINPUT_MOUSE); + + /* Event data */ + Stream_Write_UINT64(s, time); + Stream_Write_UINT64(s, flags); + Stream_Write_INT32(s, x); + Stream_Write_INT32(s, y); + Stream_SealLength(s); + + /* ainput back what we have received. AINPUT does not have any message IDs. */ + EnterCriticalSection(&ainput->lock); + GENERIC_CHANNEL_CALLBACK* callback = ainput->base.listener_callback->channel_callback; + WINPR_ASSERT(callback); + WINPR_ASSERT(callback->channel); + WINPR_ASSERT(callback->channel->Write); + const UINT rc = callback->channel->Write(callback->channel, (ULONG)Stream_Length(s), + Stream_Buffer(s), nullptr); + LeaveCriticalSection(&ainput->lock); + return rc; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ainput_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + + if (callback) + { + AINPUT_PLUGIN* ainput = (AINPUT_PLUGIN*)callback->plugin; + WINPR_ASSERT(ainput); + + /* Lock here to ensure that no ainput_send_input_event is in progress. */ + EnterCriticalSection(&ainput->lock); + free(callback); + LeaveCriticalSection(&ainput->lock); + } + return CHANNEL_RC_OK; +} + +static UINT init_plugin_cb(GENERIC_DYNVC_PLUGIN* base, WINPR_ATTR_UNUSED rdpContext* rcontext, + WINPR_ATTR_UNUSED rdpSettings* settings) +{ + AINPUT_PLUGIN* ainput = (AINPUT_PLUGIN*)base; + AInputClientContext* context = (AInputClientContext*)calloc(1, sizeof(AInputClientContext)); + if (!context) + return CHANNEL_RC_NO_MEMORY; + + context->handle = (void*)base; + context->AInputSendInputEvent = ainput_send_input_event; + + InitializeCriticalSection(&ainput->lock); + + EnterCriticalSection(&ainput->lock); + ainput->context = context; + ainput->base.iface.pInterface = context; + LeaveCriticalSection(&ainput->lock); + return CHANNEL_RC_OK; +} + +static void terminate_plugin_cb(GENERIC_DYNVC_PLUGIN* base) +{ + AINPUT_PLUGIN* ainput = (AINPUT_PLUGIN*)base; + WINPR_ASSERT(ainput); + + DeleteCriticalSection(&ainput->lock); + free(ainput->context); +} + +static const IWTSVirtualChannelCallback ainput_functions = { ainput_on_data_received, + nullptr, /* Open */ + ainput_on_close, nullptr }; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE ainput_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)) +{ + return freerdp_generic_DVCPluginEntry(pEntryPoints, TAG, AINPUT_DVC_CHANNEL_NAME, + sizeof(AINPUT_PLUGIN), sizeof(GENERIC_CHANNEL_CALLBACK), + &ainput_functions, init_plugin_cb, terminate_plugin_cb); +} diff --git a/third_party/FreeRDP/channels/ainput/client/ainput_main.h b/third_party/FreeRDP/channels/ainput/client/ainput_main.h new file mode 100644 index 0000000..5e1d5b1 --- /dev/null +++ b/third_party/FreeRDP/channels/ainput/client/ainput_main.h @@ -0,0 +1,40 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Advanced Input Virtual Channel Extension + * + * Copyright 2022 Armin Novak + * Copyright 2022 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. + */ + +#ifndef FREERDP_CHANNEL_AINPUT_CLIENT_MAIN_H +#define FREERDP_CHANNEL_AINPUT_CLIENT_MAIN_H + +#include +#include +#include +#include +#include + +#define DVC_TAG CHANNELS_TAG("ainput.client") +#ifdef WITH_DEBUG_DVC +#define DEBUG_DVC(...) WLog_DBG(DVC_TAG, __VA_ARGS__) +#else +#define DEBUG_DVC(...) \ + do \ + { \ + } while (0) +#endif + +#endif /* FREERDP_CHANNEL_AINPUT_CLIENT_MAIN_H */ diff --git a/third_party/FreeRDP/channels/ainput/common/ainput_common.h b/third_party/FreeRDP/channels/ainput/common/ainput_common.h new file mode 100644 index 0000000..82bfdbf --- /dev/null +++ b/third_party/FreeRDP/channels/ainput/common/ainput_common.h @@ -0,0 +1,60 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Input Redirection Virtual Channel + * + * Copyright 2022 Armin Novak + * Copyright 2022 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. + */ + +#ifndef FREERDP_INT_AINPUT_COMMON_H +#define FREERDP_INT_AINPUT_COMMON_H + +#include + +#include + +WINPR_ATTR_NODISCARD +static inline const char* ainput_flags_to_string(UINT64 flags, char* buffer, size_t size) +{ + char number[32] = WINPR_C_ARRAY_INIT; + + if (flags & AINPUT_FLAGS_HAVE_REL) + winpr_str_append("AINPUT_FLAGS_HAVE_REL", buffer, size, "|"); + if (flags & AINPUT_FLAGS_WHEEL) + winpr_str_append("AINPUT_FLAGS_WHEEL", buffer, size, "|"); + if (flags & AINPUT_FLAGS_MOVE) + winpr_str_append("AINPUT_FLAGS_MOVE", buffer, size, "|"); + if (flags & AINPUT_FLAGS_DOWN) + winpr_str_append("AINPUT_FLAGS_DOWN", buffer, size, "|"); + if (flags & AINPUT_FLAGS_REL) + winpr_str_append("AINPUT_FLAGS_REL", buffer, size, "|"); + if (flags & AINPUT_FLAGS_BUTTON1) + winpr_str_append("AINPUT_FLAGS_BUTTON1", buffer, size, "|"); + if (flags & AINPUT_FLAGS_BUTTON2) + winpr_str_append("AINPUT_FLAGS_BUTTON2", buffer, size, "|"); + if (flags & AINPUT_FLAGS_BUTTON3) + winpr_str_append("AINPUT_FLAGS_BUTTON3", buffer, size, "|"); + if (flags & AINPUT_XFLAGS_BUTTON1) + winpr_str_append("AINPUT_XFLAGS_BUTTON1", buffer, size, "|"); + if (flags & AINPUT_XFLAGS_BUTTON2) + winpr_str_append("AINPUT_XFLAGS_BUTTON2", buffer, size, "|"); + + _snprintf(number, sizeof(number), "[0x%08" PRIx64 "]", flags); + winpr_str_append(number, buffer, size, " "); + + return buffer; +} + +#endif /* FREERDP_INT_AINPUT_COMMON_H */ diff --git a/third_party/FreeRDP/channels/ainput/server/CMakeLists.txt b/third_party/FreeRDP/channels/ainput/server/CMakeLists.txt new file mode 100644 index 0000000..b5becfb --- /dev/null +++ b/third_party/FreeRDP/channels/ainput/server/CMakeLists.txt @@ -0,0 +1,24 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2022 Armin Novak +# Copyright 2022 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. + +define_channel_server("ainput") + +set(${MODULE_PREFIX}_SRCS ainput_main.c) + +set(${MODULE_PREFIX}_LIBS freerdp) +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry") diff --git a/third_party/FreeRDP/channels/ainput/server/ainput_main.c b/third_party/FreeRDP/channels/ainput/server/ainput_main.c new file mode 100644 index 0000000..eb1740d --- /dev/null +++ b/third_party/FreeRDP/channels/ainput/server/ainput_main.c @@ -0,0 +1,596 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Advanced Input Virtual Channel Extension + * + * Copyright 2022 Armin Novak + * Copyright 2022 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 + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "../common/ainput_common.h" + +#define TAG CHANNELS_TAG("ainput.server") + +typedef enum +{ + AINPUT_INITIAL, + AINPUT_OPENED, + AINPUT_VERSION_SENT, +} eAInputChannelState; + +typedef struct +{ + ainput_server_context context; + + BOOL opened; + + HANDLE stopEvent; + + HANDLE thread; + void* ainput_channel; + + DWORD SessionId; + + BOOL isOpened; + BOOL externalThread; + + /* Channel state */ + eAInputChannelState state; + + wStream* buffer; +} ainput_server; + +static UINT ainput_server_context_poll(ainput_server_context* context); +static BOOL ainput_server_context_handle(ainput_server_context* context, HANDLE* handle); +static UINT ainput_server_context_poll_int(ainput_server_context* context); + +static BOOL ainput_server_is_open(ainput_server_context* context) +{ + ainput_server* ainput = (ainput_server*)context; + + WINPR_ASSERT(ainput); + return ainput->isOpened; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ainput_server_open_channel(ainput_server* ainput) +{ + DWORD Error = 0; + HANDLE hEvent = nullptr; + DWORD StartTick = 0; + DWORD BytesReturned = 0; + PULONG pSessionId = nullptr; + + WINPR_ASSERT(ainput); + + if (WTSQuerySessionInformationA(ainput->context.vcm, WTS_CURRENT_SESSION, WTSSessionId, + (LPSTR*)&pSessionId, &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSQuerySessionInformationA failed!"); + return ERROR_INTERNAL_ERROR; + } + + ainput->SessionId = (DWORD)*pSessionId; + WTSFreeMemory(pSessionId); + hEvent = WTSVirtualChannelManagerGetEventHandle(ainput->context.vcm); + StartTick = GetTickCount(); + + while (ainput->ainput_channel == nullptr) + { + if (WaitForSingleObject(hEvent, 1000) == WAIT_FAILED) + { + Error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", Error); + return Error; + } + + ainput->ainput_channel = WTSVirtualChannelOpenEx(ainput->SessionId, AINPUT_DVC_CHANNEL_NAME, + WTS_CHANNEL_OPTION_DYNAMIC); + + Error = GetLastError(); + + if (Error == ERROR_NOT_FOUND) + { + WLog_DBG(TAG, "Channel %s not found", AINPUT_DVC_CHANNEL_NAME); + break; + } + + if (ainput->ainput_channel) + { + UINT32 channelId = 0; + BOOL status = TRUE; + + channelId = WTSChannelGetIdByHandle(ainput->ainput_channel); + + IFCALLRET(ainput->context.ChannelIdAssigned, status, &ainput->context, channelId); + if (!status) + { + WLog_ERR(TAG, "context->ChannelIdAssigned failed!"); + return ERROR_INTERNAL_ERROR; + } + + break; + } + + if (GetTickCount() - StartTick > 5000) + { + WLog_WARN(TAG, "Timeout opening channel %s", AINPUT_DVC_CHANNEL_NAME); + break; + } + } + + return ainput->ainput_channel ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +static UINT ainput_server_send_version(ainput_server* ainput) +{ + ULONG written = 0; + wStream* s = nullptr; + + WINPR_ASSERT(ainput); + + s = ainput->buffer; + WINPR_ASSERT(s); + + Stream_ResetPosition(s); + if (!Stream_EnsureCapacity(s, 10)) + { + WLog_WARN(TAG, "[%s] out of memory", AINPUT_DVC_CHANNEL_NAME); + return ERROR_OUTOFMEMORY; + } + + Stream_Write_UINT16(s, MSG_AINPUT_VERSION); + Stream_Write_UINT32(s, AINPUT_VERSION_MAJOR); /* Version (4 bytes) */ + Stream_Write_UINT32(s, AINPUT_VERSION_MINOR); /* Version (4 bytes) */ + + WINPR_ASSERT(Stream_GetPosition(s) <= UINT32_MAX); + if (!WTSVirtualChannelWrite(ainput->ainput_channel, Stream_BufferAs(s, char), + (ULONG)Stream_GetPosition(s), &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +static UINT ainput_server_recv_mouse_event(ainput_server* ainput, wStream* s) +{ + UINT error = CHANNEL_RC_OK; + UINT64 flags = 0; + UINT64 time = 0; + INT32 x = 0; + INT32 y = 0; + char buffer[128] = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(ainput); + WINPR_ASSERT(s); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 24)) + return ERROR_NO_DATA; + + Stream_Read_UINT64(s, time); + Stream_Read_UINT64(s, flags); + Stream_Read_INT32(s, x); + Stream_Read_INT32(s, y); + + WLog_VRB(TAG, "received: time=0x%08" PRIx64 ", flags=%s, %" PRId32 "x%" PRId32, time, + ainput_flags_to_string(flags, buffer, sizeof(buffer)), x, y); + IFCALLRET(ainput->context.MouseEvent, error, &ainput->context, time, flags, x, y); + + return error; +} + +static HANDLE ainput_server_get_channel_handle(ainput_server* ainput) +{ + void* buffer = nullptr; + DWORD BytesReturned = 0; + HANDLE ChannelEvent = nullptr; + + WINPR_ASSERT(ainput); + + if (WTSVirtualChannelQuery(ainput->ainput_channel, WTSVirtualEventHandle, &buffer, + &BytesReturned) == TRUE) + { + if (BytesReturned == sizeof(HANDLE)) + ChannelEvent = *(HANDLE*)buffer; + + WTSFreeMemory(buffer); + } + + return ChannelEvent; +} + +static DWORD WINAPI ainput_server_thread_func(LPVOID arg) +{ + DWORD nCount = 0; + HANDLE events[2] = WINPR_C_ARRAY_INIT; + ainput_server* ainput = (ainput_server*)arg; + UINT error = CHANNEL_RC_OK; + DWORD status = 0; + + WINPR_ASSERT(ainput); + + nCount = 0; + events[nCount++] = ainput->stopEvent; + + while ((error == CHANNEL_RC_OK) && (WaitForSingleObject(events[0], 0) != WAIT_OBJECT_0)) + { + switch (ainput->state) + { + case AINPUT_OPENED: + events[1] = ainput_server_get_channel_handle(ainput); + nCount = 2; + status = WaitForMultipleObjects(nCount, events, FALSE, 100); + switch (status) + { + case WAIT_TIMEOUT: + case WAIT_OBJECT_0 + 1: + case WAIT_OBJECT_0: + error = ainput_server_context_poll_int(&ainput->context); + break; + case WAIT_FAILED: + default: + WLog_WARN(TAG, "[%s] Wait for open failed", AINPUT_DVC_CHANNEL_NAME); + error = ERROR_INTERNAL_ERROR; + break; + } + break; + case AINPUT_VERSION_SENT: + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + switch (status) + { + case WAIT_TIMEOUT: + case WAIT_OBJECT_0 + 1: + case WAIT_OBJECT_0: + error = ainput_server_context_poll_int(&ainput->context); + break; + + case WAIT_FAILED: + default: + WLog_WARN(TAG, "[%s] Wait for version failed", AINPUT_DVC_CHANNEL_NAME); + error = ERROR_INTERNAL_ERROR; + break; + } + break; + default: + error = ainput_server_context_poll_int(&ainput->context); + break; + } + } + + (void)WTSVirtualChannelClose(ainput->ainput_channel); + ainput->ainput_channel = nullptr; + + if (error && ainput->context.rdpcontext) + setChannelError(ainput->context.rdpcontext, error, + "ainput_server_thread_func reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ainput_server_open(ainput_server_context* context) +{ + ainput_server* ainput = (ainput_server*)context; + + WINPR_ASSERT(ainput); + + if (!ainput->externalThread && (ainput->thread == nullptr)) + { + ainput->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + if (!ainput->stopEvent) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + ainput->thread = CreateThread(nullptr, 0, ainput_server_thread_func, ainput, 0, nullptr); + if (!ainput->thread) + { + WLog_ERR(TAG, "CreateEvent failed!"); + (void)CloseHandle(ainput->stopEvent); + ainput->stopEvent = nullptr; + return ERROR_INTERNAL_ERROR; + } + } + ainput->isOpened = TRUE; + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ainput_server_close(ainput_server_context* context) +{ + UINT error = CHANNEL_RC_OK; + ainput_server* ainput = (ainput_server*)context; + + WINPR_ASSERT(ainput); + + if (!ainput->externalThread && ainput->thread) + { + (void)SetEvent(ainput->stopEvent); + + if (WaitForSingleObject(ainput->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + return error; + } + + (void)CloseHandle(ainput->thread); + (void)CloseHandle(ainput->stopEvent); + ainput->thread = nullptr; + ainput->stopEvent = nullptr; + } + if (ainput->externalThread) + { + if (ainput->state != AINPUT_INITIAL) + { + (void)WTSVirtualChannelClose(ainput->ainput_channel); + ainput->ainput_channel = nullptr; + ainput->state = AINPUT_INITIAL; + } + } + ainput->isOpened = FALSE; + + return error; +} + +static UINT ainput_server_initialize(ainput_server_context* context, BOOL externalThread) +{ + UINT error = CHANNEL_RC_OK; + ainput_server* ainput = (ainput_server*)context; + + WINPR_ASSERT(ainput); + + if (ainput->isOpened) + { + WLog_WARN(TAG, "Application error: AINPUT channel already initialized, calling in this " + "state is not possible!"); + return ERROR_INVALID_STATE; + } + ainput->externalThread = externalThread; + return error; +} + +ainput_server_context* ainput_server_context_new(HANDLE vcm) +{ + ainput_server* ainput = (ainput_server*)calloc(1, sizeof(ainput_server)); + + if (!ainput) + return nullptr; + + ainput->context.vcm = vcm; + ainput->context.Open = ainput_server_open; + ainput->context.IsOpen = ainput_server_is_open; + ainput->context.Close = ainput_server_close; + ainput->context.Initialize = ainput_server_initialize; + ainput->context.Poll = ainput_server_context_poll; + ainput->context.ChannelHandle = ainput_server_context_handle; + + ainput->buffer = Stream_New(nullptr, 4096); + if (!ainput->buffer) + goto fail; + return &ainput->context; +fail: + WINPR_PRAGMA_DIAG_PUSH + WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC + ainput_server_context_free(&ainput->context); + WINPR_PRAGMA_DIAG_POP + return nullptr; +} + +void ainput_server_context_free(ainput_server_context* context) +{ + ainput_server* ainput = (ainput_server*)context; + if (ainput) + { + ainput_server_close(context); + Stream_Free(ainput->buffer, TRUE); + } + free(ainput); +} + +static UINT ainput_process_message(ainput_server* ainput) +{ + UINT error = ERROR_INTERNAL_ERROR; + ULONG BytesReturned = 0; + ULONG ActualBytesReturned = 0; + + WINPR_ASSERT(ainput); + WINPR_ASSERT(ainput->ainput_channel); + + wStream* s = ainput->buffer; + WINPR_ASSERT(s); + + Stream_ResetPosition(s); + const BOOL rc = WTSVirtualChannelRead(ainput->ainput_channel, 0, nullptr, 0, &BytesReturned); + if (!rc) + goto out; + + if (BytesReturned < 2) + { + error = CHANNEL_RC_OK; + goto out; + } + + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + if (WTSVirtualChannelRead(ainput->ainput_channel, 0, Stream_BufferAs(s, char), + (ULONG)Stream_Capacity(s), &ActualBytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + goto out; + } + + if (BytesReturned != ActualBytesReturned) + { + WLog_ERR(TAG, "WTSVirtualChannelRead size mismatch %" PRIu32 ", expected %" PRIu32, + ActualBytesReturned, BytesReturned); + goto out; + } + + Stream_SetLength(s, ActualBytesReturned); + { + const UINT16 MessageId = Stream_Get_UINT16(s); + + switch (MessageId) + { + case MSG_AINPUT_MOUSE: + error = ainput_server_recv_mouse_event(ainput, s); + break; + + default: + WLog_ERR(TAG, "audin_server_thread_func: unknown MessageId %" PRIu16 "", MessageId); + break; + } + } + +out: + if (error) + WLog_ERR(TAG, "Response failed with error %" PRIu32 "!", error); + + return error; +} + +BOOL ainput_server_context_handle(ainput_server_context* context, HANDLE* handle) +{ + ainput_server* ainput = (ainput_server*)context; + WINPR_ASSERT(ainput); + WINPR_ASSERT(handle); + + if (!ainput->externalThread) + { + WLog_WARN(TAG, "[%s] externalThread fail!", AINPUT_DVC_CHANNEL_NAME); + return FALSE; + } + if (ainput->state == AINPUT_INITIAL) + { + WLog_WARN(TAG, "[%s] state fail!", AINPUT_DVC_CHANNEL_NAME); + return FALSE; + } + *handle = ainput_server_get_channel_handle(ainput); + return TRUE; +} + +UINT ainput_server_context_poll_int(ainput_server_context* context) +{ + ainput_server* ainput = (ainput_server*)context; + UINT error = ERROR_INTERNAL_ERROR; + + WINPR_ASSERT(ainput); + + switch (ainput->state) + { + case AINPUT_INITIAL: + error = ainput_server_open_channel(ainput); + if (error) + WLog_ERR(TAG, "ainput_server_open_channel failed with error %" PRIu32 "!", error); + else + ainput->state = AINPUT_OPENED; + break; + case AINPUT_OPENED: + { + union + { + BYTE* pb; + void* pv; + } buffer; + DWORD BytesReturned = 0; + + buffer.pv = nullptr; + + if (WTSVirtualChannelQuery(ainput->ainput_channel, WTSVirtualChannelReady, &buffer.pv, + &BytesReturned) != TRUE) + { + WLog_ERR(TAG, "WTSVirtualChannelReady failed,"); + } + else + { + if (*buffer.pb != 0) + { + error = ainput_server_send_version(ainput); + if (error) + WLog_ERR(TAG, "audin_server_send_version failed with error %" PRIu32 "!", + error); + else + ainput->state = AINPUT_VERSION_SENT; + } + else + error = CHANNEL_RC_OK; + } + WTSFreeMemory(buffer.pv); + } + break; + case AINPUT_VERSION_SENT: + error = ainput_process_message(ainput); + break; + + default: + WLog_ERR(TAG, "AINPUT channel is in invalid state %u", ainput->state); + break; + } + + return error; +} + +UINT ainput_server_context_poll(ainput_server_context* context) +{ + ainput_server* ainput = (ainput_server*)context; + + WINPR_ASSERT(ainput); + if (!ainput->externalThread) + { + WLog_WARN(TAG, "[%s] externalThread fail!", AINPUT_DVC_CHANNEL_NAME); + return ERROR_INTERNAL_ERROR; + } + return ainput_server_context_poll_int(context); +} diff --git a/third_party/FreeRDP/channels/audin/CMakeLists.txt b/third_party/FreeRDP/channels/audin/CMakeLists.txt new file mode 100644 index 0000000..2c92a64 --- /dev/null +++ b/third_party/FreeRDP/channels/audin/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel("audin") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/third_party/FreeRDP/channels/audin/ChannelOptions.cmake b/third_party/FreeRDP/channels/audin/ChannelOptions.cmake new file mode 100644 index 0000000..9956bf9 --- /dev/null +++ b/third_party/FreeRDP/channels/audin/ChannelOptions.cmake @@ -0,0 +1,24 @@ +set(OPTION_DEFAULT ON) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT ON) + +if(ANDROID) + set(OPTION_SERVER_DEFAULT OFF) +endif() + +define_channel_options( + NAME + "audin" + TYPE + "dynamic" + DESCRIPTION + "Audio Input Redirection Virtual Channel Extension" + SPECIFICATIONS + "[MS-RDPEAI]" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/audin/client/CMakeLists.txt b/third_party/FreeRDP/channels/audin/client/CMakeLists.txt new file mode 100644 index 0000000..5ce4015 --- /dev/null +++ b/third_party/FreeRDP/channels/audin/client/CMakeLists.txt @@ -0,0 +1,58 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_client("audin") + +set(${MODULE_PREFIX}_SRCS audin_main.c audin_main.h) + +set(${MODULE_PREFIX}_LIBS freerdp winpr) + +include_directories(..) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") + +if(WITH_OSS) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "oss" "") +endif() + +if(WITH_ALSA) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "alsa" "") +endif() + +if(WITH_PULSE) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "pulse" "") +endif() + +if(WITH_OPENSLES) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "opensles" "") +endif() + +if(WITH_WINMM) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "winmm" "") +endif() + +if(WITH_MACAUDIO) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "mac" "") +endif() + +if(WITH_SNDIO) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "sndio" "") +endif() + +if(WITH_IOSAUDIO) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "ios" "") +endif() diff --git a/third_party/FreeRDP/channels/audin/client/alsa/CMakeLists.txt b/third_party/FreeRDP/channels/audin/client/alsa/CMakeLists.txt new file mode 100644 index 0000000..09af7b1 --- /dev/null +++ b/third_party/FreeRDP/channels/audin/client/alsa/CMakeLists.txt @@ -0,0 +1,31 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_client_subsystem("audin" "alsa" "") + +find_package(ALSA REQUIRED) + +set(${MODULE_PREFIX}_SRCS audin_alsa.c) + +set(${MODULE_PREFIX}_LIBS winpr freerdp ${ALSA_LIBRARIES}) + +freerdp_client_pc_add_requires_private("alsa") + +include_directories(..) +include_directories(SYSTEM ${ALSA_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") diff --git a/third_party/FreeRDP/channels/audin/client/alsa/audin_alsa.c b/third_party/FreeRDP/channels/audin/client/alsa/audin_alsa.c new file mode 100644 index 0000000..20564dc --- /dev/null +++ b/third_party/FreeRDP/channels/audin/client/alsa/audin_alsa.c @@ -0,0 +1,466 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Input Redirection Virtual Channel - ALSA implementation + * + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "audin_main.h" + +typedef struct +{ + IAudinDevice iface; + + char* device_name; + UINT32 frames_per_packet; + AUDIO_FORMAT aformat; + + HANDLE thread; + HANDLE stopEvent; + + AudinReceive receive; + void* user_data; + + rdpContext* rdpcontext; + wLog* log; + size_t bytes_per_frame; +} AudinALSADevice; + +static snd_pcm_format_t audin_alsa_format(UINT32 wFormatTag, UINT32 bitPerChannel) +{ + switch (wFormatTag) + { + case WAVE_FORMAT_PCM: + switch (bitPerChannel) + { + case 16: + return SND_PCM_FORMAT_S16_LE; + + case 8: + return SND_PCM_FORMAT_S8; + + default: + return SND_PCM_FORMAT_UNKNOWN; + } + + default: + return SND_PCM_FORMAT_UNKNOWN; + } +} + +static BOOL audin_alsa_set_params(AudinALSADevice* alsa, snd_pcm_t* capture_handle) +{ + int error = 0; + SSIZE_T s = 0; + UINT32 channels = alsa->aformat.nChannels; + snd_pcm_hw_params_t* hw_params = nullptr; + snd_pcm_format_t format = + audin_alsa_format(alsa->aformat.wFormatTag, alsa->aformat.wBitsPerSample); + + if ((error = snd_pcm_hw_params_malloc(&hw_params)) < 0) + { + WLog_Print(alsa->log, WLOG_ERROR, "snd_pcm_hw_params_malloc (%s)", snd_strerror(error)); + return FALSE; + } + + snd_pcm_hw_params_any(capture_handle, hw_params); + snd_pcm_hw_params_set_access(capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); + snd_pcm_hw_params_set_format(capture_handle, hw_params, format); + snd_pcm_hw_params_set_rate_near(capture_handle, hw_params, &alsa->aformat.nSamplesPerSec, + nullptr); + snd_pcm_hw_params_set_channels_near(capture_handle, hw_params, &channels); + snd_pcm_hw_params(capture_handle, hw_params); + snd_pcm_hw_params_free(hw_params); + snd_pcm_prepare(capture_handle); + if (channels > UINT16_MAX) + return FALSE; + s = snd_pcm_format_size(format, 1); + if ((s < 0) || (s > UINT16_MAX)) + return FALSE; + alsa->aformat.nChannels = (UINT16)channels; + alsa->bytes_per_frame = (size_t)s * channels; + return TRUE; +} + +static DWORD WINAPI audin_alsa_thread_func(LPVOID arg) +{ + DWORD error = CHANNEL_RC_OK; + BYTE* buffer = nullptr; + AudinALSADevice* alsa = (AudinALSADevice*)arg; + + WINPR_ASSERT(alsa); + + WLog_Print(alsa->log, WLOG_DEBUG, "in"); + + snd_pcm_t* capture_handle = nullptr; + const int rc = snd_pcm_open(&capture_handle, alsa->device_name, SND_PCM_STREAM_CAPTURE, 0); + if (rc < 0) + { + WLog_Print(alsa->log, WLOG_ERROR, "snd_pcm_open (%s)", snd_strerror(rc)); + error = CHANNEL_RC_INITIALIZATION_ERROR; + goto out; + } + + if (!audin_alsa_set_params(alsa, capture_handle)) + { + WLog_Print(alsa->log, WLOG_ERROR, "audin_alsa_set_params failed"); + goto out; + } + + buffer = + (BYTE*)calloc(alsa->frames_per_packet + alsa->aformat.nBlockAlign, alsa->bytes_per_frame); + + if (!buffer) + { + WLog_Print(alsa->log, WLOG_ERROR, "calloc failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + while (1) + { + size_t frames = alsa->frames_per_packet; + const DWORD status = WaitForSingleObject(alsa->stopEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_Print(alsa->log, WLOG_ERROR, "WaitForSingleObject failed with error %" PRIu32 "!", + error); + break; + } + + if (status == WAIT_OBJECT_0) + { + WLog_Print(alsa->log, WLOG_DEBUG, "alsa->stopEvent requests termination"); + break; + } + + snd_pcm_sframes_t framesRead = snd_pcm_readi(capture_handle, buffer, frames); + + if (framesRead == 0) + continue; + + if (framesRead == -EPIPE) + { + const int res = snd_pcm_recover(capture_handle, (int)framesRead, 0); + if (res < 0) + WLog_Print(alsa->log, WLOG_WARN, "snd_pcm_recover (%s)", snd_strerror(res)); + + continue; + } + else if (framesRead < 0) + { + WLog_Print(alsa->log, WLOG_ERROR, "snd_pcm_readi (%s)", snd_strerror((int)framesRead)); + error = ERROR_INTERNAL_ERROR; + break; + } + + error = alsa->receive(&alsa->aformat, buffer, + WINPR_ASSERTING_INT_CAST(size_t, framesRead) * alsa->bytes_per_frame, + alsa->user_data); + + if (error) + { + WLog_Print(alsa->log, WLOG_ERROR, + "audin_alsa_thread_receive failed with error %" PRIu32, error); + break; + } + } + + free(buffer); + + if (capture_handle) + { + const int res = snd_pcm_close(capture_handle); + if (res < 0) + WLog_Print(alsa->log, WLOG_WARN, "snd_pcm_close (%s)", snd_strerror(res)); + } + +out: + WLog_Print(alsa->log, WLOG_DEBUG, "out"); + + if (error && alsa->rdpcontext) + setChannelError(alsa->rdpcontext, error, "audin_alsa_thread_func reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_alsa_free(IAudinDevice* device) +{ + AudinALSADevice* alsa = (AudinALSADevice*)device; + + if (alsa) + free(alsa->device_name); + + free(alsa); + return CHANNEL_RC_OK; +} + +static BOOL audin_alsa_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format) +{ + if (!device || !format) + return FALSE; + + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + if (format->cbSize == 0 && (format->nSamplesPerSec <= 48000) && + (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) && + (format->nChannels == 1 || format->nChannels == 2)) + { + return TRUE; + } + + break; + + default: + return FALSE; + } + + return FALSE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_alsa_set_format(IAudinDevice* device, const AUDIO_FORMAT* format, + UINT32 FramesPerPacket) +{ + AudinALSADevice* alsa = (AudinALSADevice*)device; + + if (!alsa || !format) + return ERROR_INVALID_PARAMETER; + + alsa->aformat = *format; + alsa->frames_per_packet = FramesPerPacket; + + if (audin_alsa_format(format->wFormatTag, format->wBitsPerSample) == SND_PCM_FORMAT_UNKNOWN) + return ERROR_INTERNAL_ERROR; + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_alsa_open(IAudinDevice* device, AudinReceive receive, void* user_data) +{ + AudinALSADevice* alsa = (AudinALSADevice*)device; + + if (!device || !receive || !user_data) + return ERROR_INVALID_PARAMETER; + + alsa->receive = receive; + alsa->user_data = user_data; + + if (!(alsa->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr))) + { + WLog_Print(alsa->log, WLOG_ERROR, "CreateEvent failed!"); + goto error_out; + } + + if (!(alsa->thread = CreateThread(nullptr, 0, audin_alsa_thread_func, alsa, 0, nullptr))) + { + WLog_Print(alsa->log, WLOG_ERROR, "CreateThread failed!"); + goto error_out; + } + + return CHANNEL_RC_OK; +error_out: + (void)CloseHandle(alsa->stopEvent); + alsa->stopEvent = nullptr; + return ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_alsa_close(IAudinDevice* device) +{ + UINT error = CHANNEL_RC_OK; + AudinALSADevice* alsa = (AudinALSADevice*)device; + + if (!alsa) + return ERROR_INVALID_PARAMETER; + + if (alsa->stopEvent) + { + (void)SetEvent(alsa->stopEvent); + + if (WaitForSingleObject(alsa->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_Print(alsa->log, WLOG_ERROR, "WaitForSingleObject failed with error %" PRIu32 "", + error); + return error; + } + + (void)CloseHandle(alsa->stopEvent); + alsa->stopEvent = nullptr; + (void)CloseHandle(alsa->thread); + alsa->thread = nullptr; + } + + alsa->receive = nullptr; + alsa->user_data = nullptr; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_alsa_parse_addin_args(AudinALSADevice* device, const ADDIN_ARGV* args) +{ + int status = 0; + DWORD flags = 0; + const COMMAND_LINE_ARGUMENT_A* arg = nullptr; + AudinALSADevice* alsa = device; + COMMAND_LINE_ARGUMENT_A audin_alsa_args[] = { + { "dev", COMMAND_LINE_VALUE_REQUIRED, "", nullptr, nullptr, -1, nullptr, + "audio device name" }, + { nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr } + }; + flags = + COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + status = CommandLineParseArgumentsA(args->argc, args->argv, audin_alsa_args, flags, alsa, + nullptr, nullptr); + + if (status < 0) + return ERROR_INVALID_PARAMETER; + + arg = audin_alsa_args; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev") + { + alsa->device_name = _strdup(arg->Value); + + if (!alsa->device_name) + { + WLog_Print(alsa->log, WLOG_ERROR, "_strdup failed!"); + return CHANNEL_RC_NO_MEMORY; + } + } + CommandLineSwitchEnd(arg) + } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE alsa_freerdp_audin_client_subsystem_entry( + PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints)) +{ + const ADDIN_ARGV* args = nullptr; + AudinALSADevice* alsa = nullptr; + UINT error = 0; + alsa = (AudinALSADevice*)calloc(1, sizeof(AudinALSADevice)); + + if (!alsa) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + alsa->log = WLog_Get(TAG); + alsa->iface.Open = audin_alsa_open; + alsa->iface.FormatSupported = audin_alsa_format_supported; + alsa->iface.SetFormat = audin_alsa_set_format; + alsa->iface.Close = audin_alsa_close; + alsa->iface.Free = audin_alsa_free; + alsa->rdpcontext = pEntryPoints->rdpcontext; + args = pEntryPoints->args; + + if ((error = audin_alsa_parse_addin_args(alsa, args))) + { + WLog_Print(alsa->log, WLOG_ERROR, + "audin_alsa_parse_addin_args failed with errorcode %" PRIu32 "!", error); + goto error_out; + } + + if (!alsa->device_name) + { + alsa->device_name = _strdup("default"); + + if (!alsa->device_name) + { + WLog_Print(alsa->log, WLOG_ERROR, "_strdup failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + } + + alsa->frames_per_packet = 128; + alsa->aformat.nChannels = 2; + alsa->aformat.wBitsPerSample = 16; + alsa->aformat.wFormatTag = WAVE_FORMAT_PCM; + alsa->aformat.nSamplesPerSec = 44100; + + if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*)alsa))) + { + WLog_Print(alsa->log, WLOG_ERROR, "RegisterAudinDevice failed with error %" PRIu32 "!", + error); + goto error_out; + } + + return CHANNEL_RC_OK; +error_out: + free(alsa->device_name); + free(alsa); + return error; +} diff --git a/third_party/FreeRDP/channels/audin/client/audin_main.c b/third_party/FreeRDP/channels/audin/client/audin_main.c new file mode 100644 index 0000000..9eb71a4 --- /dev/null +++ b/third_party/FreeRDP/channels/audin/client/audin_main.c @@ -0,0 +1,1124 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Input Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2015 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include "audin_main.h" + +#define SNDIN_VERSION 0x02 + +typedef enum +{ + MSG_SNDIN_VERSION = 0x01, + MSG_SNDIN_FORMATS = 0x02, + MSG_SNDIN_OPEN = 0x03, + MSG_SNDIN_OPEN_REPLY = 0x04, + MSG_SNDIN_DATA_INCOMING = 0x05, + MSG_SNDIN_DATA = 0x06, + MSG_SNDIN_FORMATCHANGE = 0x07, +} MSG_SNDIN; + +typedef struct +{ + IWTSVirtualChannelCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + IWTSVirtualChannel* channel; + + /** + * The supported format list sent back to the server, which needs to + * be stored as reference when the server sends the format index in + * Open PDU and Format Change PDU + */ + AUDIO_FORMAT* formats; + UINT32 formats_count; +} AUDIN_CHANNEL_CALLBACK; + +typedef struct +{ + IWTSPlugin iface; + + GENERIC_LISTENER_CALLBACK* listener_callback; + + /* Parsed plugin data */ + AUDIO_FORMAT* fixed_format; + char* subsystem; + char* device_name; + + /* Device interface */ + IAudinDevice* device; + + rdpContext* rdpcontext; + BOOL attached; + wStream* data; + AUDIO_FORMAT* format; + UINT32 FramesPerPacket; + + FREERDP_DSP_CONTEXT* dsp_context; + wLog* log; + + IWTSListener* listener; + + BOOL initialized; + UINT32 version; +} AUDIN_PLUGIN; + +static BOOL audin_process_addin_args(AUDIN_PLUGIN* audin, const ADDIN_ARGV* args); + +static UINT audin_channel_write_and_free(AUDIN_CHANNEL_CALLBACK* callback, wStream* out, + BOOL freeStream) +{ + if (!callback || !out) + return ERROR_INVALID_PARAMETER; + + if (!callback->channel || !callback->channel->Write) + return ERROR_INTERNAL_ERROR; + + Stream_SealLength(out); + WINPR_ASSERT(Stream_Length(out) <= UINT32_MAX); + const UINT error = callback->channel->Write(callback->channel, (ULONG)Stream_Length(out), + Stream_Buffer(out), nullptr); + + if (freeStream) + Stream_Free(out, TRUE); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_process_version(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback, wStream* s) +{ + const UINT32 ClientVersion = SNDIN_VERSION; + UINT32 ServerVersion = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, ServerVersion); + WLog_Print(audin->log, WLOG_DEBUG, "ServerVersion=%" PRIu32 ", ClientVersion=%" PRIu32, + ServerVersion, ClientVersion); + + /* Do not answer server packet, we do not support the channel version. */ + if (ServerVersion > ClientVersion) + { + WLog_Print(audin->log, WLOG_WARN, + "Incompatible channel version server=%" PRIu32 + ", client supports version=%" PRIu32, + ServerVersion, ClientVersion); + return CHANNEL_RC_OK; + } + audin->version = ServerVersion; + + wStream* out = Stream_New(nullptr, 5); + + if (!out) + { + WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!"); + return ERROR_OUTOFMEMORY; + } + + Stream_Write_UINT8(out, MSG_SNDIN_VERSION); + Stream_Write_UINT32(out, ClientVersion); + return audin_channel_write_and_free(callback, out, TRUE); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_send_incoming_data_pdu(AUDIN_CHANNEL_CALLBACK* callback) +{ + BYTE out_data[1] = { MSG_SNDIN_DATA_INCOMING }; + + if (!callback || !callback->channel || !callback->channel->Write) + return ERROR_INTERNAL_ERROR; + + return callback->channel->Write(callback->channel, 1, out_data, nullptr); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_process_formats(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback, wStream* s) +{ + UINT error = ERROR_INTERNAL_ERROR; + UINT32 NumFormats = 0; + UINT32 cbSizeFormatsPacket = 0; + + WINPR_ASSERT(audin); + WINPR_ASSERT(callback); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, NumFormats); + WLog_Print(audin->log, WLOG_DEBUG, "NumFormats %" PRIu32 "", NumFormats); + + if ((NumFormats < 1) || (NumFormats > 1000)) + { + WLog_Print(audin->log, WLOG_ERROR, "bad NumFormats %" PRIu32 "", NumFormats); + return ERROR_INVALID_DATA; + } + + Stream_Seek_UINT32(s); /* cbSizeFormatsPacket */ + + audin->format = nullptr; + audio_formats_free(callback->formats, callback->formats_count); + callback->formats_count = 0; + + callback->formats = audio_formats_new(NumFormats); + + if (!callback->formats) + { + WLog_Print(audin->log, WLOG_ERROR, "calloc failed!"); + return ERROR_INVALID_DATA; + } + + wStream* out = Stream_New(nullptr, 9); + + if (!out) + { + error = CHANNEL_RC_NO_MEMORY; + WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!"); + goto out; + } + + Stream_Seek(out, 9); + + /* SoundFormats (variable) */ + for (UINT32 i = 0; i < NumFormats; i++) + { + AUDIO_FORMAT format = WINPR_C_ARRAY_INIT; + + if (!audio_format_read(s, &format)) + { + error = ERROR_INVALID_DATA; + goto out; + } + + audio_format_print(audin->log, WLOG_DEBUG, &format); + + if (!audio_format_compatible(audin->fixed_format, &format)) + { + audio_format_free(&format); + continue; + } + + if (freerdp_dsp_supports_format(&format, TRUE) || + audin->device->FormatSupported(audin->device, &format)) + { + /* Store the agreed format in the corresponding index */ + callback->formats[callback->formats_count++] = format; + + if (!audio_format_write(out, &format)) + { + error = CHANNEL_RC_NO_MEMORY; + WLog_Print(audin->log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!"); + goto out; + } + } + else + { + audio_format_free(&format); + } + } + + if ((error = audin_send_incoming_data_pdu(callback))) + { + WLog_Print(audin->log, WLOG_ERROR, "audin_send_incoming_data_pdu failed!"); + goto out; + } + + cbSizeFormatsPacket = (UINT32)Stream_GetPosition(out); + Stream_ResetPosition(out); + Stream_Write_UINT8(out, MSG_SNDIN_FORMATS); /* Header (1 byte) */ + Stream_Write_UINT32(out, callback->formats_count); /* NumFormats (4 bytes) */ + Stream_Write_UINT32(out, cbSizeFormatsPacket); /* cbSizeFormatsPacket (4 bytes) */ + Stream_SetPosition(out, cbSizeFormatsPacket); + error = audin_channel_write_and_free(callback, out, FALSE); +out: + + if (error != CHANNEL_RC_OK) + { + audin->format = nullptr; + audio_formats_free(callback->formats, NumFormats); + callback->formats = nullptr; + } + + Stream_Free(out, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_send_format_change_pdu(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback, + UINT32 NewFormat) +{ + WINPR_ASSERT(audin); + WINPR_ASSERT(callback); + + wStream* out = Stream_New(nullptr, 5); + + if (!out) + { + WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_OK; + } + + Stream_Write_UINT8(out, MSG_SNDIN_FORMATCHANGE); + Stream_Write_UINT32(out, NewFormat); + return audin_channel_write_and_free(callback, out, TRUE); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_send_open_reply_pdu(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback, + UINT32 Result) +{ + WINPR_ASSERT(audin); + WINPR_ASSERT(callback); + + wStream* out = Stream_New(nullptr, 5); + + if (!out) + { + WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT8(out, MSG_SNDIN_OPEN_REPLY); + Stream_Write_UINT32(out, Result); + return audin_channel_write_and_free(callback, out, TRUE); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_receive_wave_data(const AUDIO_FORMAT* format, const BYTE* data, size_t size, + void* user_data) +{ + WINPR_ASSERT(format); + + UINT error = ERROR_INTERNAL_ERROR; + AUDIN_CHANNEL_CALLBACK* callback = (AUDIN_CHANNEL_CALLBACK*)user_data; + + if (!callback) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)callback->plugin; + + if (!audin) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + if (!audin->attached) + return CHANNEL_RC_OK; + + Stream_ResetPosition(audin->data); + + if (!Stream_EnsureRemainingCapacity(audin->data, 1)) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT8(audin->data, MSG_SNDIN_DATA); + + const BOOL compatible = audio_format_compatible(format, audin->format); + if (compatible && audin->device->FormatSupported(audin->device, audin->format)) + { + if (!Stream_EnsureRemainingCapacity(audin->data, size)) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write(audin->data, data, size); + } + else + { + if (!freerdp_dsp_encode(audin->dsp_context, format, data, size, audin->data)) + return ERROR_INTERNAL_ERROR; + } + + /* Did not encode anything, skip this, the codec is not ready for output. */ + if (Stream_GetPosition(audin->data) <= 1) + return CHANNEL_RC_OK; + + audio_format_print(audin->log, WLOG_TRACE, audin->format); + WLog_Print(audin->log, WLOG_TRACE, "[%" PRIuz "/%" PRIuz "]", size, + Stream_GetPosition(audin->data) - 1); + + if ((error = audin_send_incoming_data_pdu(callback))) + { + WLog_Print(audin->log, WLOG_ERROR, "audin_send_incoming_data_pdu failed!"); + return error; + } + + return audin_channel_write_and_free(callback, audin->data, FALSE); +} + +static BOOL audin_open_device(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback) +{ + UINT error = ERROR_INTERNAL_ERROR; + AUDIO_FORMAT format = WINPR_C_ARRAY_INIT; + + if (!audin || !audin->device) + return FALSE; + + format = *audin->format; + const BOOL supported = + IFCALLRESULT(FALSE, audin->device->FormatSupported, audin->device, &format); + WLog_Print(audin->log, WLOG_DEBUG, "microphone uses %s codec", + audio_format_get_tag_string(format.wFormatTag)); + + if (!supported) + { + /* Default sample rates supported by most backends. */ + const UINT32 samplerates[] = { format.nSamplesPerSec, 96000, 48000, 44100, 22050 }; + BOOL test = FALSE; + + format.wFormatTag = WAVE_FORMAT_PCM; + format.wBitsPerSample = 16; + format.cbSize = 0; + for (size_t x = 0; x < ARRAYSIZE(samplerates); x++) + { + format.nSamplesPerSec = samplerates[x]; + for (UINT16 y = audin->format->nChannels; y > 0; y--) + { + format.nChannels = y; + format.nBlockAlign = 2 * format.nChannels; + test = IFCALLRESULT(FALSE, audin->device->FormatSupported, audin->device, &format); + if (test) + break; + } + if (test) + break; + } + if (!test) + return FALSE; + } + + IFCALLRET(audin->device->SetFormat, error, audin->device, &format, audin->FramesPerPacket); + + if (error != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "SetFormat failed with errorcode %" PRIu32 "", error); + return FALSE; + } + + if (!freerdp_dsp_context_reset(audin->dsp_context, audin->format, audin->FramesPerPacket)) + return FALSE; + + IFCALLRET(audin->device->Open, error, audin->device, audin_receive_wave_data, callback); + + if (error != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "Open failed with errorcode %" PRIu32 "", error); + return FALSE; + } + + return TRUE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_process_open(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback, wStream* s) +{ + UINT32 initialFormat = 0; + UINT32 FramesPerPacket = 0; + UINT error = CHANNEL_RC_OK; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, FramesPerPacket); + Stream_Read_UINT32(s, initialFormat); + WLog_Print(audin->log, WLOG_DEBUG, "FramesPerPacket=%" PRIu32 " initialFormat=%" PRIu32 "", + FramesPerPacket, initialFormat); + audin->FramesPerPacket = FramesPerPacket; + + if (initialFormat >= callback->formats_count) + { + WLog_Print(audin->log, WLOG_ERROR, "invalid format index %" PRIu32 " (total %" PRIu32 ")", + initialFormat, callback->formats_count); + return ERROR_INVALID_DATA; + } + + audin->format = &callback->formats[initialFormat]; + + if (!audin_open_device(audin, callback)) + return ERROR_INTERNAL_ERROR; + + if ((error = audin_send_format_change_pdu(audin, callback, initialFormat))) + { + WLog_Print(audin->log, WLOG_ERROR, "audin_send_format_change_pdu failed!"); + return error; + } + + if ((error = audin_send_open_reply_pdu(audin, callback, 0))) + WLog_Print(audin->log, WLOG_ERROR, "audin_send_open_reply_pdu failed!"); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_process_format_change(AUDIN_PLUGIN* audin, AUDIN_CHANNEL_CALLBACK* callback, + wStream* s) +{ + UINT32 NewFormat = 0; + UINT error = CHANNEL_RC_OK; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, NewFormat); + WLog_Print(audin->log, WLOG_DEBUG, "NewFormat=%" PRIu32 "", NewFormat); + + if (NewFormat >= callback->formats_count) + { + WLog_Print(audin->log, WLOG_ERROR, "invalid format index %" PRIu32 " (total %" PRIu32 ")", + NewFormat, callback->formats_count); + return ERROR_INVALID_DATA; + } + + audin->format = &callback->formats[NewFormat]; + + if (audin->device) + { + IFCALLRET(audin->device->Close, error, audin->device); + + if (error != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "Close failed with errorcode %" PRIu32 "", error); + return error; + } + } + + if (!audin_open_device(audin, callback)) + return ERROR_INTERNAL_ERROR; + + if ((error = audin_send_format_change_pdu(audin, callback, NewFormat))) + WLog_ERR(TAG, "audin_send_format_change_pdu failed!"); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data) +{ + UINT error = 0; + BYTE MessageId = 0; + AUDIN_CHANNEL_CALLBACK* callback = (AUDIN_CHANNEL_CALLBACK*)pChannelCallback; + + if (!callback || !data) + return ERROR_INVALID_PARAMETER; + + AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)callback->plugin; + + if (!audin) + return ERROR_INTERNAL_ERROR; + + if (!Stream_CheckAndLogRequiredCapacity(TAG, data, 1)) + return ERROR_NO_DATA; + + Stream_Read_UINT8(data, MessageId); + WLog_Print(audin->log, WLOG_DEBUG, "MessageId=0x%02" PRIx8 "", MessageId); + + switch (MessageId) + { + case MSG_SNDIN_VERSION: + error = audin_process_version(audin, callback, data); + break; + + case MSG_SNDIN_FORMATS: + error = audin_process_formats(audin, callback, data); + break; + + case MSG_SNDIN_OPEN: + error = audin_process_open(audin, callback, data); + break; + + case MSG_SNDIN_FORMATCHANGE: + error = audin_process_format_change(audin, callback, data); + break; + + default: + WLog_Print(audin->log, WLOG_ERROR, "unknown MessageId=0x%02" PRIx8 "", MessageId); + error = ERROR_INVALID_DATA; + break; + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + AUDIN_CHANNEL_CALLBACK* callback = (AUDIN_CHANNEL_CALLBACK*)pChannelCallback; + WINPR_ASSERT(callback); + + AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)callback->plugin; + WINPR_ASSERT(audin); + + UINT error = CHANNEL_RC_OK; + WLog_Print(audin->log, WLOG_TRACE, "..."); + + if (audin->device) + { + IFCALLRET(audin->device->Close, error, audin->device); + + if (error != CHANNEL_RC_OK) + WLog_Print(audin->log, WLOG_ERROR, "Close failed with errorcode %" PRIu32 "", error); + } + + audin->format = nullptr; + audio_formats_free(callback->formats, callback->formats_count); + free(callback); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_on_new_channel_connection(IWTSListenerCallback* pListenerCallback, + IWTSVirtualChannel* pChannel, + WINPR_ATTR_UNUSED BYTE* Data, + WINPR_ATTR_UNUSED BOOL* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +{ + GENERIC_LISTENER_CALLBACK* listener_callback = (GENERIC_LISTENER_CALLBACK*)pListenerCallback; + + if (!listener_callback || !listener_callback->plugin) + return ERROR_INTERNAL_ERROR; + + AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)listener_callback->plugin; + WLog_Print(audin->log, WLOG_TRACE, "..."); + AUDIN_CHANNEL_CALLBACK* callback = + (AUDIN_CHANNEL_CALLBACK*)calloc(1, sizeof(AUDIN_CHANNEL_CALLBACK)); + + if (!callback) + { + WLog_Print(audin->log, WLOG_ERROR, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + callback->iface.OnDataReceived = audin_on_data_received; + callback->iface.OnClose = audin_on_close; + callback->plugin = listener_callback->plugin; + callback->channel_mgr = listener_callback->channel_mgr; + callback->channel = pChannel; + *ppCallback = &callback->iface; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr) +{ + AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pPlugin; + + if (!audin) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + if (!pChannelMgr) + return ERROR_INVALID_PARAMETER; + + if (audin->initialized) + { + WLog_ERR(TAG, "[%s] channel initialized twice, aborting", AUDIN_DVC_CHANNEL_NAME); + return ERROR_INVALID_DATA; + } + + WLog_Print(audin->log, WLOG_TRACE, "..."); + audin->listener_callback = + (GENERIC_LISTENER_CALLBACK*)calloc(1, sizeof(GENERIC_LISTENER_CALLBACK)); + + if (!audin->listener_callback) + { + WLog_Print(audin->log, WLOG_ERROR, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + audin->listener_callback->iface.OnNewChannelConnection = audin_on_new_channel_connection; + audin->listener_callback->plugin = pPlugin; + audin->listener_callback->channel_mgr = pChannelMgr; + const UINT rc = pChannelMgr->CreateListener(pChannelMgr, AUDIN_DVC_CHANNEL_NAME, 0, + &audin->listener_callback->iface, &audin->listener); + + audin->initialized = rc == CHANNEL_RC_OK; + return rc; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_plugin_terminated(IWTSPlugin* pPlugin) +{ + AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pPlugin; + UINT error = CHANNEL_RC_OK; + + if (!audin) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + WLog_Print(audin->log, WLOG_TRACE, "..."); + + if (audin->listener_callback) + { + IWTSVirtualChannelManager* mgr = audin->listener_callback->channel_mgr; + if (mgr) + IFCALL(mgr->DestroyListener, mgr, audin->listener); + } + audio_formats_free(audin->fixed_format, 1); + + if (audin->device) + { + IFCALLRET(audin->device->Free, error, audin->device); + + if (error != CHANNEL_RC_OK) + { + WLog_Print(audin->log, WLOG_ERROR, "Free failed with errorcode %" PRIu32 "", error); + // don't stop on error + } + + audin->device = nullptr; + } + + freerdp_dsp_context_free(audin->dsp_context); + Stream_Free(audin->data, TRUE); + free(audin->subsystem); + free(audin->device_name); + free(audin->listener_callback); + free(audin); + return CHANNEL_RC_OK; +} + +static UINT audin_plugin_attached(IWTSPlugin* pPlugin) +{ + AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pPlugin; + UINT error = CHANNEL_RC_OK; + + if (!audin) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + audin->attached = TRUE; + return error; +} + +static UINT audin_plugin_detached(IWTSPlugin* pPlugin) +{ + AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pPlugin; + UINT error = CHANNEL_RC_OK; + + if (!audin) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + audin->attached = FALSE; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_register_device_plugin(IWTSPlugin* pPlugin, IAudinDevice* device) +{ + AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pPlugin; + + WINPR_ASSERT(audin); + + if (audin->device) + { + WLog_Print(audin->log, WLOG_ERROR, "existing device, abort."); + return ERROR_ALREADY_EXISTS; + } + + WLog_Print(audin->log, WLOG_DEBUG, "device registered."); + audin->device = device; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_load_device_plugin(AUDIN_PLUGIN* audin, const char* name, const ADDIN_ARGV* args) +{ + WINPR_ASSERT(audin); + + FREERDP_AUDIN_DEVICE_ENTRY_POINTS entryPoints = WINPR_C_ARRAY_INIT; + UINT error = ERROR_INTERNAL_ERROR; + + PVIRTUALCHANNELENTRY pvce = + freerdp_load_channel_addin_entry(AUDIN_CHANNEL_NAME, name, nullptr, 0); + PFREERDP_AUDIN_DEVICE_ENTRY entry = WINPR_FUNC_PTR_CAST(pvce, PFREERDP_AUDIN_DEVICE_ENTRY); + + if (entry == nullptr) + { + WLog_Print(audin->log, WLOG_ERROR, + "freerdp_load_channel_addin_entry did not return any function pointers for %s ", + name); + return ERROR_INVALID_FUNCTION; + } + + entryPoints.plugin = &audin->iface; + entryPoints.pRegisterAudinDevice = audin_register_device_plugin; + entryPoints.args = args; + entryPoints.rdpcontext = audin->rdpcontext; + + error = entry(&entryPoints); + if (error) + { + WLog_Print(audin->log, WLOG_ERROR, "%s entry returned error %" PRIu32 ".", name, error); + return error; + } + + WLog_Print(audin->log, WLOG_INFO, "Loaded %s backend for audin", name); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_set_subsystem(AUDIN_PLUGIN* audin, const char* subsystem) +{ + WINPR_ASSERT(audin); + + free(audin->subsystem); + audin->subsystem = _strdup(subsystem); + + if (!audin->subsystem) + { + WLog_Print(audin->log, WLOG_ERROR, "_strdup failed!"); + return ERROR_NOT_ENOUGH_MEMORY; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_set_device_name(AUDIN_PLUGIN* audin, const char* device_name) +{ + WINPR_ASSERT(audin); + + free(audin->device_name); + audin->device_name = _strdup(device_name); + + if (!audin->device_name) + { + WLog_Print(audin->log, WLOG_ERROR, "_strdup failed!"); + return ERROR_NOT_ENOUGH_MEMORY; + } + + return CHANNEL_RC_OK; +} + +BOOL audin_process_addin_args(AUDIN_PLUGIN* audin, const ADDIN_ARGV* args) +{ + COMMAND_LINE_ARGUMENT_A audin_args[] = { + { "sys", COMMAND_LINE_VALUE_REQUIRED, "", nullptr, nullptr, -1, nullptr, + "subsystem" }, + { "dev", COMMAND_LINE_VALUE_REQUIRED, "", nullptr, nullptr, -1, nullptr, "device" }, + { "format", COMMAND_LINE_VALUE_REQUIRED, "", nullptr, nullptr, -1, nullptr, + "format" }, + { "rate", COMMAND_LINE_VALUE_REQUIRED, "", nullptr, nullptr, -1, nullptr, "rate" }, + { "channel", COMMAND_LINE_VALUE_REQUIRED, "", nullptr, nullptr, -1, nullptr, + "channel" }, + { nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr } + }; + + if (!args || args->argc == 1) + return TRUE; + + const DWORD flags = + COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + const int status = CommandLineParseArgumentsA(args->argc, args->argv, audin_args, flags, audin, + nullptr, nullptr); + + if (status != 0) + return FALSE; + + const COMMAND_LINE_ARGUMENT_A* arg = audin_args; + errno = 0; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "sys") + { + const UINT error = audin_set_subsystem(audin, arg->Value); + if (error != CHANNEL_RC_OK) + { + WLog_Print(audin->log, WLOG_ERROR, + "audin_set_subsystem failed with error %" PRIu32 "!", error); + return FALSE; + } + } + CommandLineSwitchCase(arg, "dev") + { + const UINT error = audin_set_device_name(audin, arg->Value); + if (error != CHANNEL_RC_OK) + { + WLog_Print(audin->log, WLOG_ERROR, + "audin_set_device_name failed with error %" PRIu32 "!", error); + return FALSE; + } + } + CommandLineSwitchCase(arg, "format") + { + unsigned long val = strtoul(arg->Value, nullptr, 0); + + if ((errno != 0) || (val > UINT16_MAX)) + return FALSE; + + audin->fixed_format->wFormatTag = (UINT16)val; + } + CommandLineSwitchCase(arg, "rate") + { + unsigned long val = strtoul(arg->Value, nullptr, 0); + + if ((errno != 0) || (val == 0) || (val > UINT32_MAX)) + return FALSE; + + audin->fixed_format->nSamplesPerSec = (UINT32)val; + } + CommandLineSwitchCase(arg, "channel") + { + unsigned long val = strtoul(arg->Value, nullptr, 0); + + if ((errno != 0) || (val <= UINT16_MAX)) + audin->fixed_format->nChannels = (UINT16)val; + } + CommandLineSwitchDefault(arg) + { + } + CommandLineSwitchEnd(arg) + } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr); + + return TRUE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE audin_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)) +{ + struct SubsystemEntry + { + char* subsystem; + char* device; + }; + UINT error = CHANNEL_RC_INITIALIZATION_ERROR; + struct SubsystemEntry entries[] = { +#if defined(WITH_PULSE) + { "pulse", "" }, +#endif +#if defined(WITH_OSS) + { "oss", "default" }, +#endif +#if defined(WITH_ALSA) + { "alsa", "default" }, +#endif +#if defined(WITH_OPENSLES) + { "opensles", "default" }, +#endif +#if defined(WITH_WINMM) + { "winmm", "default" }, +#endif +#if defined(WITH_MACAUDIO) + { "mac", "default" }, +#endif +#if defined(WITH_IOSAUDIO) + { "ios", "default" }, +#endif +#if defined(WITH_SNDIO) + { "sndio", "default" }, +#endif + { nullptr, nullptr } + }; + struct SubsystemEntry* entry = &entries[0]; + WINPR_ASSERT(pEntryPoints); + WINPR_ASSERT(pEntryPoints->GetPlugin); + AUDIN_PLUGIN* audin = (AUDIN_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints, AUDIN_CHANNEL_NAME); + + if (audin != nullptr) + return CHANNEL_RC_ALREADY_INITIALIZED; + + audin = (AUDIN_PLUGIN*)calloc(1, sizeof(AUDIN_PLUGIN)); + + if (!audin) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + audin->log = WLog_Get(TAG); + audin->data = Stream_New(nullptr, 4096); + audin->fixed_format = audio_format_new(); + + if (!audin->fixed_format) + goto out; + + if (!audin->data) + goto out; + + audin->dsp_context = freerdp_dsp_context_new(TRUE); + + if (!audin->dsp_context) + goto out; + + audin->attached = TRUE; + audin->iface.Initialize = audin_plugin_initialize; + audin->iface.Connected = nullptr; + audin->iface.Disconnected = nullptr; + audin->iface.Terminated = audin_plugin_terminated; + audin->iface.Attached = audin_plugin_attached; + audin->iface.Detached = audin_plugin_detached; + + { + const ADDIN_ARGV* args = pEntryPoints->GetPluginData(pEntryPoints); + audin->rdpcontext = pEntryPoints->GetRdpContext(pEntryPoints); + + if (args) + { + if (!audin_process_addin_args(audin, args)) + goto out; + } + + if (audin->subsystem) + { + if ((error = audin_load_device_plugin(audin, audin->subsystem, args))) + { + WLog_Print( + audin->log, WLOG_ERROR, + "Unable to load microphone redirection subsystem %s because of error %" PRIu32 + "", + audin->subsystem, error); + goto out; + } + } + else + { + while (entry && entry->subsystem && !audin->device) + { + if ((error = audin_set_subsystem(audin, entry->subsystem))) + { + WLog_Print(audin->log, WLOG_ERROR, + "audin_set_subsystem for %s failed with error %" PRIu32 "!", + entry->subsystem, error); + } + else if ((error = audin_set_device_name(audin, entry->device))) + { + WLog_Print(audin->log, WLOG_ERROR, + "audin_set_device_name for %s failed with error %" PRIu32 "!", + entry->subsystem, error); + } + else if ((error = audin_load_device_plugin(audin, audin->subsystem, args))) + { + WLog_Print(audin->log, WLOG_ERROR, + "audin_load_device_plugin %s failed with error %" PRIu32 "!", + entry->subsystem, error); + } + + entry++; + } + } + } + + if (audin->device == nullptr) + { + /* If we have no audin device do not register plugin but still return OK or the client will + * just disconnect due to a missing microphone. */ + WLog_Print(audin->log, WLOG_ERROR, "No microphone device could be found."); + error = CHANNEL_RC_OK; + goto out; + } + + error = pEntryPoints->RegisterPlugin(pEntryPoints, AUDIN_CHANNEL_NAME, &audin->iface); + if (error == CHANNEL_RC_OK) + return error; + +out: + audin_plugin_terminated(&audin->iface); + return error; +} diff --git a/third_party/FreeRDP/channels/audin/client/audin_main.h b/third_party/FreeRDP/channels/audin/client/audin_main.h new file mode 100644 index 0000000..1e6a498 --- /dev/null +++ b/third_party/FreeRDP/channels/audin/client/audin_main.h @@ -0,0 +1,33 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Input Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_AUDIN_CLIENT_MAIN_H +#define FREERDP_CHANNEL_AUDIN_CLIENT_MAIN_H + +#include + +#include +#include +#include +#include +#include + +#define TAG CHANNELS_TAG("audin.client") + +#endif /* FREERDP_CHANNEL_AUDIN_CLIENT_MAIN_H */ diff --git a/third_party/FreeRDP/channels/audin/client/ios/CMakeLists.txt b/third_party/FreeRDP/channels/audin/client/ios/CMakeLists.txt new file mode 100644 index 0000000..5122875 --- /dev/null +++ b/third_party/FreeRDP/channels/audin/client/ios/CMakeLists.txt @@ -0,0 +1,31 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright (c) 2015 Armin Novak +# Copyright (c) 2015 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. + +define_channel_client_subsystem("audin" "ios" "") +find_library(CORE_AUDIO CoreAudio) +find_library(AVFOUNDATION AVFoundation) +find_library(AUDIO_TOOL AudioToolbox) + +set(${MODULE_PREFIX}_SRCS audin_ios.m) + +set(${MODULE_PREFIX}_LIBS winpr freerdp ${AVFOUNDATION} ${CORE_AUDIO} ${AUDIO_TOOL}) + +include_directories(..) +include_directories(SYSTEM ${MAC_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") diff --git a/third_party/FreeRDP/channels/audin/client/ios/audin_ios.m b/third_party/FreeRDP/channels/audin/client/ios/audin_ios.m new file mode 100644 index 0000000..e0682c6 --- /dev/null +++ b/third_party/FreeRDP/channels/audin/client/ios/audin_ios.m @@ -0,0 +1,335 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Input Redirection Virtual Channel - iOS implementation + * + * Copyright (c) 2015 Armin Novak + * Copyright 2015 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 + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#import + +#define __COREFOUNDATION_CFPLUGINCOM__ 1 +#define IUNKNOWN_C_GUTS \ + void *_reserved; \ + void *QueryInterface; \ + void *AddRef; \ + void *Release + +#include +#include +#include + +#include +#include + +#include "audin_main.h" + +#define IOS_AUDIO_QUEUE_NUM_BUFFERS 100 + +typedef struct +{ + IAudinDevice iface; + + AUDIO_FORMAT format; + UINT32 FramesPerPacket; + int dev_unit; + + AudinReceive receive; + void *user_data; + + rdpContext *rdpcontext; + + bool isOpen; + AudioQueueRef audioQueue; + AudioStreamBasicDescription audioFormat; + AudioQueueBufferRef audioBuffers[IOS_AUDIO_QUEUE_NUM_BUFFERS]; +} AudinIosDevice; + +static AudioFormatID audin_ios_get_format(const AUDIO_FORMAT *format) +{ + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + return kAudioFormatLinearPCM; + + default: + return 0; + } +} + +static AudioFormatFlags audin_ios_get_flags_for_format(const AUDIO_FORMAT *format) +{ + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + return kAudioFormatFlagIsSignedInteger; + + default: + return 0; + } +} + +static BOOL audin_ios_format_supported(IAudinDevice *device, const AUDIO_FORMAT *format) +{ + AudinIosDevice *ios = (AudinIosDevice *)device; + AudioFormatID req_fmt = 0; + + if (device == nullptr || format == nullptr) + return FALSE; + + req_fmt = audin_ios_get_format(format); + + if (req_fmt == 0) + return FALSE; + + return TRUE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_ios_set_format(IAudinDevice *device, const AUDIO_FORMAT *format, + UINT32 FramesPerPacket) +{ + AudinIosDevice *ios = (AudinIosDevice *)device; + + if (device == nullptr || format == nullptr) + return ERROR_INVALID_PARAMETER; + + ios->FramesPerPacket = FramesPerPacket; + ios->format = *format; + WLog_INFO(TAG, "Audio Format %s [channels=%d, samples=%d, bits=%d]", + audio_format_get_tag_string(format->wFormatTag), format->nChannels, + format->nSamplesPerSec, format->wBitsPerSample); + ios->audioFormat.mBitsPerChannel = format->wBitsPerSample; + + if (format->wBitsPerSample == 0) + ios->audioFormat.mBitsPerChannel = 16; + + ios->audioFormat.mChannelsPerFrame = ios->format.nChannels; + ios->audioFormat.mFramesPerPacket = 1; + + ios->audioFormat.mBytesPerFrame = + ios->audioFormat.mChannelsPerFrame * (ios->audioFormat.mBitsPerChannel / 8); + ios->audioFormat.mBytesPerPacket = + ios->audioFormat.mBytesPerFrame * ios->audioFormat.mFramesPerPacket; + + ios->audioFormat.mFormatFlags = audin_ios_get_flags_for_format(format); + ios->audioFormat.mFormatID = audin_ios_get_format(format); + ios->audioFormat.mReserved = 0; + ios->audioFormat.mSampleRate = ios->format.nSamplesPerSec; + return CHANNEL_RC_OK; +} + +static void ios_audio_queue_input_cb(void *aqData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, + const AudioTimeStamp *inStartTime, UInt32 inNumPackets, + const AudioStreamPacketDescription *inPacketDesc) +{ + AudinIosDevice *ios = (AudinIosDevice *)aqData; + UINT error = CHANNEL_RC_OK; + const BYTE *buffer = inBuffer->mAudioData; + int buffer_size = inBuffer->mAudioDataByteSize; + (void)inAQ; + (void)inStartTime; + (void)inNumPackets; + (void)inPacketDesc; + + if (buffer_size > 0) + error = ios->receive(&ios->format, buffer, buffer_size, ios->user_data); + + AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, nullptr); + + if (error) + { + WLog_ERR(TAG, "ios->receive failed with error %" PRIu32 "", error); + SetLastError(ERROR_INTERNAL_ERROR); + } +} + +static UINT audin_ios_close(IAudinDevice *device) +{ + UINT errCode = CHANNEL_RC_OK; + char errString[1024]; + OSStatus devStat; + AudinIosDevice *ios = (AudinIosDevice *)device; + + if (device == nullptr) + return ERROR_INVALID_PARAMETER; + + if (ios->isOpen) + { + devStat = AudioQueueStop(ios->audioQueue, true); + + if (devStat != 0) + { + errCode = GetLastError(); + WLog_ERR(TAG, "AudioQueueStop failed with %s [%" PRIu32 "]", + winpr_strerror(errCode, errString, sizeof(errString)), errCode); + } + + ios->isOpen = false; + } + + if (ios->audioQueue) + { + devStat = AudioQueueDispose(ios->audioQueue, true); + + if (devStat != 0) + { + errCode = GetLastError(); + WLog_ERR(TAG, "AudioQueueDispose failed with %s [%" PRIu32 "]", + winpr_strerror(errCode, errString, sizeof(errString)), errCode); + } + + ios->audioQueue = nullptr; + } + + ios->receive = nullptr; + ios->user_data = nullptr; + return errCode; +} + +static UINT audin_ios_open(IAudinDevice *device, AudinReceive receive, void *user_data) +{ + AudinIosDevice *ios = (AudinIosDevice *)device; + DWORD errCode; + char errString[1024]; + OSStatus devStat; + + ios->receive = receive; + ios->user_data = user_data; + devStat = AudioQueueNewInput(&(ios->audioFormat), ios_audio_queue_input_cb, ios, nullptr, + kCFRunLoopCommonModes, 0, &(ios->audioQueue)); + + if (devStat != 0) + { + errCode = GetLastError(); + WLog_ERR(TAG, "AudioQueueNewInput failed with %s [%" PRIu32 "]", + winpr_strerror(errCode, errString, sizeof(errString)), errCode); + goto err_out; + } + + for (size_t index = 0; index < IOS_AUDIO_QUEUE_NUM_BUFFERS; index++) + { + devStat = AudioQueueAllocateBuffer(ios->audioQueue, + ios->FramesPerPacket * 2 * ios->format.nChannels, + &ios->audioBuffers[index]); + + if (devStat != 0) + { + errCode = GetLastError(); + WLog_ERR(TAG, "AudioQueueAllocateBuffer failed with %s [%" PRIu32 "]", + winpr_strerror(errCode, errString, sizeof(errString)), errCode); + goto err_out; + } + + devStat = AudioQueueEnqueueBuffer(ios->audioQueue, ios->audioBuffers[index], 0, nullptr); + + if (devStat != 0) + { + errCode = GetLastError(); + WLog_ERR(TAG, "AudioQueueEnqueueBuffer failed with %s [%" PRIu32 "]", + winpr_strerror(errCode, errString, sizeof(errString)), errCode); + goto err_out; + } + } + + devStat = AudioQueueStart(ios->audioQueue, nullptr); + + if (devStat != 0) + { + errCode = GetLastError(); + WLog_ERR(TAG, "AudioQueueStart failed with %s [%" PRIu32 "]", + winpr_strerror(errCode, errString, sizeof(errString)), errCode); + goto err_out; + } + + ios->isOpen = true; + return CHANNEL_RC_OK; +err_out: + audin_ios_close(device); + return CHANNEL_RC_INITIALIZATION_ERROR; +} + +static UINT audin_ios_free(IAudinDevice *device) +{ + AudinIosDevice *ios = (AudinIosDevice *)device; + int error; + + if (device == nullptr) + return ERROR_INVALID_PARAMETER; + + if ((error = audin_ios_close(device))) + { + WLog_ERR(TAG, "audin_oss_close failed with error code %d!", error); + } + + free(ios); + return CHANNEL_RC_OK; +} + +FREERDP_ENTRY_POINT(UINT VCAPITYPE ios_freerdp_audin_client_subsystem_entry( + PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints)) +{ + DWORD errCode; + char errString[1024]; + const ADDIN_ARGV *args; + AudinIosDevice *ios; + UINT error; + ios = (AudinIosDevice *)calloc(1, sizeof(AudinIosDevice)); + + if (!ios) + { + errCode = GetLastError(); + WLog_ERR(TAG, "calloc failed with %s [%" PRIu32 "]", + winpr_strerror(errCode, errString, sizeof(errString)), errCode); + return CHANNEL_RC_NO_MEMORY; + } + + ios->iface.Open = audin_ios_open; + ios->iface.FormatSupported = audin_ios_format_supported; + ios->iface.SetFormat = audin_ios_set_format; + ios->iface.Close = audin_ios_close; + ios->iface.Free = audin_ios_free; + ios->rdpcontext = pEntryPoints->rdpcontext; + ios->dev_unit = -1; + args = pEntryPoints->args; + + if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice *)ios))) + { + WLog_ERR(TAG, "RegisterAudinDevice failed with error %" PRIu32 "!", error); + goto error_out; + } + + return CHANNEL_RC_OK; +error_out: + free(ios); + return error; +} diff --git a/third_party/FreeRDP/channels/audin/client/mac/CMakeLists.txt b/third_party/FreeRDP/channels/audin/client/mac/CMakeLists.txt new file mode 100644 index 0000000..a496f80 --- /dev/null +++ b/third_party/FreeRDP/channels/audin/client/mac/CMakeLists.txt @@ -0,0 +1,32 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright (c) 2015 Armin Novak +# Copyright (c) 2015 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. + +define_channel_client_subsystem("audin" "mac" "") +find_library(CORE_AUDIO CoreAudio) +find_library(AVFOUNDATION AVFoundation) +find_library(AUDIO_TOOL AudioToolbox) +find_library(APP_SERVICES ApplicationServices) + +set(${MODULE_PREFIX}_SRCS audin_mac.m) + +set(${MODULE_PREFIX}_LIBS winpr freerdp ${AVFOUNDATION} ${CORE_AUDIO} ${AUDIO_TOOL} ${APP_SERVICES}) + +include_directories(..) +include_directories(SYSTEM ${MAC_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") diff --git a/third_party/FreeRDP/channels/audin/client/mac/audin_mac.m b/third_party/FreeRDP/channels/audin/client/mac/audin_mac.m new file mode 100644 index 0000000..f9e02ef --- /dev/null +++ b/third_party/FreeRDP/channels/audin/client/mac/audin_mac.m @@ -0,0 +1,465 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Input Redirection Virtual Channel - Mac OS X implementation + * + * Copyright (c) 2015 Armin Novak + * Copyright 2015 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 + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#import + +#define __COREFOUNDATION_CFPLUGINCOM__ 1 +#define IUNKNOWN_C_GUTS \ + void *_reserved; \ + void *QueryInterface; \ + void *AddRef; \ + void *Release + +#include +#include +#include +#include + +#include +#include + +#include "audin_main.h" + +#define MAC_AUDIO_QUEUE_NUM_BUFFERS 100 + +/* Fix for #4462: Provide type alias if not declared (Mac OS < 10.10) + * https://developer.apple.com/documentation/coreaudio/audioformatid + */ +#ifndef AudioFormatID +typedef UInt32 AudioFormatID; +#endif + +#ifndef AudioFormatFlags +typedef UInt32 AudioFormatFlags; +#endif + +typedef struct +{ + IAudinDevice iface; + + AUDIO_FORMAT format; + UINT32 FramesPerPacket; + int dev_unit; + + AudinReceive receive; + void *user_data; + + rdpContext *rdpcontext; + + bool isAuthorized; + bool isOpen; + AudioQueueRef audioQueue; + AudioStreamBasicDescription audioFormat; + AudioQueueBufferRef audioBuffers[MAC_AUDIO_QUEUE_NUM_BUFFERS]; +} AudinMacDevice; + +static AudioFormatID audin_mac_get_format(const AUDIO_FORMAT *format) +{ + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + return kAudioFormatLinearPCM; + + default: + return 0; + } +} + +static AudioFormatFlags audin_mac_get_flags_for_format(const AUDIO_FORMAT *format) +{ + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + return kAudioFormatFlagIsSignedInteger; + + default: + return 0; + } +} + +static BOOL audin_mac_format_supported(IAudinDevice *device, const AUDIO_FORMAT *format) +{ + AudinMacDevice *mac = (AudinMacDevice *)device; + AudioFormatID req_fmt = 0; + + if (!mac->isAuthorized) + return FALSE; + + if (device == nullptr || format == nullptr) + return FALSE; + + if (format->nChannels != 2) + return FALSE; + + req_fmt = audin_mac_get_format(format); + + if (req_fmt == 0) + return FALSE; + + return TRUE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_mac_set_format(IAudinDevice *device, const AUDIO_FORMAT *format, + UINT32 FramesPerPacket) +{ + AudinMacDevice *mac = (AudinMacDevice *)device; + + if (!mac->isAuthorized) + return ERROR_INTERNAL_ERROR; + + if (device == nullptr || format == nullptr) + return ERROR_INVALID_PARAMETER; + + mac->FramesPerPacket = FramesPerPacket; + mac->format = *format; + WLog_INFO(TAG, "Audio Format %s [channels=%d, samples=%d, bits=%d]", + audio_format_get_tag_string(format->wFormatTag), format->nChannels, + format->nSamplesPerSec, format->wBitsPerSample); + mac->audioFormat.mBitsPerChannel = format->wBitsPerSample; + + if (format->wBitsPerSample == 0) + mac->audioFormat.mBitsPerChannel = 16; + + mac->audioFormat.mChannelsPerFrame = mac->format.nChannels; + mac->audioFormat.mFramesPerPacket = 1; + + mac->audioFormat.mBytesPerFrame = + mac->audioFormat.mChannelsPerFrame * (mac->audioFormat.mBitsPerChannel / 8); + mac->audioFormat.mBytesPerPacket = + mac->audioFormat.mBytesPerFrame * mac->audioFormat.mFramesPerPacket; + + mac->audioFormat.mFormatFlags = audin_mac_get_flags_for_format(format); + mac->audioFormat.mFormatID = audin_mac_get_format(format); + mac->audioFormat.mReserved = 0; + mac->audioFormat.mSampleRate = mac->format.nSamplesPerSec; + return CHANNEL_RC_OK; +} + +static void mac_audio_queue_input_cb(void *aqData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, + const AudioTimeStamp *inStartTime, UInt32 inNumPackets, + const AudioStreamPacketDescription *inPacketDesc) +{ + AudinMacDevice *mac = (AudinMacDevice *)aqData; + UINT error = CHANNEL_RC_OK; + const BYTE *buffer = inBuffer->mAudioData; + int buffer_size = inBuffer->mAudioDataByteSize; + (void)inAQ; + (void)inStartTime; + (void)inNumPackets; + (void)inPacketDesc; + + if (buffer_size > 0) + error = mac->receive(&mac->format, buffer, buffer_size, mac->user_data); + + AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, nullptr); + + if (error) + { + WLog_ERR(TAG, "mac->receive failed with error %" PRIu32 "", error); + SetLastError(ERROR_INTERNAL_ERROR); + } +} + +static UINT audin_mac_close(IAudinDevice *device) +{ + UINT errCode = CHANNEL_RC_OK; + char errString[1024]; + OSStatus devStat; + AudinMacDevice *mac = (AudinMacDevice *)device; + + if (!mac->isAuthorized) + return ERROR_INTERNAL_ERROR; + + if (device == nullptr) + return ERROR_INVALID_PARAMETER; + + if (mac->isOpen) + { + devStat = AudioQueueStop(mac->audioQueue, true); + + if (devStat != 0) + { + errCode = GetLastError(); + WLog_ERR(TAG, "AudioQueueStop failed with %s [%" PRIu32 "]", + winpr_strerror(errCode, errString, sizeof(errString)), errCode); + } + + mac->isOpen = false; + } + + if (mac->audioQueue) + { + devStat = AudioQueueDispose(mac->audioQueue, true); + + if (devStat != 0) + { + errCode = GetLastError(); + WLog_ERR(TAG, "AudioQueueDispose failed with %s [%" PRIu32 "]", + winpr_strerror(errCode, errString, sizeof(errString)), errCode); + } + + mac->audioQueue = nullptr; + } + + mac->receive = nullptr; + mac->user_data = nullptr; + return errCode; +} + +static UINT audin_mac_open(IAudinDevice *device, AudinReceive receive, void *user_data) +{ + AudinMacDevice *mac = (AudinMacDevice *)device; + DWORD errCode; + char errString[1024]; + OSStatus devStat; + + if (!mac->isAuthorized) + return ERROR_INTERNAL_ERROR; + + mac->receive = receive; + mac->user_data = user_data; + devStat = AudioQueueNewInput(&(mac->audioFormat), mac_audio_queue_input_cb, mac, nullptr, + kCFRunLoopCommonModes, 0, &(mac->audioQueue)); + + if (devStat != 0) + { + errCode = GetLastError(); + WLog_ERR(TAG, "AudioQueueNewInput failed with %s [%" PRIu32 "]", + winpr_strerror(errCode, errString, sizeof(errString)), errCode); + goto err_out; + } + + for (size_t index = 0; index < MAC_AUDIO_QUEUE_NUM_BUFFERS; index++) + { + devStat = AudioQueueAllocateBuffer(mac->audioQueue, + mac->FramesPerPacket * 2 * mac->format.nChannels, + &mac->audioBuffers[index]); + + if (devStat != 0) + { + errCode = GetLastError(); + WLog_ERR(TAG, "AudioQueueAllocateBuffer failed with %s [%" PRIu32 "]", + winpr_strerror(errCode, errString, sizeof(errString)), errCode); + goto err_out; + } + + devStat = AudioQueueEnqueueBuffer(mac->audioQueue, mac->audioBuffers[index], 0, nullptr); + + if (devStat != 0) + { + errCode = GetLastError(); + WLog_ERR(TAG, "AudioQueueEnqueueBuffer failed with %s [%" PRIu32 "]", + winpr_strerror(errCode, errString, sizeof(errString)), errCode); + goto err_out; + } + } + + devStat = AudioQueueStart(mac->audioQueue, nullptr); + + if (devStat != 0) + { + errCode = GetLastError(); + WLog_ERR(TAG, "AudioQueueStart failed with %s [%" PRIu32 "]", + winpr_strerror(errCode, errString, sizeof(errString)), errCode); + goto err_out; + } + + mac->isOpen = true; + return CHANNEL_RC_OK; +err_out: + audin_mac_close(device); + return CHANNEL_RC_INITIALIZATION_ERROR; +} + +static UINT audin_mac_free(IAudinDevice *device) +{ + AudinMacDevice *mac = (AudinMacDevice *)device; + int error; + + if (device == nullptr) + return ERROR_INVALID_PARAMETER; + + if ((error = audin_mac_close(device))) + { + WLog_ERR(TAG, "audin_oss_close failed with error code %d!", error); + } + + free(mac); + return CHANNEL_RC_OK; +} + +static UINT audin_mac_parse_addin_args(AudinMacDevice *device, const ADDIN_ARGV *args) +{ + DWORD errCode; + char errString[1024]; + int status; + char *str_num, *eptr; + DWORD flags; + const COMMAND_LINE_ARGUMENT_A *arg; + COMMAND_LINE_ARGUMENT_A audin_mac_args[] = { + { "dev", COMMAND_LINE_VALUE_REQUIRED, "", nullptr, nullptr, -1, nullptr, + "audio device name" }, + { nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr } + }; + + AudinMacDevice *mac = (AudinMacDevice *)device; + + if (args->argc == 1) + return CHANNEL_RC_OK; + + flags = + COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + status = CommandLineParseArgumentsA(args->argc, args->argv, audin_mac_args, flags, mac, nullptr, + nullptr); + + if (status < 0) + return ERROR_INVALID_PARAMETER; + + arg = audin_mac_args; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev") + { + str_num = _strdup(arg->Value); + + if (!str_num) + { + errCode = GetLastError(); + WLog_ERR(TAG, "_strdup failed with %s [%d]", + winpr_strerror(errCode, errString, sizeof(errString)), errCode); + return CHANNEL_RC_NO_MEMORY; + } + + mac->dev_unit = strtol(str_num, &eptr, 10); + + if (mac->dev_unit < 0 || *eptr != '\0') + mac->dev_unit = -1; + + free(str_num); + } + CommandLineSwitchEnd(arg) + } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr); + + return CHANNEL_RC_OK; +} + +FREERDP_ENTRY_POINT(UINT VCAPITYPE mac_freerdp_audin_client_subsystem_entry( + PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints)) +{ + DWORD errCode; + char errString[1024]; + const ADDIN_ARGV *args; + AudinMacDevice *mac; + UINT error; + mac = (AudinMacDevice *)calloc(1, sizeof(AudinMacDevice)); + + if (!mac) + { + errCode = GetLastError(); + WLog_ERR(TAG, "calloc failed with %s [%" PRIu32 "]", + winpr_strerror(errCode, errString, sizeof(errString)), errCode); + return CHANNEL_RC_NO_MEMORY; + } + + mac->iface.Open = audin_mac_open; + mac->iface.FormatSupported = audin_mac_format_supported; + mac->iface.SetFormat = audin_mac_set_format; + mac->iface.Close = audin_mac_close; + mac->iface.Free = audin_mac_free; + mac->rdpcontext = pEntryPoints->rdpcontext; + mac->dev_unit = -1; + args = pEntryPoints->args; + + if ((error = audin_mac_parse_addin_args(mac, args))) + { + WLog_ERR(TAG, "audin_mac_parse_addin_args failed with %" PRIu32 "!", error); + goto error_out; + } + + if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice *)mac))) + { + WLog_ERR(TAG, "RegisterAudinDevice failed with error %" PRIu32 "!", error); + goto error_out; + } + +#if defined(MAC_OS_X_VERSION_10_14) + if (@available(macOS 10.14, *)) + { + @autoreleasepool + { + AVAuthorizationStatus status = + [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio]; + switch (status) + { + case AVAuthorizationStatusAuthorized: + mac->isAuthorized = TRUE; + break; + case AVAuthorizationStatusNotDetermined: + [AVCaptureDevice + requestAccessForMediaType:AVMediaTypeAudio + completionHandler:^(BOOL granted) { + if (granted == YES) + { + mac->isAuthorized = TRUE; + } + else + WLog_WARN(TAG, "Microphone access denied by user"); + }]; + break; + case AVAuthorizationStatusRestricted: + WLog_WARN(TAG, "Microphone access restricted by policy"); + break; + case AVAuthorizationStatusDenied: + WLog_WARN(TAG, "Microphone access denied by policy"); + break; + default: + break; + } + } + } +#endif + + return CHANNEL_RC_OK; +error_out: + free(mac); + return error; +} diff --git a/third_party/FreeRDP/channels/audin/client/opensles/CMakeLists.txt b/third_party/FreeRDP/channels/audin/client/opensles/CMakeLists.txt new file mode 100644 index 0000000..2174996 --- /dev/null +++ b/third_party/FreeRDP/channels/audin/client/opensles/CMakeLists.txt @@ -0,0 +1,29 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2013 Armin Novak +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client_subsystem("audin" "opensles" "") + +find_package(OpenSLES REQUIRED) + +set(${MODULE_PREFIX}_SRCS opensl_io.c audin_opensl_es.c) + +set(${MODULE_PREFIX}_LIBS winpr freerdp ${OpenSLES_LIBRARIES}) + +include_directories(..) +include_directories(SYSTEM ${OpenSLES_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") diff --git a/third_party/FreeRDP/channels/audin/client/opensles/audin_opensl_es.c b/third_party/FreeRDP/channels/audin/client/opensles/audin_opensl_es.c new file mode 100644 index 0000000..52281ae --- /dev/null +++ b/third_party/FreeRDP/channels/audin/client/opensles/audin_opensl_es.c @@ -0,0 +1,334 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Input Redirection Virtual Channel - OpenSL ES implementation + * + * Copyright 2013 Armin Novak + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#include "audin_main.h" +#include "opensl_io.h" + +typedef struct +{ + IAudinDevice iface; + + char* device_name; + OPENSL_STREAM* stream; + + AUDIO_FORMAT format; + UINT32 frames_per_packet; + + UINT32 bytes_per_channel; + + AudinReceive receive; + + void* user_data; + + rdpContext* rdpcontext; + wLog* log; +} AudinOpenSLESDevice; + +static UINT audin_opensles_close(IAudinDevice* device); + +static void audin_receive(void* context, const void* data, size_t size) +{ + UINT error; + AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)context; + + if (!opensles || !data) + { + WLog_ERR(TAG, "Invalid arguments context=%p, data=%p", opensles, data); + return; + } + + error = opensles->receive(&opensles->format, data, size, opensles->user_data); + + if (error && opensles->rdpcontext) + setChannelError(opensles->rdpcontext, error, "audin_receive reported an error"); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_opensles_free(IAudinDevice* device) +{ + AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)device; + + if (!opensles) + return ERROR_INVALID_PARAMETER; + + WLog_Print(opensles->log, WLOG_DEBUG, "device=%p", (void*)device); + + free(opensles->device_name); + free(opensles); + return CHANNEL_RC_OK; +} + +static BOOL audin_opensles_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format) +{ + AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)device; + + if (!opensles || !format) + return FALSE; + + WLog_Print(opensles->log, WLOG_DEBUG, "device=%p, format=%p", (void*)opensles, (void*)format); + WINPR_ASSERT(format); + + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: /* PCM */ + if (format->cbSize == 0 && (format->nSamplesPerSec <= 48000) && + (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) && + (format->nChannels >= 1 && format->nChannels <= 2)) + { + return TRUE; + } + + break; + + default: + WLog_Print(opensles->log, WLOG_DEBUG, "Encoding '%s' [0x%04" PRIX16 "] not supported", + audio_format_get_tag_string(format->wFormatTag), format->wFormatTag); + break; + } + + return FALSE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_opensles_set_format(IAudinDevice* device, const AUDIO_FORMAT* format, + UINT32 FramesPerPacket) +{ + AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)device; + + if (!opensles || !format) + return ERROR_INVALID_PARAMETER; + + WLog_Print(opensles->log, WLOG_DEBUG, "device=%p, format=%p, FramesPerPacket=%" PRIu32 "", + (void*)device, (void*)format, FramesPerPacket); + WINPR_ASSERT(format); + + opensles->format = *format; + + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + opensles->frames_per_packet = FramesPerPacket; + + switch (format->wBitsPerSample) + { + case 4: + opensles->bytes_per_channel = 1; + break; + + case 8: + opensles->bytes_per_channel = 1; + break; + + case 16: + opensles->bytes_per_channel = 2; + break; + + default: + return ERROR_UNSUPPORTED_TYPE; + } + + break; + + default: + WLog_Print(opensles->log, WLOG_ERROR, + "Encoding '%" PRIu16 "' [%04" PRIX16 "] not supported", format->wFormatTag, + format->wFormatTag); + return ERROR_UNSUPPORTED_TYPE; + } + + WLog_Print(opensles->log, WLOG_DEBUG, "frames_per_packet=%" PRIu32, + opensles->frames_per_packet); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_opensles_open(IAudinDevice* device, AudinReceive receive, void* user_data) +{ + AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)device; + + if (!opensles || !receive || !user_data) + return ERROR_INVALID_PARAMETER; + + WLog_Print(opensles->log, WLOG_DEBUG, "device=%p, receive=%p, user_data=%p", (void*)device, + (void*)receive, (void*)user_data); + + if (opensles->stream) + goto error_out; + + if (!(opensles->stream = android_OpenRecDevice( + opensles, audin_receive, opensles->format.nSamplesPerSec, opensles->format.nChannels, + opensles->frames_per_packet, opensles->format.wBitsPerSample))) + { + WLog_Print(opensles->log, WLOG_ERROR, "android_OpenRecDevice failed!"); + goto error_out; + } + + opensles->receive = receive; + opensles->user_data = user_data; + return CHANNEL_RC_OK; +error_out: + audin_opensles_close(device); + return ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT audin_opensles_close(IAudinDevice* device) +{ + AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)device; + + if (!opensles) + return ERROR_INVALID_PARAMETER; + + WLog_Print(opensles->log, WLOG_DEBUG, "device=%p", (void*)device); + android_CloseRecDevice(opensles->stream); + opensles->receive = nullptr; + opensles->user_data = nullptr; + opensles->stream = nullptr; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_opensles_parse_addin_args(AudinOpenSLESDevice* device, const ADDIN_ARGV* args) +{ + UINT status; + DWORD flags; + const COMMAND_LINE_ARGUMENT_A* arg; + AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)device; + COMMAND_LINE_ARGUMENT_A audin_opensles_args[] = { + { "dev", COMMAND_LINE_VALUE_REQUIRED, "", nullptr, nullptr, -1, nullptr, + "audio device name" }, + { nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr } + }; + + WLog_Print(opensles->log, WLOG_DEBUG, "device=%p, args=%p", (void*)device, (void*)args); + flags = + COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + status = CommandLineParseArgumentsA(args->argc, args->argv, audin_opensles_args, flags, + opensles, nullptr, nullptr); + + if (status < 0) + return status; + + arg = audin_opensles_args; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev") + { + opensles->device_name = _strdup(arg->Value); + + if (!opensles->device_name) + { + WLog_Print(opensles->log, WLOG_ERROR, "_strdup failed!"); + return CHANNEL_RC_NO_MEMORY; + } + } + CommandLineSwitchEnd(arg) + } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE opensles_freerdp_audin_client_subsystem_entry( + PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints)) +{ + UINT error = ERROR_INTERNAL_ERROR; + AudinOpenSLESDevice* opensles = (AudinOpenSLESDevice*)calloc(1, sizeof(AudinOpenSLESDevice)); + + if (!opensles) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + opensles->log = WLog_Get(TAG); + opensles->iface.Open = audin_opensles_open; + opensles->iface.FormatSupported = audin_opensles_format_supported; + opensles->iface.SetFormat = audin_opensles_set_format; + opensles->iface.Close = audin_opensles_close; + opensles->iface.Free = audin_opensles_free; + opensles->rdpcontext = pEntryPoints->rdpcontext; + const ADDIN_ARGV* args = pEntryPoints->args; + + if ((error = audin_opensles_parse_addin_args(opensles, args))) + { + WLog_Print(opensles->log, WLOG_ERROR, + "audin_opensles_parse_addin_args failed with errorcode %" PRIu32 "!", error); + goto error_out; + } + + if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*)opensles))) + { + WLog_Print(opensles->log, WLOG_ERROR, "RegisterAudinDevice failed with error %" PRIu32 "!", + error); + goto error_out; + } + + return CHANNEL_RC_OK; +error_out: + free(opensles); + return error; +} diff --git a/third_party/FreeRDP/channels/audin/client/opensles/opensl_io.c b/third_party/FreeRDP/channels/audin/client/opensles/opensl_io.c new file mode 100644 index 0000000..865b11e --- /dev/null +++ b/third_party/FreeRDP/channels/audin/client/opensles/opensl_io.c @@ -0,0 +1,388 @@ +/* +opensl_io.c: +Android OpenSL input/output module +Copyright (c) 2012, Victor Lazzarini +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +#include "audin_main.h" +#include "opensl_io.h" +#define CONV16BIT 32768 +#define CONVMYFLT (1. / 32768.) + +typedef struct +{ + size_t size; + void* data; +} queue_element; + +struct opensl_stream +{ + // engine interfaces + SLObjectItf engineObject; + SLEngineItf engineEngine; + + // device interfaces + SLDeviceVolumeItf deviceVolume; + + // recorder interfaces + SLObjectItf recorderObject; + SLRecordItf recorderRecord; + SLAndroidSimpleBufferQueueItf recorderBufferQueue; + + unsigned int inchannels; + unsigned int sr; + unsigned int buffersize; + unsigned int bits_per_sample; + + queue_element* prep; + queue_element* next; + + void* context; + opensl_receive_t receive; +}; + +static void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void* context); + +// creates the OpenSL ES audio engine +static SLresult openSLCreateEngine(OPENSL_STREAM* p) +{ + SLresult result; + // create engine + result = slCreateEngine(&(p->engineObject), 0, nullptr, 0, nullptr, nullptr); + + if (result != SL_RESULT_SUCCESS) + goto engine_end; + + // realize the engine + result = (*p->engineObject)->Realize(p->engineObject, SL_BOOLEAN_FALSE); + + if (result != SL_RESULT_SUCCESS) + goto engine_end; + + // get the engine interface, which is needed in order to create other objects + result = (*p->engineObject)->GetInterface(p->engineObject, SL_IID_ENGINE, &(p->engineEngine)); + + if (result != SL_RESULT_SUCCESS) + goto engine_end; + + // get the volume interface - important, this is optional! + result = + (*p->engineObject)->GetInterface(p->engineObject, SL_IID_DEVICEVOLUME, &(p->deviceVolume)); + + if (result != SL_RESULT_SUCCESS) + { + p->deviceVolume = nullptr; + result = SL_RESULT_SUCCESS; + } + +engine_end: + WINPR_ASSERT(SL_RESULT_SUCCESS == result); + return result; +} + +// Open the OpenSL ES device for input +static SLresult openSLRecOpen(OPENSL_STREAM* p) +{ + SLresult result; + SLuint32 sr = p->sr; + SLuint32 channels = p->inchannels; + WINPR_ASSERT(!p->recorderObject); + + if (channels) + { + switch (sr) + { + case 8000: + sr = SL_SAMPLINGRATE_8; + break; + + case 11025: + sr = SL_SAMPLINGRATE_11_025; + break; + + case 16000: + sr = SL_SAMPLINGRATE_16; + break; + + case 22050: + sr = SL_SAMPLINGRATE_22_05; + break; + + case 24000: + sr = SL_SAMPLINGRATE_24; + break; + + case 32000: + sr = SL_SAMPLINGRATE_32; + break; + + case 44100: + sr = SL_SAMPLINGRATE_44_1; + break; + + case 48000: + sr = SL_SAMPLINGRATE_48; + break; + + case 64000: + sr = SL_SAMPLINGRATE_64; + break; + + case 88200: + sr = SL_SAMPLINGRATE_88_2; + break; + + case 96000: + sr = SL_SAMPLINGRATE_96; + break; + + case 192000: + sr = SL_SAMPLINGRATE_192; + break; + + default: + return -1; + } + + // configure audio source + SLDataLocator_IODevice loc_dev = { SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, + SL_DEFAULTDEVICEID_AUDIOINPUT, nullptr }; + SLDataSource audioSrc = { &loc_dev, nullptr }; + // configure audio sink + int speakers; + + if (channels > 1) + speakers = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + else + speakers = SL_SPEAKER_FRONT_CENTER; + + SLDataLocator_AndroidSimpleBufferQueue loc_bq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, + 2 }; + SLDataFormat_PCM format_pcm; + format_pcm.formatType = SL_DATAFORMAT_PCM; + format_pcm.numChannels = channels; + format_pcm.samplesPerSec = sr; + format_pcm.channelMask = speakers; + format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN; + + if (16 == p->bits_per_sample) + { + format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; + format_pcm.containerSize = 16; + } + else if (8 == p->bits_per_sample) + { + format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_8; + format_pcm.containerSize = 8; + } + else + WINPR_ASSERT(0); + + SLDataSink audioSnk = { &loc_bq, &format_pcm }; + // create audio recorder + // (requires the RECORD_AUDIO permission) + const SLInterfaceID id[] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE }; + const SLboolean req[] = { SL_BOOLEAN_TRUE }; + result = (*p->engineEngine) + ->CreateAudioRecorder(p->engineEngine, &(p->recorderObject), &audioSrc, + &audioSnk, 1, id, req); + WINPR_ASSERT(!result); + + if (SL_RESULT_SUCCESS != result) + goto end_recopen; + + // realize the audio recorder + result = (*p->recorderObject)->Realize(p->recorderObject, SL_BOOLEAN_FALSE); + WINPR_ASSERT(!result); + + if (SL_RESULT_SUCCESS != result) + goto end_recopen; + + // get the record interface + result = (*p->recorderObject) + ->GetInterface(p->recorderObject, SL_IID_RECORD, &(p->recorderRecord)); + WINPR_ASSERT(!result); + + if (SL_RESULT_SUCCESS != result) + goto end_recopen; + + // get the buffer queue interface + result = (*p->recorderObject) + ->GetInterface(p->recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + &(p->recorderBufferQueue)); + WINPR_ASSERT(!result); + + if (SL_RESULT_SUCCESS != result) + goto end_recopen; + + // register callback on the buffer queue + result = (*p->recorderBufferQueue) + ->RegisterCallback(p->recorderBufferQueue, bqRecorderCallback, p); + WINPR_ASSERT(!result); + + if (SL_RESULT_SUCCESS != result) + goto end_recopen; + + end_recopen: + return result; + } + else + return SL_RESULT_SUCCESS; +} + +// close the OpenSL IO and destroy the audio engine +static void openSLDestroyEngine(OPENSL_STREAM* p) +{ + // destroy audio recorder object, and invalidate all associated interfaces + if (p->recorderObject != nullptr) + { + (*p->recorderObject)->Destroy(p->recorderObject); + p->recorderObject = nullptr; + p->recorderRecord = nullptr; + p->recorderBufferQueue = nullptr; + } + + // destroy engine object, and invalidate all associated interfaces + if (p->engineObject != nullptr) + { + (*p->engineObject)->Destroy(p->engineObject); + p->engineObject = nullptr; + p->engineEngine = nullptr; + } +} + +static queue_element* opensles_queue_element_new(size_t size) +{ + queue_element* q = calloc(1, sizeof(queue_element)); + + if (!q) + goto fail; + + q->size = size; + q->data = malloc(size); + + if (!q->data) + goto fail; + + return q; +fail: + free(q); + return nullptr; +} + +static void opensles_queue_element_free(void* obj) +{ + queue_element* e = (queue_element*)obj; + + if (e) + free(e->data); + + free(e); +} + +// open the android audio device for input +OPENSL_STREAM* android_OpenRecDevice(void* context, opensl_receive_t receive, int sr, + int inchannels, int bufferframes, int bits_per_sample) +{ + OPENSL_STREAM* p; + + if (!context || !receive) + return nullptr; + + p = (OPENSL_STREAM*)calloc(1, sizeof(OPENSL_STREAM)); + + if (!p) + return nullptr; + + p->context = context; + p->receive = receive; + p->inchannels = inchannels; + p->sr = sr; + p->buffersize = bufferframes; + p->bits_per_sample = bits_per_sample; + + if ((p->bits_per_sample != 8) && (p->bits_per_sample != 16)) + goto fail; + + if (openSLCreateEngine(p) != SL_RESULT_SUCCESS) + goto fail; + + if (openSLRecOpen(p) != SL_RESULT_SUCCESS) + goto fail; + + /* Create receive buffers, prepare them and start recording */ + p->prep = opensles_queue_element_new(p->buffersize * p->bits_per_sample / 8); + p->next = opensles_queue_element_new(p->buffersize * p->bits_per_sample / 8); + + if (!p->prep || !p->next) + goto fail; + + (*p->recorderBufferQueue)->Enqueue(p->recorderBufferQueue, p->next->data, p->next->size); + (*p->recorderBufferQueue)->Enqueue(p->recorderBufferQueue, p->prep->data, p->prep->size); + (*p->recorderRecord)->SetRecordState(p->recorderRecord, SL_RECORDSTATE_RECORDING); + return p; +fail: + android_CloseRecDevice(p); + return nullptr; +} + +// close the android audio device +void android_CloseRecDevice(OPENSL_STREAM* p) +{ + if (p == nullptr) + return; + + opensles_queue_element_free(p->next); + opensles_queue_element_free(p->prep); + openSLDestroyEngine(p); + free(p); +} + +// this callback handler is called every time a buffer finishes recording +static void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void* context) +{ + OPENSL_STREAM* p = (OPENSL_STREAM*)context; + queue_element* e; + + if (!p) + return; + + e = p->next; + + if (!e) + return; + + if (!p->context || !p->receive) + WLog_WARN(TAG, "Missing receive callback=%p, context=%p", p->receive, p->context); + else + p->receive(p->context, e->data, e->size); + + p->next = p->prep; + p->prep = e; + (*p->recorderBufferQueue)->Enqueue(p->recorderBufferQueue, e->data, e->size); +} diff --git a/third_party/FreeRDP/channels/audin/client/opensles/opensl_io.h b/third_party/FreeRDP/channels/audin/client/opensles/opensl_io.h new file mode 100644 index 0000000..4f9b2db --- /dev/null +++ b/third_party/FreeRDP/channels/audin/client/opensles/opensl_io.h @@ -0,0 +1,67 @@ +/* +opensl_io.c: +Android OpenSL input/output module header +Copyright (c) 2012, Victor Lazzarini +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef FREERDP_CHANNEL_AUDIN_CLIENT_OPENSL_IO_H +#define FREERDP_CHANNEL_AUDIN_CLIENT_OPENSL_IO_H + +#include +#include + +#include + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + + typedef struct opensl_stream OPENSL_STREAM; + + typedef void (*opensl_receive_t)(void* context, const void* data, size_t size); + + /* + Close the audio device + */ + FREERDP_LOCAL void android_CloseRecDevice(OPENSL_STREAM* p); + + /* + Open the audio device with a given sampling rate (sr), input and output channels and IO buffer + size in frames. Returns a handle to the OpenSL stream + */ + WINPR_ATTR_MALLOC(android_CloseRecDevice, 1) + WINPR_ATTR_NODISCARD + FREERDP_LOCAL OPENSL_STREAM* android_OpenRecDevice(void* context, opensl_receive_t receive, + int sr, int inchannels, int bufferframes, + int bits_per_sample); +#ifdef __cplusplus +}; +#endif + +#endif /* FREERDP_CHANNEL_AUDIN_CLIENT_OPENSL_IO_H */ diff --git a/third_party/FreeRDP/channels/audin/client/oss/CMakeLists.txt b/third_party/FreeRDP/channels/audin/client/oss/CMakeLists.txt new file mode 100644 index 0000000..3532c63 --- /dev/null +++ b/third_party/FreeRDP/channels/audin/client/oss/CMakeLists.txt @@ -0,0 +1,31 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright (c) 2015 Rozhuk Ivan +# +# 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. + +define_channel_client_subsystem("audin" "oss" "") + +find_package(OSS REQUIRED) + +set(${MODULE_PREFIX}_SRCS audin_oss.c) + +set(${MODULE_PREFIX}_LIBS winpr freerdp ${OSS_LIBRARIES}) + +include_directories(..) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +include_directories(SYSTEM ${OSS_INCLUDE_DIRS}) +cleaning_configure_file(${CMAKE_SOURCE_DIR}/cmake/oss-includes.h.in ${CMAKE_CURRENT_BINARY_DIR}/oss-includes.h @ONLY) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") diff --git a/third_party/FreeRDP/channels/audin/client/oss/audin_oss.c b/third_party/FreeRDP/channels/audin/client/oss/audin_oss.c new file mode 100644 index 0000000..f709130 --- /dev/null +++ b/third_party/FreeRDP/channels/audin/client/oss/audin_oss.c @@ -0,0 +1,449 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Input Redirection Virtual Channel - OSS implementation + * + * Copyright (c) 2015 Rozhuk Ivan + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "audin_main.h" + +typedef struct +{ + IAudinDevice iface; + + HANDLE thread; + HANDLE stopEvent; + + AUDIO_FORMAT format; + UINT32 FramesPerPacket; + int dev_unit; + + AudinReceive receive; + void* user_data; + + rdpContext* rdpcontext; +} AudinOSSDevice; + +static void OSS_LOG_ERR(const char* _text, int _error) +{ + if ((_error) != 0) + { + char buffer[256] = WINPR_C_ARRAY_INIT; + WLog_ERR(TAG, "%s: %i - %s\n", (_text), (_error), + winpr_strerror((_error), buffer, sizeof(buffer))); + } +} + +static UINT32 audin_oss_get_format(const AUDIO_FORMAT* format) +{ + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + switch (format->wBitsPerSample) + { + case 8: + return AFMT_S8; + + case 16: + return AFMT_S16_LE; + + default: + break; + } + + break; + default: + break; + } + + return 0; +} + +static BOOL audin_oss_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format) +{ + if (device == nullptr || format == nullptr) + return FALSE; + + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + if (format->cbSize != 0 || format->nSamplesPerSec > 48000 || + (format->wBitsPerSample != 8 && format->wBitsPerSample != 16) || + (format->nChannels != 1 && format->nChannels != 2)) + return FALSE; + + break; + + default: + return FALSE; + } + + return TRUE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_oss_set_format(IAudinDevice* device, const AUDIO_FORMAT* format, + UINT32 FramesPerPacket) +{ + AudinOSSDevice* oss = (AudinOSSDevice*)device; + + if (device == nullptr || format == nullptr) + return ERROR_INVALID_PARAMETER; + + oss->FramesPerPacket = FramesPerPacket; + oss->format = *format; + return CHANNEL_RC_OK; +} + +static DWORD WINAPI audin_oss_thread_func(LPVOID arg) +{ + char dev_name[PATH_MAX] = "/dev/dsp"; + int pcm_handle = -1; + BYTE* buffer = nullptr; + unsigned long tmp = 0; + size_t buffer_size = 0; + AudinOSSDevice* oss = (AudinOSSDevice*)arg; + UINT error = 0; + DWORD status = 0; + + if (oss == nullptr) + { + error = ERROR_INVALID_PARAMETER; + goto err_out; + } + + if (oss->dev_unit != -1) + (void)sprintf_s(dev_name, (PATH_MAX - 1), "/dev/dsp%i", oss->dev_unit); + + WLog_INFO(TAG, "open: %s", dev_name); + + if ((pcm_handle = open(dev_name, O_RDONLY)) < 0) + { + OSS_LOG_ERR("sound dev open failed", errno); + error = ERROR_INTERNAL_ERROR; + goto err_out; + } + + /* Set format. */ + tmp = audin_oss_get_format(&oss->format); + + if (ioctl(pcm_handle, SNDCTL_DSP_SETFMT, &tmp) == -1) + OSS_LOG_ERR("SNDCTL_DSP_SETFMT failed", errno); + + tmp = oss->format.nChannels; + + if (ioctl(pcm_handle, SNDCTL_DSP_CHANNELS, &tmp) == -1) + OSS_LOG_ERR("SNDCTL_DSP_CHANNELS failed", errno); + + tmp = oss->format.nSamplesPerSec; + + if (ioctl(pcm_handle, SNDCTL_DSP_SPEED, &tmp) == -1) + OSS_LOG_ERR("SNDCTL_DSP_SPEED failed", errno); + + tmp = oss->format.nBlockAlign; + + if (ioctl(pcm_handle, SNDCTL_DSP_SETFRAGMENT, &tmp) == -1) + OSS_LOG_ERR("SNDCTL_DSP_SETFRAGMENT failed", errno); + + buffer_size = + (1ull * oss->FramesPerPacket * oss->format.nChannels * (oss->format.wBitsPerSample / 8ull)); + buffer = (BYTE*)calloc((buffer_size + sizeof(void*)), sizeof(BYTE)); + + if (nullptr == buffer) + { + OSS_LOG_ERR("malloc() fail", errno); + error = ERROR_NOT_ENOUGH_MEMORY; + goto err_out; + } + + while (1) + { + SSIZE_T stmp = -1; + status = WaitForSingleObject(oss->stopEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + goto err_out; + } + + if (status == WAIT_OBJECT_0) + break; + + stmp = read(pcm_handle, buffer, buffer_size); + + /* Error happen. */ + if (stmp < 0) + { + OSS_LOG_ERR("read() error", errno); + continue; + } + + if ((size_t)stmp < buffer_size) /* Not enough data. */ + continue; + + if ((error = oss->receive(&oss->format, buffer, buffer_size, oss->user_data))) + { + WLog_ERR(TAG, "oss->receive failed with error %" PRIu32 "", error); + break; + } + } + +err_out: + + if (error && oss && oss->rdpcontext) + setChannelError(oss->rdpcontext, error, "audin_oss_thread_func reported an error"); + + if (pcm_handle != -1) + { + WLog_INFO(TAG, "close: %s", dev_name); + close(pcm_handle); + } + + free(buffer); + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_oss_open(IAudinDevice* device, AudinReceive receive, void* user_data) +{ + AudinOSSDevice* oss = (AudinOSSDevice*)device; + oss->receive = receive; + oss->user_data = user_data; + + if (!(oss->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (!(oss->thread = CreateThread(nullptr, 0, audin_oss_thread_func, oss, 0, nullptr))) + { + WLog_ERR(TAG, "CreateThread failed!"); + (void)CloseHandle(oss->stopEvent); + oss->stopEvent = nullptr; + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_oss_close(IAudinDevice* device) +{ + UINT error = 0; + AudinOSSDevice* oss = (AudinOSSDevice*)device; + + if (device == nullptr) + return ERROR_INVALID_PARAMETER; + + if (oss->stopEvent != nullptr) + { + (void)SetEvent(oss->stopEvent); + + if (WaitForSingleObject(oss->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + return error; + } + + (void)CloseHandle(oss->stopEvent); + oss->stopEvent = nullptr; + (void)CloseHandle(oss->thread); + oss->thread = nullptr; + } + + oss->receive = nullptr; + oss->user_data = nullptr; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_oss_free(IAudinDevice* device) +{ + AudinOSSDevice* oss = (AudinOSSDevice*)device; + UINT error = 0; + + if (device == nullptr) + return ERROR_INVALID_PARAMETER; + + if ((error = audin_oss_close(device))) + { + WLog_ERR(TAG, "audin_oss_close failed with error code %" PRIu32 "!", error); + } + + free(oss); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_oss_parse_addin_args(AudinOSSDevice* device, const ADDIN_ARGV* args) +{ + int status = 0; + char* str_num = nullptr; + char* eptr = nullptr; + DWORD flags = 0; + const COMMAND_LINE_ARGUMENT_A* arg = nullptr; + AudinOSSDevice* oss = device; + COMMAND_LINE_ARGUMENT_A audin_oss_args[] = { + { "dev", COMMAND_LINE_VALUE_REQUIRED, "", nullptr, nullptr, -1, nullptr, + "audio device name" }, + { nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr } + }; + + flags = + COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + status = CommandLineParseArgumentsA(args->argc, args->argv, audin_oss_args, flags, oss, nullptr, + nullptr); + + if (status < 0) + return ERROR_INVALID_PARAMETER; + + arg = audin_oss_args; + errno = 0; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev") + { + str_num = _strdup(arg->Value); + + if (!str_num) + { + WLog_ERR(TAG, "_strdup failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + { + long val = strtol(str_num, &eptr, 10); + + if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX)) + { + free(str_num); + return CHANNEL_RC_NULL_DATA; + } + + oss->dev_unit = (INT32)val; + } + + if (oss->dev_unit < 0 || *eptr != '\0') + oss->dev_unit = -1; + + free(str_num); + } + CommandLineSwitchEnd(arg) + } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE oss_freerdp_audin_client_subsystem_entry( + PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints)) +{ + const ADDIN_ARGV* args = nullptr; + AudinOSSDevice* oss = nullptr; + UINT error = 0; + oss = (AudinOSSDevice*)calloc(1, sizeof(AudinOSSDevice)); + + if (!oss) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + oss->iface.Open = audin_oss_open; + oss->iface.FormatSupported = audin_oss_format_supported; + oss->iface.SetFormat = audin_oss_set_format; + oss->iface.Close = audin_oss_close; + oss->iface.Free = audin_oss_free; + oss->rdpcontext = pEntryPoints->rdpcontext; + oss->dev_unit = -1; + args = pEntryPoints->args; + + if ((error = audin_oss_parse_addin_args(oss, args))) + { + WLog_ERR(TAG, "audin_oss_parse_addin_args failed with errorcode %" PRIu32 "!", error); + goto error_out; + } + + if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*)oss))) + { + WLog_ERR(TAG, "RegisterAudinDevice failed with error %" PRIu32 "!", error); + goto error_out; + } + + return CHANNEL_RC_OK; +error_out: + free(oss); + return error; +} diff --git a/third_party/FreeRDP/channels/audin/client/pulse/CMakeLists.txt b/third_party/FreeRDP/channels/audin/client/pulse/CMakeLists.txt new file mode 100644 index 0000000..9635c18 --- /dev/null +++ b/third_party/FreeRDP/channels/audin/client/pulse/CMakeLists.txt @@ -0,0 +1,30 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_client_subsystem("audin" "pulse" "") + +find_package(PulseAudio REQUIRED) +freerdp_client_pc_add_requires_private("libpulse") + +set(${MODULE_PREFIX}_SRCS audin_pulse.c) + +set(${MODULE_PREFIX}_LIBS winpr freerdp ${PULSEAUDIO_LIBRARY} ${PULSEAUDIO_MAINLOOP_LIBRARY}) + +include_directories(..) +include_directories(SYSTEM ${PULSEAUDIO_INCLUDE_DIR}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") diff --git a/third_party/FreeRDP/channels/audin/client/pulse/audin_pulse.c b/third_party/FreeRDP/channels/audin/client/pulse/audin_pulse.c new file mode 100644 index 0000000..b46904d --- /dev/null +++ b/third_party/FreeRDP/channels/audin/client/pulse/audin_pulse.c @@ -0,0 +1,584 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Input Redirection Virtual Channel - PulseAudio implementation + * + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "audin_main.h" + +typedef struct +{ + IAudinDevice iface; + + char* device_name; + char* client_name; + char* stream_name; + UINT32 frames_per_packet; + pa_threaded_mainloop* mainloop; + pa_context* context; + pa_sample_spec sample_spec; + pa_stream* stream; + AUDIO_FORMAT format; + + size_t bytes_per_frame; + size_t buffer_frames; + + AudinReceive receive; + void* user_data; + + rdpContext* rdpcontext; + wLog* log; +} AudinPulseDevice; + +static const char* pulse_context_state_string(pa_context_state_t state) +{ + switch (state) + { + case PA_CONTEXT_UNCONNECTED: + return "PA_CONTEXT_UNCONNECTED"; + case PA_CONTEXT_CONNECTING: + return "PA_CONTEXT_CONNECTING"; + case PA_CONTEXT_AUTHORIZING: + return "PA_CONTEXT_AUTHORIZING"; + case PA_CONTEXT_SETTING_NAME: + return "PA_CONTEXT_SETTING_NAME"; + case PA_CONTEXT_READY: + return "PA_CONTEXT_READY"; + case PA_CONTEXT_FAILED: + return "PA_CONTEXT_FAILED"; + case PA_CONTEXT_TERMINATED: + return "PA_CONTEXT_TERMINATED"; + default: + return "UNKNOWN"; + } +} + +static const char* pulse_stream_state_string(pa_stream_state_t state) +{ + switch (state) + { + case PA_STREAM_UNCONNECTED: + return "PA_STREAM_UNCONNECTED"; + case PA_STREAM_CREATING: + return "PA_STREAM_CREATING"; + case PA_STREAM_READY: + return "PA_STREAM_READY"; + case PA_STREAM_FAILED: + return "PA_STREAM_FAILED"; + case PA_STREAM_TERMINATED: + return "PA_STREAM_TERMINATED"; + default: + return "UNKNOWN"; + } +} + +static void audin_pulse_context_state_callback(pa_context* context, void* userdata) +{ + AudinPulseDevice* pulse = (AudinPulseDevice*)userdata; + pa_context_state_t state = pa_context_get_state(context); + + WLog_Print(pulse->log, WLOG_DEBUG, "context state %s", pulse_context_state_string(state)); + switch (state) + { + case PA_CONTEXT_READY: + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + default: + break; + } +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_pulse_connect(IAudinDevice* device) +{ + pa_context_state_t state = PA_CONTEXT_FAILED; + AudinPulseDevice* pulse = (AudinPulseDevice*)device; + + if (!pulse->context) + return ERROR_INVALID_PARAMETER; + + if (pa_context_connect(pulse->context, nullptr, PA_CONTEXT_NOFLAGS, nullptr)) + { + WLog_Print(pulse->log, WLOG_ERROR, "pa_context_connect failed (%d)", + pa_context_errno(pulse->context)); + return ERROR_INTERNAL_ERROR; + } + + pa_threaded_mainloop_lock(pulse->mainloop); + + if (pa_threaded_mainloop_start(pulse->mainloop) < 0) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + WLog_Print(pulse->log, WLOG_ERROR, "pa_threaded_mainloop_start failed (%d)", + pa_context_errno(pulse->context)); + return ERROR_INTERNAL_ERROR; + } + + for (;;) + { + state = pa_context_get_state(pulse->context); + + if (state == PA_CONTEXT_READY) + break; + + if (!PA_CONTEXT_IS_GOOD(state)) + { + WLog_Print(pulse->log, WLOG_ERROR, "bad context state (%s: %d)", + pulse_context_state_string(state), pa_context_errno(pulse->context)); + pa_context_disconnect(pulse->context); + return ERROR_INVALID_STATE; + } + + pa_threaded_mainloop_wait(pulse->mainloop); + } + + pa_threaded_mainloop_unlock(pulse->mainloop); + WLog_Print(pulse->log, WLOG_DEBUG, "connected"); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_pulse_free(IAudinDevice* device) +{ + AudinPulseDevice* pulse = (AudinPulseDevice*)device; + + if (!pulse) + return ERROR_INVALID_PARAMETER; + + if (pulse->mainloop) + { + pa_threaded_mainloop_stop(pulse->mainloop); + } + + if (pulse->context) + { + pa_context_disconnect(pulse->context); + pa_context_unref(pulse->context); + pulse->context = nullptr; + } + + if (pulse->mainloop) + { + pa_threaded_mainloop_free(pulse->mainloop); + pulse->mainloop = nullptr; + } + + free(pulse->device_name); + free(pulse->client_name); + free(pulse->stream_name); + free(pulse); + return CHANNEL_RC_OK; +} + +static BOOL audin_pulse_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format) +{ + AudinPulseDevice* pulse = (AudinPulseDevice*)device; + + if (!pulse || !format) + return FALSE; + + if (!pulse->context) + return 0; + + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + if (format->cbSize == 0 && (format->nSamplesPerSec <= PA_RATE_MAX) && + (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) && + (format->nChannels >= 1 && format->nChannels <= PA_CHANNELS_MAX)) + { + return TRUE; + } + + break; + + default: + return FALSE; + } + + return FALSE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_pulse_set_format(IAudinDevice* device, const AUDIO_FORMAT* format, + UINT32 FramesPerPacket) +{ + AudinPulseDevice* pulse = (AudinPulseDevice*)device; + + if (!pulse || !format) + return ERROR_INVALID_PARAMETER; + + if (!pulse->context) + return ERROR_INVALID_PARAMETER; + + if (FramesPerPacket > 0) + pulse->frames_per_packet = FramesPerPacket; + + pa_sample_format_t sformat = PA_SAMPLE_INVALID; + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: /* PCM */ + switch (format->wBitsPerSample) + { + case 8: + sformat = PA_SAMPLE_U8; + break; + + case 16: + sformat = PA_SAMPLE_S16LE; + break; + + default: + return ERROR_INTERNAL_ERROR; + } + + break; + + default: + return ERROR_INTERNAL_ERROR; + } + + const pa_sample_spec sample_spec = { + .format = sformat, + .rate = format->nSamplesPerSec, + .channels = WINPR_ASSERTING_INT_CAST(uint8_t, format->nChannels), + }; + + pulse->sample_spec = sample_spec; + pulse->format = *format; + return CHANNEL_RC_OK; +} + +static void audin_pulse_stream_state_callback(pa_stream* stream, void* userdata) +{ + AudinPulseDevice* pulse = (AudinPulseDevice*)userdata; + WINPR_ASSERT(pulse); + + pa_stream_state_t state = pa_stream_get_state(stream); + + WLog_Print(pulse->log, WLOG_DEBUG, "stream state %s", pulse_stream_state_string(state)); + switch (state) + { + case PA_STREAM_READY: + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + case PA_STREAM_UNCONNECTED: + case PA_STREAM_CREATING: + default: + break; + } +} + +static void audin_pulse_stream_request_callback(pa_stream* stream, size_t length, void* userdata) +{ + const void* data = nullptr; + AudinPulseDevice* pulse = (AudinPulseDevice*)userdata; + UINT error = CHANNEL_RC_OK; + pa_stream_peek(stream, &data, &length); + error = + IFCALLRESULT(CHANNEL_RC_OK, pulse->receive, &pulse->format, data, length, pulse->user_data); + pa_stream_drop(stream); + + if (error && pulse->rdpcontext) + setChannelError(pulse->rdpcontext, error, "audin_pulse_thread_func reported an error"); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_pulse_close(IAudinDevice* device) +{ + AudinPulseDevice* pulse = (AudinPulseDevice*)device; + + if (!pulse) + return ERROR_INVALID_PARAMETER; + + if (pulse->stream) + { + pa_threaded_mainloop_lock(pulse->mainloop); + pa_stream_disconnect(pulse->stream); + pa_stream_unref(pulse->stream); + pulse->stream = nullptr; + pa_threaded_mainloop_unlock(pulse->mainloop); + } + + pulse->receive = nullptr; + pulse->user_data = nullptr; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_pulse_open(IAudinDevice* device, AudinReceive receive, void* user_data) +{ + pa_stream_state_t state = PA_STREAM_FAILED; + pa_buffer_attr buffer_attr = WINPR_C_ARRAY_INIT; + AudinPulseDevice* pulse = (AudinPulseDevice*)device; + + if (!pulse || !receive || !user_data) + return ERROR_INVALID_PARAMETER; + + if (!pulse->context) + return ERROR_INVALID_PARAMETER; + + if (!pulse->sample_spec.rate || pulse->stream) + return ERROR_INVALID_PARAMETER; + + pulse->receive = receive; + pulse->user_data = user_data; + pa_threaded_mainloop_lock(pulse->mainloop); + pulse->stream = pa_stream_new(pulse->context, pulse->stream_name, &pulse->sample_spec, nullptr); + + if (!pulse->stream) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + WLog_Print(pulse->log, WLOG_DEBUG, "pa_stream_new failed (%d)", + pa_context_errno(pulse->context)); + const int rc = pa_context_errno(pulse->context); + return (UINT)rc; + } + + pulse->bytes_per_frame = pa_frame_size(&pulse->sample_spec); + pa_stream_set_state_callback(pulse->stream, audin_pulse_stream_state_callback, pulse); + pa_stream_set_read_callback(pulse->stream, audin_pulse_stream_request_callback, pulse); + buffer_attr.maxlength = (UINT32)-1; + buffer_attr.tlength = (UINT32)-1; + buffer_attr.prebuf = (UINT32)-1; + buffer_attr.minreq = (UINT32)-1; + /* 500ms latency */ + const size_t frag = pulse->bytes_per_frame * pulse->frames_per_packet; + WINPR_ASSERT(frag <= UINT32_MAX); + buffer_attr.fragsize = (uint32_t)frag; + + if (buffer_attr.fragsize % pulse->format.nBlockAlign) + buffer_attr.fragsize += + pulse->format.nBlockAlign - buffer_attr.fragsize % pulse->format.nBlockAlign; + + if (pa_stream_connect_record(pulse->stream, pulse->device_name, &buffer_attr, + PA_STREAM_ADJUST_LATENCY) < 0) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + WLog_Print(pulse->log, WLOG_ERROR, "pa_stream_connect_playback failed (%d)", + pa_context_errno(pulse->context)); + const int rc = pa_context_errno(pulse->context); + return (UINT)rc; + } + + while (pulse->stream) + { + state = pa_stream_get_state(pulse->stream); + + if (state == PA_STREAM_READY) + break; + + if (!PA_STREAM_IS_GOOD(state)) + { + audin_pulse_close(device); + WLog_Print(pulse->log, WLOG_ERROR, "bad stream state (%s: %d)", + pulse_stream_state_string(state), pa_context_errno(pulse->context)); + pa_threaded_mainloop_unlock(pulse->mainloop); + const int rc = pa_context_errno(pulse->context); + return (UINT)rc; + } + + pa_threaded_mainloop_wait(pulse->mainloop); + } + + pa_threaded_mainloop_unlock(pulse->mainloop); + pulse->buffer_frames = 0; + WLog_Print(pulse->log, WLOG_DEBUG, "connected"); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_pulse_parse_addin_args(AudinPulseDevice* pulse, const ADDIN_ARGV* args) +{ + COMMAND_LINE_ARGUMENT_A audin_pulse_args[] = { + { "dev", COMMAND_LINE_VALUE_REQUIRED, "", nullptr, nullptr, -1, nullptr, + "audio device name" }, + { "client_name", COMMAND_LINE_VALUE_REQUIRED, "", nullptr, nullptr, -1, + nullptr, "name of pulse client" }, + { "stream_name", COMMAND_LINE_VALUE_REQUIRED, "", nullptr, nullptr, -1, + nullptr, "name of pulse stream" }, + { nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr } + }; + + const DWORD flags = + COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + const int status = CommandLineParseArgumentsA(args->argc, args->argv, audin_pulse_args, flags, + pulse, nullptr, nullptr); + + if (status < 0) + return ERROR_INVALID_PARAMETER; + + const COMMAND_LINE_ARGUMENT_A* arg = audin_pulse_args; + + const char* client_name = nullptr; + const char* stream_name = nullptr; + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev") + { + pulse->device_name = _strdup(arg->Value); + + if (!pulse->device_name) + { + WLog_Print(pulse->log, WLOG_ERROR, "_strdup failed!"); + return CHANNEL_RC_NO_MEMORY; + } + } + CommandLineSwitchEnd(arg) + } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr); + + if (!client_name) + client_name = freerdp_getApplicationDetailsString(); + if (!stream_name) + stream_name = freerdp_getApplicationDetailsString(); + + pulse->client_name = _strdup(client_name); + pulse->stream_name = _strdup(stream_name); + if (!pulse->client_name || !pulse->stream_name) + return ERROR_OUTOFMEMORY; + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE pulse_freerdp_audin_client_subsystem_entry( + PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints)) +{ + const ADDIN_ARGV* args = nullptr; + AudinPulseDevice* pulse = nullptr; + UINT error = 0; + pulse = (AudinPulseDevice*)calloc(1, sizeof(AudinPulseDevice)); + + if (!pulse) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + pulse->log = WLog_Get(TAG); + pulse->iface.Open = audin_pulse_open; + pulse->iface.FormatSupported = audin_pulse_format_supported; + pulse->iface.SetFormat = audin_pulse_set_format; + pulse->iface.Close = audin_pulse_close; + pulse->iface.Free = audin_pulse_free; + pulse->rdpcontext = pEntryPoints->rdpcontext; + args = pEntryPoints->args; + + if ((error = audin_pulse_parse_addin_args(pulse, args))) + { + WLog_Print(pulse->log, WLOG_ERROR, + "audin_pulse_parse_addin_args failed with error %" PRIu32 "!", error); + goto error_out; + } + + pulse->mainloop = pa_threaded_mainloop_new(); + + if (!pulse->mainloop) + { + WLog_Print(pulse->log, WLOG_ERROR, "pa_threaded_mainloop_new failed"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + + pulse->context = + pa_context_new(pa_threaded_mainloop_get_api(pulse->mainloop), pulse->client_name); + + if (!pulse->context) + { + WLog_Print(pulse->log, WLOG_ERROR, "pa_context_new failed"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + + pa_context_set_state_callback(pulse->context, audin_pulse_context_state_callback, pulse); + + if ((error = audin_pulse_connect(&pulse->iface))) + { + WLog_Print(pulse->log, WLOG_ERROR, "audin_pulse_connect failed"); + goto error_out; + } + + if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, &pulse->iface))) + { + WLog_Print(pulse->log, WLOG_ERROR, "RegisterAudinDevice failed with error %" PRIu32 "!", + error); + goto error_out; + } + + return CHANNEL_RC_OK; +error_out: + audin_pulse_free(&pulse->iface); + return error; +} diff --git a/third_party/FreeRDP/channels/audin/client/sndio/CMakeLists.txt b/third_party/FreeRDP/channels/audin/client/sndio/CMakeLists.txt new file mode 100644 index 0000000..93e595b --- /dev/null +++ b/third_party/FreeRDP/channels/audin/client/sndio/CMakeLists.txt @@ -0,0 +1,30 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright (c) 2015 Rozhuk Ivan +# Copyright (c) 2020 Ingo Feinerer +# +# 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. + +define_channel_client_subsystem("audin" "sndio" "") + +find_package(SNDIO REQUIRED) + +set(${MODULE_PREFIX}_SRCS audin_sndio.c) + +set(${MODULE_PREFIX}_LIBS winpr freerdp ${SNDIO_LIBRARIES}) + +include_directories(..) +include_directories(SYSTEM ${SNDIO_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") diff --git a/third_party/FreeRDP/channels/audin/client/sndio/audin_sndio.c b/third_party/FreeRDP/channels/audin/client/sndio/audin_sndio.c new file mode 100644 index 0000000..d922855 --- /dev/null +++ b/third_party/FreeRDP/channels/audin/client/sndio/audin_sndio.c @@ -0,0 +1,353 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Input Redirection Virtual Channel - sndio implementation + * + * Copyright (c) 2015 Rozhuk Ivan + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2020 Ingo Feinerer + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include + +#include +#include + +#include "audin_main.h" + +typedef struct +{ + IAudinDevice device; + + HANDLE thread; + HANDLE stopEvent; + + AUDIO_FORMAT format; + UINT32 FramesPerPacket; + + AudinReceive receive; + void* user_data; + + rdpContext* rdpcontext; +} AudinSndioDevice; + +static BOOL audin_sndio_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format) +{ + if (device == nullptr || format == nullptr) + return FALSE; + + return (format->wFormatTag == WAVE_FORMAT_PCM); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_sndio_set_format(IAudinDevice* device, AUDIO_FORMAT* format, + UINT32 FramesPerPacket) +{ + AudinSndioDevice* sndio = (AudinSndioDevice*)device; + + if (device == nullptr || format == nullptr) + return ERROR_INVALID_PARAMETER; + + if (format->wFormatTag != WAVE_FORMAT_PCM) + return ERROR_INTERNAL_ERROR; + + sndio->format = *format; + sndio->FramesPerPacket = FramesPerPacket; + + return CHANNEL_RC_OK; +} + +static void* audin_sndio_thread_func(void* arg) +{ + struct sio_hdl* hdl; + struct sio_par par; + BYTE* buffer = nullptr; + size_t n, nbytes; + AudinSndioDevice* sndio = (AudinSndioDevice*)arg; + UINT error = 0; + DWORD status; + + if (arg == nullptr) + { + error = ERROR_INVALID_PARAMETER; + goto err_out; + } + + hdl = sio_open(SIO_DEVANY, SIO_REC, 0); + if (hdl == nullptr) + { + WLog_ERR(TAG, "could not open audio device"); + error = ERROR_INTERNAL_ERROR; + goto err_out; + } + + sio_initpar(&par); + par.bits = sndio->format.wBitsPerSample; + par.rchan = sndio->format.nChannels; + par.rate = sndio->format.nSamplesPerSec; + if (!sio_setpar(hdl, &par)) + { + WLog_ERR(TAG, "could not set audio parameters"); + error = ERROR_INTERNAL_ERROR; + goto err_out; + } + if (!sio_getpar(hdl, &par)) + { + WLog_ERR(TAG, "could not get audio parameters"); + error = ERROR_INTERNAL_ERROR; + goto err_out; + } + + if (!sio_start(hdl)) + { + WLog_ERR(TAG, "could not start audio device"); + error = ERROR_INTERNAL_ERROR; + goto err_out; + } + + nbytes = + (sndio->FramesPerPacket * sndio->format.nChannels * (sndio->format.wBitsPerSample / 8)); + buffer = (BYTE*)calloc((nbytes + sizeof(void*)), sizeof(BYTE)); + + if (buffer == nullptr) + { + error = ERROR_NOT_ENOUGH_MEMORY; + goto err_out; + } + + while (1) + { + status = WaitForSingleObject(sndio->stopEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + goto err_out; + } + + if (status == WAIT_OBJECT_0) + break; + + n = sio_read(hdl, buffer, nbytes); + + if (n == 0) + { + WLog_ERR(TAG, "could not read"); + continue; + } + + if (n < nbytes) + continue; + + if ((error = sndio->receive(&sndio->format, buffer, nbytes, sndio->user_data))) + { + WLog_ERR(TAG, "sndio->receive failed with error %" PRIu32 "", error); + break; + } + } + +err_out: + if (error && sndio && sndio->rdpcontext) + setChannelError(sndio->rdpcontext, error, "audin_sndio_thread_func reported an error"); + + if (hdl != nullptr) + { + WLog_INFO(TAG, "sio_close"); + sio_stop(hdl); + sio_close(hdl); + } + + free(buffer); + ExitThread(0); + return nullptr; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_sndio_open(IAudinDevice* device, AudinReceive receive, void* user_data) +{ + AudinSndioDevice* sndio = (AudinSndioDevice*)device; + sndio->receive = receive; + sndio->user_data = user_data; + + if (!(sndio->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr))) + { + WLog_ERR(TAG, "CreateEvent failed"); + return ERROR_INTERNAL_ERROR; + } + + if (!(sndio->thread = CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)audin_sndio_thread_func, + sndio, 0, nullptr))) + { + WLog_ERR(TAG, "CreateThread failed"); + (void)CloseHandle(sndio->stopEvent); + sndio->stopEvent = nullptr; + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_sndio_close(IAudinDevice* device) +{ + UINT error; + AudinSndioDevice* sndio = (AudinSndioDevice*)device; + + if (device == nullptr) + return ERROR_INVALID_PARAMETER; + + if (sndio->stopEvent != nullptr) + { + (void)SetEvent(sndio->stopEvent); + + if (WaitForSingleObject(sndio->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + return error; + } + + (void)CloseHandle(sndio->stopEvent); + sndio->stopEvent = nullptr; + (void)CloseHandle(sndio->thread); + sndio->thread = nullptr; + } + + sndio->receive = nullptr; + sndio->user_data = nullptr; + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_sndio_free(IAudinDevice* device) +{ + AudinSndioDevice* sndio = (AudinSndioDevice*)device; + int error; + + if (device == nullptr) + return ERROR_INVALID_PARAMETER; + + if ((error = audin_sndio_close(device))) + { + WLog_ERR(TAG, "audin_sndio_close failed with error code %d", error); + } + + free(sndio); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_sndio_parse_addin_args(AudinSndioDevice* device, ADDIN_ARGV* args) +{ + int status; + DWORD flags; + COMMAND_LINE_ARGUMENT_A* arg; + AudinSndioDevice* sndio = (AudinSndioDevice*)device; + COMMAND_LINE_ARGUMENT_A audin_sndio_args[] = { { nullptr, 0, nullptr, nullptr, nullptr, -1, + nullptr, nullptr } }; + flags = + COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + status = CommandLineParseArgumentsA(args->argc, (const char**)args->argv, audin_sndio_args, + flags, sndio, nullptr, nullptr); + + if (status < 0) + return ERROR_INVALID_PARAMETER; + + arg = audin_sndio_args; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) CommandLineSwitchEnd(arg) + } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE sndio_freerdp_audin_client_subsystem_entry( + PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints)) +{ + ADDIN_ARGV* args; + AudinSndioDevice* sndio; + UINT ret = CHANNEL_RC_OK; + sndio = (AudinSndioDevice*)calloc(1, sizeof(AudinSndioDevice)); + + if (sndio == nullptr) + return CHANNEL_RC_NO_MEMORY; + + sndio->device.Open = audin_sndio_open; + sndio->device.FormatSupported = audin_sndio_format_supported; + sndio->device.SetFormat = audin_sndio_set_format; + sndio->device.Close = audin_sndio_close; + sndio->device.Free = audin_sndio_free; + sndio->rdpcontext = pEntryPoints->rdpcontext; + args = pEntryPoints->args; + + if (args->argc > 1) + { + ret = audin_sndio_parse_addin_args(sndio, args); + + if (ret != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "error parsing arguments"); + goto error; + } + } + + if ((ret = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, (IAudinDevice*)sndio))) + { + WLog_ERR(TAG, "RegisterAudinDevice failed with error %" PRIu32 "", ret); + goto error; + } + + return ret; +error: + audin_sndio_free(&sndio->device); + return ret; +} diff --git a/third_party/FreeRDP/channels/audin/client/winmm/CMakeLists.txt b/third_party/FreeRDP/channels/audin/client/winmm/CMakeLists.txt new file mode 100644 index 0000000..903d849 --- /dev/null +++ b/third_party/FreeRDP/channels/audin/client/winmm/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_client_subsystem("audin" "winmm" "") + +set(${MODULE_PREFIX}_SRCS audin_winmm.c) + +set(${MODULE_PREFIX}_LIBS winpr freerdp winmm.lib) + +include_directories(..) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") diff --git a/third_party/FreeRDP/channels/audin/client/winmm/audin_winmm.c b/third_party/FreeRDP/channels/audin/client/winmm/audin_winmm.c new file mode 100644 index 0000000..0c1cd38 --- /dev/null +++ b/third_party/FreeRDP/channels/audin/client/winmm/audin_winmm.c @@ -0,0 +1,568 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Input Redirection Virtual Channel - WinMM implementation + * + * Copyright 2013 Zhang Zhaolong + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "audin_main.h" + +/* fix missing definitions in mingw */ +#ifndef WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE +#define WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE 0x0010 +#endif + +typedef struct +{ + IAudinDevice iface; + + char* device_name; + AudinReceive receive; + void* user_data; + HANDLE thread; + HANDLE stopEvent; + HWAVEIN hWaveIn; + PWAVEFORMATEX* ppwfx; + PWAVEFORMATEX pwfx_cur; + UINT32 ppwfx_size; + UINT32 cFormats; + UINT32 frames_per_packet; + rdpContext* rdpcontext; + wLog* log; +} AudinWinmmDevice; + +static void CALLBACK waveInProc(HWAVEIN hWaveIn, UINT uMsg, DWORD_PTR dwInstance, + DWORD_PTR dwParam1, DWORD_PTR dwParam2) +{ + AudinWinmmDevice* winmm = (AudinWinmmDevice*)dwInstance; + PWAVEHDR pWaveHdr; + UINT error = CHANNEL_RC_OK; + MMRESULT mmResult; + + switch (uMsg) + { + case WIM_CLOSE: + break; + + case WIM_DATA: + pWaveHdr = (WAVEHDR*)dwParam1; + + if (WHDR_DONE == (WHDR_DONE & pWaveHdr->dwFlags)) + { + if (pWaveHdr->dwBytesRecorded && + !(WaitForSingleObject(winmm->stopEvent, 0) == WAIT_OBJECT_0)) + { + AUDIO_FORMAT format; + format.cbSize = winmm->pwfx_cur->cbSize; + format.nBlockAlign = winmm->pwfx_cur->nBlockAlign; + format.nAvgBytesPerSec = winmm->pwfx_cur->nAvgBytesPerSec; + format.nChannels = winmm->pwfx_cur->nChannels; + format.nSamplesPerSec = winmm->pwfx_cur->nSamplesPerSec; + format.wBitsPerSample = winmm->pwfx_cur->wBitsPerSample; + format.wFormatTag = winmm->pwfx_cur->wFormatTag; + + if ((error = winmm->receive(&format, pWaveHdr->lpData, + pWaveHdr->dwBytesRecorded, winmm->user_data))) + break; + + mmResult = waveInAddBuffer(hWaveIn, pWaveHdr, sizeof(WAVEHDR)); + + if (mmResult != MMSYSERR_NOERROR) + error = ERROR_INTERNAL_ERROR; + } + } + + break; + + case WIM_OPEN: + break; + + default: + break; + } + + if (error && winmm->rdpcontext) + setChannelError(winmm->rdpcontext, error, "waveInProc reported an error"); +} + +static BOOL log_mmresult(AudinWinmmDevice* winmm, const char* what, MMRESULT result) +{ + if (result != MMSYSERR_NOERROR) + { + CHAR buffer[8192] = WINPR_C_ARRAY_INIT; + CHAR msg[8192] = WINPR_C_ARRAY_INIT; + CHAR cmsg[8192] = WINPR_C_ARRAY_INIT; + waveInGetErrorTextA(result, buffer, sizeof(buffer)); + + _snprintf(msg, sizeof(msg) - 1, "%s failed. %" PRIu32 " [%s]", what, result, buffer); + _snprintf(cmsg, sizeof(cmsg) - 1, "audin_winmm_thread_func reported an error '%s'", msg); + WLog_Print(winmm->log, WLOG_DEBUG, "%s", msg); + if (winmm->rdpcontext) + setChannelError(winmm->rdpcontext, ERROR_INTERNAL_ERROR, cmsg); + return FALSE; + } + return TRUE; +} + +static BOOL test_format_supported(const PWAVEFORMATEX pwfx) +{ + MMRESULT rc; + WAVEINCAPSA caps = WINPR_C_ARRAY_INIT; + + rc = waveInGetDevCapsA(WAVE_MAPPER, &caps, sizeof(caps)); + if (rc != MMSYSERR_NOERROR) + return FALSE; + + switch (pwfx->nChannels) + { + case 1: + if ((caps.dwFormats & + (WAVE_FORMAT_1M08 | WAVE_FORMAT_2M08 | WAVE_FORMAT_4M08 | WAVE_FORMAT_96M08 | + WAVE_FORMAT_1M16 | WAVE_FORMAT_2M16 | WAVE_FORMAT_4M16 | WAVE_FORMAT_96M16)) == 0) + return FALSE; + break; + case 2: + if ((caps.dwFormats & + (WAVE_FORMAT_1S08 | WAVE_FORMAT_2S08 | WAVE_FORMAT_4S08 | WAVE_FORMAT_96S08 | + WAVE_FORMAT_1S16 | WAVE_FORMAT_2S16 | WAVE_FORMAT_4S16 | WAVE_FORMAT_96S16)) == 0) + return FALSE; + break; + default: + return FALSE; + } + + rc = waveInOpen(nullptr, WAVE_MAPPER, pwfx, 0, 0, + WAVE_FORMAT_QUERY | WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE); + return (rc == MMSYSERR_NOERROR); +} + +static DWORD WINAPI audin_winmm_thread_func(LPVOID arg) +{ + AudinWinmmDevice* winmm = (AudinWinmmDevice*)arg; + char* buffer = nullptr; + int size = 0; + WAVEHDR waveHdr[4] = WINPR_C_ARRAY_INIT; + DWORD status = 0; + MMRESULT rc = 0; + + if (!winmm->hWaveIn) + { + rc = waveInOpen(&winmm->hWaveIn, WAVE_MAPPER, winmm->pwfx_cur, (DWORD_PTR)waveInProc, + (DWORD_PTR)winmm, + CALLBACK_FUNCTION | WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE); + if (!log_mmresult(winmm, "waveInOpen", rc)) + return ERROR_INTERNAL_ERROR; + } + + size = + (winmm->pwfx_cur->wBitsPerSample * winmm->pwfx_cur->nChannels * winmm->frames_per_packet + + 7) / + 8; + + for (int i = 0; i < 4; i++) + { + buffer = (char*)malloc(size); + + if (!buffer) + return CHANNEL_RC_NO_MEMORY; + + waveHdr[i].dwBufferLength = size; + waveHdr[i].dwFlags = 0; + waveHdr[i].lpData = buffer; + rc = waveInPrepareHeader(winmm->hWaveIn, &waveHdr[i], sizeof(waveHdr[i])); + + if (!log_mmresult(winmm, "waveInPrepareHeader", rc)) + { + } + + rc = waveInAddBuffer(winmm->hWaveIn, &waveHdr[i], sizeof(waveHdr[i])); + + if (!log_mmresult(winmm, "waveInAddBuffer", rc)) + { + } + } + + rc = waveInStart(winmm->hWaveIn); + + if (!log_mmresult(winmm, "waveInStart", rc)) + { + } + + status = WaitForSingleObject(winmm->stopEvent, INFINITE); + + if (status == WAIT_FAILED) + { + WLog_Print(winmm->log, WLOG_DEBUG, "WaitForSingleObject failed."); + + if (winmm->rdpcontext) + setChannelError(winmm->rdpcontext, ERROR_INTERNAL_ERROR, + "audin_winmm_thread_func reported an error"); + } + + rc = waveInReset(winmm->hWaveIn); + + if (!log_mmresult(winmm, "waveInReset", rc)) + { + } + + for (int i = 0; i < 4; i++) + { + rc = waveInUnprepareHeader(winmm->hWaveIn, &waveHdr[i], sizeof(waveHdr[i])); + + if (!log_mmresult(winmm, "waveInUnprepareHeader", rc)) + { + } + + free(waveHdr[i].lpData); + } + + rc = waveInClose(winmm->hWaveIn); + + if (!log_mmresult(winmm, "waveInClose", rc)) + { + } + + winmm->hWaveIn = nullptr; + return 0; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_winmm_free(IAudinDevice* device) +{ + AudinWinmmDevice* winmm = (AudinWinmmDevice*)device; + + if (!winmm) + return ERROR_INVALID_PARAMETER; + + for (UINT32 i = 0; i < winmm->cFormats; i++) + { + free(winmm->ppwfx[i]); + } + + free(winmm->ppwfx); + free(winmm->device_name); + free(winmm); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_winmm_close(IAudinDevice* device) +{ + DWORD status; + UINT error = CHANNEL_RC_OK; + AudinWinmmDevice* winmm = (AudinWinmmDevice*)device; + + if (!winmm) + return ERROR_INVALID_PARAMETER; + + (void)SetEvent(winmm->stopEvent); + status = WaitForSingleObject(winmm->thread, INFINITE); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_Print(winmm->log, WLOG_ERROR, "WaitForSingleObject failed with error %" PRIu32 "!", + error); + return error; + } + + (void)CloseHandle(winmm->thread); + (void)CloseHandle(winmm->stopEvent); + winmm->thread = nullptr; + winmm->stopEvent = nullptr; + winmm->receive = nullptr; + winmm->user_data = nullptr; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_winmm_set_format(IAudinDevice* device, const AUDIO_FORMAT* format, + UINT32 FramesPerPacket) +{ + AudinWinmmDevice* winmm = (AudinWinmmDevice*)device; + + if (!winmm || !format) + return ERROR_INVALID_PARAMETER; + + winmm->frames_per_packet = FramesPerPacket; + + for (UINT32 i = 0; i < winmm->cFormats; i++) + { + const PWAVEFORMATEX ppwfx = winmm->ppwfx[i]; + if ((ppwfx->wFormatTag == format->wFormatTag) && (ppwfx->nChannels == format->nChannels) && + (ppwfx->wBitsPerSample == format->wBitsPerSample) && + (ppwfx->nSamplesPerSec == format->nSamplesPerSec)) + { + /* BUG: Many devices report to support stereo recording but fail here. + * Ensure we always use mono. */ + if (ppwfx->nChannels > 1) + { + ppwfx->nChannels = 1; + } + + if (ppwfx->nBlockAlign != 2) + { + ppwfx->nBlockAlign = 2; + ppwfx->nAvgBytesPerSec = ppwfx->nSamplesPerSec * ppwfx->nBlockAlign; + } + + if (!test_format_supported(ppwfx)) + return ERROR_INVALID_PARAMETER; + winmm->pwfx_cur = ppwfx; + return CHANNEL_RC_OK; + } + } + + return ERROR_INVALID_PARAMETER; +} + +static BOOL audin_winmm_format_supported(IAudinDevice* device, const AUDIO_FORMAT* format) +{ + AudinWinmmDevice* winmm = (AudinWinmmDevice*)device; + PWAVEFORMATEX pwfx; + BYTE* data; + + if (!winmm || !format) + return FALSE; + + if (format->wFormatTag != WAVE_FORMAT_PCM) + return FALSE; + + if (format->nChannels != 1) + return FALSE; + + pwfx = (PWAVEFORMATEX)malloc(sizeof(WAVEFORMATEX) + format->cbSize); + + if (!pwfx) + return FALSE; + + pwfx->cbSize = format->cbSize; + pwfx->wFormatTag = format->wFormatTag; + pwfx->nChannels = format->nChannels; + pwfx->nSamplesPerSec = format->nSamplesPerSec; + pwfx->nBlockAlign = format->nBlockAlign; + pwfx->wBitsPerSample = format->wBitsPerSample; + data = (BYTE*)pwfx + sizeof(WAVEFORMATEX); + memcpy(data, format->data, format->cbSize); + + pwfx->nAvgBytesPerSec = pwfx->nSamplesPerSec * pwfx->nBlockAlign; + + if (!test_format_supported(pwfx)) + goto fail; + + if (winmm->cFormats >= winmm->ppwfx_size) + { + PWAVEFORMATEX* tmp_ppwfx; + tmp_ppwfx = realloc(winmm->ppwfx, sizeof(PWAVEFORMATEX) * winmm->ppwfx_size * 2); + + if (!tmp_ppwfx) + goto fail; + + winmm->ppwfx_size *= 2; + winmm->ppwfx = tmp_ppwfx; + } + + winmm->ppwfx[winmm->cFormats++] = pwfx; + return TRUE; + +fail: + free(pwfx); + return FALSE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_winmm_open(IAudinDevice* device, AudinReceive receive, void* user_data) +{ + AudinWinmmDevice* winmm = (AudinWinmmDevice*)device; + + if (!winmm || !receive || !user_data) + return ERROR_INVALID_PARAMETER; + + winmm->receive = receive; + winmm->user_data = user_data; + + if (!(winmm->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr))) + { + WLog_Print(winmm->log, WLOG_ERROR, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (!(winmm->thread = CreateThread(nullptr, 0, audin_winmm_thread_func, winmm, 0, nullptr))) + { + WLog_Print(winmm->log, WLOG_ERROR, "CreateThread failed!"); + (void)CloseHandle(winmm->stopEvent); + winmm->stopEvent = nullptr; + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT audin_winmm_parse_addin_args(AudinWinmmDevice* device, const ADDIN_ARGV* args) +{ + int status; + DWORD flags; + const COMMAND_LINE_ARGUMENT_A* arg; + AudinWinmmDevice* winmm = (AudinWinmmDevice*)device; + COMMAND_LINE_ARGUMENT_A audin_winmm_args[] = { + { "dev", COMMAND_LINE_VALUE_REQUIRED, "", nullptr, nullptr, -1, nullptr, + "audio device name" }, + { nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr } + }; + + flags = + COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + status = CommandLineParseArgumentsA(args->argc, args->argv, audin_winmm_args, flags, winmm, + nullptr, nullptr); + arg = audin_winmm_args; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev") + { + winmm->device_name = _strdup(arg->Value); + + if (!winmm->device_name) + { + WLog_Print(winmm->log, WLOG_ERROR, "_strdup failed!"); + return CHANNEL_RC_NO_MEMORY; + } + } + CommandLineSwitchEnd(arg) + } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE winmm_freerdp_audin_client_subsystem_entry( + PFREERDP_AUDIN_DEVICE_ENTRY_POINTS pEntryPoints)) +{ + const ADDIN_ARGV* args; + AudinWinmmDevice* winmm; + UINT error; + + if (waveInGetNumDevs() == 0) + { + WLog_Print(WLog_Get(TAG), WLOG_ERROR, "No microphone available!"); + return ERROR_DEVICE_NOT_AVAILABLE; + } + + winmm = (AudinWinmmDevice*)calloc(1, sizeof(AudinWinmmDevice)); + + if (!winmm) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + winmm->log = WLog_Get(TAG); + winmm->iface.Open = audin_winmm_open; + winmm->iface.FormatSupported = audin_winmm_format_supported; + winmm->iface.SetFormat = audin_winmm_set_format; + winmm->iface.Close = audin_winmm_close; + winmm->iface.Free = audin_winmm_free; + winmm->rdpcontext = pEntryPoints->rdpcontext; + args = pEntryPoints->args; + + if ((error = audin_winmm_parse_addin_args(winmm, args))) + { + WLog_Print(winmm->log, WLOG_ERROR, + "audin_winmm_parse_addin_args failed with error %" PRIu32 "!", error); + goto error_out; + } + + if (!winmm->device_name) + { + winmm->device_name = _strdup("default"); + + if (!winmm->device_name) + { + WLog_Print(winmm->log, WLOG_ERROR, "_strdup failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + } + + winmm->ppwfx_size = 10; + winmm->ppwfx = calloc(winmm->ppwfx_size, sizeof(PWAVEFORMATEX)); + + if (!winmm->ppwfx) + { + WLog_Print(winmm->log, WLOG_ERROR, "malloc failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + + if ((error = pEntryPoints->pRegisterAudinDevice(pEntryPoints->plugin, &winmm->iface))) + { + WLog_Print(winmm->log, WLOG_ERROR, "RegisterAudinDevice failed with error %" PRIu32 "!", + error); + goto error_out; + } + + return CHANNEL_RC_OK; +error_out: + free(winmm->ppwfx); + free(winmm->device_name); + free(winmm); + return error; +} diff --git a/third_party/FreeRDP/channels/audin/server/CMakeLists.txt b/third_party/FreeRDP/channels/audin/server/CMakeLists.txt new file mode 100644 index 0000000..d081e23 --- /dev/null +++ b/third_party/FreeRDP/channels/audin/server/CMakeLists.txt @@ -0,0 +1,23 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_server("audin") + +set(${MODULE_PREFIX}_SRCS audin.c) + +set(${MODULE_PREFIX}_LIBS freerdp) +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry") diff --git a/third_party/FreeRDP/channels/audin/server/audin.c b/third_party/FreeRDP/channels/audin/server/audin.c new file mode 100644 index 0000000..c625911 --- /dev/null +++ b/third_party/FreeRDP/channels/audin/server/audin.c @@ -0,0 +1,927 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Server Audio Input Virtual Channel + * + * Copyright 2012 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2022 Pascal Nowack + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define AUDIN_TAG CHANNELS_TAG("audin.server") + +#define SNDIN_HEADER_SIZE 1 + +typedef enum +{ + MSG_SNDIN_VERSION = 0x01, + MSG_SNDIN_FORMATS = 0x02, + MSG_SNDIN_OPEN = 0x03, + MSG_SNDIN_OPEN_REPLY = 0x04, + MSG_SNDIN_DATA_INCOMING = 0x05, + MSG_SNDIN_DATA = 0x06, + MSG_SNDIN_FORMATCHANGE = 0x07, +} MSG_SNDIN; + +typedef struct +{ + audin_server_context context; + + HANDLE stopEvent; + + HANDLE thread; + void* audin_channel; + + DWORD SessionId; + + AUDIO_FORMAT* audin_server_formats; + UINT32 audin_n_server_formats; + AUDIO_FORMAT* audin_negotiated_format; + UINT32 audin_client_format_idx; + wLog* log; +} audin_server; + +static UINT audin_server_recv_version(audin_server_context* context, wStream* s, + const SNDIN_PDU* header) +{ + audin_server* audin = (audin_server*)context; + SNDIN_VERSION pdu = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + if (!Stream_CheckAndLogRequiredLengthWLog(audin->log, s, 4)) + return ERROR_NO_DATA; + + { + const UINT32 version = Stream_Get_UINT32(s); + switch (version) + { + case SNDIN_VERSION_Version_1: + pdu.Version = SNDIN_VERSION_Version_1; + break; + case SNDIN_VERSION_Version_2: + pdu.Version = SNDIN_VERSION_Version_2; + break; + default: + pdu.Version = SNDIN_VERSION_Version_2; + WLog_Print(audin->log, WLOG_WARN, + "Received unsupported channel version %" PRIu32 + ", using highest supported version %u", + version, pdu.Version); + break; + } + } + + IFCALLRET(context->ReceiveVersion, error, context, &pdu); + if (error) + WLog_Print(audin->log, WLOG_ERROR, "context->ReceiveVersion failed with error %" PRIu32 "", + error); + + return error; +} + +static UINT audin_server_recv_formats(audin_server_context* context, wStream* s, + const SNDIN_PDU* header) +{ + audin_server* audin = (audin_server*)context; + SNDIN_FORMATS pdu = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + /* Implementations MUST, at a minimum, support WAVE_FORMAT_PCM (0x0001) */ + if (!Stream_CheckAndLogRequiredLengthWLog(audin->log, s, 4 + 4 + 18)) + return ERROR_NO_DATA; + + Stream_Read_UINT32(s, pdu.NumFormats); + Stream_Read_UINT32(s, pdu.cbSizeFormatsPacket); + + if (pdu.NumFormats == 0) + { + WLog_Print(audin->log, WLOG_ERROR, "Sound Formats PDU contains no formats"); + return ERROR_INVALID_DATA; + } + + pdu.SoundFormats = audio_formats_new(pdu.NumFormats); + if (!pdu.SoundFormats) + { + WLog_Print(audin->log, WLOG_ERROR, "Failed to allocate %u SoundFormats", pdu.NumFormats); + return ERROR_NOT_ENOUGH_MEMORY; + } + + for (UINT32 i = 0; i < pdu.NumFormats; ++i) + { + AUDIO_FORMAT* format = &pdu.SoundFormats[i]; + + if (!audio_format_read(s, format)) + goto fail; + + audio_format_print(audin->log, WLOG_DEBUG, format); + } + + if (pdu.cbSizeFormatsPacket != Stream_GetPosition(s)) + { + WLog_Print(audin->log, WLOG_WARN, + "cbSizeFormatsPacket is invalid! Expected: %u Got: %zu. Fixing size", + pdu.cbSizeFormatsPacket, Stream_GetPosition(s)); + const size_t pos = Stream_GetPosition(s); + if (pos > UINT32_MAX) + { + WLog_Print(audin->log, WLOG_ERROR, "Stream too long, %" PRIuz " exceeds UINT32_MAX", + pos); + error = ERROR_INVALID_PARAMETER; + goto fail; + } + pdu.cbSizeFormatsPacket = (UINT32)pos; + } + + pdu.ExtraDataSize = Stream_GetRemainingLength(s); + + IFCALLRET(context->ReceiveFormats, error, context, &pdu); + if (error) + WLog_Print(audin->log, WLOG_ERROR, "context->ReceiveFormats failed with error %" PRIu32 "", + error); + +fail: + audio_formats_free(pdu.SoundFormats, pdu.NumFormats); + + return error; +} + +static UINT audin_server_recv_open_reply(audin_server_context* context, wStream* s, + const SNDIN_PDU* header) +{ + audin_server* audin = (audin_server*)context; + SNDIN_OPEN_REPLY pdu = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + if (!Stream_CheckAndLogRequiredLengthWLog(audin->log, s, 4)) + return ERROR_NO_DATA; + + Stream_Read_UINT32(s, pdu.Result); + + IFCALLRET(context->OpenReply, error, context, &pdu); + if (error) + WLog_Print(audin->log, WLOG_ERROR, "context->OpenReply failed with error %" PRIu32 "", + error); + + return error; +} + +static UINT audin_server_recv_data_incoming(audin_server_context* context, + WINPR_ATTR_UNUSED wStream* s, const SNDIN_PDU* header) +{ + audin_server* audin = (audin_server*)context; + SNDIN_DATA_INCOMING pdu = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + IFCALLRET(context->IncomingData, error, context, &pdu); + if (error) + WLog_Print(audin->log, WLOG_ERROR, "context->IncomingData failed with error %" PRIu32 "", + error); + + return error; +} + +static UINT audin_server_recv_data(audin_server_context* context, wStream* s, + const SNDIN_PDU* header) +{ + audin_server* audin = (audin_server*)context; + SNDIN_DATA pdu = WINPR_C_ARRAY_INIT; + wStream dataBuffer = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + pdu.Data = Stream_StaticInit(&dataBuffer, Stream_Pointer(s), Stream_GetRemainingLength(s)); + + IFCALLRET(context->Data, error, context, &pdu); + if (error) + WLog_Print(audin->log, WLOG_ERROR, "context->Data failed with error %" PRIu32 "", error); + + return error; +} + +static UINT audin_server_recv_format_change(audin_server_context* context, wStream* s, + const SNDIN_PDU* header) +{ + audin_server* audin = (audin_server*)context; + SNDIN_FORMATCHANGE pdu = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + if (!Stream_CheckAndLogRequiredLengthWLog(audin->log, s, 4)) + return ERROR_NO_DATA; + + Stream_Read_UINT32(s, pdu.NewFormat); + + IFCALLRET(context->ReceiveFormatChange, error, context, &pdu); + if (error) + WLog_Print(audin->log, WLOG_ERROR, + "context->ReceiveFormatChange failed with error %" PRIu32 "", error); + + return error; +} + +static DWORD WINAPI audin_server_thread_func(LPVOID arg) +{ + wStream* s = nullptr; + void* buffer = nullptr; + DWORD nCount = 0; + HANDLE events[8] = WINPR_C_ARRAY_INIT; + BOOL ready = FALSE; + HANDLE ChannelEvent = nullptr; + DWORD BytesReturned = 0; + audin_server* audin = (audin_server*)arg; + UINT error = CHANNEL_RC_OK; + DWORD status = ERROR_INTERNAL_ERROR; + + WINPR_ASSERT(audin); + + if (WTSVirtualChannelQuery(audin->audin_channel, WTSVirtualEventHandle, &buffer, + &BytesReturned) == TRUE) + { + if (BytesReturned == sizeof(HANDLE)) + ChannelEvent = *(HANDLE*)buffer; + + WTSFreeMemory(buffer); + } + else + { + WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelQuery failed"); + error = ERROR_INTERNAL_ERROR; + goto out; + } + + nCount = 0; + events[nCount++] = audin->stopEvent; + events[nCount++] = ChannelEvent; + + /* Wait for the client to confirm that the Audio Input dynamic channel is ready */ + + while (1) + { + status = WaitForMultipleObjects(nCount, events, FALSE, 100); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_Print(audin->log, WLOG_ERROR, + "WaitForMultipleObjects failed with error %" PRIu32 "", error); + goto out; + } + if (status == WAIT_OBJECT_0) + goto out; + + if (WTSVirtualChannelQuery(audin->audin_channel, WTSVirtualChannelReady, &buffer, + &BytesReturned) == FALSE) + { + WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelQuery failed"); + error = ERROR_INTERNAL_ERROR; + goto out; + } + + ready = *((BOOL*)buffer); + WTSFreeMemory(buffer); + + if (ready) + break; + } + + s = Stream_New(nullptr, 4096); + + if (!s) + { + WLog_Print(audin->log, WLOG_ERROR, "Stream_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + if (ready) + { + SNDIN_VERSION version = WINPR_C_ARRAY_INIT; + + version.Version = audin->context.serverVersion; + + if ((error = audin->context.SendVersion(&audin->context, &version))) + { + WLog_Print(audin->log, WLOG_ERROR, "SendVersion failed with error %" PRIu32 "!", error); + goto out_capacity; + } + } + + while (ready) + { + SNDIN_PDU header = WINPR_C_ARRAY_INIT; + + if ((status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE)) == WAIT_OBJECT_0) + break; + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_Print(audin->log, WLOG_ERROR, + "WaitForMultipleObjects failed with error %" PRIu32 "", error); + break; + } + if (status == WAIT_OBJECT_0) + break; + + Stream_ResetPosition(s); + + if (!WTSVirtualChannelRead(audin->audin_channel, 0, nullptr, 0, &BytesReturned)) + { + WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelRead failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (BytesReturned < 1) + continue; + + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + break; + + WINPR_ASSERT(Stream_Capacity(s) <= UINT32_MAX); + if (WTSVirtualChannelRead(audin->audin_channel, 0, Stream_BufferAs(s, char), + (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE) + { + WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelRead failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + Stream_SetLength(s, BytesReturned); + if (!Stream_CheckAndLogRequiredLengthWLog(audin->log, s, SNDIN_HEADER_SIZE)) + { + error = ERROR_INTERNAL_ERROR; + break; + } + + Stream_Read_UINT8(s, header.MessageId); + + switch (header.MessageId) + { + case MSG_SNDIN_VERSION: + error = audin_server_recv_version(&audin->context, s, &header); + break; + case MSG_SNDIN_FORMATS: + error = audin_server_recv_formats(&audin->context, s, &header); + break; + case MSG_SNDIN_OPEN_REPLY: + error = audin_server_recv_open_reply(&audin->context, s, &header); + break; + case MSG_SNDIN_DATA_INCOMING: + error = audin_server_recv_data_incoming(&audin->context, s, &header); + break; + case MSG_SNDIN_DATA: + error = audin_server_recv_data(&audin->context, s, &header); + break; + case MSG_SNDIN_FORMATCHANGE: + error = audin_server_recv_format_change(&audin->context, s, &header); + break; + default: + WLog_Print(audin->log, WLOG_ERROR, + "audin_server_thread_func: unknown or invalid MessageId %" PRIu8 "", + header.MessageId); + error = ERROR_INVALID_DATA; + break; + } + if (error) + break; + } + +out_capacity: + Stream_Free(s, TRUE); +out: + (void)WTSVirtualChannelClose(audin->audin_channel); + audin->audin_channel = nullptr; + + if (error && audin->context.rdpcontext) + setChannelError(audin->context.rdpcontext, error, + "audin_server_thread_func reported an error"); + + ExitThread(error); + return error; +} + +static BOOL audin_server_open(audin_server_context* context) +{ + audin_server* audin = (audin_server*)context; + + WINPR_ASSERT(audin); + if (!audin->thread) + { + PULONG pSessionId = nullptr; + DWORD BytesReturned = 0; + audin->SessionId = WTS_CURRENT_SESSION; + UINT32 channelId = 0; + BOOL status = TRUE; + + if (WTSQuerySessionInformationA(context->vcm, WTS_CURRENT_SESSION, WTSSessionId, + (LPSTR*)&pSessionId, &BytesReturned)) + { + audin->SessionId = (DWORD)*pSessionId; + WTSFreeMemory(pSessionId); + } + + audin->audin_channel = WTSVirtualChannelOpenEx(audin->SessionId, AUDIN_DVC_CHANNEL_NAME, + WTS_CHANNEL_OPTION_DYNAMIC); + + if (!audin->audin_channel) + { + WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelOpenEx failed!"); + return FALSE; + } + + channelId = WTSChannelGetIdByHandle(audin->audin_channel); + + IFCALLRET(context->ChannelIdAssigned, status, context, channelId); + if (!status) + { + WLog_Print(audin->log, WLOG_ERROR, "context->ChannelIdAssigned failed!"); + return FALSE; + } + + if (!(audin->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr))) + { + WLog_Print(audin->log, WLOG_ERROR, "CreateEvent failed!"); + return FALSE; + } + + if (!(audin->thread = + CreateThread(nullptr, 0, audin_server_thread_func, (void*)audin, 0, nullptr))) + { + WLog_Print(audin->log, WLOG_ERROR, "CreateThread failed!"); + (void)CloseHandle(audin->stopEvent); + audin->stopEvent = nullptr; + return FALSE; + } + + return TRUE; + } + + WLog_Print(audin->log, WLOG_ERROR, "thread already running!"); + return FALSE; +} + +static BOOL audin_server_is_open(audin_server_context* context) +{ + audin_server* audin = (audin_server*)context; + + WINPR_ASSERT(audin); + return audin->thread != nullptr; +} + +static BOOL audin_server_close(audin_server_context* context) +{ + audin_server* audin = (audin_server*)context; + WINPR_ASSERT(audin); + + if (audin->thread) + { + (void)SetEvent(audin->stopEvent); + + if (WaitForSingleObject(audin->thread, INFINITE) == WAIT_FAILED) + { + WLog_Print(audin->log, WLOG_ERROR, "WaitForSingleObject failed with error %" PRIu32 "", + GetLastError()); + return FALSE; + } + + (void)CloseHandle(audin->thread); + (void)CloseHandle(audin->stopEvent); + audin->thread = nullptr; + audin->stopEvent = nullptr; + } + + if (audin->audin_channel) + { + (void)WTSVirtualChannelClose(audin->audin_channel); + audin->audin_channel = nullptr; + } + + audin->audin_negotiated_format = nullptr; + + return TRUE; +} + +static wStream* audin_server_packet_new(wLog* log, size_t size, BYTE MessageId) +{ + WINPR_ASSERT(log); + + /* Allocate what we need plus header bytes */ + wStream* s = Stream_New(nullptr, size + SNDIN_HEADER_SIZE); + if (!s) + { + WLog_Print(log, WLOG_ERROR, "Stream_New failed!"); + return nullptr; + } + + Stream_Write_UINT8(s, MessageId); + + return s; +} + +static UINT audin_server_packet_send(audin_server_context* context, wStream* s) +{ + audin_server* audin = (audin_server*)context; + UINT error = CHANNEL_RC_OK; + ULONG written = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(s); + + const size_t pos = Stream_GetPosition(s); + WINPR_ASSERT(pos <= UINT32_MAX); + if (!WTSVirtualChannelWrite(audin->audin_channel, Stream_BufferAs(s, char), (UINT32)pos, + &written)) + { + WLog_Print(audin->log, WLOG_ERROR, "WTSVirtualChannelWrite failed!"); + error = ERROR_INTERNAL_ERROR; + goto out; + } + + if (written < Stream_GetPosition(s)) + { + WLog_Print(audin->log, WLOG_WARN, "Unexpected bytes written: %" PRIu32 "/%" PRIuz "", + written, Stream_GetPosition(s)); + } + +out: + Stream_Free(s, TRUE); + return error; +} + +static UINT audin_server_send_version(audin_server_context* context, const SNDIN_VERSION* version) +{ + audin_server* audin = (audin_server*)context; + + WINPR_ASSERT(context); + WINPR_ASSERT(version); + + wStream* s = audin_server_packet_new(audin->log, 4, MSG_SNDIN_VERSION); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + Stream_Write_UINT32(s, version->Version); + + return audin_server_packet_send(context, s); +} + +static UINT audin_server_send_formats(audin_server_context* context, const SNDIN_FORMATS* formats) +{ + audin_server* audin = (audin_server*)context; + + WINPR_ASSERT(audin); + WINPR_ASSERT(formats); + + wStream* s = audin_server_packet_new(audin->log, 4 + 4 + 18, MSG_SNDIN_FORMATS); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + Stream_Write_UINT32(s, formats->NumFormats); + Stream_Write_UINT32(s, formats->cbSizeFormatsPacket); + + for (UINT32 i = 0; i < formats->NumFormats; ++i) + { + AUDIO_FORMAT* format = &formats->SoundFormats[i]; + + if (!audio_format_write(s, format)) + { + WLog_Print(audin->log, WLOG_ERROR, "Failed to write audio format"); + Stream_Free(s, TRUE); + return CHANNEL_RC_NO_MEMORY; + } + } + + return audin_server_packet_send(context, s); +} + +static UINT audin_server_send_open(audin_server_context* context, const SNDIN_OPEN* open) +{ + audin_server* audin = (audin_server*)context; + WINPR_ASSERT(audin); + WINPR_ASSERT(open); + + wStream* s = audin_server_packet_new(audin->log, 4 + 4 + 18 + 22, MSG_SNDIN_OPEN); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + Stream_Write_UINT32(s, open->FramesPerPacket); + Stream_Write_UINT32(s, open->initialFormat); + + Stream_Write_UINT16(s, open->captureFormat.wFormatTag); + Stream_Write_UINT16(s, open->captureFormat.nChannels); + Stream_Write_UINT32(s, open->captureFormat.nSamplesPerSec); + Stream_Write_UINT32(s, open->captureFormat.nAvgBytesPerSec); + Stream_Write_UINT16(s, open->captureFormat.nBlockAlign); + Stream_Write_UINT16(s, open->captureFormat.wBitsPerSample); + + if (open->ExtraFormatData) + { + Stream_Write_UINT16(s, 22); /* cbSize */ + + Stream_Write_UINT16(s, open->ExtraFormatData->Samples.wReserved); + Stream_Write_UINT32(s, open->ExtraFormatData->dwChannelMask); + + Stream_Write_UINT32(s, open->ExtraFormatData->SubFormat.Data1); + Stream_Write_UINT16(s, open->ExtraFormatData->SubFormat.Data2); + Stream_Write_UINT16(s, open->ExtraFormatData->SubFormat.Data3); + Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[0]); + Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[1]); + Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[2]); + Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[3]); + Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[4]); + Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[5]); + Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[6]); + Stream_Write_UINT8(s, open->ExtraFormatData->SubFormat.Data4[7]); + } + else + { + WINPR_ASSERT(open->captureFormat.wFormatTag != WAVE_FORMAT_EXTENSIBLE); + + Stream_Write_UINT16(s, 0); /* cbSize */ + } + + return audin_server_packet_send(context, s); +} + +static UINT audin_server_send_format_change(audin_server_context* context, + const SNDIN_FORMATCHANGE* format_change) +{ + audin_server* audin = (audin_server*)context; + + WINPR_ASSERT(context); + WINPR_ASSERT(format_change); + + wStream* s = audin_server_packet_new(audin->log, 4, MSG_SNDIN_FORMATCHANGE); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + Stream_Write_UINT32(s, format_change->NewFormat); + + return audin_server_packet_send(context, s); +} + +static UINT audin_server_receive_version_default(audin_server_context* audin_ctx, + const SNDIN_VERSION* version) +{ + audin_server* audin = (audin_server*)audin_ctx; + SNDIN_FORMATS formats = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(audin); + WINPR_ASSERT(version); + + if (version->Version == 0) + { + WLog_Print(audin->log, WLOG_ERROR, "Received invalid AUDIO_INPUT version from client"); + return ERROR_INVALID_DATA; + } + + WLog_Print(audin->log, WLOG_DEBUG, "AUDIO_INPUT version of client: %u", version->Version); + + formats.NumFormats = audin->audin_n_server_formats; + formats.SoundFormats = audin->audin_server_formats; + + return audin->context.SendFormats(&audin->context, &formats); +} + +static UINT send_open(audin_server* audin) +{ + SNDIN_OPEN open = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(audin); + + open.FramesPerPacket = 441; + open.initialFormat = audin->audin_client_format_idx; + open.captureFormat.wFormatTag = WAVE_FORMAT_PCM; + open.captureFormat.nChannels = 2; + open.captureFormat.nSamplesPerSec = 44100; + open.captureFormat.nAvgBytesPerSec = 44100 * 2 * 2; + open.captureFormat.nBlockAlign = 4; + open.captureFormat.wBitsPerSample = 16; + + WINPR_ASSERT(audin->context.SendOpen); + return audin->context.SendOpen(&audin->context, &open); +} + +static UINT audin_server_receive_formats_default(audin_server_context* context, + const SNDIN_FORMATS* formats) +{ + audin_server* audin = (audin_server*)context; + WINPR_ASSERT(audin); + WINPR_ASSERT(formats); + + if (audin->audin_negotiated_format) + { + WLog_Print(audin->log, WLOG_ERROR, + "Received client formats, but negotiation was already done"); + return ERROR_INVALID_DATA; + } + + for (UINT32 i = 0; i < audin->audin_n_server_formats; ++i) + { + for (UINT32 j = 0; j < formats->NumFormats; ++j) + { + if (audio_format_compatible(&audin->audin_server_formats[i], &formats->SoundFormats[j])) + { + audin->audin_negotiated_format = &audin->audin_server_formats[i]; + audin->audin_client_format_idx = i; + return send_open(audin); + } + } + } + + WLog_Print(audin->log, WLOG_ERROR, "Could not agree on a audio format with the server"); + + return ERROR_INVALID_DATA; +} + +static UINT audin_server_receive_format_change_default(audin_server_context* context, + const SNDIN_FORMATCHANGE* format_change) +{ + audin_server* audin = (audin_server*)context; + + WINPR_ASSERT(audin); + WINPR_ASSERT(format_change); + + if (format_change->NewFormat != audin->audin_client_format_idx) + { + WLog_Print(audin->log, WLOG_ERROR, + "NewFormat in FormatChange differs from requested format"); + return ERROR_INVALID_DATA; + } + + WLog_Print(audin->log, WLOG_DEBUG, "Received Format Change PDU: %u", format_change->NewFormat); + + return CHANNEL_RC_OK; +} + +static UINT +audin_server_incoming_data_default(audin_server_context* context, + WINPR_ATTR_UNUSED const SNDIN_DATA_INCOMING* data_incoming) +{ + audin_server* audin = (audin_server*)context; + WINPR_ASSERT(audin); + WINPR_ASSERT(data_incoming); + + /* TODO: Implement bandwidth measure of clients uplink */ + WLog_Print(audin->log, WLOG_DEBUG, "Received Incoming Data PDU"); + return CHANNEL_RC_OK; +} + +static UINT audin_server_open_reply_default(audin_server_context* context, + const SNDIN_OPEN_REPLY* open_reply) +{ + audin_server* audin = (audin_server*)context; + WINPR_ASSERT(audin); + WINPR_ASSERT(open_reply); + + /* TODO: Implement failure handling */ + WLog_Print(audin->log, WLOG_DEBUG, "Open Reply PDU: Result: %" PRIu32, open_reply->Result); + return CHANNEL_RC_OK; +} + +audin_server_context* audin_server_context_new(HANDLE vcm) +{ + audin_server* audin = (audin_server*)calloc(1, sizeof(audin_server)); + + if (!audin) + { + WLog_ERR(AUDIN_TAG, "calloc failed!"); + return nullptr; + } + audin->log = WLog_Get(AUDIN_TAG); + audin->context.vcm = vcm; + audin->context.Open = audin_server_open; + audin->context.IsOpen = audin_server_is_open; + audin->context.Close = audin_server_close; + + audin->context.SendVersion = audin_server_send_version; + audin->context.SendFormats = audin_server_send_formats; + audin->context.SendOpen = audin_server_send_open; + audin->context.SendFormatChange = audin_server_send_format_change; + + /* Default values */ + audin->context.serverVersion = SNDIN_VERSION_Version_2; + audin->context.ReceiveVersion = audin_server_receive_version_default; + audin->context.ReceiveFormats = audin_server_receive_formats_default; + audin->context.ReceiveFormatChange = audin_server_receive_format_change_default; + audin->context.IncomingData = audin_server_incoming_data_default; + audin->context.OpenReply = audin_server_open_reply_default; + + return &audin->context; +} + +void audin_server_context_free(audin_server_context* context) +{ + audin_server* audin = (audin_server*)context; + + if (!audin) + return; + + audin_server_close(context); + audio_formats_free(audin->audin_server_formats, audin->audin_n_server_formats); + audin->audin_server_formats = nullptr; + free(audin); +} + +BOOL audin_server_set_formats(audin_server_context* context, SSIZE_T count, + const AUDIO_FORMAT* formats) +{ + audin_server* audin = (audin_server*)context; + WINPR_ASSERT(audin); + + audio_formats_free(audin->audin_server_formats, audin->audin_n_server_formats); + audin->audin_n_server_formats = 0; + audin->audin_server_formats = nullptr; + audin->audin_negotiated_format = nullptr; + + if (count < 0) + { + const size_t audin_n_server_formats = + server_audin_get_formats(&audin->audin_server_formats); + WINPR_ASSERT(audin_n_server_formats <= UINT32_MAX); + + audin->audin_n_server_formats = (UINT32)audin_n_server_formats; + } + else + { + const size_t scount = (size_t)count; + AUDIO_FORMAT* audin_server_formats = audio_formats_new(scount); + if (!audin_server_formats) + return count == 0; + + for (SSIZE_T x = 0; x < count; x++) + { + if (!audio_format_copy(&formats[x], &audin_server_formats[x])) + { + audio_formats_free(audin_server_formats, scount); + return FALSE; + } + } + + WINPR_ASSERT(count <= UINT32_MAX); + audin->audin_server_formats = audin_server_formats; + audin->audin_n_server_formats = (UINT32)count; + } + return audin->audin_n_server_formats > 0; +} + +const AUDIO_FORMAT* audin_server_get_negotiated_format(const audin_server_context* context) +{ + const audin_server* audin = (const audin_server*)context; + WINPR_ASSERT(audin); + + return audin->audin_negotiated_format; +} diff --git a/third_party/FreeRDP/channels/client/CMakeLists.txt b/third_party/FreeRDP/channels/client/CMakeLists.txt new file mode 100644 index 0000000..d63b6ec --- /dev/null +++ b/third_party/FreeRDP/channels/client/CMakeLists.txt @@ -0,0 +1,165 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# Copyright 2024 Armin Novak +# 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 "freerdp-channels-client") +set(MODULE_PREFIX "FREERDP_CHANNELS_CLIENT") + +set(${MODULE_PREFIX}_SRCS + ${CMAKE_CURRENT_BINARY_DIR}/tables.c ${CMAKE_CURRENT_SOURCE_DIR}/tables.h ${CMAKE_CURRENT_SOURCE_DIR}/addin.c + ${CMAKE_CURRENT_SOURCE_DIR}/addin.h ${CMAKE_CURRENT_SOURCE_DIR}/generic_dynvc.c +) + +if(CHANNEL_STATIC_CLIENT_ENTRIES) + list(REMOVE_DUPLICATES CHANNEL_STATIC_CLIENT_ENTRIES) +endif() + +foreach(STATIC_ENTRY ${CHANNEL_STATIC_CLIENT_ENTRIES}) + foreach(STATIC_MODULE ${CHANNEL_STATIC_CLIENT_MODULES}) + foreach(ENTRY ${${STATIC_MODULE}_CLIENT_ENTRY}) + if(${ENTRY} STREQUAL ${STATIC_ENTRY}) + set(STATIC_MODULE_NAME ${${STATIC_MODULE}_CLIENT_NAME}) + set(STATIC_MODULE_CHANNEL ${${STATIC_MODULE}_CLIENT_CHANNEL}) + list(APPEND ${MODULE_PREFIX}_LIBS ${STATIC_MODULE_NAME}) + + set(ENTRY_POINT_NAME "${STATIC_MODULE_CHANNEL}_${ENTRY}") + if(${ENTRY} STREQUAL "VirtualChannelEntry") + set(ENTRY_POINT_IMPORT "extern BOOL VCAPITYPE ${ENTRY_POINT_NAME}(PCHANNEL_ENTRY_POINTS);") + elseif(${ENTRY} STREQUAL "VirtualChannelEntryEx") + set(ENTRY_POINT_IMPORT "extern BOOL VCAPITYPE ${ENTRY_POINT_NAME}(PCHANNEL_ENTRY_POINTS_EX,PVOID);") + elseif(${ENTRY} MATCHES "DVCPluginEntry$") + set(ENTRY_POINT_IMPORT "extern UINT VCAPITYPE ${ENTRY_POINT_NAME}(IDRDYNVC_ENTRY_POINTS* pEntryPoints);") + elseif(${ENTRY} MATCHES "DeviceServiceEntry$") + set(ENTRY_POINT_IMPORT + "extern UINT VCAPITYPE ${ENTRY_POINT_NAME}(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints);" + ) + else() + set(ENTRY_POINT_IMPORT "extern UINT VCAPITYPE ${ENTRY_POINT_NAME}(void);") + endif() + + string(APPEND ${STATIC_ENTRY}_IMPORTS "\n${ENTRY_POINT_IMPORT}") + string(APPEND ${STATIC_ENTRY}_TABLE "\n\t{ \"${STATIC_MODULE_CHANNEL}\", ${ENTRY_POINT_NAME} },") + endif() + endforeach() + endforeach() +endforeach() + +string(APPEND CLIENT_STATIC_ENTRY_TABLES_LIST "\nextern const STATIC_ENTRY_TABLE CLIENT_STATIC_ENTRY_TABLES[];\n") +string(APPEND CLIENT_STATIC_ENTRY_TABLES_LIST "const STATIC_ENTRY_TABLE CLIENT_STATIC_ENTRY_TABLES[] =\n{") + +foreach(STATIC_ENTRY ${CHANNEL_STATIC_CLIENT_ENTRIES}) + set(CLIENT_STATIC_ENTRY_IMPORTS "${CLIENT_STATIC_ENTRY_IMPORTS}\n${${STATIC_ENTRY}_IMPORTS}") + if(${STATIC_ENTRY} STREQUAL "VirtualChannelEntry") + set(CLIENT_STATIC_ENTRY_TYPE "STATIC_ENTRY_VC") + set(CLIENT_STATIC_ENTRY_INITIALIZER ".csevc") + elseif(${STATIC_ENTRY} STREQUAL "VirtualChannelEntryEx") + set(CLIENT_STATIC_ENTRY_TYPE "STATIC_ENTRY_VCEX") + set(CLIENT_STATIC_ENTRY_INITIALIZER ".csevcex") + elseif(${STATIC_ENTRY} MATCHES "DVCPluginEntry$") + set(CLIENT_STATIC_ENTRY_TYPE "STATIC_ENTRY_DVC") + set(CLIENT_STATIC_ENTRY_INITIALIZER ".csedvc") + elseif(${STATIC_ENTRY} MATCHES "DeviceServiceEntry$") + set(CLIENT_STATIC_ENTRY_TYPE "STATIC_ENTRY_DSE") + set(CLIENT_STATIC_ENTRY_INITIALIZER ".csedse") + else() + set(CLIENT_STATIC_ENTRY_TYPE "STATIC_ENTRY") + set(CLIENT_STATIC_ENTRY_INITIALIZER ".cse") + endif() + + string(APPEND CLIENT_STATIC_ENTRY_TABLES + "\nextern const ${CLIENT_STATIC_ENTRY_TYPE} CLIENT_${STATIC_ENTRY}_TABLE[];\n" + ) + string(APPEND CLIENT_STATIC_ENTRY_TABLES "const ${CLIENT_STATIC_ENTRY_TYPE} CLIENT_${STATIC_ENTRY}_TABLE[] =\n{") + string(APPEND CLIENT_STATIC_ENTRY_TABLES "\n${${STATIC_ENTRY}_TABLE}") + string(APPEND CLIENT_STATIC_ENTRY_TABLES "\n\t{ nullptr, nullptr }\n};") + string(APPEND CLIENT_STATIC_ENTRY_TABLES_LIST + "\n\t{ \"${STATIC_ENTRY}\", { ${CLIENT_STATIC_ENTRY_INITIALIZER} = CLIENT_${STATIC_ENTRY}_TABLE } }," + ) +endforeach() + +string(APPEND CLIENT_STATIC_ENTRY_TABLES_LIST "\n\t{ nullptr, { .cse = nullptr } }\n};") + +set(CLIENT_STATIC_ADDIN_TABLE "extern const STATIC_ADDIN_TABLE CLIENT_STATIC_ADDIN_TABLE[];\n") +string(APPEND CLIENT_STATIC_ADDIN_TABLE "const STATIC_ADDIN_TABLE CLIENT_STATIC_ADDIN_TABLE[] =\n{") + +foreach(STATIC_MODULE ${CHANNEL_STATIC_CLIENT_MODULES}) + set(STATIC_MODULE_NAME ${${STATIC_MODULE}_CLIENT_NAME}) + set(STATIC_MODULE_CHANNEL ${${STATIC_MODULE}_CLIENT_CHANNEL}) + string(TOUPPER "CLIENT_${STATIC_MODULE_CHANNEL}_SUBSYSTEM_TABLE" SUBSYSTEM_TABLE_NAME) + set(SUBSYSTEM_TABLE + "extern const STATIC_SUBSYSTEM_ENTRY ${SUBSYSTEM_TABLE_NAME}[];\nconst STATIC_SUBSYSTEM_ENTRY ${SUBSYSTEM_TABLE_NAME}[] =\n{" + ) + get_target_property(CHANNEL_SUBSYSTEMS ${STATIC_MODULE_NAME} SUBSYSTEMS) + if(CHANNEL_SUBSYSTEMS MATCHES "NOTFOUND") + set(CHANNEL_SUBSYSTEMS "") + endif() + foreach(STATIC_SUBSYSTEM ${CHANNEL_SUBSYSTEMS}) + if(${STATIC_SUBSYSTEM} MATCHES "^([^-]*)-(.*)") + string(REGEX REPLACE "^([^-]*)-(.*)" "\\1" STATIC_SUBSYSTEM_NAME ${STATIC_SUBSYSTEM}) + string(REGEX REPLACE "^([^-]*)-(.*)" "\\2" STATIC_SUBSYSTEM_TYPE ${STATIC_SUBSYSTEM}) + else() + set(STATIC_SUBSYSTEM_NAME "${STATIC_SUBSYSTEM}") + set(STATIC_SUBSYSTEM_TYPE "") + endif() + string(LENGTH "${STATIC_SUBSYSTEM_TYPE}" _type_length) + set(SUBSYSTEM_MODULE_NAME "${STATIC_MODULE_NAME}-${STATIC_SUBSYSTEM}") + list(APPEND ${MODULE_PREFIX}_LIBS ${SUBSYSTEM_MODULE_NAME}) + if(_type_length GREATER 0) + set(STATIC_SUBSYSTEM_ENTRY + "${STATIC_SUBSYSTEM_NAME}_freerdp_${STATIC_MODULE_CHANNEL}_client_${STATIC_SUBSYSTEM_TYPE}_subsystem_entry" + ) + else() + set(STATIC_SUBSYSTEM_ENTRY "${STATIC_SUBSYSTEM_NAME}_freerdp_${STATIC_MODULE_CHANNEL}_client_subsystem_entry") + endif() + string(APPEND SUBSYSTEM_TABLE + "\n\t{ \"${STATIC_SUBSYSTEM_NAME}\", \"${STATIC_SUBSYSTEM_TYPE}\", ${STATIC_SUBSYSTEM_ENTRY} }," + ) + set(SUBSYSTEM_IMPORT "extern UINT VCAPITYPE ${STATIC_SUBSYSTEM_ENTRY}(void*);") + string(APPEND CLIENT_STATIC_SUBSYSTEM_IMPORTS "\n${SUBSYSTEM_IMPORT}") + endforeach() + string(APPEND SUBSYSTEM_TABLE "\n\t{ nullptr, nullptr, nullptr }\n};") + string(APPEND CLIENT_STATIC_SUBSYSTEM_TABLES "\n${SUBSYSTEM_TABLE}") + + foreach(ENTRY ${${STATIC_MODULE}_CLIENT_ENTRY}) + set(ENTRY_POINT_NAME ${STATIC_MODULE_CHANNEL}_${ENTRY}) + if(${ENTRY} STREQUAL "VirtualChannelEntry") + set(ENTRY_INITIALIZER ".csevc") + elseif(${ENTRY} STREQUAL "VirtualChannelEntryEx") + set(ENTRY_INITIALIZER ".csevcex") + elseif(${ENTRY} MATCHES "DVCPluginEntry$") + set(ENTRY_INITIALIZER ".csedvc") + elseif(${ENTRY} MATCHES "DeviceServiceEntry$") + set(ENTRY_INITIALIZER ".csedse") + else() + set(ENTRY_INITIALIZER ".cse") + endif() + string( + APPEND + CLIENT_STATIC_ADDIN_TABLE + "\n\t{ \"${STATIC_MODULE_CHANNEL}\", \"${ENTRY}\", { ${ENTRY_INITIALIZER} = ${ENTRY_POINT_NAME} }, ${SUBSYSTEM_TABLE_NAME} }," + ) + endforeach() +endforeach() +string(APPEND CLIENT_STATIC_ADDIN_TABLE "\n\t{ nullptr, nullptr, { .cse = nullptr }, nullptr }\n};") + +cleaning_configure_file(${CMAKE_CURRENT_SOURCE_DIR}/tables.c.in ${CMAKE_CURRENT_BINARY_DIR}/tables.c) + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} freerdp winpr) + +set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} PARENT_SCOPE) +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} PARENT_SCOPE) diff --git a/third_party/FreeRDP/channels/client/addin.c b/third_party/FreeRDP/channels/client/addin.c new file mode 100644 index 0000000..6ba1697 --- /dev/null +++ b/third_party/FreeRDP/channels/client/addin.c @@ -0,0 +1,759 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Channel Addins + * + * Copyright 2012 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "tables.h" + +#include "addin.h" + +#include +#define TAG CHANNELS_TAG("addin") + +extern const STATIC_ENTRY_TABLE CLIENT_STATIC_ENTRY_TABLES[]; + +static void* freerdp_channels_find_static_entry_in_table(const STATIC_ENTRY_TABLE* table, + const char* identifier) +{ + size_t index = 0; + const STATIC_ENTRY* pEntry = &table->table.cse[index++]; + + while (pEntry->entry != nullptr) + { + static_entry_fn_t fkt = pEntry->entry; + if (strcmp(pEntry->name, identifier) == 0) + return WINPR_FUNC_PTR_CAST(fkt, void*); + + pEntry = &table->table.cse[index++]; + } + + return nullptr; +} + +void* freerdp_channels_client_find_static_entry(const char* name, const char* identifier) +{ + size_t index = 0; + const STATIC_ENTRY_TABLE* pEntry = &CLIENT_STATIC_ENTRY_TABLES[index++]; + + while (pEntry->table.cse != nullptr) + { + if (strcmp(pEntry->name, name) == 0) + { + return freerdp_channels_find_static_entry_in_table(pEntry, identifier); + } + + pEntry = &CLIENT_STATIC_ENTRY_TABLES[index++]; + } + + return nullptr; +} + +extern const STATIC_ADDIN_TABLE CLIENT_STATIC_ADDIN_TABLE[]; + +static FREERDP_ADDIN** freerdp_channels_list_client_static_addins( + WINPR_ATTR_UNUSED LPCSTR pszName, WINPR_ATTR_UNUSED LPCSTR pszSubsystem, + WINPR_ATTR_UNUSED LPCSTR pszType, WINPR_ATTR_UNUSED DWORD dwFlags) +{ + DWORD nAddins = 0; + FREERDP_ADDIN** ppAddins = nullptr; + const STATIC_SUBSYSTEM_ENTRY* subsystems = nullptr; + nAddins = 0; + ppAddins = (FREERDP_ADDIN**)calloc(128, sizeof(FREERDP_ADDIN*)); + + if (!ppAddins) + { + WLog_ERR(TAG, "calloc failed!"); + return nullptr; + } + + ppAddins[nAddins] = nullptr; + + for (size_t i = 0; CLIENT_STATIC_ADDIN_TABLE[i].name != nullptr; i++) + { + FREERDP_ADDIN* pAddin = (FREERDP_ADDIN*)calloc(1, sizeof(FREERDP_ADDIN)); + const STATIC_ADDIN_TABLE* table = &CLIENT_STATIC_ADDIN_TABLE[i]; + if (!pAddin) + { + WLog_ERR(TAG, "calloc failed!"); + goto error_out; + } + + (void)sprintf_s(pAddin->cName, ARRAYSIZE(pAddin->cName), "%s", table->name); + pAddin->dwFlags = FREERDP_ADDIN_CLIENT; + pAddin->dwFlags |= FREERDP_ADDIN_STATIC; + pAddin->dwFlags |= FREERDP_ADDIN_NAME; + ppAddins[nAddins++] = pAddin; + subsystems = table->table; + + for (size_t j = 0; subsystems[j].name != nullptr; j++) + { + pAddin = (FREERDP_ADDIN*)calloc(1, sizeof(FREERDP_ADDIN)); + + if (!pAddin) + { + WLog_ERR(TAG, "calloc failed!"); + goto error_out; + } + + (void)sprintf_s(pAddin->cName, ARRAYSIZE(pAddin->cName), "%s", table->name); + (void)sprintf_s(pAddin->cSubsystem, ARRAYSIZE(pAddin->cSubsystem), "%s", + subsystems[j].name); + pAddin->dwFlags = FREERDP_ADDIN_CLIENT; + pAddin->dwFlags |= FREERDP_ADDIN_STATIC; + pAddin->dwFlags |= FREERDP_ADDIN_NAME; + pAddin->dwFlags |= FREERDP_ADDIN_SUBSYSTEM; + ppAddins[nAddins++] = pAddin; + } + } + + return ppAddins; +error_out: + freerdp_channels_addin_list_free(ppAddins); + return nullptr; +} + +static HANDLE FindFirstFileUTF8(LPCSTR pszSearchPath, WIN32_FIND_DATAW* FindData) +{ + HANDLE hdl = INVALID_HANDLE_VALUE; + if (!pszSearchPath) + return hdl; + WCHAR* wpath = ConvertUtf8ToWCharAlloc(pszSearchPath, nullptr); + if (!wpath) + return hdl; + + hdl = FindFirstFileW(wpath, FindData); + free(wpath); + + return hdl; +} + +static FREERDP_ADDIN** freerdp_channels_list_dynamic_addins(LPCSTR pszName, LPCSTR pszSubsystem, + LPCSTR pszType, + WINPR_ATTR_UNUSED DWORD dwFlags) +{ + int nDashes = 0; + HANDLE hFind = nullptr; + DWORD nAddins = 0; + LPSTR pszPattern = nullptr; + size_t cchPattern = 0; + LPCSTR pszAddinPath = FREERDP_ADDIN_PATH; + LPCSTR pszInstallPrefix = FREERDP_INSTALL_PREFIX; + LPCSTR pszExtension = nullptr; + LPSTR pszSearchPath = nullptr; + size_t cchSearchPath = 0; + size_t cchAddinPath = 0; + size_t cchInstallPrefix = 0; + FREERDP_ADDIN** ppAddins = nullptr; + WIN32_FIND_DATAW FindData = WINPR_C_ARRAY_INIT; + cchAddinPath = strnlen(pszAddinPath, sizeof(FREERDP_ADDIN_PATH)); + cchInstallPrefix = strnlen(pszInstallPrefix, sizeof(FREERDP_INSTALL_PREFIX)); + pszExtension = PathGetSharedLibraryExtensionA(0); + cchPattern = 128 + strnlen(pszExtension, MAX_PATH) + 2; + pszPattern = (LPSTR)malloc(cchPattern + 1); + + if (!pszPattern) + { + WLog_ERR(TAG, "malloc failed!"); + return nullptr; + } + + if (pszName && pszSubsystem && pszType) + { + (void)sprintf_s(pszPattern, cchPattern, FREERDP_SHARED_LIBRARY_PREFIX "%s-client-%s-%s.%s", + pszName, pszSubsystem, pszType, pszExtension); + } + else if (pszName && pszType) + { + (void)sprintf_s(pszPattern, cchPattern, FREERDP_SHARED_LIBRARY_PREFIX "%s-client-?-%s.%s", + pszName, pszType, pszExtension); + } + else if (pszName) + { + (void)sprintf_s(pszPattern, cchPattern, FREERDP_SHARED_LIBRARY_PREFIX "%s-client*.%s", + pszName, pszExtension); + } + else + { + (void)sprintf_s(pszPattern, cchPattern, FREERDP_SHARED_LIBRARY_PREFIX "?-client*.%s", + pszExtension); + } + + cchPattern = strnlen(pszPattern, cchPattern); + cchSearchPath = cchInstallPrefix + cchAddinPath + cchPattern + 3; + pszSearchPath = (LPSTR)calloc(cchSearchPath + 1, sizeof(char)); + + if (!pszSearchPath) + { + WLog_ERR(TAG, "malloc failed!"); + free(pszPattern); + return nullptr; + } + + CopyMemory(pszSearchPath, pszInstallPrefix, cchInstallPrefix); + pszSearchPath[cchInstallPrefix] = '\0'; + const HRESULT hr1 = NativePathCchAppendA(pszSearchPath, cchSearchPath + 1, pszAddinPath); + const HRESULT hr2 = NativePathCchAppendA(pszSearchPath, cchSearchPath + 1, pszPattern); + free(pszPattern); + + if (FAILED(hr1) || FAILED(hr2)) + { + free(pszSearchPath); + return nullptr; + } + + hFind = FindFirstFileUTF8(pszSearchPath, &FindData); + + free(pszSearchPath); + nAddins = 0; + ppAddins = (FREERDP_ADDIN**)calloc(128, sizeof(FREERDP_ADDIN*)); + + if (!ppAddins) + { + FindClose(hFind); + WLog_ERR(TAG, "calloc failed!"); + return nullptr; + } + + if (hFind == INVALID_HANDLE_VALUE) + return ppAddins; + + do + { + char* cFileName = nullptr; + BOOL used = FALSE; + FREERDP_ADDIN* pAddin = (FREERDP_ADDIN*)calloc(1, sizeof(FREERDP_ADDIN)); + + if (!pAddin) + { + WLog_ERR(TAG, "calloc failed!"); + goto error_out; + } + + cFileName = + ConvertWCharNToUtf8Alloc(FindData.cFileName, ARRAYSIZE(FindData.cFileName), nullptr); + if (!cFileName) + goto skip; + + nDashes = 0; + for (size_t index = 0; cFileName[index]; index++) + nDashes += (cFileName[index] == '-') ? 1 : 0; + + if (nDashes == 1) + { + size_t len = 0; + char* p[2] = WINPR_C_ARRAY_INIT; + /* -client. */ + p[0] = cFileName; + p[1] = strchr(p[0], '-'); + if (!p[1]) + goto skip; + p[1] += 1; + + len = (size_t)(p[1] - p[0]); + if (len < 1) + { + WLog_WARN(TAG, "Skipping file '%s', invalid format", cFileName); + goto skip; + } + strncpy(pAddin->cName, p[0], MIN(ARRAYSIZE(pAddin->cName), len - 1)); + + pAddin->dwFlags = FREERDP_ADDIN_CLIENT; + pAddin->dwFlags |= FREERDP_ADDIN_DYNAMIC; + pAddin->dwFlags |= FREERDP_ADDIN_NAME; + ppAddins[nAddins++] = pAddin; + + used = TRUE; + } + else if (nDashes == 2) + { + size_t len = 0; + char* p[4] = WINPR_C_ARRAY_INIT; + /* -client-. */ + p[0] = cFileName; + p[1] = strchr(p[0], '-'); + if (!p[1]) + goto skip; + p[1] += 1; + p[2] = strchr(p[1], '-'); + if (!p[2]) + goto skip; + p[2] += 1; + p[3] = strchr(p[2], '.'); + if (!p[3]) + goto skip; + p[3] += 1; + + len = (size_t)(p[1] - p[0]); + if (len < 1) + { + WLog_WARN(TAG, "Skipping file '%s', invalid format", cFileName); + goto skip; + } + strncpy(pAddin->cName, p[0], MIN(ARRAYSIZE(pAddin->cName), len - 1)); + + len = (size_t)(p[3] - p[2]); + if (len < 1) + { + WLog_WARN(TAG, "Skipping file '%s', invalid format", cFileName); + goto skip; + } + strncpy(pAddin->cSubsystem, p[2], MIN(ARRAYSIZE(pAddin->cSubsystem), len - 1)); + + pAddin->dwFlags = FREERDP_ADDIN_CLIENT; + pAddin->dwFlags |= FREERDP_ADDIN_DYNAMIC; + pAddin->dwFlags |= FREERDP_ADDIN_NAME; + pAddin->dwFlags |= FREERDP_ADDIN_SUBSYSTEM; + ppAddins[nAddins++] = pAddin; + + used = TRUE; + } + else if (nDashes == 3) + { + size_t len = 0; + char* p[5] = WINPR_C_ARRAY_INIT; + /* -client--. */ + p[0] = cFileName; + p[1] = strchr(p[0], '-'); + if (!p[1]) + goto skip; + p[1] += 1; + p[2] = strchr(p[1], '-'); + if (!p[2]) + goto skip; + p[2] += 1; + p[3] = strchr(p[2], '-'); + if (!p[3]) + goto skip; + p[3] += 1; + p[4] = strchr(p[3], '.'); + if (!p[4]) + goto skip; + p[4] += 1; + + len = (size_t)(p[1] - p[0]); + if (len < 1) + { + WLog_WARN(TAG, "Skipping file '%s', invalid format", cFileName); + goto skip; + } + strncpy(pAddin->cName, p[0], MIN(ARRAYSIZE(pAddin->cName), len - 1)); + + len = (size_t)(p[3] - p[2]); + if (len < 1) + { + WLog_WARN(TAG, "Skipping file '%s', invalid format", cFileName); + goto skip; + } + strncpy(pAddin->cSubsystem, p[2], MIN(ARRAYSIZE(pAddin->cSubsystem), len - 1)); + + len = (size_t)(p[4] - p[3]); + if (len < 1) + { + WLog_WARN(TAG, "Skipping file '%s', invalid format", cFileName); + goto skip; + } + strncpy(pAddin->cType, p[3], MIN(ARRAYSIZE(pAddin->cType), len - 1)); + + pAddin->dwFlags = FREERDP_ADDIN_CLIENT; + pAddin->dwFlags |= FREERDP_ADDIN_DYNAMIC; + pAddin->dwFlags |= FREERDP_ADDIN_NAME; + pAddin->dwFlags |= FREERDP_ADDIN_SUBSYSTEM; + pAddin->dwFlags |= FREERDP_ADDIN_TYPE; + ppAddins[nAddins++] = pAddin; + + used = TRUE; + } + + skip: + free(cFileName); + if (!used) + free(pAddin); + + } while (FindNextFileW(hFind, &FindData)); + + FindClose(hFind); + ppAddins[nAddins] = nullptr; + return ppAddins; +error_out: + FindClose(hFind); + freerdp_channels_addin_list_free(ppAddins); + return nullptr; +} + +FREERDP_ADDIN** freerdp_channels_list_addins(LPCSTR pszName, LPCSTR pszSubsystem, LPCSTR pszType, + DWORD dwFlags) +{ + if (dwFlags & FREERDP_ADDIN_STATIC) + return freerdp_channels_list_client_static_addins(pszName, pszSubsystem, pszType, dwFlags); + else if (dwFlags & FREERDP_ADDIN_DYNAMIC) + return freerdp_channels_list_dynamic_addins(pszName, pszSubsystem, pszType, dwFlags); + + return nullptr; +} + +void freerdp_channels_addin_list_free(FREERDP_ADDIN** ppAddins) +{ + if (!ppAddins) + return; + + for (size_t index = 0; ppAddins[index] != nullptr; index++) + free(ppAddins[index]); + + free((void*)ppAddins); +} + +extern const STATIC_ENTRY CLIENT_VirtualChannelEntryEx_TABLE[]; + +static BOOL freerdp_channels_is_virtual_channel_entry_ex(LPCSTR pszName) +{ + for (size_t i = 0; CLIENT_VirtualChannelEntryEx_TABLE[i].name != nullptr; i++) + { + const STATIC_ENTRY* entry = &CLIENT_VirtualChannelEntryEx_TABLE[i]; + + if (!strncmp(entry->name, pszName, MAX_PATH)) + return TRUE; + } + + return FALSE; +} + +PVIRTUALCHANNELENTRY freerdp_channels_load_static_addin_entry(LPCSTR pszName, LPCSTR pszSubsystem, + LPCSTR pszType, DWORD dwFlags) +{ + const STATIC_ADDIN_TABLE* table = CLIENT_STATIC_ADDIN_TABLE; + const char* type = nullptr; + + if (!pszName) + return nullptr; + + if (dwFlags & FREERDP_ADDIN_CHANNEL_DYNAMIC) + type = "DVCPluginEntry"; + else if (dwFlags & FREERDP_ADDIN_CHANNEL_DEVICE) + type = "DeviceServiceEntry"; + else if (dwFlags & FREERDP_ADDIN_CHANNEL_STATIC) + { + if (dwFlags & FREERDP_ADDIN_CHANNEL_ENTRYEX) + type = "VirtualChannelEntryEx"; + else + type = "VirtualChannelEntry"; + } + + for (; table->name != nullptr; table++) + { + if (strncmp(table->name, pszName, MAX_PATH) == 0) + { + if (type && (strncmp(table->type, type, MAX_PATH) != 0)) + continue; + + if (pszSubsystem != nullptr) + { + const STATIC_SUBSYSTEM_ENTRY* subsystems = table->table; + + for (; subsystems->name != nullptr; subsystems++) + { + /* If the pszSubsystem is an empty string use the default backend. */ + if ((strnlen(pszSubsystem, 1) == + 0) || /* we only want to know if strnlen is > 0 */ + (strncmp(subsystems->name, pszSubsystem, MAX_PATH) == 0)) + { + static_subsystem_entry_fn_t fkt = subsystems->entry; + + if (pszType) + { + if (strncmp(subsystems->type, pszType, MAX_PATH) == 0) + return WINPR_FUNC_PTR_CAST(fkt, PVIRTUALCHANNELENTRY); + } + else + return WINPR_FUNC_PTR_CAST(fkt, PVIRTUALCHANNELENTRY); + } + } + } + else + { + if (dwFlags & FREERDP_ADDIN_CHANNEL_ENTRYEX) + { + if (!freerdp_channels_is_virtual_channel_entry_ex(pszName)) + return nullptr; + } + + return table->entry.csevc; + } + } + } + + return nullptr; +} + +typedef struct +{ + wMessageQueue* queue; + wStream* data_in; + HANDLE thread; + char* channel_name; + rdpContext* ctx; + LPVOID userdata; + MsgHandler msg_handler; +} msg_proc_internals; + +static DWORD WINAPI channel_client_thread_proc(LPVOID userdata) +{ + UINT error = CHANNEL_RC_OK; + wStream* data = nullptr; + wMessage message = WINPR_C_ARRAY_INIT; + msg_proc_internals* internals = userdata; + + WINPR_ASSERT(internals); + + while (1) + { + if (!MessageQueue_Wait(internals->queue)) + { + WLog_ERR(TAG, "MessageQueue_Wait failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + if (!MessageQueue_Peek(internals->queue, &message, TRUE)) + { + WLog_ERR(TAG, "MessageQueue_Peek failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (message.id == WMQ_QUIT) + break; + + if (message.id == 0) + { + data = (wStream*)message.wParam; + + if ((error = internals->msg_handler(internals->userdata, data))) + { + WLog_ERR(TAG, "msg_handler failed with error %" PRIu32 "!", error); + break; + } + } + } + if (error && internals->ctx) + { + char msg[128]; + (void)_snprintf(msg, 127, + "%s_virtual_channel_client_thread reported an" + " error", + internals->channel_name); + setChannelError(internals->ctx, error, msg); + } + ExitThread(error); + return error; +} + +static void free_msg(void* obj) +{ + wMessage* msg = (wMessage*)obj; + + if (msg && (msg->id == 0)) + { + wStream* s = (wStream*)msg->wParam; + Stream_Free(s, TRUE); + } +} + +static void channel_client_handler_free(msg_proc_internals* internals) +{ + if (!internals) + return; + + if (internals->thread) + (void)CloseHandle(internals->thread); + MessageQueue_Free(internals->queue); + Stream_Free(internals->data_in, TRUE); + free(internals->channel_name); + free(internals); +} + +/* Create message queue and thread or not, depending on settings */ +void* channel_client_create_handler(rdpContext* ctx, LPVOID userdata, MsgHandler msg_handler, + const char* channel_name) +{ + msg_proc_internals* internals = calloc(1, sizeof(msg_proc_internals)); + if (!internals) + { + WLog_ERR(TAG, "calloc failed!"); + return nullptr; + } + internals->msg_handler = msg_handler; + internals->userdata = userdata; + if (channel_name) + { + internals->channel_name = _strdup(channel_name); + if (!internals->channel_name) + goto fail; + } + WINPR_ASSERT(ctx); + WINPR_ASSERT(ctx->settings); + internals->ctx = ctx; + if ((freerdp_settings_get_uint32(ctx->settings, FreeRDP_ThreadingFlags) & + THREADING_FLAGS_DISABLE_THREADS) == 0) + { + wObject obj = WINPR_C_ARRAY_INIT; + obj.fnObjectFree = free_msg; + internals->queue = MessageQueue_New(&obj); + if (!internals->queue) + { + WLog_ERR(TAG, "MessageQueue_New failed!"); + goto fail; + } + + if (!(internals->thread = CreateThread(nullptr, 0, channel_client_thread_proc, + (void*)internals, 0, nullptr))) + { + WLog_ERR(TAG, "CreateThread failed!"); + goto fail; + } + } + return internals; + +fail: + channel_client_handler_free(internals); + return nullptr; +} +/* post a message in the queue or directly call the processing handler */ +UINT channel_client_post_message(void* MsgsHandle, LPVOID pData, UINT32 dataLength, + UINT32 totalLength, UINT32 dataFlags) +{ + msg_proc_internals* internals = MsgsHandle; + wStream* data_in = nullptr; + + if (!internals) + { + /* TODO: return some error here */ + return CHANNEL_RC_OK; + } + + if ((dataFlags & CHANNEL_FLAG_SUSPEND) || (dataFlags & CHANNEL_FLAG_RESUME)) + { + return CHANNEL_RC_OK; + } + + if (dataFlags & CHANNEL_FLAG_FIRST) + { + if (internals->data_in) + { + if (!Stream_EnsureCapacity(internals->data_in, totalLength)) + return CHANNEL_RC_NO_MEMORY; + } + else + internals->data_in = Stream_New(nullptr, totalLength); + } + + if (!(data_in = internals->data_in)) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if (!Stream_EnsureRemainingCapacity(data_in, dataLength)) + { + Stream_Free(internals->data_in, TRUE); + internals->data_in = nullptr; + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write(data_in, pData, dataLength); + + if (dataFlags & CHANNEL_FLAG_LAST) + { + if (Stream_Capacity(data_in) != Stream_GetPosition(data_in)) + { + WLog_ERR(TAG, "%s_plugin_process_received: read error", internals->channel_name); + return ERROR_INTERNAL_ERROR; + } + + internals->data_in = nullptr; + Stream_SealLength(data_in); + Stream_ResetPosition(data_in); + + if ((freerdp_settings_get_uint32(internals->ctx->settings, FreeRDP_ThreadingFlags) & + THREADING_FLAGS_DISABLE_THREADS) != 0) + { + UINT error = CHANNEL_RC_OK; + if ((error = internals->msg_handler(internals->userdata, data_in))) + { + WLog_ERR(TAG, + "msg_handler failed with error" + " %" PRIu32 "!", + error); + return ERROR_INTERNAL_ERROR; + } + } + else if (!MessageQueue_Post(internals->queue, nullptr, 0, (void*)data_in, nullptr)) + { + WLog_ERR(TAG, "MessageQueue_Post failed!"); + return ERROR_INTERNAL_ERROR; + } + } + return CHANNEL_RC_OK; +} +/* Tear down queue and thread */ +UINT channel_client_quit_handler(void* MsgsHandle) +{ + msg_proc_internals* internals = MsgsHandle; + UINT rc = 0; + if (!internals) + { + /* TODO: return some error here */ + return CHANNEL_RC_OK; + } + + WINPR_ASSERT(internals->ctx); + WINPR_ASSERT(internals->ctx->settings); + + if ((freerdp_settings_get_uint32(internals->ctx->settings, FreeRDP_ThreadingFlags) & + THREADING_FLAGS_DISABLE_THREADS) == 0) + { + if (internals->queue && internals->thread) + { + if (MessageQueue_PostQuit(internals->queue, 0) && + (WaitForSingleObject(internals->thread, INFINITE) == WAIT_FAILED)) + { + rc = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", rc); + return rc; + } + } + } + + channel_client_handler_free(internals); + return CHANNEL_RC_OK; +} diff --git a/third_party/FreeRDP/channels/client/addin.h b/third_party/FreeRDP/channels/client/addin.h new file mode 100644 index 0000000..ecb6b3d --- /dev/null +++ b/third_party/FreeRDP/channels/client/addin.h @@ -0,0 +1,36 @@ +/* + * FreeRDP: A Remote Desktop Protocol Implementation + * Channel Addins + * + * Copyright 2012 Marc-Andre Moreau + * + * 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 +#include + +#include + +typedef UINT (*MsgHandler)(LPVOID userdata, wStream* data); + +WINPR_ATTR_NODISCARD +FREERDP_API void* channel_client_create_handler(rdpContext* ctx, LPVOID userdata, + MsgHandler handler, const char* channel_name); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT channel_client_post_message(void* MsgsHandle, LPVOID pData, UINT32 dataLength, + UINT32 totalLength, UINT32 dataFlags); + +FREERDP_LOCAL UINT channel_client_quit_handler(void* MsgsHandle); diff --git a/third_party/FreeRDP/channels/client/generic_dynvc.c b/third_party/FreeRDP/channels/client/generic_dynvc.c new file mode 100644 index 0000000..9b2b261 --- /dev/null +++ b/third_party/FreeRDP/channels/client/generic_dynvc.c @@ -0,0 +1,215 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Dynamic channel + * + * Copyright 2022 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#define TAG FREERDP_TAG("genericdynvc") + +static UINT generic_on_new_channel_connection(IWTSListenerCallback* pListenerCallback, + IWTSVirtualChannel* pChannel, + WINPR_ATTR_UNUSED BYTE* Data, + WINPR_ATTR_UNUSED BOOL* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +{ + GENERIC_CHANNEL_CALLBACK* callback = nullptr; + GENERIC_DYNVC_PLUGIN* plugin = nullptr; + GENERIC_LISTENER_CALLBACK* listener_callback = (GENERIC_LISTENER_CALLBACK*)pListenerCallback; + + if (!listener_callback || !listener_callback->plugin) + return ERROR_INTERNAL_ERROR; + + plugin = (GENERIC_DYNVC_PLUGIN*)listener_callback->plugin; + WLog_Print(plugin->log, WLOG_TRACE, "..."); + + callback = (GENERIC_CHANNEL_CALLBACK*)calloc(1, plugin->channelCallbackSize); + if (!callback) + { + WLog_Print(plugin->log, WLOG_ERROR, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + /* implant configured channel callbacks */ + callback->iface = *plugin->channel_callbacks; + + callback->plugin = listener_callback->plugin; + callback->channel_mgr = listener_callback->channel_mgr; + callback->channel = pChannel; + + listener_callback->channel_callback = callback; + listener_callback->channel = pChannel; + + *ppCallback = &callback->iface; + return CHANNEL_RC_OK; +} + +static UINT generic_dynvc_plugin_initialize(IWTSPlugin* pPlugin, + IWTSVirtualChannelManager* pChannelMgr) +{ + UINT rc = 0; + GENERIC_LISTENER_CALLBACK* listener_callback = nullptr; + GENERIC_DYNVC_PLUGIN* plugin = (GENERIC_DYNVC_PLUGIN*)pPlugin; + + if (!plugin) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + if (!pChannelMgr) + return ERROR_INVALID_PARAMETER; + + if (plugin->initialized) + { + WLog_ERR(TAG, "[%s] channel initialized twice, aborting", plugin->dynvc_name); + return ERROR_INVALID_DATA; + } + + WLog_Print(plugin->log, WLOG_TRACE, "..."); + listener_callback = (GENERIC_LISTENER_CALLBACK*)calloc(1, sizeof(GENERIC_LISTENER_CALLBACK)); + if (!listener_callback) + { + WLog_Print(plugin->log, WLOG_ERROR, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + plugin->listener_callback = listener_callback; + listener_callback->iface.OnNewChannelConnection = generic_on_new_channel_connection; + listener_callback->plugin = pPlugin; + listener_callback->channel_mgr = pChannelMgr; + rc = pChannelMgr->CreateListener(pChannelMgr, plugin->dynvc_name, 0, &listener_callback->iface, + &plugin->listener); + + plugin->listener->pInterface = plugin->iface.pInterface; + plugin->initialized = (rc == CHANNEL_RC_OK); + return rc; +} + +static UINT generic_plugin_terminated(IWTSPlugin* pPlugin) +{ + GENERIC_DYNVC_PLUGIN* plugin = (GENERIC_DYNVC_PLUGIN*)pPlugin; + UINT error = CHANNEL_RC_OK; + + if (!plugin) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + WLog_Print(plugin->log, WLOG_TRACE, "..."); + + /* some channels (namely rdpei), look at initialized to see if they should continue to run */ + plugin->initialized = FALSE; + + if (plugin->terminatePluginFn) + plugin->terminatePluginFn(plugin); + + if (plugin->listener_callback) + { + IWTSVirtualChannelManager* mgr = plugin->listener_callback->channel_mgr; + if (mgr) + IFCALL(mgr->DestroyListener, mgr, plugin->listener); + } + + free(plugin->listener_callback); + free(plugin->dynvc_name); + free(plugin); + return error; +} + +static UINT generic_dynvc_plugin_attached(IWTSPlugin* pPlugin) +{ + GENERIC_DYNVC_PLUGIN* pluginn = (GENERIC_DYNVC_PLUGIN*)pPlugin; + UINT error = CHANNEL_RC_OK; + + if (!pluginn) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + pluginn->attached = TRUE; + return error; +} + +static UINT generic_dynvc_plugin_detached(IWTSPlugin* pPlugin) +{ + GENERIC_DYNVC_PLUGIN* plugin = (GENERIC_DYNVC_PLUGIN*)pPlugin; + UINT error = CHANNEL_RC_OK; + + if (!plugin) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + plugin->attached = FALSE; + return error; +} + +UINT freerdp_generic_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints, const char* logTag, + const char* name, size_t pluginSize, size_t channelCallbackSize, + const IWTSVirtualChannelCallback* channel_callbacks, + DYNVC_PLUGIN_INIT_FN initPluginFn, + DYNVC_PLUGIN_TERMINATE_FN terminatePluginFn) +{ + GENERIC_DYNVC_PLUGIN* plugin = nullptr; + UINT error = CHANNEL_RC_INITIALIZATION_ERROR; + + WINPR_ASSERT(pEntryPoints); + WINPR_ASSERT(pEntryPoints->GetPlugin); + WINPR_ASSERT(logTag); + WINPR_ASSERT(name); + WINPR_ASSERT(pluginSize >= sizeof(*plugin)); + WINPR_ASSERT(channelCallbackSize >= sizeof(GENERIC_CHANNEL_CALLBACK)); + + plugin = (GENERIC_DYNVC_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints, name); + if (plugin != nullptr) + return CHANNEL_RC_ALREADY_INITIALIZED; + + plugin = (GENERIC_DYNVC_PLUGIN*)calloc(1, pluginSize); + if (!plugin) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + plugin->log = WLog_Get(logTag); + plugin->attached = TRUE; + plugin->channel_callbacks = channel_callbacks; + plugin->channelCallbackSize = channelCallbackSize; + plugin->iface.Initialize = generic_dynvc_plugin_initialize; + plugin->iface.Connected = nullptr; + plugin->iface.Disconnected = nullptr; + plugin->iface.Terminated = generic_plugin_terminated; + plugin->iface.Attached = generic_dynvc_plugin_attached; + plugin->iface.Detached = generic_dynvc_plugin_detached; + plugin->terminatePluginFn = terminatePluginFn; + + if (initPluginFn) + { + rdpSettings* settings = pEntryPoints->GetRdpSettings(pEntryPoints); + rdpContext* context = pEntryPoints->GetRdpContext(pEntryPoints); + + error = initPluginFn(plugin, context, settings); + if (error != CHANNEL_RC_OK) + goto error; + } + + plugin->dynvc_name = _strdup(name); + if (!plugin->dynvc_name) + goto error; + + error = pEntryPoints->RegisterPlugin(pEntryPoints, name, &plugin->iface); + if (error == CHANNEL_RC_OK) + return error; + +error: + generic_plugin_terminated(&plugin->iface); + return error; +} diff --git a/third_party/FreeRDP/channels/client/tables.c.in b/third_party/FreeRDP/channels/client/tables.c.in new file mode 100644 index 0000000..2c34a30 --- /dev/null +++ b/third_party/FreeRDP/channels/client/tables.c.in @@ -0,0 +1,33 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Static Entry Point Tables + * + * Copyright 2012 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "tables.h" + +${CLIENT_STATIC_TYPEDEFS} +${CLIENT_STATIC_ENTRY_IMPORTS} +${CLIENT_STATIC_SUBSYSTEM_IMPORTS} +${CLIENT_STATIC_ENTRY_TABLES} +${CLIENT_STATIC_ENTRY_TABLES_LIST} +${CLIENT_STATIC_SUBSYSTEM_TABLES} +${CLIENT_STATIC_ADDIN_TABLE} + diff --git a/third_party/FreeRDP/channels/client/tables.h b/third_party/FreeRDP/channels/client/tables.h new file mode 100644 index 0000000..813d239 --- /dev/null +++ b/third_party/FreeRDP/channels/client/tables.h @@ -0,0 +1,107 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Static Entry Point Tables + * + * Copyright 2012 Marc-Andre Moreau + * + * 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 +#include +#include +#include +#include + +/* The 'entry' function pointers have variable arguments. */ +WINPR_PRAGMA_DIAG_PUSH +WINPR_PRAGMA_DIAG_IGNORED_STRICT_PROTOTYPES + +typedef UINT(VCAPITYPE* static_entry_fn_t)(); +typedef struct +{ + const char* name; + WINPR_ATTR_NODISCARD static_entry_fn_t entry; +} STATIC_ENTRY; + +typedef BOOL(VCAPITYPE* static_entry_vc_fn_t)(PCHANNEL_ENTRY_POINTS); +typedef struct +{ + const char* name; + WINPR_ATTR_NODISCARD static_entry_vc_fn_t entry; +} STATIC_ENTRY_VC; + +typedef BOOL(VCAPITYPE* static_entry_vcex_fn_t)(PCHANNEL_ENTRY_POINTS_EX, PVOID); +typedef struct +{ + const char* name; + WINPR_ATTR_NODISCARD static_entry_vcex_fn_t entry; +} STATIC_ENTRY_VCEX; + +typedef UINT(VCAPITYPE* static_entry_dvc_fn_t)(IDRDYNVC_ENTRY_POINTS*); +typedef struct +{ + const char* name; + WINPR_ATTR_NODISCARD static_entry_dvc_fn_t entry; +} STATIC_ENTRY_DVC; + +typedef UINT(VCAPITYPE* static_entry_dse_fn_t)(PDEVICE_SERVICE_ENTRY_POINTS); +typedef struct +{ + const char* name; + WINPR_ATTR_NODISCARD static_entry_dse_fn_t entry; +} STATIC_ENTRY_DSE; + +typedef union +{ + const STATIC_ENTRY* cse; + const STATIC_ENTRY_VC* csevc; + const STATIC_ENTRY_VCEX* csevcex; + const STATIC_ENTRY_DVC* csedvc; + const STATIC_ENTRY_DSE* csedse; +} static_entry_u; + +typedef union +{ + WINPR_ATTR_NODISCARD static_entry_fn_t cse; + WINPR_ATTR_NODISCARD static_entry_vc_fn_t csevc; + WINPR_ATTR_NODISCARD static_entry_vcex_fn_t csevcex; + WINPR_ATTR_NODISCARD static_entry_dvc_fn_t csedvc; + WINPR_ATTR_NODISCARD static_entry_dse_fn_t csedse; +} static_entry_fn_u; + +typedef struct +{ + const char* name; + static_entry_u table; +} STATIC_ENTRY_TABLE; + +typedef UINT(VCAPITYPE* static_subsystem_entry_fn_t)(void*); +typedef struct +{ + const char* name; + const char* type; + WINPR_ATTR_NODISCARD static_subsystem_entry_fn_t entry; +} STATIC_SUBSYSTEM_ENTRY; + +typedef struct +{ + const char* name; + const char* type; + static_entry_fn_u entry; + const STATIC_SUBSYSTEM_ENTRY* table; +} STATIC_ADDIN_TABLE; + +WINPR_PRAGMA_DIAG_POP diff --git a/third_party/FreeRDP/channels/cliprdr/CMakeLists.txt b/third_party/FreeRDP/channels/cliprdr/CMakeLists.txt new file mode 100644 index 0000000..fb9addd --- /dev/null +++ b/third_party/FreeRDP/channels/cliprdr/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel("cliprdr") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/third_party/FreeRDP/channels/cliprdr/ChannelOptions.cmake b/third_party/FreeRDP/channels/cliprdr/ChannelOptions.cmake new file mode 100644 index 0000000..0787c08 --- /dev/null +++ b/third_party/FreeRDP/channels/cliprdr/ChannelOptions.cmake @@ -0,0 +1,20 @@ +set(OPTION_DEFAULT ON) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options( + NAME + "cliprdr" + TYPE + "static" + DESCRIPTION + "Clipboard Virtual Channel Extension" + SPECIFICATIONS + "[MS-RDPECLIP]" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/cliprdr/client/CMakeLists.txt b/third_party/FreeRDP/channels/cliprdr/client/CMakeLists.txt new file mode 100644 index 0000000..956620d --- /dev/null +++ b/third_party/FreeRDP/channels/cliprdr/client/CMakeLists.txt @@ -0,0 +1,25 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_client("cliprdr") + +set(${MODULE_PREFIX}_SRCS cliprdr_format.c cliprdr_format.h cliprdr_main.c cliprdr_main.h ../cliprdr_common.h + ../cliprdr_common.c +) + +set(${MODULE_PREFIX}_LIBS winpr freerdp) +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntryEx") diff --git a/third_party/FreeRDP/channels/cliprdr/client/cliprdr_format.c b/third_party/FreeRDP/channels/cliprdr/client/cliprdr_format.c new file mode 100644 index 0000000..fb2ca7d --- /dev/null +++ b/third_party/FreeRDP/channels/cliprdr/client/cliprdr_format.c @@ -0,0 +1,280 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Clipboard Virtual Channel + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "cliprdr_main.h" +#include "cliprdr_format.h" +#include "../cliprdr_common.h" + +CLIPRDR_FORMAT_LIST cliprdr_filter_format_list(const CLIPRDR_FORMAT_LIST* list, const UINT32 mask, + const UINT32 checkMask) +{ + const UINT32 maskData = + checkMask & (CLIPRDR_FLAG_LOCAL_TO_REMOTE | CLIPRDR_FLAG_REMOTE_TO_LOCAL); + const UINT32 maskFiles = + checkMask & (CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES | CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES); + WINPR_ASSERT(list); + + CLIPRDR_FORMAT_LIST filtered = WINPR_C_ARRAY_INIT; + filtered.common.msgType = CB_FORMAT_LIST; + filtered.numFormats = list->numFormats; + filtered.formats = calloc(filtered.numFormats, sizeof(CLIPRDR_FORMAT)); + + size_t wpos = 0; + if ((mask & checkMask) == checkMask) + { + for (size_t x = 0; x < list->numFormats; x++) + { + const CLIPRDR_FORMAT* format = &list->formats[x]; + CLIPRDR_FORMAT* cur = &filtered.formats[x]; + cur->formatId = format->formatId; + if (format->formatName) + cur->formatName = _strdup(format->formatName); + wpos++; + } + } + else if ((mask & maskFiles) != 0) + { + for (size_t x = 0; x < list->numFormats; x++) + { + const CLIPRDR_FORMAT* format = &list->formats[x]; + CLIPRDR_FORMAT* cur = &filtered.formats[wpos]; + + if (!format->formatName) + continue; + if (strcmp(format->formatName, type_FileGroupDescriptorW) == 0 || + strcmp(format->formatName, type_FileContents) == 0) + { + cur->formatId = format->formatId; + cur->formatName = _strdup(format->formatName); + wpos++; + } + } + } + else if ((mask & maskData) != 0) + { + for (size_t x = 0; x < list->numFormats; x++) + { + const CLIPRDR_FORMAT* format = &list->formats[x]; + CLIPRDR_FORMAT* cur = &filtered.formats[wpos]; + + if (!format->formatName || + (strcmp(format->formatName, type_FileGroupDescriptorW) != 0 && + strcmp(format->formatName, type_FileContents) != 0)) + { + cur->formatId = format->formatId; + if (format->formatName) + cur->formatName = _strdup(format->formatName); + wpos++; + } + } + } + WINPR_ASSERT(wpos <= UINT32_MAX); + filtered.numFormats = (UINT32)wpos; + return filtered; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT cliprdr_process_format_list(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen, + UINT16 msgFlags) +{ + CLIPRDR_FORMAT_LIST formatList = WINPR_C_ARRAY_INIT; + CLIPRDR_FORMAT_LIST filteredFormatList = WINPR_C_ARRAY_INIT; + CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr); + UINT error = CHANNEL_RC_OK; + + formatList.common.msgType = CB_FORMAT_LIST; + formatList.common.msgFlags = msgFlags; + formatList.common.dataLen = dataLen; + + if ((error = + cliprdr_read_format_list(cliprdr->log, s, &formatList, cliprdr->useLongFormatNames))) + goto error_out; + + { + const UINT32 mask = freerdp_settings_get_uint32(context->rdpcontext->settings, + FreeRDP_ClipboardFeatureMask); + filteredFormatList = cliprdr_filter_format_list( + &formatList, mask, CLIPRDR_FLAG_REMOTE_TO_LOCAL | CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES); + } + + if (filteredFormatList.numFormats == 0) + goto error_out; + + { + const DWORD level = WLOG_DEBUG; + if (WLog_IsLevelActive(cliprdr->log, level)) + { + WLog_Print(cliprdr->log, level, "ServerFormatList: numFormats: %" PRIu32 "", + formatList.numFormats); + for (size_t x = 0; x < formatList.numFormats; x++) + { + const CLIPRDR_FORMAT* format = &formatList.formats[x]; + WLog_Print(cliprdr->log, level, "[%" PRIuz "]: id=0x%08" PRIx32 " [%s|%s]", x, + format->formatId, ClipboardGetFormatIdString(format->formatId), + format->formatName); + } + + WLog_Print(cliprdr->log, level, "ServerFormatList [filtered]: numFormats: %" PRIu32 "", + filteredFormatList.numFormats); + for (size_t x = 0; x < filteredFormatList.numFormats; x++) + { + const CLIPRDR_FORMAT* format = &filteredFormatList.formats[x]; + WLog_Print(cliprdr->log, level, "[%" PRIuz "]: id=0x%08" PRIx32 " [%s|%s]", x, + format->formatId, ClipboardGetFormatIdString(format->formatId), + format->formatName); + } + } + } + + if (context->ServerFormatList) + { + if ((error = context->ServerFormatList(context, &filteredFormatList))) + WLog_Print(cliprdr->log, WLOG_ERROR, "ServerFormatList failed with error %" PRIu32 "", + error); + } + +error_out: + cliprdr_free_format_list(&filteredFormatList); + cliprdr_free_format_list(&formatList); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT cliprdr_process_format_list_response(cliprdrPlugin* cliprdr, WINPR_ATTR_UNUSED wStream* s, + UINT32 dataLen, UINT16 msgFlags) +{ + CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse = WINPR_C_ARRAY_INIT; + CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr); + UINT error = CHANNEL_RC_OK; + + WLog_Print(cliprdr->log, WLOG_DEBUG, "ServerFormatListResponse"); + + formatListResponse.common.msgType = CB_FORMAT_LIST_RESPONSE; + formatListResponse.common.msgFlags = msgFlags; + formatListResponse.common.dataLen = dataLen; + + IFCALLRET(context->ServerFormatListResponse, error, context, &formatListResponse); + if (error) + WLog_Print(cliprdr->log, WLOG_ERROR, + "ServerFormatListResponse failed with error %" PRIu32 "!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT cliprdr_process_format_data_request(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen, + UINT16 msgFlags) +{ + CLIPRDR_FORMAT_DATA_REQUEST formatDataRequest = WINPR_C_ARRAY_INIT; + CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr); + UINT error = CHANNEL_RC_OK; + + formatDataRequest.common.msgType = CB_FORMAT_DATA_REQUEST; + formatDataRequest.common.msgFlags = msgFlags; + formatDataRequest.common.dataLen = dataLen; + + if ((error = cliprdr_read_format_data_request(s, &formatDataRequest))) + return error; + + WLog_Print(cliprdr->log, WLOG_DEBUG, "ServerFormatDataRequest (0x%08" PRIx32 " [%s])", + formatDataRequest.requestedFormatId, + ClipboardGetFormatIdString(formatDataRequest.requestedFormatId)); + + const UINT32 mask = + freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask); + if ((mask & (CLIPRDR_FLAG_LOCAL_TO_REMOTE | CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES)) == 0) + { + return cliprdr_send_error_response(cliprdr, CB_FORMAT_DATA_RESPONSE); + } + + context->lastRequestedFormatId = formatDataRequest.requestedFormatId; + IFCALLRET(context->ServerFormatDataRequest, error, context, &formatDataRequest); + if (error) + WLog_Print(cliprdr->log, WLOG_ERROR, + "ServerFormatDataRequest failed with error %" PRIu32 "!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT cliprdr_process_format_data_response(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen, + UINT16 msgFlags) +{ + CLIPRDR_FORMAT_DATA_RESPONSE formatDataResponse = WINPR_C_ARRAY_INIT; + CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr); + UINT error = CHANNEL_RC_OK; + + WLog_Print(cliprdr->log, WLOG_DEBUG, + "ServerFormatDataResponse: msgFlags=0x%08" PRIx32 ", dataLen=%" PRIu32, msgFlags, + dataLen); + + formatDataResponse.common.msgType = CB_FORMAT_DATA_RESPONSE; + formatDataResponse.common.msgFlags = msgFlags; + formatDataResponse.common.dataLen = dataLen; + + if ((error = cliprdr_read_format_data_response(s, &formatDataResponse))) + return error; + + const UINT32 mask = + freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask); + if ((mask & (CLIPRDR_FLAG_REMOTE_TO_LOCAL | CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES)) == 0) + { + WLog_Print(cliprdr->log, WLOG_WARN, + "Received ServerFormatDataResponse but remote -> local clipboard is disabled"); + return CHANNEL_RC_OK; + } + + IFCALLRET(context->ServerFormatDataResponse, error, context, &formatDataResponse); + if (error) + WLog_Print(cliprdr->log, WLOG_ERROR, + "ServerFormatDataResponse failed with error %" PRIu32 "!", error); + + return error; +} diff --git a/third_party/FreeRDP/channels/cliprdr/client/cliprdr_format.h b/third_party/FreeRDP/channels/cliprdr/client/cliprdr_format.h new file mode 100644 index 0000000..26ac242 --- /dev/null +++ b/third_party/FreeRDP/channels/cliprdr/client/cliprdr_format.h @@ -0,0 +1,59 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Clipboard Virtual Channel + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_CLIPRDR_CLIENT_FORMAT_H +#define FREERDP_CHANNEL_CLIPRDR_CLIENT_FORMAT_H + +#include +#include + +#include +#include + +#include "cliprdr_main.h" + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL +UINT cliprdr_process_format_list(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen, + UINT16 msgFlags); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL +UINT cliprdr_process_format_list_response(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen, + UINT16 msgFlags); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL +UINT cliprdr_process_format_data_request(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen, + UINT16 msgFlags); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL +UINT cliprdr_process_format_data_response(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen, + UINT16 msgFlags); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL +CLIPRDR_FORMAT_LIST cliprdr_filter_format_list(const CLIPRDR_FORMAT_LIST* list, UINT32 mask, + UINT32 checkMask); + +#endif /* FREERDP_CHANNEL_CLIPRDR_CLIENT_FORMAT_H */ diff --git a/third_party/FreeRDP/channels/cliprdr/client/cliprdr_main.c b/third_party/FreeRDP/channels/cliprdr/client/cliprdr_main.c new file mode 100644 index 0000000..2fdb3db --- /dev/null +++ b/third_party/FreeRDP/channels/cliprdr/client/cliprdr_main.c @@ -0,0 +1,1209 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Clipboard Virtual Channel + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "../../../channels/client/addin.h" + +#include "cliprdr_main.h" +#include "cliprdr_format.h" +#include "../cliprdr_common.h" + +const char type_FileGroupDescriptorW[] = "FileGroupDescriptorW"; +const char type_FileContents[] = "FileContents"; + +CliprdrClientContext* cliprdr_get_client_interface(cliprdrPlugin* cliprdr) +{ + CliprdrClientContext* pInterface = nullptr; + + if (!cliprdr) + return nullptr; + + pInterface = (CliprdrClientContext*)cliprdr->channelEntryPoints.pInterface; + return pInterface; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_packet_send(cliprdrPlugin* cliprdr, wStream* s) +{ + UINT status = CHANNEL_RC_OK; + + WINPR_ASSERT(cliprdr); + WINPR_ASSERT(s); + + const size_t pos = Stream_GetPosition(s); + WINPR_ASSERT(pos >= 8ULL); + WINPR_ASSERT(pos <= UINT32_MAX - 8); + + const uint32_t dataLen = WINPR_ASSERTING_INT_CAST(uint32_t, pos - 8UL); + + Stream_SetPosition(s, 4); + Stream_Write_UINT32(s, dataLen); + Stream_SetPosition(s, pos); + + WLog_Print(cliprdr->log, WLOG_DEBUG, "Cliprdr Sending (%" PRIuz " bytes)", pos); + + if (!cliprdr) + { + status = CHANNEL_RC_BAD_INIT_HANDLE; + } + else + { + WINPR_ASSERT(cliprdr->channelEntryPoints.pVirtualChannelWriteEx); + status = cliprdr->channelEntryPoints.pVirtualChannelWriteEx( + cliprdr->InitHandle, cliprdr->OpenHandle, Stream_Buffer(s), + (UINT32)Stream_GetPosition(s), s); + } + + if (status != CHANNEL_RC_OK) + { + Stream_Free(s, TRUE); + WLog_Print(cliprdr->log, WLOG_ERROR, "VirtualChannelWrite failed with %s [%08" PRIX32 "]", + WTSErrorToString(status), status); + } + + return status; +} + +UINT cliprdr_send_error_response(cliprdrPlugin* cliprdr, UINT16 type) +{ + wStream* s = cliprdr_packet_new(type, CB_RESPONSE_FAIL, 0); + if (!s) + { + WLog_Print(cliprdr->log, WLOG_ERROR, "cliprdr_packet_new failed!"); + return ERROR_OUTOFMEMORY; + } + + return cliprdr_packet_send(cliprdr, s); +} + +static void cliprdr_print_general_capability_flags(wLog* log, UINT32 flags) +{ + WLog_Print(log, WLOG_DEBUG, "generalFlags (0x%08" PRIX32 ") {", flags); + + if (flags & CB_USE_LONG_FORMAT_NAMES) + WLog_Print(log, WLOG_DEBUG, "\tCB_USE_LONG_FORMAT_NAMES"); + + if (flags & CB_STREAM_FILECLIP_ENABLED) + WLog_Print(log, WLOG_DEBUG, "\tCB_STREAM_FILECLIP_ENABLED"); + + if (flags & CB_FILECLIP_NO_FILE_PATHS) + WLog_Print(log, WLOG_DEBUG, "\tCB_FILECLIP_NO_FILE_PATHS"); + + if (flags & CB_CAN_LOCK_CLIPDATA) + WLog_Print(log, WLOG_DEBUG, "\tCB_CAN_LOCK_CLIPDATA"); + + if (flags & CB_HUGE_FILE_SUPPORT_ENABLED) + WLog_Print(log, WLOG_DEBUG, "\tCB_HUGE_FILE_SUPPORT_ENABLED"); + + WLog_Print(log, WLOG_DEBUG, "}"); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_process_general_capability(cliprdrPlugin* cliprdr, wStream* s) +{ + UINT32 version = 0; + UINT32 generalFlags = 0; + CLIPRDR_CAPABILITIES capabilities = WINPR_C_ARRAY_INIT; + CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet = WINPR_C_ARRAY_INIT; + CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr); + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(cliprdr); + WINPR_ASSERT(s); + + if (!context) + { + WLog_Print(cliprdr->log, WLOG_ERROR, "cliprdr_get_client_interface failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (!Stream_CheckAndLogRequiredLengthWLog(cliprdr->log, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, version); /* version (4 bytes) */ + Stream_Read_UINT32(s, generalFlags); /* generalFlags (4 bytes) */ + WLog_Print(cliprdr->log, WLOG_DEBUG, "Version: %" PRIu32 "", version); + + cliprdr_print_general_capability_flags(cliprdr->log, generalFlags); + + cliprdr->useLongFormatNames = (generalFlags & CB_USE_LONG_FORMAT_NAMES) != 0; + cliprdr->streamFileClipEnabled = (generalFlags & CB_STREAM_FILECLIP_ENABLED) != 0; + cliprdr->fileClipNoFilePaths = (generalFlags & CB_FILECLIP_NO_FILE_PATHS) != 0; + cliprdr->canLockClipData = (generalFlags & CB_CAN_LOCK_CLIPDATA) != 0; + cliprdr->hasHugeFileSupport = (generalFlags & CB_HUGE_FILE_SUPPORT_ENABLED) != 0; + cliprdr->capabilitiesReceived = TRUE; + + capabilities.common.msgType = CB_CLIP_CAPS; + capabilities.cCapabilitiesSets = 1; + capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET*)&(generalCapabilitySet); + generalCapabilitySet.capabilitySetType = CB_CAPSTYPE_GENERAL; + generalCapabilitySet.capabilitySetLength = 12; + generalCapabilitySet.version = version; + generalCapabilitySet.generalFlags = generalFlags; + IFCALLRET(context->ServerCapabilities, error, context, &capabilities); + + if (error) + WLog_Print(cliprdr->log, WLOG_ERROR, "ServerCapabilities failed with error %" PRIu32 "!", + error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_process_clip_caps(cliprdrPlugin* cliprdr, wStream* s, + WINPR_ATTR_UNUSED UINT32 length, + WINPR_ATTR_UNUSED UINT16 flags) +{ + UINT16 lengthCapability = 0; + UINT16 cCapabilitiesSets = 0; + UINT16 capabilitySetType = 0; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(cliprdr); + WINPR_ASSERT(s); + + if (!Stream_CheckAndLogRequiredLengthWLog(cliprdr->log, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, cCapabilitiesSets); /* cCapabilitiesSets (2 bytes) */ + Stream_Seek_UINT16(s); /* pad1 (2 bytes) */ + WLog_Print(cliprdr->log, WLOG_DEBUG, "ServerCapabilities"); + + for (UINT16 index = 0; index < cCapabilitiesSets; index++) + { + if (!Stream_CheckAndLogRequiredLengthWLog(cliprdr->log, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, capabilitySetType); /* capabilitySetType (2 bytes) */ + Stream_Read_UINT16(s, lengthCapability); /* lengthCapability (2 bytes) */ + + if ((lengthCapability < 4) || + (!Stream_CheckAndLogRequiredLengthWLog(cliprdr->log, s, lengthCapability - 4U))) + return ERROR_INVALID_DATA; + + switch (capabilitySetType) + { + case CB_CAPSTYPE_GENERAL: + if ((error = cliprdr_process_general_capability(cliprdr, s))) + { + WLog_Print(cliprdr->log, WLOG_ERROR, + "cliprdr_process_general_capability failed with error %" PRIu32 "!", + error); + return error; + } + + break; + + default: + WLog_Print(cliprdr->log, WLOG_ERROR, "unknown cliprdr capability set: %" PRIu16 "", + capabilitySetType); + return CHANNEL_RC_BAD_PROC; + } + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_process_monitor_ready(cliprdrPlugin* cliprdr, WINPR_ATTR_UNUSED wStream* s, + UINT32 length, UINT16 flags) +{ + CLIPRDR_MONITOR_READY monitorReady = WINPR_C_ARRAY_INIT; + CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr); + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(cliprdr); + WINPR_ASSERT(s); + + WLog_Print(cliprdr->log, WLOG_DEBUG, "MonitorReady"); + + if (!cliprdr->capabilitiesReceived) + { + /** + * The clipboard capabilities pdu from server to client is optional, + * but a server using it must send it before sending the monitor ready pdu. + * When the server capabilities pdu is not used, default capabilities + * corresponding to a generalFlags field set to zero are assumed. + */ + cliprdr->useLongFormatNames = FALSE; + cliprdr->streamFileClipEnabled = FALSE; + cliprdr->fileClipNoFilePaths = TRUE; + cliprdr->canLockClipData = FALSE; + } + + monitorReady.common.msgType = CB_MONITOR_READY; + monitorReady.common.msgFlags = flags; + monitorReady.common.dataLen = length; + IFCALLRET(context->MonitorReady, error, context, &monitorReady); + + if (error) + WLog_Print(cliprdr->log, WLOG_ERROR, "MonitorReady failed with error %" PRIu32 "!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_process_filecontents_request(cliprdrPlugin* cliprdr, wStream* s, UINT32 length, + UINT16 flags) +{ + CLIPRDR_FILE_CONTENTS_REQUEST request = WINPR_C_ARRAY_INIT; + CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr); + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(cliprdr); + WINPR_ASSERT(s); + + WLog_Print(cliprdr->log, WLOG_DEBUG, "FileContentsRequest"); + + request.common.msgType = CB_FILECONTENTS_REQUEST; + request.common.msgFlags = flags; + request.common.dataLen = length; + + if ((error = cliprdr_read_file_contents_request(s, &request))) + return error; + + const UINT32 mask = + freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask); + if ((mask & (CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES)) == 0) + { + WLog_Print(cliprdr->log, WLOG_WARN, "local -> remote file copy disabled, ignoring request"); + return cliprdr_send_error_response(cliprdr, CB_FILECONTENTS_RESPONSE); + } + IFCALLRET(context->ServerFileContentsRequest, error, context, &request); + + if (error) + WLog_Print(cliprdr->log, WLOG_ERROR, + "ServerFileContentsRequest failed with error %" PRIu32 "!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_process_filecontents_response(cliprdrPlugin* cliprdr, wStream* s, UINT32 length, + UINT16 flags) +{ + CLIPRDR_FILE_CONTENTS_RESPONSE response = WINPR_C_ARRAY_INIT; + CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr); + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(cliprdr); + WINPR_ASSERT(s); + + WLog_Print(cliprdr->log, WLOG_DEBUG, "FileContentsResponse"); + + response.common.msgType = CB_FILECONTENTS_RESPONSE; + response.common.msgFlags = flags; + response.common.dataLen = length; + + if ((error = cliprdr_read_file_contents_response(s, &response))) + return error; + + IFCALLRET(context->ServerFileContentsResponse, error, context, &response); + + if (error) + WLog_Print(cliprdr->log, WLOG_ERROR, + "ServerFileContentsResponse failed with error %" PRIu32 "!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_process_lock_clipdata(cliprdrPlugin* cliprdr, wStream* s, UINT32 length, + UINT16 flags) +{ + CLIPRDR_LOCK_CLIPBOARD_DATA lockClipboardData = WINPR_C_ARRAY_INIT; + CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr); + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(cliprdr); + WINPR_ASSERT(s); + + WLog_Print(cliprdr->log, WLOG_DEBUG, "LockClipData"); + + if (!Stream_CheckAndLogRequiredLengthWLog(cliprdr->log, s, 4)) + return ERROR_INVALID_DATA; + + lockClipboardData.common.msgType = CB_LOCK_CLIPDATA; + lockClipboardData.common.msgFlags = flags; + lockClipboardData.common.dataLen = length; + Stream_Read_UINT32(s, lockClipboardData.clipDataId); /* clipDataId (4 bytes) */ + IFCALLRET(context->ServerLockClipboardData, error, context, &lockClipboardData); + + if (error) + WLog_Print(cliprdr->log, WLOG_ERROR, + "ServerLockClipboardData failed with error %" PRIu32 "!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_process_unlock_clipdata(cliprdrPlugin* cliprdr, wStream* s, UINT32 length, + UINT16 flags) +{ + CLIPRDR_UNLOCK_CLIPBOARD_DATA unlockClipboardData = WINPR_C_ARRAY_INIT; + CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr); + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(cliprdr); + WINPR_ASSERT(s); + + WLog_Print(cliprdr->log, WLOG_DEBUG, "UnlockClipData"); + + if ((error = cliprdr_read_unlock_clipdata(s, &unlockClipboardData))) + return error; + + unlockClipboardData.common.msgType = CB_UNLOCK_CLIPDATA; + unlockClipboardData.common.msgFlags = flags; + unlockClipboardData.common.dataLen = length; + + IFCALLRET(context->ServerUnlockClipboardData, error, context, &unlockClipboardData); + + if (error) + WLog_Print(cliprdr->log, WLOG_ERROR, + "ServerUnlockClipboardData failed with error %" PRIu32 "!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_order_recv(LPVOID userdata, wStream* s) +{ + cliprdrPlugin* cliprdr = userdata; + UINT16 msgType = 0; + UINT16 msgFlags = 0; + UINT32 dataLen = 0; + UINT error = 0; + + WINPR_ASSERT(cliprdr); + WINPR_ASSERT(s); + + if (!Stream_CheckAndLogRequiredLengthWLog(cliprdr->log, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, msgType); /* msgType (2 bytes) */ + Stream_Read_UINT16(s, msgFlags); /* msgFlags (2 bytes) */ + Stream_Read_UINT32(s, dataLen); /* dataLen (4 bytes) */ + + if (!Stream_CheckAndLogRequiredLengthWLog(cliprdr->log, s, dataLen)) + return ERROR_INVALID_DATA; + + char buffer1[64] = WINPR_C_ARRAY_INIT; + char buffer2[64] = WINPR_C_ARRAY_INIT; + WLog_Print(cliprdr->log, WLOG_DEBUG, + "msgType: %s (%" PRIu16 "), msgFlags: %s dataLen: %" PRIu32 "", + CB_MSG_TYPE_STRING(msgType, buffer1, sizeof(buffer1)), msgType, + CB_MSG_FLAGS_STRING(msgFlags, buffer2, sizeof(buffer2)), dataLen); + + switch (msgType) + { + case CB_CLIP_CAPS: + if ((error = cliprdr_process_clip_caps(cliprdr, s, dataLen, msgFlags))) + WLog_Print(cliprdr->log, WLOG_ERROR, + "cliprdr_process_clip_caps failed with error %" PRIu32 "!", error); + + break; + + case CB_MONITOR_READY: + if ((error = cliprdr_process_monitor_ready(cliprdr, s, dataLen, msgFlags))) + WLog_Print(cliprdr->log, WLOG_ERROR, + "cliprdr_process_monitor_ready failed with error %" PRIu32 "!", error); + + break; + + case CB_FORMAT_LIST: + if ((error = cliprdr_process_format_list(cliprdr, s, dataLen, msgFlags))) + WLog_Print(cliprdr->log, WLOG_ERROR, + "cliprdr_process_format_list failed with error %" PRIu32 "!", error); + + break; + + case CB_FORMAT_LIST_RESPONSE: + if ((error = cliprdr_process_format_list_response(cliprdr, s, dataLen, msgFlags))) + WLog_Print(cliprdr->log, WLOG_ERROR, + "cliprdr_process_format_list_response failed with error %" PRIu32 "!", + error); + + break; + + case CB_FORMAT_DATA_REQUEST: + if ((error = cliprdr_process_format_data_request(cliprdr, s, dataLen, msgFlags))) + WLog_Print(cliprdr->log, WLOG_ERROR, + "cliprdr_process_format_data_request failed with error %" PRIu32 "!", + error); + + break; + + case CB_FORMAT_DATA_RESPONSE: + if ((error = cliprdr_process_format_data_response(cliprdr, s, dataLen, msgFlags))) + WLog_Print(cliprdr->log, WLOG_ERROR, + "cliprdr_process_format_data_response failed with error %" PRIu32 "!", + error); + + break; + + case CB_FILECONTENTS_REQUEST: + if ((error = cliprdr_process_filecontents_request(cliprdr, s, dataLen, msgFlags))) + WLog_Print(cliprdr->log, WLOG_ERROR, + "cliprdr_process_filecontents_request failed with error %" PRIu32 "!", + error); + + break; + + case CB_FILECONTENTS_RESPONSE: + if ((error = cliprdr_process_filecontents_response(cliprdr, s, dataLen, msgFlags))) + WLog_Print(cliprdr->log, WLOG_ERROR, + "cliprdr_process_filecontents_response failed with error %" PRIu32 "!", + error); + + break; + + case CB_LOCK_CLIPDATA: + if ((error = cliprdr_process_lock_clipdata(cliprdr, s, dataLen, msgFlags))) + WLog_Print(cliprdr->log, WLOG_ERROR, + "cliprdr_process_lock_clipdata failed with error %" PRIu32 "!", error); + + break; + + case CB_UNLOCK_CLIPDATA: + if ((error = cliprdr_process_unlock_clipdata(cliprdr, s, dataLen, msgFlags))) + WLog_Print(cliprdr->log, WLOG_ERROR, + "cliprdr_process_unlock_clipdata failed with error %" PRIu32 "!", error); + + break; + + default: + error = CHANNEL_RC_BAD_PROC; + WLog_Print(cliprdr->log, WLOG_ERROR, "unknown msgType %" PRIu16 "", msgType); + break; + } + + Stream_Free(s, TRUE); + return error; +} + +/** + * Callback Interface + */ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_client_capabilities(CliprdrClientContext* context, + const CLIPRDR_CAPABILITIES* capabilities) +{ + wStream* s = nullptr; + UINT32 flags = 0; + const CLIPRDR_GENERAL_CAPABILITY_SET* generalCapabilitySet = nullptr; + cliprdrPlugin* cliprdr = nullptr; + + WINPR_ASSERT(context); + + cliprdr = (cliprdrPlugin*)context->handle; + WINPR_ASSERT(cliprdr); + + s = cliprdr_packet_new(CB_CLIP_CAPS, 0, 4 + CB_CAPSTYPE_GENERAL_LEN); + + if (!s) + { + WLog_Print(cliprdr->log, WLOG_ERROR, "cliprdr_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_Write_UINT16(s, 1); /* cCapabilitiesSets */ + Stream_Write_UINT16(s, 0); /* pad1 */ + generalCapabilitySet = (const CLIPRDR_GENERAL_CAPABILITY_SET*)capabilities->capabilitySets; + Stream_Write_UINT16(s, generalCapabilitySet->capabilitySetType); /* capabilitySetType */ + Stream_Write_UINT16(s, generalCapabilitySet->capabilitySetLength); /* lengthCapability */ + Stream_Write_UINT32(s, generalCapabilitySet->version); /* version */ + flags = generalCapabilitySet->generalFlags; + + /* Client capabilities are sent in response to server capabilities. + * -> Do not request features the server does not support. + * -> Update clipboard context feature state to what was agreed upon. + */ + if (!cliprdr->useLongFormatNames) + flags &= (uint32_t)~CB_USE_LONG_FORMAT_NAMES; + if (!cliprdr->streamFileClipEnabled) + flags &= (uint32_t)~CB_STREAM_FILECLIP_ENABLED; + if (!cliprdr->fileClipNoFilePaths) + flags &= (uint32_t)~CB_FILECLIP_NO_FILE_PATHS; + if (!cliprdr->canLockClipData) + flags &= (uint32_t)~CB_CAN_LOCK_CLIPDATA; + if (!cliprdr->hasHugeFileSupport) + flags &= (uint32_t)~CB_HUGE_FILE_SUPPORT_ENABLED; + + cliprdr->useLongFormatNames = (flags & CB_USE_LONG_FORMAT_NAMES) != 0; + cliprdr->streamFileClipEnabled = (flags & CB_STREAM_FILECLIP_ENABLED) != 0; + cliprdr->fileClipNoFilePaths = (flags & CB_FILECLIP_NO_FILE_PATHS) != 0; + cliprdr->canLockClipData = (flags & CB_CAN_LOCK_CLIPDATA) != 0; + cliprdr->hasHugeFileSupport = (flags & CB_HUGE_FILE_SUPPORT_ENABLED) != 0; + + Stream_Write_UINT32(s, flags); /* generalFlags */ + WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientCapabilities"); + + cliprdr->initialFormatListSent = FALSE; + + return cliprdr_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_temp_directory(CliprdrClientContext* context, + const CLIPRDR_TEMP_DIRECTORY* tempDirectory) +{ + wStream* s = nullptr; + cliprdrPlugin* cliprdr = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(tempDirectory); + + cliprdr = (cliprdrPlugin*)context->handle; + WINPR_ASSERT(cliprdr); + + const size_t tmpDirCharLen = sizeof(tempDirectory->szTempDir) / sizeof(WCHAR); + s = cliprdr_packet_new(CB_TEMP_DIRECTORY, 0, tmpDirCharLen * sizeof(WCHAR)); + + if (!s) + { + WLog_Print(cliprdr->log, WLOG_ERROR, "cliprdr_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (Stream_Write_UTF16_String_From_UTF8(s, tmpDirCharLen - 1, tempDirectory->szTempDir, + ARRAYSIZE(tempDirectory->szTempDir), TRUE) < 0) + { + Stream_Free(s, TRUE); + return ERROR_INTERNAL_ERROR; + } + /* Path must be 260 UTF16 characters with '\0' termination. + * ensure this here */ + Stream_Write_UINT16(s, 0); + + WLog_Print(cliprdr->log, WLOG_DEBUG, "TempDirectory: %s", tempDirectory->szTempDir); + return cliprdr_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_client_format_list(CliprdrClientContext* context, + const CLIPRDR_FORMAT_LIST* formatList) +{ + wStream* s = nullptr; + cliprdrPlugin* cliprdr = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(formatList); + + cliprdr = (cliprdrPlugin*)context->handle; + WINPR_ASSERT(cliprdr); + + { + const UINT32 mask = CB_RESPONSE_OK | CB_RESPONSE_FAIL; + if ((formatList->common.msgFlags & mask) != 0) + WLog_Print(cliprdr->log, WLOG_WARN, + "Sending clipboard request with invalid flags msgFlags = 0x%08" PRIx32 + ". Correct in your client!", + formatList->common.msgFlags & mask); + } + + const UINT32 mask = + freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask); + CLIPRDR_FORMAT_LIST filterList = cliprdr_filter_format_list( + formatList, mask, CLIPRDR_FLAG_LOCAL_TO_REMOTE | CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES); + + /* Allow initial format list from monitor ready, but ignore later attempts */ + if ((filterList.numFormats == 0) && cliprdr->initialFormatListSent) + { + cliprdr_free_format_list(&filterList); + return CHANNEL_RC_OK; + } + cliprdr->initialFormatListSent = TRUE; + + const uint32_t level = WLOG_DEBUG; + if (WLog_IsLevelActive(cliprdr->log, level)) + { + WLog_Print(cliprdr->log, level, "ClientFormatList: numFormats: %" PRIu32 "", + formatList->numFormats); + for (size_t x = 0; x < filterList.numFormats; x++) + { + const CLIPRDR_FORMAT* format = &filterList.formats[x]; + WLog_Print(cliprdr->log, level, "[%" PRIuz "]: id=0x%08" PRIx32 " [%s|%s]", x, + format->formatId, ClipboardGetFormatIdString(format->formatId), + format->formatName); + } + } + + s = cliprdr_packet_format_list_new(&filterList, cliprdr->useLongFormatNames, FALSE); + cliprdr_free_format_list(&filterList); + + if (!s) + { + WLog_Print(cliprdr->log, WLOG_ERROR, "cliprdr_packet_format_list_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + return cliprdr_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +cliprdr_client_format_list_response(CliprdrClientContext* context, + const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse) +{ + wStream* s = nullptr; + cliprdrPlugin* cliprdr = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(formatListResponse); + + cliprdr = (cliprdrPlugin*)context->handle; + WINPR_ASSERT(cliprdr); + + s = cliprdr_packet_new(CB_FORMAT_LIST_RESPONSE, formatListResponse->common.msgFlags, 0); + + if (!s) + { + WLog_Print(cliprdr->log, WLOG_ERROR, "cliprdr_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientFormatListResponse"); + return cliprdr_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_client_lock_clipboard_data(CliprdrClientContext* context, + const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData) +{ + wStream* s = nullptr; + cliprdrPlugin* cliprdr = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(lockClipboardData); + + cliprdr = (cliprdrPlugin*)context->handle; + WINPR_ASSERT(cliprdr); + + s = cliprdr_packet_lock_clipdata_new(lockClipboardData); + + if (!s) + { + WLog_Print(cliprdr->log, WLOG_ERROR, "cliprdr_packet_lock_clipdata_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientLockClipboardData: clipDataId: 0x%08" PRIX32 "", + lockClipboardData->clipDataId); + return cliprdr_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +cliprdr_client_unlock_clipboard_data(CliprdrClientContext* context, + const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData) +{ + wStream* s = nullptr; + cliprdrPlugin* cliprdr = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(unlockClipboardData); + + cliprdr = (cliprdrPlugin*)context->handle; + WINPR_ASSERT(cliprdr); + + s = cliprdr_packet_unlock_clipdata_new(unlockClipboardData); + + if (!s) + { + WLog_Print(cliprdr->log, WLOG_ERROR, "cliprdr_packet_unlock_clipdata_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientUnlockClipboardData: clipDataId: 0x%08" PRIX32 "", + unlockClipboardData->clipDataId); + return cliprdr_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_client_format_data_request(CliprdrClientContext* context, + const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(formatDataRequest); + + cliprdrPlugin* cliprdr = (cliprdrPlugin*)context->handle; + WINPR_ASSERT(cliprdr); + + const UINT32 mask = + freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask); + if ((mask & (CLIPRDR_FLAG_REMOTE_TO_LOCAL | CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES)) == 0) + { + WLog_Print(cliprdr->log, WLOG_WARN, "remote -> local copy disabled, ignoring request"); + return CHANNEL_RC_OK; + } + + wStream* s = cliprdr_packet_new(CB_FORMAT_DATA_REQUEST, 0, 4); + if (!s) + { + WLog_Print(cliprdr->log, WLOG_ERROR, "cliprdr_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_Write_UINT32(s, formatDataRequest->requestedFormatId); /* requestedFormatId (4 bytes) */ + WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientFormatDataRequest(0x%08" PRIx32 " [%s])", + formatDataRequest->requestedFormatId, + ClipboardGetFormatIdString(formatDataRequest->requestedFormatId)); + return cliprdr_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +cliprdr_client_format_data_response(CliprdrClientContext* context, + const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(formatDataResponse); + + cliprdrPlugin* cliprdr = (cliprdrPlugin*)context->handle; + WINPR_ASSERT(cliprdr); + + WINPR_ASSERT( + (freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask) & + (CLIPRDR_FLAG_LOCAL_TO_REMOTE | CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES)) != 0); + + wStream* s = cliprdr_packet_new(CB_FORMAT_DATA_RESPONSE, formatDataResponse->common.msgFlags, + formatDataResponse->common.dataLen); + + if (!s) + { + WLog_Print(cliprdr->log, WLOG_ERROR, "cliprdr_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_Write(s, formatDataResponse->requestedFormatData, formatDataResponse->common.dataLen); + WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientFormatDataResponse"); + return cliprdr_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +cliprdr_client_file_contents_request(CliprdrClientContext* context, + const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + wStream* s = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(fileContentsRequest); + + cliprdrPlugin* cliprdr = (cliprdrPlugin*)context->handle; + if (!cliprdr) + return ERROR_INTERNAL_ERROR; + + const UINT32 mask = + freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask); + if ((mask & CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES) == 0) + { + WLog_Print(cliprdr->log, WLOG_WARN, "remote -> local file copy disabled, ignoring request"); + return CHANNEL_RC_OK; + } + + if (!cliprdr->hasHugeFileSupport) + { + if (((UINT64)fileContentsRequest->cbRequested + fileContentsRequest->nPositionLow) > + UINT32_MAX) + return ERROR_INVALID_PARAMETER; + if (fileContentsRequest->nPositionHigh != 0) + return ERROR_INVALID_PARAMETER; + } + + s = cliprdr_packet_file_contents_request_new(fileContentsRequest); + + if (!s) + { + WLog_Print(cliprdr->log, WLOG_ERROR, "cliprdr_packet_file_contents_request_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientFileContentsRequest: streamId: 0x%08" PRIX32 "", + fileContentsRequest->streamId); + return cliprdr_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +cliprdr_client_file_contents_response(CliprdrClientContext* context, + const CLIPRDR_FILE_CONTENTS_RESPONSE* fileContentsResponse) +{ + wStream* s = nullptr; + cliprdrPlugin* cliprdr = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(fileContentsResponse); + + cliprdr = (cliprdrPlugin*)context->handle; + WINPR_ASSERT(cliprdr); + + const UINT32 mask = + freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask); + if ((mask & CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES) == 0) + return cliprdr_send_error_response(cliprdr, CB_FILECONTENTS_RESPONSE); + + s = cliprdr_packet_file_contents_response_new(fileContentsResponse); + + if (!s) + { + WLog_Print(cliprdr->log, WLOG_ERROR, "cliprdr_packet_file_contents_response_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientFileContentsResponse: streamId: 0x%08" PRIX32 "", + fileContentsResponse->streamId); + return cliprdr_packet_send(cliprdr, s); +} + +static VOID VCAPITYPE cliprdr_virtual_channel_open_event_ex(LPVOID lpUserParam, DWORD openHandle, + UINT event, LPVOID pData, + UINT32 dataLength, UINT32 totalLength, + UINT32 dataFlags) +{ + UINT error = CHANNEL_RC_OK; + cliprdrPlugin* cliprdr = (cliprdrPlugin*)lpUserParam; + WINPR_ASSERT(cliprdr); + + switch (event) + { + case CHANNEL_EVENT_DATA_RECEIVED: + if (cliprdr->OpenHandle != openHandle) + { + WLog_Print(cliprdr->log, WLOG_ERROR, "error no match"); + return; + } + if ((error = channel_client_post_message(cliprdr->MsgsHandle, pData, dataLength, + totalLength, dataFlags))) + WLog_Print(cliprdr->log, WLOG_ERROR, "failed with error %" PRIu32 "", error); + + break; + + case CHANNEL_EVENT_WRITE_CANCELLED: + case CHANNEL_EVENT_WRITE_COMPLETE: + { + wStream* s = (wStream*)pData; + Stream_Free(s, TRUE); + } + break; + + case CHANNEL_EVENT_USER: + break; + default: + break; + } + + if (error && cliprdr->context->rdpcontext) + setChannelError(cliprdr->context->rdpcontext, error, + "cliprdr_virtual_channel_open_event_ex reported an error"); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_virtual_channel_event_connected(cliprdrPlugin* cliprdr, + WINPR_ATTR_UNUSED LPVOID pData, + WINPR_ATTR_UNUSED UINT32 dataLength) +{ + DWORD status = 0; + WINPR_ASSERT(cliprdr); + WINPR_ASSERT(cliprdr->context); + + WINPR_ASSERT(cliprdr->channelEntryPoints.pVirtualChannelOpenEx); + status = cliprdr->channelEntryPoints.pVirtualChannelOpenEx( + cliprdr->InitHandle, &cliprdr->OpenHandle, cliprdr->channelDef.name, + cliprdr_virtual_channel_open_event_ex); + if (status != CHANNEL_RC_OK) + return status; + + cliprdr->MsgsHandle = channel_client_create_handler( + cliprdr->context->rdpcontext, cliprdr, cliprdr_order_recv, CLIPRDR_SVC_CHANNEL_NAME); + if (!cliprdr->MsgsHandle) + return ERROR_INTERNAL_ERROR; + + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_virtual_channel_event_disconnected(cliprdrPlugin* cliprdr) +{ + UINT rc = 0; + + WINPR_ASSERT(cliprdr); + + channel_client_quit_handler(cliprdr->MsgsHandle); + cliprdr->MsgsHandle = nullptr; + + if (cliprdr->OpenHandle == 0) + return CHANNEL_RC_OK; + + WINPR_ASSERT(cliprdr->channelEntryPoints.pVirtualChannelCloseEx); + rc = cliprdr->channelEntryPoints.pVirtualChannelCloseEx(cliprdr->InitHandle, + cliprdr->OpenHandle); + + if (CHANNEL_RC_OK != rc) + { + WLog_Print(cliprdr->log, WLOG_ERROR, "pVirtualChannelClose failed with %s [%08" PRIX32 "]", + WTSErrorToString(rc), rc); + return rc; + } + + cliprdr->OpenHandle = 0; + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_virtual_channel_event_terminated(cliprdrPlugin* cliprdr) +{ + WINPR_ASSERT(cliprdr); + + cliprdr->InitHandle = nullptr; + free(cliprdr->context); + free(cliprdr); + return CHANNEL_RC_OK; +} + +static VOID VCAPITYPE cliprdr_virtual_channel_init_event_ex(LPVOID lpUserParam, LPVOID pInitHandle, + UINT event, LPVOID pData, + UINT dataLength) +{ + UINT error = CHANNEL_RC_OK; + cliprdrPlugin* cliprdr = (cliprdrPlugin*)lpUserParam; + WINPR_ASSERT(cliprdr); + + if (cliprdr->InitHandle != pInitHandle) + { + WLog_Print(cliprdr->log, WLOG_ERROR, "error no match"); + return; + } + + switch (event) + { + case CHANNEL_EVENT_CONNECTED: + if ((error = cliprdr_virtual_channel_event_connected(cliprdr, pData, dataLength))) + WLog_Print(cliprdr->log, WLOG_ERROR, + "cliprdr_virtual_channel_event_connected failed with error %" PRIu32 "!", + error); + + break; + + case CHANNEL_EVENT_DISCONNECTED: + if ((error = cliprdr_virtual_channel_event_disconnected(cliprdr))) + WLog_Print(cliprdr->log, WLOG_ERROR, + "cliprdr_virtual_channel_event_disconnected failed with error %" PRIu32 + "!", + error); + + break; + + case CHANNEL_EVENT_TERMINATED: + if ((error = cliprdr_virtual_channel_event_terminated(cliprdr))) + WLog_Print(cliprdr->log, WLOG_ERROR, + "cliprdr_virtual_channel_event_terminated failed with error %" PRIu32 + "!", + error); + break; + default: + break; + } + + if (error && cliprdr->context->rdpcontext) + setChannelError(cliprdr->context->rdpcontext, error, + "cliprdr_virtual_channel_init_event reported an error"); +} + +/* cliprdr is always built-in */ +#define VirtualChannelEntryEx cliprdr_VirtualChannelEntryEx + +FREERDP_ENTRY_POINT(BOOL VCAPITYPE VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS_EX pEntryPoints, + PVOID pInitHandle)) +{ + UINT rc = 0; + CHANNEL_ENTRY_POINTS_FREERDP_EX* pEntryPointsEx = nullptr; + cliprdrPlugin* cliprdr = (cliprdrPlugin*)calloc(1, sizeof(cliprdrPlugin)); + + wLog* log = WLog_Get(CHANNELS_TAG("cliprdr.client")); + WINPR_ASSERT(log); + + if (!cliprdr) + { + WLog_Print(log, WLOG_ERROR, "calloc failed!"); + return FALSE; + } + + cliprdr->log = log; + cliprdr->channelDef.options = CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP | + CHANNEL_OPTION_COMPRESS_RDP | CHANNEL_OPTION_SHOW_PROTOCOL; + (void)sprintf_s(cliprdr->channelDef.name, ARRAYSIZE(cliprdr->channelDef.name), + CLIPRDR_SVC_CHANNEL_NAME); + pEntryPointsEx = (CHANNEL_ENTRY_POINTS_FREERDP_EX*)pEntryPoints; + WINPR_ASSERT(pEntryPointsEx); + + if ((pEntryPointsEx->cbSize >= sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)) && + (pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER)) + { + CliprdrClientContext* context = + (CliprdrClientContext*)calloc(1, sizeof(CliprdrClientContext)); + + if (!context) + { + WLog_Print(cliprdr->log, WLOG_ERROR, "calloc failed!"); + free(cliprdr); + return FALSE; + } + + context->handle = (void*)cliprdr; + context->custom = nullptr; + context->ClientCapabilities = cliprdr_client_capabilities; + context->TempDirectory = cliprdr_temp_directory; + context->ClientFormatList = cliprdr_client_format_list; + context->ClientFormatListResponse = cliprdr_client_format_list_response; + context->ClientLockClipboardData = cliprdr_client_lock_clipboard_data; + context->ClientUnlockClipboardData = cliprdr_client_unlock_clipboard_data; + context->ClientFormatDataRequest = cliprdr_client_format_data_request; + context->ClientFormatDataResponse = cliprdr_client_format_data_response; + context->ClientFileContentsRequest = cliprdr_client_file_contents_request; + context->ClientFileContentsResponse = cliprdr_client_file_contents_response; + cliprdr->context = context; + context->rdpcontext = pEntryPointsEx->context; + } + + WLog_Print(cliprdr->log, WLOG_DEBUG, "VirtualChannelEntryEx"); + CopyMemory(&(cliprdr->channelEntryPoints), pEntryPoints, + sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)); + cliprdr->InitHandle = pInitHandle; + rc = cliprdr->channelEntryPoints.pVirtualChannelInitEx( + cliprdr, cliprdr->context, pInitHandle, &cliprdr->channelDef, 1, + VIRTUAL_CHANNEL_VERSION_WIN2000, cliprdr_virtual_channel_init_event_ex); + + if (CHANNEL_RC_OK != rc) + { + WLog_Print(cliprdr->log, WLOG_ERROR, "pVirtualChannelInit failed with %s [%08" PRIX32 "]", + WTSErrorToString(rc), rc); + free(cliprdr->context); + free(cliprdr); + return FALSE; + } + + cliprdr->channelEntryPoints.pInterface = cliprdr->context; + return TRUE; +} diff --git a/third_party/FreeRDP/channels/cliprdr/client/cliprdr_main.h b/third_party/FreeRDP/channels/cliprdr/client/cliprdr_main.h new file mode 100644 index 0000000..42b6b59 --- /dev/null +++ b/third_party/FreeRDP/channels/cliprdr/client/cliprdr_main.h @@ -0,0 +1,63 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Clipboard Virtual Channel + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_CLIPRDR_CLIENT_MAIN_H +#define FREERDP_CHANNEL_CLIPRDR_CLIENT_MAIN_H + +#include + +#include +#include +#include +#include + +typedef struct +{ + CHANNEL_DEF channelDef; + CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints; + + CliprdrClientContext* context; + + wLog* log; + void* InitHandle; + DWORD OpenHandle; + void* MsgsHandle; + + BOOL capabilitiesReceived; + BOOL useLongFormatNames; + BOOL streamFileClipEnabled; + BOOL fileClipNoFilePaths; + BOOL canLockClipData; + BOOL hasHugeFileSupport; + BOOL initialFormatListSent; +} cliprdrPlugin; + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL CliprdrClientContext* cliprdr_get_client_interface(cliprdrPlugin* cliprdr); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT cliprdr_send_error_response(cliprdrPlugin* cliprdr, UINT16 type); + +FREERDP_LOCAL extern const char type_FileGroupDescriptorW[]; + +FREERDP_LOCAL extern const char type_FileContents[]; + +#endif /* FREERDP_CHANNEL_CLIPRDR_CLIENT_MAIN_H */ diff --git a/third_party/FreeRDP/channels/cliprdr/cliprdr_common.c b/third_party/FreeRDP/channels/cliprdr/cliprdr_common.c new file mode 100644 index 0000000..06b752a --- /dev/null +++ b/third_party/FreeRDP/channels/cliprdr/cliprdr_common.c @@ -0,0 +1,565 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Cliprdr common + * + * Copyright 2013 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2019 Kobi Mizrachi + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#define TAG CHANNELS_TAG("cliprdr.common") + +#include "cliprdr_common.h" + +static const char* CB_MSG_TYPE_STR(UINT32 type) +{ + switch (type) + { + case CB_TYPE_NONE: + return "CB_TYPE_NONE"; + case CB_MONITOR_READY: + return "CB_MONITOR_READY"; + case CB_FORMAT_LIST: + return "CB_FORMAT_LIST"; + case CB_FORMAT_LIST_RESPONSE: + return "CB_FORMAT_LIST_RESPONSE"; + case CB_FORMAT_DATA_REQUEST: + return "CB_FORMAT_DATA_REQUEST"; + case CB_FORMAT_DATA_RESPONSE: + return "CB_FORMAT_DATA_RESPONSE"; + case CB_TEMP_DIRECTORY: + return "CB_TEMP_DIRECTORY"; + case CB_CLIP_CAPS: + return "CB_CLIP_CAPS"; + case CB_FILECONTENTS_REQUEST: + return "CB_FILECONTENTS_REQUEST"; + case CB_FILECONTENTS_RESPONSE: + return "CB_FILECONTENTS_RESPONSE"; + case CB_LOCK_CLIPDATA: + return "CB_LOCK_CLIPDATA"; + case CB_UNLOCK_CLIPDATA: + return "CB_UNLOCK_CLIPDATA"; + default: + return "UNKNOWN"; + } +} + +const char* CB_MSG_TYPE_STRING(UINT16 type, char* buffer, size_t size) +{ + (void)_snprintf(buffer, size, "%s [0x%04" PRIx16 "]", CB_MSG_TYPE_STR(type), type); + return buffer; +} + +const char* CB_MSG_FLAGS_STRING(UINT16 msgFlags, char* buffer, size_t size) +{ + if ((msgFlags & CB_RESPONSE_OK) != 0) + winpr_str_append("CB_RESPONSE_OK", buffer, size, "|"); + if ((msgFlags & CB_RESPONSE_FAIL) != 0) + winpr_str_append("CB_RESPONSE_FAIL", buffer, size, "|"); + if ((msgFlags & CB_ASCII_NAMES) != 0) + winpr_str_append("CB_ASCII_NAMES", buffer, size, "|"); + + const size_t len = strnlen(buffer, size); + if (!len) + winpr_str_append("NONE", buffer, size, ""); + + char val[32] = WINPR_C_ARRAY_INIT; + (void)_snprintf(val, sizeof(val), "[0x%04" PRIx16 "]", msgFlags); + winpr_str_append(val, buffer, size, "|"); + return buffer; +} + +static BOOL cliprdr_validate_file_contents_request(const CLIPRDR_FILE_CONTENTS_REQUEST* request) +{ + /* + * [MS-RDPECLIP] 2.2.5.3 File Contents Request PDU (CLIPRDR_FILECONTENTS_REQUEST). + * + * A request for the size of the file identified by the lindex field. The size MUST be + * returned as a 64-bit, unsigned integer. The cbRequested field MUST be set to + * 0x00000008 and both the nPositionLow and nPositionHigh fields MUST be + * set to 0x00000000. + */ + + if (request->dwFlags & FILECONTENTS_SIZE) + { + if (request->cbRequested != sizeof(UINT64)) + { + WLog_ERR(TAG, "cbRequested must be %" PRIuz ", got %" PRIu32 "", sizeof(UINT64), + request->cbRequested); + return FALSE; + } + + if (request->nPositionHigh != 0 || request->nPositionLow != 0) + { + WLog_ERR(TAG, "nPositionHigh and nPositionLow must be set to 0"); + return FALSE; + } + } + + return TRUE; +} + +wStream* cliprdr_packet_new(UINT16 msgType, UINT16 msgFlags, size_t dataLen) +{ + WINPR_ASSERT(dataLen < UINT32_MAX); + wStream* s = Stream_New(nullptr, dataLen + 8ULL); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return nullptr; + } + + Stream_Write_UINT16(s, msgType); + Stream_Write_UINT16(s, msgFlags); + /* Write actual length after the entire packet has been constructed. */ + Stream_Write_UINT32(s, 0); + return s; +} + +static void cliprdr_write_file_contents_request(wStream* s, + const CLIPRDR_FILE_CONTENTS_REQUEST* request) +{ + Stream_Write_UINT32(s, request->streamId); /* streamId (4 bytes) */ + Stream_Write_UINT32(s, request->listIndex); /* listIndex (4 bytes) */ + Stream_Write_UINT32(s, request->dwFlags); /* dwFlags (4 bytes) */ + Stream_Write_UINT32(s, request->nPositionLow); /* nPositionLow (4 bytes) */ + Stream_Write_UINT32(s, request->nPositionHigh); /* nPositionHigh (4 bytes) */ + Stream_Write_UINT32(s, request->cbRequested); /* cbRequested (4 bytes) */ + + if (request->haveClipDataId) + Stream_Write_UINT32(s, request->clipDataId); /* clipDataId (4 bytes) */ +} + +static inline void cliprdr_write_lock_unlock_clipdata(wStream* s, UINT32 clipDataId) +{ + Stream_Write_UINT32(s, clipDataId); +} + +static void cliprdr_write_lock_clipdata(wStream* s, + const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData) +{ + cliprdr_write_lock_unlock_clipdata(s, lockClipboardData->clipDataId); +} + +static void cliprdr_write_unlock_clipdata(wStream* s, + const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData) +{ + cliprdr_write_lock_unlock_clipdata(s, unlockClipboardData->clipDataId); +} + +static void cliprdr_write_file_contents_response(wStream* s, + const CLIPRDR_FILE_CONTENTS_RESPONSE* response) +{ + Stream_Write_UINT32(s, response->streamId); /* streamId (4 bytes) */ + Stream_Write(s, response->requestedData, response->cbRequested); +} + +wStream* cliprdr_packet_lock_clipdata_new(const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData) +{ + wStream* s = nullptr; + + if (!lockClipboardData) + return nullptr; + + s = cliprdr_packet_new(CB_LOCK_CLIPDATA, 0, 4); + + if (!s) + return nullptr; + + cliprdr_write_lock_clipdata(s, lockClipboardData); + return s; +} + +wStream* +cliprdr_packet_unlock_clipdata_new(const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData) +{ + wStream* s = nullptr; + + if (!unlockClipboardData) + return nullptr; + + s = cliprdr_packet_new(CB_UNLOCK_CLIPDATA, 0, 4); + + if (!s) + return nullptr; + + cliprdr_write_unlock_clipdata(s, unlockClipboardData); + return s; +} + +wStream* cliprdr_packet_file_contents_request_new(const CLIPRDR_FILE_CONTENTS_REQUEST* request) +{ + wStream* s = nullptr; + + if (!request) + return nullptr; + + s = cliprdr_packet_new(CB_FILECONTENTS_REQUEST, 0, 28); + + if (!s) + return nullptr; + + cliprdr_write_file_contents_request(s, request); + return s; +} + +wStream* cliprdr_packet_file_contents_response_new(const CLIPRDR_FILE_CONTENTS_RESPONSE* response) +{ + wStream* s = nullptr; + + if (!response) + return nullptr; + + s = cliprdr_packet_new(CB_FILECONTENTS_RESPONSE, response->common.msgFlags, + 4 + response->cbRequested); + + if (!s) + return nullptr; + + cliprdr_write_file_contents_response(s, response); + return s; +} + +wStream* cliprdr_packet_format_list_new(const CLIPRDR_FORMAT_LIST* formatList, + BOOL useLongFormatNames, BOOL useAsciiNames) +{ + WINPR_ASSERT(formatList); + + if (formatList->common.msgType != CB_FORMAT_LIST) + WLog_WARN(TAG, "called with invalid type %08" PRIx32, formatList->common.msgType); + + if (useLongFormatNames && useAsciiNames) + WLog_WARN(TAG, "called with invalid arguments useLongFormatNames=true && " + "useAsciiNames=true. useAsciiNames requires " + "useLongFormatNames=false, ignoring argument."); + + const UINT32 length = formatList->numFormats * 36; + const size_t formatNameCharSize = + (useLongFormatNames || !useAsciiNames) ? sizeof(WCHAR) : sizeof(CHAR); + + wStream* s = cliprdr_packet_new(CB_FORMAT_LIST, 0, length); + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_new failed!"); + return nullptr; + } + + for (UINT32 index = 0; index < formatList->numFormats; index++) + { + const CLIPRDR_FORMAT* format = &(formatList->formats[index]); + + const char* szFormatName = format->formatName; + size_t formatNameLength = 0; + if (szFormatName) + formatNameLength = strlen(szFormatName); + + size_t formatNameMaxLength = formatNameLength + 1; /* Ensure '\0' termination in output */ + if (!Stream_EnsureRemainingCapacity(s, + 4 + MAX(32, formatNameMaxLength * formatNameCharSize))) + goto fail; + + Stream_Write_UINT32(s, format->formatId); /* formatId (4 bytes) */ + + if (!useLongFormatNames) + { + formatNameMaxLength = useAsciiNames ? 32 : 16; + formatNameLength = MIN(formatNameMaxLength - 1, formatNameLength); + } + + if (szFormatName && (formatNameLength > 0)) + { + if (useAsciiNames) + { + Stream_Write(s, szFormatName, formatNameLength); + Stream_Zero(s, formatNameMaxLength - formatNameLength); + } + else + { + if (Stream_Write_UTF16_String_From_UTF8(s, formatNameMaxLength, szFormatName, + formatNameLength, TRUE) < 0) + goto fail; + } + } + else + Stream_Zero(s, formatNameMaxLength * formatNameCharSize); + } + + return s; + +fail: + Stream_Free(s, TRUE); + return nullptr; +} + +UINT cliprdr_read_unlock_clipdata(wStream* s, CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData) +{ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, unlockClipboardData->clipDataId); /* clipDataId (4 bytes) */ + return CHANNEL_RC_OK; +} + +UINT cliprdr_read_format_data_request(wStream* s, CLIPRDR_FORMAT_DATA_REQUEST* request) +{ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, request->requestedFormatId); /* requestedFormatId (4 bytes) */ + return CHANNEL_RC_OK; +} + +UINT cliprdr_read_format_data_response(wStream* s, CLIPRDR_FORMAT_DATA_RESPONSE* response) +{ + response->requestedFormatData = nullptr; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, response->common.dataLen)) + return ERROR_INVALID_DATA; + + if (response->common.dataLen) + response->requestedFormatData = Stream_ConstPointer(s); + + return CHANNEL_RC_OK; +} + +UINT cliprdr_read_file_contents_request(wStream* s, CLIPRDR_FILE_CONTENTS_REQUEST* request) +{ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 24)) + return ERROR_INVALID_DATA; + + request->haveClipDataId = FALSE; + Stream_Read_UINT32(s, request->streamId); /* streamId (4 bytes) */ + Stream_Read_UINT32(s, request->listIndex); /* listIndex (4 bytes) */ + Stream_Read_UINT32(s, request->dwFlags); /* dwFlags (4 bytes) */ + Stream_Read_UINT32(s, request->nPositionLow); /* nPositionLow (4 bytes) */ + Stream_Read_UINT32(s, request->nPositionHigh); /* nPositionHigh (4 bytes) */ + Stream_Read_UINT32(s, request->cbRequested); /* cbRequested (4 bytes) */ + + if (Stream_GetRemainingLength(s) >= 4) + { + Stream_Read_UINT32(s, request->clipDataId); /* clipDataId (4 bytes) */ + request->haveClipDataId = TRUE; + } + + if (!cliprdr_validate_file_contents_request(request)) + return ERROR_BAD_ARGUMENTS; + + return CHANNEL_RC_OK; +} + +UINT cliprdr_read_file_contents_response(wStream* s, CLIPRDR_FILE_CONTENTS_RESPONSE* response) +{ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, response->streamId); /* streamId (4 bytes) */ + response->requestedData = Stream_ConstPointer(s); /* requestedFileContentsData */ + + WINPR_ASSERT(response->common.dataLen >= 4); + response->cbRequested = response->common.dataLen - 4; + return CHANNEL_RC_OK; +} + +UINT cliprdr_read_format_list(wLog* log, wStream* s, CLIPRDR_FORMAT_LIST* formatList, + BOOL useLongFormatNames) +{ + UINT32 index = 0; + size_t formatNameLength = 0; + const char* szFormatName = nullptr; + const WCHAR* wszFormatName = nullptr; + wStream sub1buffer = WINPR_C_ARRAY_INIT; + CLIPRDR_FORMAT* formats = nullptr; + UINT error = ERROR_INTERNAL_ERROR; + + const BOOL asciiNames = (formatList->common.msgFlags & CB_ASCII_NAMES) != 0; + + index = 0; + /* empty format list */ + formatList->formats = nullptr; + formatList->numFormats = 0; + + wStream* sub1 = + Stream_StaticConstInit(&sub1buffer, Stream_ConstPointer(s), formatList->common.dataLen); + if (!Stream_SafeSeek(s, formatList->common.dataLen)) + return ERROR_INVALID_DATA; + + if (!formatList->common.dataLen) + { + } + else if (!useLongFormatNames) + { + const size_t cap = Stream_Capacity(sub1) / 36ULL; + if (cap > UINT32_MAX) + { + WLog_Print(log, WLOG_ERROR, "Invalid short format list length: %" PRIuz "", cap); + return ERROR_INTERNAL_ERROR; + } + formatList->numFormats = (UINT32)cap; + + if (formatList->numFormats) + formats = (CLIPRDR_FORMAT*)calloc(formatList->numFormats, sizeof(CLIPRDR_FORMAT)); + + if (!formats) + { + WLog_Print(log, WLOG_ERROR, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + formatList->formats = formats; + + while (Stream_GetRemainingLength(sub1) >= 4) + { + if (index >= formatList->numFormats) + goto error_out; + + CLIPRDR_FORMAT* format = &formats[index]; + + Stream_Read_UINT32(sub1, format->formatId); /* formatId (4 bytes) */ + + /* According to MS-RDPECLIP 2.2.3.1.1.1 formatName is "a 32-byte block containing + * the *null-terminated* name assigned to the Clipboard Format: (32 ASCII 8 characters + * or 16 Unicode characters)" + * However, both Windows RDSH and mstsc violate this specs as seen in the following + * example of a transferred short format name string: [R.i.c.h. .T.e.x.t. .F.o.r.m.a.t.] + * These are 16 unicode characters - *without* terminating null ! + */ + + szFormatName = Stream_ConstPointer(sub1); + wszFormatName = Stream_ConstPointer(sub1); + if (!Stream_SafeSeek(sub1, 32)) + goto error_out; + + free(format->formatName); + format->formatName = nullptr; + + if (asciiNames) + { + if (szFormatName[0]) + { + /* ensure null termination */ + format->formatName = strndup(szFormatName, 31); + if (!format->formatName) + { + WLog_Print(log, WLOG_ERROR, "malloc failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + } + } + else + { + if (wszFormatName[0]) + { + format->formatName = ConvertWCharNToUtf8Alloc(wszFormatName, 16, nullptr); + if (!format->formatName) + goto error_out; + } + } + + index++; + } + } + else + { + wStream sub2buffer = sub1buffer; + wStream* sub2 = &sub2buffer; + + while (Stream_GetRemainingLength(sub1) > 0) + { + size_t rest = 0; + if (!Stream_SafeSeek(sub1, 4)) /* formatId (4 bytes) */ + goto error_out; + + wszFormatName = Stream_ConstPointer(sub1); + rest = Stream_GetRemainingLength(sub1); + formatNameLength = _wcsnlen(wszFormatName, rest / sizeof(WCHAR)); + + if (!Stream_SafeSeek(sub1, (formatNameLength + 1) * sizeof(WCHAR))) + goto error_out; + formatList->numFormats++; + } + + if (formatList->numFormats) + formats = (CLIPRDR_FORMAT*)calloc(formatList->numFormats, sizeof(CLIPRDR_FORMAT)); + + if (!formats) + { + WLog_Print(log, WLOG_ERROR, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + formatList->formats = formats; + + while (Stream_GetRemainingLength(sub2) >= 4) + { + if (index >= formatList->numFormats) + goto error_out; + + size_t rest = 0; + CLIPRDR_FORMAT* format = &formats[index]; + + Stream_Read_UINT32(sub2, format->formatId); /* formatId (4 bytes) */ + + free(format->formatName); + format->formatName = nullptr; + + wszFormatName = Stream_ConstPointer(sub2); + rest = Stream_GetRemainingLength(sub2); + formatNameLength = _wcsnlen(wszFormatName, rest / sizeof(WCHAR)); + if (!Stream_SafeSeek(sub2, (formatNameLength + 1) * sizeof(WCHAR))) + goto error_out; + + if (formatNameLength) + { + format->formatName = + ConvertWCharNToUtf8Alloc(wszFormatName, formatNameLength, nullptr); + if (!format->formatName) + goto error_out; + } + + index++; + } + } + + return CHANNEL_RC_OK; + +error_out: + cliprdr_free_format_list(formatList); + return error; +} + +void cliprdr_free_format_list(CLIPRDR_FORMAT_LIST* formatList) +{ + if (formatList == nullptr) + return; + + if (formatList->formats) + { + for (UINT32 index = 0; index < formatList->numFormats; index++) + { + free(formatList->formats[index].formatName); + } + + free(formatList->formats); + formatList->formats = nullptr; + formatList->numFormats = 0; + } +} diff --git a/third_party/FreeRDP/channels/cliprdr/cliprdr_common.h b/third_party/FreeRDP/channels/cliprdr/cliprdr_common.h new file mode 100644 index 0000000..94b1426 --- /dev/null +++ b/third_party/FreeRDP/channels/cliprdr/cliprdr_common.h @@ -0,0 +1,97 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Cliprdr common + * + * Copyright 2013 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2019 Kobi Mizrachi + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPECLIP_COMMON_H +#define FREERDP_CHANNEL_RDPECLIP_COMMON_H + +#include +#include + +#include +#include + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL const char* CB_MSG_TYPE_STRING(UINT16 type, char* buffer, size_t size); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL const char* CB_MSG_FLAGS_STRING(UINT16 msgFlags, char* buffer, size_t size); + +WINPR_ATTR_MALLOC(Stream_Free, 1) +WINPR_ATTR_NODISCARD +FREERDP_LOCAL wStream* cliprdr_packet_new(UINT16 msgType, UINT16 msgFlags, size_t dataLen); + +WINPR_ATTR_MALLOC(Stream_Free, 1) +WINPR_ATTR_NODISCARD +FREERDP_LOCAL wStream* +cliprdr_packet_lock_clipdata_new(const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData); + +WINPR_ATTR_MALLOC(Stream_Free, 1) +WINPR_ATTR_NODISCARD +FREERDP_LOCAL wStream* +cliprdr_packet_unlock_clipdata_new(const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData); + +WINPR_ATTR_MALLOC(Stream_Free, 1) +WINPR_ATTR_NODISCARD +FREERDP_LOCAL wStream* +cliprdr_packet_file_contents_request_new(const CLIPRDR_FILE_CONTENTS_REQUEST* request); + +WINPR_ATTR_MALLOC(Stream_Free, 1) +WINPR_ATTR_NODISCARD +FREERDP_LOCAL wStream* +cliprdr_packet_file_contents_response_new(const CLIPRDR_FILE_CONTENTS_RESPONSE* response); + +WINPR_ATTR_MALLOC(Stream_Free, 1) +WINPR_ATTR_NODISCARD +FREERDP_LOCAL wStream* cliprdr_packet_format_list_new(const CLIPRDR_FORMAT_LIST* formatList, + BOOL useLongFormatNames, BOOL useAsciiNames); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT cliprdr_read_lock_clipdata(wStream* s, + CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT cliprdr_read_unlock_clipdata(wStream* s, + CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT cliprdr_read_format_data_request(wStream* s, + CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT cliprdr_read_format_data_response(wStream* s, + CLIPRDR_FORMAT_DATA_RESPONSE* response); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT +cliprdr_read_file_contents_request(wStream* s, CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT cliprdr_read_file_contents_response(wStream* s, + CLIPRDR_FILE_CONTENTS_RESPONSE* response); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT cliprdr_read_format_list(wLog* log, wStream* s, CLIPRDR_FORMAT_LIST* formatList, + BOOL useLongFormatNames); + +FREERDP_LOCAL void cliprdr_free_format_list(CLIPRDR_FORMAT_LIST* formatList); + +#endif /* FREERDP_CHANNEL_RDPECLIP_COMMON_H */ diff --git a/third_party/FreeRDP/channels/cliprdr/server/CMakeLists.txt b/third_party/FreeRDP/channels/cliprdr/server/CMakeLists.txt new file mode 100644 index 0000000..8b20b9d --- /dev/null +++ b/third_party/FreeRDP/channels/cliprdr/server/CMakeLists.txt @@ -0,0 +1,23 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_server("cliprdr") + +set(${MODULE_PREFIX}_SRCS cliprdr_main.c cliprdr_main.h ../cliprdr_common.h ../cliprdr_common.c) + +set(${MODULE_PREFIX}_LIBS freerdp) +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntry") diff --git a/third_party/FreeRDP/channels/cliprdr/server/cliprdr_main.c b/third_party/FreeRDP/channels/cliprdr/server/cliprdr_main.c new file mode 100644 index 0000000..ac93398 --- /dev/null +++ b/third_party/FreeRDP/channels/cliprdr/server/cliprdr_main.c @@ -0,0 +1,1523 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Clipboard Virtual Channel Extension + * + * Copyright 2013 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include + +#include +#include +#include "cliprdr_main.h" +#include "../cliprdr_common.h" + +/** + * Initialization Sequence\n + * Client Server\n + * | |\n + * |<----------------------Server Clipboard Capabilities PDU-----------------|\n + * |<-----------------------------Monitor Ready PDU--------------------------|\n + * |-----------------------Client Clipboard Capabilities PDU---------------->|\n + * |---------------------------Temporary Directory PDU---------------------->|\n + * |-------------------------------Format List PDU-------------------------->|\n + * |<--------------------------Format List Response PDU----------------------|\n + * + */ + +/** + * Data Transfer Sequences\n + * Shared Local\n + * Clipboard Owner Clipboard Owner\n + * | |\n + * |-------------------------------------------------------------------------|\n _ + * |-------------------------------Format List PDU-------------------------->|\n | + * |<--------------------------Format List Response PDU----------------------|\n _| Copy + * Sequence + * |<---------------------Lock Clipboard Data PDU (Optional)-----------------|\n + * |-------------------------------------------------------------------------|\n + * |-------------------------------------------------------------------------|\n _ + * |<--------------------------Format Data Request PDU-----------------------|\n | Paste + * Sequence Palette, + * |---------------------------Format Data Response PDU--------------------->|\n _| Metafile, + * File List Data + * |-------------------------------------------------------------------------|\n + * |-------------------------------------------------------------------------|\n _ + * |<------------------------Format Contents Request PDU---------------------|\n | Paste + * Sequence + * |-------------------------Format Contents Response PDU------------------->|\n _| File + * Stream Data + * |<---------------------Lock Clipboard Data PDU (Optional)-----------------|\n + * |-------------------------------------------------------------------------|\n + * + */ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_packet_send(CliprdrServerPrivate* cliprdr, wStream* s) +{ + UINT rc = 0; + size_t pos = 0; + BOOL status = 0; + UINT32 dataLen = 0; + ULONG written = 0; + + WINPR_ASSERT(cliprdr); + + pos = Stream_GetPosition(s); + if ((pos < 8) || (pos > UINT32_MAX)) + { + rc = ERROR_NO_DATA; + goto fail; + } + + dataLen = (UINT32)(pos - 8); + Stream_SetPosition(s, 4); + Stream_Write_UINT32(s, dataLen); + + WINPR_ASSERT(pos <= UINT32_MAX); + status = WTSVirtualChannelWrite(cliprdr->ChannelHandle, Stream_BufferAs(s, char), (UINT32)pos, + &written); + rc = status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +fail: + Stream_Free(s, TRUE); + return rc; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_capabilities(CliprdrServerContext* context, + const CLIPRDR_CAPABILITIES* capabilities) +{ + size_t offset = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(capabilities); + + CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*)context->handle; + + if (capabilities->common.msgType != CB_CLIP_CAPS) + WLog_WARN(TAG, "called with invalid type %08" PRIx32, capabilities->common.msgType); + + if (capabilities->cCapabilitiesSets > UINT16_MAX) + { + WLog_ERR(TAG, "Invalid number of capability sets in clipboard caps"); + return ERROR_INVALID_PARAMETER; + } + + wStream* s = cliprdr_packet_new(CB_CLIP_CAPS, 0, 4 + CB_CAPSTYPE_GENERAL_LEN); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_Write_UINT16(s, + (UINT16)capabilities->cCapabilitiesSets); /* cCapabilitiesSets (2 bytes) */ + Stream_Write_UINT16(s, 0); /* pad1 (2 bytes) */ + for (UINT32 x = 0; x < capabilities->cCapabilitiesSets; x++) + { + const CLIPRDR_CAPABILITY_SET* cap = + (const CLIPRDR_CAPABILITY_SET*)(((const BYTE*)capabilities->capabilitySets) + offset); + offset += cap->capabilitySetLength; + + switch (cap->capabilitySetType) + { + case CB_CAPSTYPE_GENERAL: + { + const CLIPRDR_GENERAL_CAPABILITY_SET* generalCapabilitySet = + (const CLIPRDR_GENERAL_CAPABILITY_SET*)cap; + Stream_Write_UINT16( + s, generalCapabilitySet->capabilitySetType); /* capabilitySetType (2 bytes) */ + Stream_Write_UINT16( + s, generalCapabilitySet->capabilitySetLength); /* lengthCapability (2 bytes) */ + Stream_Write_UINT32(s, generalCapabilitySet->version); /* version (4 bytes) */ + Stream_Write_UINT32( + s, generalCapabilitySet->generalFlags); /* generalFlags (4 bytes) */ + } + break; + + default: + WLog_WARN(TAG, "Unknown capability set type %08" PRIx16, cap->capabilitySetType); + if (!Stream_SafeSeek(s, cap->capabilitySetLength)) + { + WLog_ERR(TAG, "short stream"); + Stream_Free(s, TRUE); + return ERROR_NO_DATA; + } + break; + } + } + WLog_DBG(TAG, "ServerCapabilities"); + return cliprdr_server_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_monitor_ready(CliprdrServerContext* context, + const CLIPRDR_MONITOR_READY* monitorReady) +{ + wStream* s = nullptr; + CliprdrServerPrivate* cliprdr = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(monitorReady); + + cliprdr = (CliprdrServerPrivate*)context->handle; + + if (monitorReady->common.msgType != CB_MONITOR_READY) + WLog_WARN(TAG, "called with invalid type %08" PRIx32, monitorReady->common.msgType); + + s = cliprdr_packet_new(CB_MONITOR_READY, monitorReady->common.msgFlags, + monitorReady->common.dataLen); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + WLog_DBG(TAG, "ServerMonitorReady"); + return cliprdr_server_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_format_list(CliprdrServerContext* context, + const CLIPRDR_FORMAT_LIST* formatList) +{ + wStream* s = nullptr; + CliprdrServerPrivate* cliprdr = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(formatList); + + cliprdr = (CliprdrServerPrivate*)context->handle; + + s = cliprdr_packet_format_list_new(formatList, context->useLongFormatNames, FALSE); + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_format_list_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + WLog_DBG(TAG, "ServerFormatList: numFormats: %" PRIu32 "", formatList->numFormats); + return cliprdr_server_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +cliprdr_server_format_list_response(CliprdrServerContext* context, + const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse) +{ + wStream* s = nullptr; + CliprdrServerPrivate* cliprdr = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(formatListResponse); + + cliprdr = (CliprdrServerPrivate*)context->handle; + if (formatListResponse->common.msgType != CB_FORMAT_LIST_RESPONSE) + WLog_WARN(TAG, "called with invalid type %08" PRIx32, formatListResponse->common.msgType); + + s = cliprdr_packet_new(CB_FORMAT_LIST_RESPONSE, formatListResponse->common.msgFlags, + formatListResponse->common.dataLen); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + WLog_DBG(TAG, "ServerFormatListResponse"); + return cliprdr_server_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_lock_clipboard_data(CliprdrServerContext* context, + const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData) +{ + wStream* s = nullptr; + CliprdrServerPrivate* cliprdr = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(lockClipboardData); + + cliprdr = (CliprdrServerPrivate*)context->handle; + if (lockClipboardData->common.msgType != CB_LOCK_CLIPDATA) + WLog_WARN(TAG, "called with invalid type %08" PRIx32, lockClipboardData->common.msgType); + + s = cliprdr_packet_lock_clipdata_new(lockClipboardData); + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_lock_clipdata_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + WLog_DBG(TAG, "ServerLockClipboardData: clipDataId: 0x%08" PRIX32 "", + lockClipboardData->clipDataId); + return cliprdr_server_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +cliprdr_server_unlock_clipboard_data(CliprdrServerContext* context, + const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData) +{ + wStream* s = nullptr; + CliprdrServerPrivate* cliprdr = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(unlockClipboardData); + + cliprdr = (CliprdrServerPrivate*)context->handle; + if (unlockClipboardData->common.msgType != CB_UNLOCK_CLIPDATA) + WLog_WARN(TAG, "called with invalid type %08" PRIx32, unlockClipboardData->common.msgType); + + s = cliprdr_packet_unlock_clipdata_new(unlockClipboardData); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_unlock_clipdata_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + WLog_DBG(TAG, "ServerUnlockClipboardData: clipDataId: 0x%08" PRIX32 "", + unlockClipboardData->clipDataId); + return cliprdr_server_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_format_data_request(CliprdrServerContext* context, + const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest) +{ + wStream* s = nullptr; + CliprdrServerPrivate* cliprdr = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(formatDataRequest); + + cliprdr = (CliprdrServerPrivate*)context->handle; + if (formatDataRequest->common.msgType != CB_FORMAT_DATA_REQUEST) + WLog_WARN(TAG, "called with invalid type %08" PRIx32, formatDataRequest->common.msgType); + + s = cliprdr_packet_new(CB_FORMAT_DATA_REQUEST, formatDataRequest->common.msgFlags, + formatDataRequest->common.dataLen); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_Write_UINT32(s, formatDataRequest->requestedFormatId); /* requestedFormatId (4 bytes) */ + WLog_DBG(TAG, "ClientFormatDataRequest"); + return cliprdr_server_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +cliprdr_server_format_data_response(CliprdrServerContext* context, + const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse) +{ + wStream* s = nullptr; + CliprdrServerPrivate* cliprdr = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(formatDataResponse); + + cliprdr = (CliprdrServerPrivate*)context->handle; + + if (formatDataResponse->common.msgType != CB_FORMAT_DATA_RESPONSE) + WLog_WARN(TAG, "called with invalid type %08" PRIx32, formatDataResponse->common.msgType); + + s = cliprdr_packet_new(CB_FORMAT_DATA_RESPONSE, formatDataResponse->common.msgFlags, + formatDataResponse->common.dataLen); + + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_Write(s, formatDataResponse->requestedFormatData, formatDataResponse->common.dataLen); + WLog_DBG(TAG, "ServerFormatDataResponse"); + return cliprdr_server_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +cliprdr_server_file_contents_request(CliprdrServerContext* context, + const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + wStream* s = nullptr; + CliprdrServerPrivate* cliprdr = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(fileContentsRequest); + + cliprdr = (CliprdrServerPrivate*)context->handle; + + if (fileContentsRequest->common.msgType != CB_FILECONTENTS_REQUEST) + WLog_WARN(TAG, "called with invalid type %08" PRIx32, fileContentsRequest->common.msgType); + + s = cliprdr_packet_file_contents_request_new(fileContentsRequest); + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_file_contents_request_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + WLog_DBG(TAG, "ServerFileContentsRequest: streamId: 0x%08" PRIX32 "", + fileContentsRequest->streamId); + return cliprdr_server_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +cliprdr_server_file_contents_response(CliprdrServerContext* context, + const CLIPRDR_FILE_CONTENTS_RESPONSE* fileContentsResponse) +{ + wStream* s = nullptr; + CliprdrServerPrivate* cliprdr = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(fileContentsResponse); + + cliprdr = (CliprdrServerPrivate*)context->handle; + + if (fileContentsResponse->common.msgType != CB_FILECONTENTS_RESPONSE) + WLog_WARN(TAG, "called with invalid type %08" PRIx32, fileContentsResponse->common.msgType); + + s = cliprdr_packet_file_contents_response_new(fileContentsResponse); + if (!s) + { + WLog_ERR(TAG, "cliprdr_packet_file_contents_response_new failed!"); + return ERROR_INTERNAL_ERROR; + } + + WLog_DBG(TAG, "ServerFileContentsResponse: streamId: 0x%08" PRIX32 "", + fileContentsResponse->streamId); + return cliprdr_server_packet_send(cliprdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_receive_general_capability(CliprdrServerContext* context, wStream* s, + CLIPRDR_GENERAL_CAPABILITY_SET* cap_set) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(cap_set); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, cap_set->version); /* version (4 bytes) */ + Stream_Read_UINT32(s, cap_set->generalFlags); /* generalFlags (4 bytes) */ + + if (context->useLongFormatNames) + context->useLongFormatNames = (cap_set->generalFlags & CB_USE_LONG_FORMAT_NAMES) != 0; + + if (context->streamFileClipEnabled) + context->streamFileClipEnabled = (cap_set->generalFlags & CB_STREAM_FILECLIP_ENABLED) != 0; + + if (context->fileClipNoFilePaths) + context->fileClipNoFilePaths = (cap_set->generalFlags & CB_FILECLIP_NO_FILE_PATHS) != 0; + + if (context->canLockClipData) + context->canLockClipData = (cap_set->generalFlags & CB_CAN_LOCK_CLIPDATA) != 0; + + if (context->hasHugeFileSupport) + context->hasHugeFileSupport = (cap_set->generalFlags & CB_HUGE_FILE_SUPPORT_ENABLED) != 0; + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_receive_capabilities(CliprdrServerContext* context, wStream* s, + const CLIPRDR_HEADER* header) +{ + UINT16 capabilitySetType = 0; + UINT16 capabilitySetLength = 0; + UINT error = ERROR_INVALID_DATA; + size_t cap_sets_size = 0; + CLIPRDR_CAPABILITIES capabilities = WINPR_C_ARRAY_INIT; + CLIPRDR_CAPABILITY_SET* capSet = nullptr; + + WINPR_ASSERT(context); + WINPR_UNUSED(header); + + WLog_DBG(TAG, "CliprdrClientCapabilities"); + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, capabilities.cCapabilitiesSets); /* cCapabilitiesSets (2 bytes) */ + Stream_Seek_UINT16(s); /* pad1 (2 bytes) */ + + for (size_t index = 0; index < capabilities.cCapabilitiesSets; index++) + { + void* tmp = nullptr; + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + goto out; + Stream_Read_UINT16(s, capabilitySetType); /* capabilitySetType (2 bytes) */ + Stream_Read_UINT16(s, capabilitySetLength); /* capabilitySetLength (2 bytes) */ + + cap_sets_size += capabilitySetLength; + + if (cap_sets_size > 0) + tmp = realloc(capabilities.capabilitySets, cap_sets_size); + if (tmp == nullptr) + { + WLog_ERR(TAG, "capabilities.capabilitySets realloc failed!"); + free(capabilities.capabilitySets); + return CHANNEL_RC_NO_MEMORY; + } + + capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET*)tmp; + + capSet = &(capabilities.capabilitySets[index]); + + capSet->capabilitySetType = capabilitySetType; + capSet->capabilitySetLength = capabilitySetLength; + + switch (capSet->capabilitySetType) + { + case CB_CAPSTYPE_GENERAL: + error = cliprdr_server_receive_general_capability( + context, s, (CLIPRDR_GENERAL_CAPABILITY_SET*)capSet); + if (error) + { + WLog_ERR(TAG, + "cliprdr_server_receive_general_capability failed with error %" PRIu32 + "", + error); + goto out; + } + break; + + default: + WLog_ERR(TAG, "unknown cliprdr capability set: %" PRIu16 "", + capSet->capabilitySetType); + goto out; + } + } + + error = CHANNEL_RC_OK; + IFCALLRET(context->ClientCapabilities, error, context, &capabilities); +out: + free(capabilities.capabilitySets); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_receive_temporary_directory(CliprdrServerContext* context, wStream* s, + const CLIPRDR_HEADER* header) +{ + size_t length = 0; + CLIPRDR_TEMP_DIRECTORY tempDirectory = WINPR_C_ARRAY_INIT; + CliprdrServerPrivate* cliprdr = nullptr; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_UNUSED(header); + + cliprdr = (CliprdrServerPrivate*)context->handle; + WINPR_ASSERT(cliprdr); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, + ARRAYSIZE(cliprdr->temporaryDirectory) * sizeof(WCHAR))) + return CHANNEL_RC_NO_MEMORY; + + const WCHAR* wszTempDir = Stream_ConstPointer(s); + + if (wszTempDir[ARRAYSIZE(cliprdr->temporaryDirectory) - 1] != 0) + { + WLog_ERR(TAG, "wszTempDir[259] was not 0"); + return ERROR_INVALID_DATA; + } + + if (ConvertWCharNToUtf8(wszTempDir, ARRAYSIZE(cliprdr->temporaryDirectory), + cliprdr->temporaryDirectory, + ARRAYSIZE(cliprdr->temporaryDirectory)) < 0) + { + WLog_ERR(TAG, "failed to convert temporary directory name"); + return ERROR_INVALID_DATA; + } + + length = strnlen(cliprdr->temporaryDirectory, ARRAYSIZE(cliprdr->temporaryDirectory)); + + if (length >= ARRAYSIZE(cliprdr->temporaryDirectory)) + length = ARRAYSIZE(cliprdr->temporaryDirectory) - 1; + + CopyMemory(tempDirectory.szTempDir, cliprdr->temporaryDirectory, length); + tempDirectory.szTempDir[length] = '\0'; + WLog_DBG(TAG, "CliprdrTemporaryDirectory: %s", cliprdr->temporaryDirectory); + IFCALLRET(context->TempDirectory, error, context, &tempDirectory); + + if (error) + WLog_ERR(TAG, "TempDirectory failed with error %" PRIu32 "!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_receive_format_list(CliprdrServerContext* context, wStream* s, + const CLIPRDR_HEADER* header) +{ + CLIPRDR_FORMAT_LIST formatList = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + formatList.common.msgType = CB_FORMAT_LIST; + formatList.common.msgFlags = header->msgFlags; + formatList.common.dataLen = header->dataLen; + + wLog* log = WLog_Get(TAG); + if ((error = cliprdr_read_format_list(log, s, &formatList, context->useLongFormatNames))) + goto out; + + WLog_Print(log, WLOG_DEBUG, "ClientFormatList: numFormats: %" PRIu32 "", formatList.numFormats); + IFCALLRET(context->ClientFormatList, error, context, &formatList); + + if (error) + WLog_Print(log, WLOG_ERROR, "ClientFormatList failed with error %" PRIu32 "!", error); + +out: + cliprdr_free_format_list(&formatList); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_receive_format_list_response(CliprdrServerContext* context, wStream* s, + const CLIPRDR_HEADER* header) +{ + CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + WINPR_UNUSED(s); + WLog_DBG(TAG, "CliprdrClientFormatListResponse"); + formatListResponse.common.msgType = CB_FORMAT_LIST_RESPONSE; + formatListResponse.common.msgFlags = header->msgFlags; + formatListResponse.common.dataLen = header->dataLen; + IFCALLRET(context->ClientFormatListResponse, error, context, &formatListResponse); + + if (error) + WLog_ERR(TAG, "ClientFormatListResponse failed with error %" PRIu32 "!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_receive_lock_clipdata(CliprdrServerContext* context, wStream* s, + const CLIPRDR_HEADER* header) +{ + CLIPRDR_LOCK_CLIPBOARD_DATA lockClipboardData = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + WLog_DBG(TAG, "CliprdrClientLockClipData"); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + lockClipboardData.common.msgType = CB_LOCK_CLIPDATA; + lockClipboardData.common.msgFlags = header->msgFlags; + lockClipboardData.common.dataLen = header->dataLen; + Stream_Read_UINT32(s, lockClipboardData.clipDataId); /* clipDataId (4 bytes) */ + IFCALLRET(context->ClientLockClipboardData, error, context, &lockClipboardData); + + if (error) + WLog_ERR(TAG, "ClientLockClipboardData failed with error %" PRIu32 "!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_receive_unlock_clipdata(CliprdrServerContext* context, wStream* s, + const CLIPRDR_HEADER* header) +{ + CLIPRDR_UNLOCK_CLIPBOARD_DATA unlockClipboardData = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + WLog_DBG(TAG, "CliprdrClientUnlockClipData"); + + unlockClipboardData.common.msgType = CB_UNLOCK_CLIPDATA; + unlockClipboardData.common.msgFlags = header->msgFlags; + unlockClipboardData.common.dataLen = header->dataLen; + + if ((error = cliprdr_read_unlock_clipdata(s, &unlockClipboardData))) + return error; + + IFCALLRET(context->ClientUnlockClipboardData, error, context, &unlockClipboardData); + + if (error) + WLog_ERR(TAG, "ClientUnlockClipboardData failed with error %" PRIu32 "!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_receive_format_data_request(CliprdrServerContext* context, wStream* s, + const CLIPRDR_HEADER* header) +{ + CLIPRDR_FORMAT_DATA_REQUEST formatDataRequest = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + WLog_DBG(TAG, "CliprdrClientFormatDataRequest"); + formatDataRequest.common.msgType = CB_FORMAT_DATA_REQUEST; + formatDataRequest.common.msgFlags = header->msgFlags; + formatDataRequest.common.dataLen = header->dataLen; + + if ((error = cliprdr_read_format_data_request(s, &formatDataRequest))) + return error; + + context->lastRequestedFormatId = formatDataRequest.requestedFormatId; + IFCALLRET(context->ClientFormatDataRequest, error, context, &formatDataRequest); + + if (error) + WLog_ERR(TAG, "ClientFormatDataRequest failed with error %" PRIu32 "!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_receive_format_data_response(CliprdrServerContext* context, wStream* s, + const CLIPRDR_HEADER* header) +{ + CLIPRDR_FORMAT_DATA_RESPONSE formatDataResponse = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + WLog_DBG(TAG, "CliprdrClientFormatDataResponse"); + formatDataResponse.common.msgType = CB_FORMAT_DATA_RESPONSE; + formatDataResponse.common.msgFlags = header->msgFlags; + formatDataResponse.common.dataLen = header->dataLen; + + if ((error = cliprdr_read_format_data_response(s, &formatDataResponse))) + return error; + + IFCALLRET(context->ClientFormatDataResponse, error, context, &formatDataResponse); + + if (error) + WLog_ERR(TAG, "ClientFormatDataResponse failed with error %" PRIu32 "!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_receive_filecontents_request(CliprdrServerContext* context, wStream* s, + const CLIPRDR_HEADER* header) +{ + CLIPRDR_FILE_CONTENTS_REQUEST request = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + WLog_DBG(TAG, "CliprdrClientFileContentsRequest"); + request.common.msgType = CB_FILECONTENTS_REQUEST; + request.common.msgFlags = header->msgFlags; + request.common.dataLen = header->dataLen; + + if ((error = cliprdr_read_file_contents_request(s, &request))) + return error; + + if (!context->hasHugeFileSupport) + { + if (request.nPositionHigh > 0) + return ERROR_INVALID_DATA; + if ((UINT64)request.nPositionLow + request.cbRequested > UINT32_MAX) + return ERROR_INVALID_DATA; + } + IFCALLRET(context->ClientFileContentsRequest, error, context, &request); + + if (error) + WLog_ERR(TAG, "ClientFileContentsRequest failed with error %" PRIu32 "!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_receive_filecontents_response(CliprdrServerContext* context, wStream* s, + const CLIPRDR_HEADER* header) +{ + CLIPRDR_FILE_CONTENTS_RESPONSE response = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + WLog_DBG(TAG, "CliprdrClientFileContentsResponse"); + + response.common.msgType = CB_FILECONTENTS_RESPONSE; + response.common.msgFlags = header->msgFlags; + response.common.dataLen = header->dataLen; + + if ((error = cliprdr_read_file_contents_response(s, &response))) + return error; + + IFCALLRET(context->ClientFileContentsResponse, error, context, &response); + + if (error) + WLog_ERR(TAG, "ClientFileContentsResponse failed with error %" PRIu32 "!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_receive_pdu(CliprdrServerContext* context, wStream* s, + const CLIPRDR_HEADER* header) +{ + UINT error = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + char buffer1[64] = WINPR_C_ARRAY_INIT; + char buffer2[64] = WINPR_C_ARRAY_INIT; + WLog_DBG(TAG, "CliprdrServerReceivePdu: msgType: %s, msgFlags: %s dataLen: %" PRIu32 "", + CB_MSG_TYPE_STRING(header->msgType, buffer1, sizeof(buffer1)), + CB_MSG_FLAGS_STRING(header->msgFlags, buffer2, sizeof(buffer2)), header->dataLen); + + switch (header->msgType) + { + case CB_CLIP_CAPS: + if ((error = cliprdr_server_receive_capabilities(context, s, header))) + WLog_ERR(TAG, "cliprdr_server_receive_capabilities failed with error %" PRIu32 "!", + error); + + break; + + case CB_TEMP_DIRECTORY: + if ((error = cliprdr_server_receive_temporary_directory(context, s, header))) + WLog_ERR(TAG, + "cliprdr_server_receive_temporary_directory failed with error %" PRIu32 + "!", + error); + + break; + + case CB_FORMAT_LIST: + if ((error = cliprdr_server_receive_format_list(context, s, header))) + WLog_ERR(TAG, "cliprdr_server_receive_format_list failed with error %" PRIu32 "!", + error); + + break; + + case CB_FORMAT_LIST_RESPONSE: + if ((error = cliprdr_server_receive_format_list_response(context, s, header))) + WLog_ERR(TAG, + "cliprdr_server_receive_format_list_response failed with error %" PRIu32 + "!", + error); + + break; + + case CB_LOCK_CLIPDATA: + if ((error = cliprdr_server_receive_lock_clipdata(context, s, header))) + WLog_ERR(TAG, "cliprdr_server_receive_lock_clipdata failed with error %" PRIu32 "!", + error); + + break; + + case CB_UNLOCK_CLIPDATA: + if ((error = cliprdr_server_receive_unlock_clipdata(context, s, header))) + WLog_ERR(TAG, + "cliprdr_server_receive_unlock_clipdata failed with error %" PRIu32 "!", + error); + + break; + + case CB_FORMAT_DATA_REQUEST: + if ((error = cliprdr_server_receive_format_data_request(context, s, header))) + WLog_ERR(TAG, + "cliprdr_server_receive_format_data_request failed with error %" PRIu32 + "!", + error); + + break; + + case CB_FORMAT_DATA_RESPONSE: + if ((error = cliprdr_server_receive_format_data_response(context, s, header))) + WLog_ERR(TAG, + "cliprdr_server_receive_format_data_response failed with error %" PRIu32 + "!", + error); + + break; + + case CB_FILECONTENTS_REQUEST: + if ((error = cliprdr_server_receive_filecontents_request(context, s, header))) + WLog_ERR(TAG, + "cliprdr_server_receive_filecontents_request failed with error %" PRIu32 + "!", + error); + + break; + + case CB_FILECONTENTS_RESPONSE: + if ((error = cliprdr_server_receive_filecontents_response(context, s, header))) + WLog_ERR(TAG, + "cliprdr_server_receive_filecontents_response failed with error %" PRIu32 + "!", + error); + + break; + + default: + error = ERROR_INVALID_DATA; + WLog_ERR(TAG, "Unexpected clipboard PDU type: %" PRIu16 "", header->msgType); + break; + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_init(CliprdrServerContext* context) +{ + UINT32 generalFlags = 0; + CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet = WINPR_C_ARRAY_INIT; + UINT error = 0; + CLIPRDR_MONITOR_READY monitorReady = WINPR_C_ARRAY_INIT; + CLIPRDR_CAPABILITIES capabilities = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(context); + + monitorReady.common.msgType = CB_MONITOR_READY; + capabilities.common.msgType = CB_CLIP_CAPS; + + if (context->useLongFormatNames) + generalFlags |= CB_USE_LONG_FORMAT_NAMES; + + if (context->streamFileClipEnabled) + generalFlags |= CB_STREAM_FILECLIP_ENABLED; + + if (context->fileClipNoFilePaths) + generalFlags |= CB_FILECLIP_NO_FILE_PATHS; + + if (context->canLockClipData) + generalFlags |= CB_CAN_LOCK_CLIPDATA; + + if (context->hasHugeFileSupport) + generalFlags |= CB_HUGE_FILE_SUPPORT_ENABLED; + + capabilities.common.msgType = CB_CLIP_CAPS; + capabilities.common.msgFlags = 0; + capabilities.common.dataLen = 4 + CB_CAPSTYPE_GENERAL_LEN; + capabilities.cCapabilitiesSets = 1; + capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET*)&generalCapabilitySet; + generalCapabilitySet.capabilitySetType = CB_CAPSTYPE_GENERAL; + generalCapabilitySet.capabilitySetLength = CB_CAPSTYPE_GENERAL_LEN; + generalCapabilitySet.version = CB_CAPS_VERSION_2; + generalCapabilitySet.generalFlags = generalFlags; + + if ((error = context->ServerCapabilities(context, &capabilities))) + { + WLog_ERR(TAG, "ServerCapabilities failed with error %" PRIu32 "!", error); + return error; + } + + if ((error = context->MonitorReady(context, &monitorReady))) + { + WLog_ERR(TAG, "MonitorReady failed with error %" PRIu32 "!", error); + return error; + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_read(CliprdrServerContext* context) +{ + wStream* s = nullptr; + size_t position = 0; + DWORD BytesToRead = 0; + DWORD BytesReturned = 0; + CLIPRDR_HEADER header = WINPR_C_ARRAY_INIT; + CliprdrServerPrivate* cliprdr = nullptr; + UINT error = 0; + DWORD status = 0; + + WINPR_ASSERT(context); + + cliprdr = (CliprdrServerPrivate*)context->handle; + WINPR_ASSERT(cliprdr); + + s = cliprdr->s; + + if (Stream_GetPosition(s) < CLIPRDR_HEADER_LENGTH) + { + BytesReturned = 0; + BytesToRead = (UINT32)(CLIPRDR_HEADER_LENGTH - Stream_GetPosition(s)); + status = WaitForSingleObject(cliprdr->ChannelEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + return error; + } + + if (status == WAIT_TIMEOUT) + return CHANNEL_RC_OK; + + if (!WTSVirtualChannelRead(cliprdr->ChannelHandle, 0, Stream_Pointer(s), BytesToRead, + &BytesReturned)) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_Seek(s, BytesReturned); + } + + if (Stream_GetPosition(s) >= CLIPRDR_HEADER_LENGTH) + { + position = Stream_GetPosition(s); + Stream_ResetPosition(s); + Stream_Read_UINT16(s, header.msgType); /* msgType (2 bytes) */ + Stream_Read_UINT16(s, header.msgFlags); /* msgFlags (2 bytes) */ + Stream_Read_UINT32(s, header.dataLen); /* dataLen (4 bytes) */ + + if (!Stream_EnsureRemainingCapacity(s, header.dataLen)) + { + WLog_ERR(TAG, "Stream_EnsureCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_SetPosition(s, position); + + if (Stream_GetPosition(s) < (header.dataLen + CLIPRDR_HEADER_LENGTH)) + { + BytesReturned = 0; + BytesToRead = + (UINT32)((header.dataLen + CLIPRDR_HEADER_LENGTH) - Stream_GetPosition(s)); + status = WaitForSingleObject(cliprdr->ChannelEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + return error; + } + + if (status == WAIT_TIMEOUT) + return CHANNEL_RC_OK; + + if (!WTSVirtualChannelRead(cliprdr->ChannelHandle, 0, Stream_Pointer(s), BytesToRead, + &BytesReturned)) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_Seek(s, BytesReturned); + } + + if (Stream_GetPosition(s) >= (header.dataLen + CLIPRDR_HEADER_LENGTH)) + { + Stream_SetPosition(s, (header.dataLen + CLIPRDR_HEADER_LENGTH)); + Stream_SealLength(s); + Stream_SetPosition(s, CLIPRDR_HEADER_LENGTH); + + if ((error = cliprdr_server_receive_pdu(context, s, &header))) + { + WLog_ERR(TAG, "cliprdr_server_receive_pdu failed with error code %" PRIu32 "!", + error); + return error; + } + + Stream_ResetPosition(s); + /* check for trailing zero bytes */ + status = WaitForSingleObject(cliprdr->ChannelEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + return error; + } + + if (status == WAIT_TIMEOUT) + return CHANNEL_RC_OK; + + BytesReturned = 0; + BytesToRead = 4; + + if (!WTSVirtualChannelRead(cliprdr->ChannelHandle, 0, Stream_Pointer(s), BytesToRead, + &BytesReturned)) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (BytesReturned == 4) + { + Stream_Read_UINT16(s, header.msgType); /* msgType (2 bytes) */ + Stream_Read_UINT16(s, header.msgFlags); /* msgFlags (2 bytes) */ + + if (!header.msgType) + { + /* ignore trailing bytes */ + Stream_ResetPosition(s); + } + } + else + { + Stream_Seek(s, BytesReturned); + } + } + } + + return CHANNEL_RC_OK; +} + +static DWORD WINAPI cliprdr_server_thread(LPVOID arg) +{ + DWORD status = 0; + DWORD nCount = 0; + HANDLE events[MAXIMUM_WAIT_OBJECTS] = WINPR_C_ARRAY_INIT; + HANDLE ChannelEvent = nullptr; + CliprdrServerContext* context = (CliprdrServerContext*)arg; + CliprdrServerPrivate* cliprdr = nullptr; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + + cliprdr = (CliprdrServerPrivate*)context->handle; + WINPR_ASSERT(cliprdr); + + ChannelEvent = context->GetEventHandle(context); + + events[nCount++] = cliprdr->StopEvent; + events[nCount++] = ChannelEvent; + + if (context->autoInitializationSequence) + { + if ((error = cliprdr_server_init(context))) + { + WLog_ERR(TAG, "cliprdr_server_init failed with error %" PRIu32 "!", error); + goto out; + } + } + + while (1) + { + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "", error); + goto out; + } + + status = WaitForSingleObject(cliprdr->StopEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + goto out; + } + + if (status == WAIT_OBJECT_0) + break; + + status = WaitForSingleObject(ChannelEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + goto out; + } + + if (status == WAIT_OBJECT_0) + { + if ((error = context->CheckEventHandle(context))) + { + WLog_ERR(TAG, "CheckEventHandle failed with error %" PRIu32 "!", error); + break; + } + } + } + +out: + + if (error && context->rdpcontext) + setChannelError(context->rdpcontext, error, "cliprdr_server_thread reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_open(CliprdrServerContext* context) +{ + void* buffer = nullptr; + DWORD BytesReturned = 0; + CliprdrServerPrivate* cliprdr = nullptr; + + WINPR_ASSERT(context); + + cliprdr = (CliprdrServerPrivate*)context->handle; + WINPR_ASSERT(cliprdr); + + cliprdr->ChannelHandle = + WTSVirtualChannelOpen(cliprdr->vcm, WTS_CURRENT_SESSION, CLIPRDR_SVC_CHANNEL_NAME); + + if (!cliprdr->ChannelHandle) + { + WLog_ERR(TAG, "WTSVirtualChannelOpen for cliprdr failed!"); + return ERROR_INTERNAL_ERROR; + } + + cliprdr->ChannelEvent = nullptr; + + if (WTSVirtualChannelQuery(cliprdr->ChannelHandle, WTSVirtualEventHandle, &buffer, + &BytesReturned)) + { + if (BytesReturned != sizeof(HANDLE)) + { + WLog_ERR(TAG, "BytesReturned has not size of HANDLE!"); + return ERROR_INTERNAL_ERROR; + } + + cliprdr->ChannelEvent = *(HANDLE*)buffer; + WTSFreeMemory(buffer); + } + + if (!cliprdr->ChannelEvent) + { + WLog_ERR(TAG, "WTSVirtualChannelQuery for cliprdr failed!"); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_close(CliprdrServerContext* context) +{ + CliprdrServerPrivate* cliprdr = nullptr; + + WINPR_ASSERT(context); + + cliprdr = (CliprdrServerPrivate*)context->handle; + WINPR_ASSERT(cliprdr); + + if (cliprdr->ChannelHandle) + { + (void)WTSVirtualChannelClose(cliprdr->ChannelHandle); + cliprdr->ChannelHandle = nullptr; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_start(CliprdrServerContext* context) +{ + UINT error = 0; + CliprdrServerPrivate* cliprdr = nullptr; + + WINPR_ASSERT(context); + + cliprdr = (CliprdrServerPrivate*)context->handle; + WINPR_ASSERT(cliprdr); + + if (!cliprdr->ChannelHandle) + { + if ((error = context->Open(context))) + { + WLog_ERR(TAG, "Open failed!"); + return error; + } + } + + if (!(cliprdr->StopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (!(cliprdr->Thread = + CreateThread(nullptr, 0, cliprdr_server_thread, (void*)context, 0, nullptr))) + { + WLog_ERR(TAG, "CreateThread failed!"); + (void)CloseHandle(cliprdr->StopEvent); + cliprdr->StopEvent = nullptr; + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_stop(CliprdrServerContext* context) +{ + UINT error = CHANNEL_RC_OK; + CliprdrServerPrivate* cliprdr = nullptr; + + WINPR_ASSERT(context); + + cliprdr = (CliprdrServerPrivate*)context->handle; + WINPR_ASSERT(cliprdr); + + if (cliprdr->StopEvent) + { + (void)SetEvent(cliprdr->StopEvent); + + if (WaitForSingleObject(cliprdr->Thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + return error; + } + + (void)CloseHandle(cliprdr->Thread); + (void)CloseHandle(cliprdr->StopEvent); + } + + if (cliprdr->ChannelHandle) + return context->Close(context); + + return error; +} + +static HANDLE cliprdr_server_get_event_handle(CliprdrServerContext* context) +{ + CliprdrServerPrivate* cliprdr = nullptr; + + WINPR_ASSERT(context); + + cliprdr = (CliprdrServerPrivate*)context->handle; + WINPR_ASSERT(cliprdr); + return cliprdr->ChannelEvent; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT cliprdr_server_check_event_handle(CliprdrServerContext* context) +{ + return cliprdr_server_read(context); +} + +CliprdrServerContext* cliprdr_server_context_new(HANDLE vcm) +{ + CliprdrServerPrivate* cliprdr = nullptr; + CliprdrServerContext* context = (CliprdrServerContext*)calloc(1, sizeof(CliprdrServerContext)); + + if (context) + { + context->autoInitializationSequence = TRUE; + context->Open = cliprdr_server_open; + context->Close = cliprdr_server_close; + context->Start = cliprdr_server_start; + context->Stop = cliprdr_server_stop; + context->GetEventHandle = cliprdr_server_get_event_handle; + context->CheckEventHandle = cliprdr_server_check_event_handle; + context->ServerCapabilities = cliprdr_server_capabilities; + context->MonitorReady = cliprdr_server_monitor_ready; + context->ServerFormatList = cliprdr_server_format_list; + context->ServerFormatListResponse = cliprdr_server_format_list_response; + context->ServerLockClipboardData = cliprdr_server_lock_clipboard_data; + context->ServerUnlockClipboardData = cliprdr_server_unlock_clipboard_data; + context->ServerFormatDataRequest = cliprdr_server_format_data_request; + context->ServerFormatDataResponse = cliprdr_server_format_data_response; + context->ServerFileContentsRequest = cliprdr_server_file_contents_request; + context->ServerFileContentsResponse = cliprdr_server_file_contents_response; + cliprdr = context->handle = (CliprdrServerPrivate*)calloc(1, sizeof(CliprdrServerPrivate)); + + if (cliprdr) + { + cliprdr->vcm = vcm; + cliprdr->s = Stream_New(nullptr, 4096); + + if (!cliprdr->s) + { + WLog_ERR(TAG, "Stream_New failed!"); + free(context->handle); + free(context); + return nullptr; + } + } + else + { + WLog_ERR(TAG, "calloc failed!"); + free(context); + return nullptr; + } + } + + return context; +} + +void cliprdr_server_context_free(CliprdrServerContext* context) +{ + CliprdrServerPrivate* cliprdr = nullptr; + + if (!context) + return; + + cliprdr = (CliprdrServerPrivate*)context->handle; + + if (cliprdr) + { + Stream_Free(cliprdr->s, TRUE); + } + + free(context->handle); + free(context); +} diff --git a/third_party/FreeRDP/channels/cliprdr/server/cliprdr_main.h b/third_party/FreeRDP/channels/cliprdr/server/cliprdr_main.h new file mode 100644 index 0000000..f2e7162 --- /dev/null +++ b/third_party/FreeRDP/channels/cliprdr/server/cliprdr_main.h @@ -0,0 +1,47 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Clipboard Virtual Channel Extension + * + * Copyright 2013 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_CLIPRDR_SERVER_MAIN_H +#define FREERDP_CHANNEL_CLIPRDR_SERVER_MAIN_H + +#include +#include +#include +#include + +#include +#include + +#define TAG CHANNELS_TAG("cliprdr.server") + +#define CLIPRDR_HEADER_LENGTH 8 + +typedef struct +{ + HANDLE vcm; + HANDLE Thread; + HANDLE StopEvent; + void* ChannelHandle; + HANDLE ChannelEvent; + + wStream* s; + char temporaryDirectory[260]; +} CliprdrServerPrivate; + +#endif /* FREERDP_CHANNEL_CLIPRDR_SERVER_MAIN_H */ diff --git a/third_party/FreeRDP/channels/disp/CMakeLists.txt b/third_party/FreeRDP/channels/disp/CMakeLists.txt new file mode 100644 index 0000000..04281c0 --- /dev/null +++ b/third_party/FreeRDP/channels/disp/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel("disp") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/third_party/FreeRDP/channels/disp/ChannelOptions.cmake b/third_party/FreeRDP/channels/disp/ChannelOptions.cmake new file mode 100644 index 0000000..416ec17 --- /dev/null +++ b/third_party/FreeRDP/channels/disp/ChannelOptions.cmake @@ -0,0 +1,20 @@ +set(OPTION_DEFAULT ON) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options( + NAME + "disp" + TYPE + "dynamic" + DESCRIPTION + "Display Update Virtual Channel Extension" + SPECIFICATIONS + "[MS-RDPEDISP]" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/disp/client/CMakeLists.txt b/third_party/FreeRDP/channels/disp/client/CMakeLists.txt new file mode 100644 index 0000000..6287e09 --- /dev/null +++ b/third_party/FreeRDP/channels/disp/client/CMakeLists.txt @@ -0,0 +1,25 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2013 Marc-Andre Moreau +# +# 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. + +define_channel_client("disp") + +set(${MODULE_PREFIX}_SRCS disp_main.c disp_main.h ../disp_common.c ../disp_common.h) + +set(${MODULE_PREFIX}_LIBS winpr) +include_directories(..) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") diff --git a/third_party/FreeRDP/channels/disp/client/disp_main.c b/third_party/FreeRDP/channels/disp/client/disp_main.c new file mode 100644 index 0000000..a0f3809 --- /dev/null +++ b/third_party/FreeRDP/channels/disp/client/disp_main.c @@ -0,0 +1,328 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Display Update Virtual Channel Extension + * + * Copyright 2013 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 David PHAM-VAN + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "disp_main.h" +#include "../disp_common.h" + +typedef struct +{ + GENERIC_DYNVC_PLUGIN base; + + DispClientContext* context; + UINT32 MaxNumMonitors; + UINT32 MaxMonitorAreaFactorA; + UINT32 MaxMonitorAreaFactorB; +} DISP_PLUGIN; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +disp_send_display_control_monitor_layout_pdu(GENERIC_CHANNEL_CALLBACK* callback, UINT32 NumMonitors, + const DISPLAY_CONTROL_MONITOR_LAYOUT* Monitors) +{ + UINT status = 0; + wStream* s = nullptr; + DISP_PLUGIN* disp = nullptr; + UINT32 MonitorLayoutSize = 0; + DISPLAY_CONTROL_HEADER header = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(callback); + WINPR_ASSERT(Monitors || (NumMonitors == 0)); + + disp = (DISP_PLUGIN*)callback->plugin; + WINPR_ASSERT(disp); + + MonitorLayoutSize = DISPLAY_CONTROL_MONITOR_LAYOUT_SIZE; + header.length = 8 + 8 + (NumMonitors * MonitorLayoutSize); + header.type = DISPLAY_CONTROL_PDU_TYPE_MONITOR_LAYOUT; + + s = Stream_New(nullptr, header.length); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if ((status = disp_write_header(s, &header))) + { + WLog_ERR(TAG, "Failed to write header with error %" PRIu32 "!", status); + goto out; + } + + if (NumMonitors > disp->MaxNumMonitors) + NumMonitors = disp->MaxNumMonitors; + + Stream_Write_UINT32(s, MonitorLayoutSize); /* MonitorLayoutSize (4 bytes) */ + Stream_Write_UINT32(s, NumMonitors); /* NumMonitors (4 bytes) */ + WLog_DBG(TAG, "NumMonitors=%" PRIu32 "", NumMonitors); + + for (UINT32 index = 0; index < NumMonitors; index++) + { + DISPLAY_CONTROL_MONITOR_LAYOUT current = Monitors[index]; + current.Width -= (current.Width % 2); + + if (current.Width < 200) + current.Width = 200; + + if (current.Width > 8192) + current.Width = 8192; + + if (current.Width % 2) + current.Width++; + + if (current.Height < 200) + current.Height = 200; + + if (current.Height > 8192) + current.Height = 8192; + + Stream_Write_UINT32(s, current.Flags); /* Flags (4 bytes) */ + Stream_Write_INT32(s, current.Left); /* Left (4 bytes) */ + Stream_Write_INT32(s, current.Top); /* Top (4 bytes) */ + Stream_Write_UINT32(s, current.Width); /* Width (4 bytes) */ + Stream_Write_UINT32(s, current.Height); /* Height (4 bytes) */ + Stream_Write_UINT32(s, current.PhysicalWidth); /* PhysicalWidth (4 bytes) */ + Stream_Write_UINT32(s, current.PhysicalHeight); /* PhysicalHeight (4 bytes) */ + Stream_Write_UINT32(s, current.Orientation); /* Orientation (4 bytes) */ + Stream_Write_UINT32(s, current.DesktopScaleFactor); /* DesktopScaleFactor (4 bytes) */ + Stream_Write_UINT32(s, current.DeviceScaleFactor); /* DeviceScaleFactor (4 bytes) */ + WLog_DBG(TAG, + "\t%" PRIu32 " : Flags: 0x%08" PRIX32 " Left/Top: (%" PRId32 ",%" PRId32 + ") W/H=%" PRIu32 "x%" PRIu32 ")", + index, current.Flags, current.Left, current.Top, current.Width, current.Height); + WLog_DBG(TAG, + "\t PhysicalWidth: %" PRIu32 " PhysicalHeight: %" PRIu32 + " Orientation: %s DesktopScaleFactor=%" PRIu32 " DeviceScaleFactor=%" PRIu32 "", + current.PhysicalWidth, current.PhysicalHeight, + freerdp_desktop_rotation_flags_to_string(current.Orientation), + current.DesktopScaleFactor, current.DeviceScaleFactor); + } + +out: + Stream_SealLength(s); + status = callback->channel->Write(callback->channel, (UINT32)Stream_Length(s), Stream_Buffer(s), + nullptr); + Stream_Free(s, TRUE); + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT disp_recv_display_control_caps_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + DISP_PLUGIN* disp = nullptr; + DispClientContext* context = nullptr; + UINT ret = CHANNEL_RC_OK; + + WINPR_ASSERT(callback); + WINPR_ASSERT(s); + + disp = (DISP_PLUGIN*)callback->plugin; + WINPR_ASSERT(disp); + + context = disp->context; + WINPR_ASSERT(context); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 12)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, disp->MaxNumMonitors); /* MaxNumMonitors (4 bytes) */ + Stream_Read_UINT32(s, disp->MaxMonitorAreaFactorA); /* MaxMonitorAreaFactorA (4 bytes) */ + Stream_Read_UINT32(s, disp->MaxMonitorAreaFactorB); /* MaxMonitorAreaFactorB (4 bytes) */ + + if (context->DisplayControlCaps) + ret = context->DisplayControlCaps(context, disp->MaxNumMonitors, + disp->MaxMonitorAreaFactorA, disp->MaxMonitorAreaFactorB); + + return ret; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT disp_recv_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + UINT32 error = 0; + DISPLAY_CONTROL_HEADER header = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(callback); + WINPR_ASSERT(s); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + if ((error = disp_read_header(s, &header))) + { + WLog_ERR(TAG, "disp_read_header failed with error %" PRIu32 "!", error); + return error; + } + + if (!Stream_EnsureRemainingCapacity(s, header.length)) + { + WLog_ERR(TAG, "not enough remaining data"); + return ERROR_INVALID_DATA; + } + + switch (header.type) + { + case DISPLAY_CONTROL_PDU_TYPE_CAPS: + return disp_recv_display_control_caps_pdu(callback, s); + + default: + WLog_ERR(TAG, "Type %" PRIu32 " not recognized!", header.type); + return ERROR_INTERNAL_ERROR; + } +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT disp_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data) +{ + GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + return disp_recv_pdu(callback, data); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT disp_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + free(pChannelCallback); + return CHANNEL_RC_OK; +} + +/** + * Channel Client Interface + */ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT disp_send_monitor_layout(DispClientContext* context, UINT32 NumMonitors, + DISPLAY_CONTROL_MONITOR_LAYOUT* Monitors) +{ + DISP_PLUGIN* disp = nullptr; + GENERIC_CHANNEL_CALLBACK* callback = nullptr; + + WINPR_ASSERT(context); + + disp = (DISP_PLUGIN*)context->handle; + WINPR_ASSERT(disp); + + callback = disp->base.listener_callback->channel_callback; + + return disp_send_display_control_monitor_layout_pdu(callback, NumMonitors, Monitors); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT disp_plugin_initialize(GENERIC_DYNVC_PLUGIN* base, + WINPR_ATTR_UNUSED rdpContext* rcontext, + WINPR_ATTR_UNUSED rdpSettings* settings) +{ + DispClientContext* context = nullptr; + DISP_PLUGIN* disp = (DISP_PLUGIN*)base; + + WINPR_ASSERT(disp); + disp->MaxNumMonitors = 16; + disp->MaxMonitorAreaFactorA = 8192; + disp->MaxMonitorAreaFactorB = 8192; + + context = (DispClientContext*)calloc(1, sizeof(DispClientContext)); + if (!context) + { + WLog_Print(base->log, WLOG_ERROR, "unable to allocate DispClientContext"); + return CHANNEL_RC_NO_MEMORY; + } + + context->handle = (void*)disp; + context->SendMonitorLayout = disp_send_monitor_layout; + + disp->base.iface.pInterface = disp->context = context; + + return CHANNEL_RC_OK; +} + +static void disp_plugin_terminated(GENERIC_DYNVC_PLUGIN* base) +{ + DISP_PLUGIN* disp = (DISP_PLUGIN*)base; + + WINPR_ASSERT(disp); + + free(disp->context); +} + +static const IWTSVirtualChannelCallback disp_callbacks = { disp_on_data_received, + nullptr, /* Open */ + disp_on_close, nullptr }; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE disp_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)) +{ + return freerdp_generic_DVCPluginEntry(pEntryPoints, TAG, DISP_DVC_CHANNEL_NAME, + sizeof(DISP_PLUGIN), sizeof(GENERIC_CHANNEL_CALLBACK), + &disp_callbacks, disp_plugin_initialize, + disp_plugin_terminated); +} diff --git a/third_party/FreeRDP/channels/disp/client/disp_main.h b/third_party/FreeRDP/channels/disp/client/disp_main.h new file mode 100644 index 0000000..d5ce446 --- /dev/null +++ b/third_party/FreeRDP/channels/disp/client/disp_main.h @@ -0,0 +1,36 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Display Update Virtual Channel Extension + * + * Copyright 2013 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_DISP_CLIENT_MAIN_H +#define FREERDP_CHANNEL_DISP_CLIENT_MAIN_H + +#include + +#include +#include +#include +#include + +#include + +#define TAG CHANNELS_TAG("disp.client") + +#endif /* FREERDP_CHANNEL_DISP_CLIENT_MAIN_H */ diff --git a/third_party/FreeRDP/channels/disp/disp_common.c b/third_party/FreeRDP/channels/disp/disp_common.c new file mode 100644 index 0000000..9387691 --- /dev/null +++ b/third_party/FreeRDP/channels/disp/disp_common.c @@ -0,0 +1,55 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RDPEDISP Virtual Channel Extension + * + * Copyright 2019 Kobi Mizrachi + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#define TAG CHANNELS_TAG("disp.common") + +#include "disp_common.h" + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT disp_read_header(wStream* s, DISPLAY_CONTROL_HEADER* header) +{ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, header->type); + Stream_Read_UINT32(s, header->length); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT disp_write_header(wStream* s, const DISPLAY_CONTROL_HEADER* header) +{ + Stream_Write_UINT32(s, header->type); + Stream_Write_UINT32(s, header->length); + return CHANNEL_RC_OK; +} diff --git a/third_party/FreeRDP/channels/disp/disp_common.h b/third_party/FreeRDP/channels/disp/disp_common.h new file mode 100644 index 0000000..d13f4f8 --- /dev/null +++ b/third_party/FreeRDP/channels/disp/disp_common.h @@ -0,0 +1,35 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RDPEDISP Virtual Channel Extension + * + * Copyright 2019 Kobi Mizrachi + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_DISP_COMMON_H +#define FREERDP_CHANNEL_DISP_COMMON_H + +#include +#include + +#include +#include + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT disp_read_header(wStream* s, DISPLAY_CONTROL_HEADER* header); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT disp_write_header(wStream* s, const DISPLAY_CONTROL_HEADER* header); + +#endif /* FREERDP_CHANNEL_DISP_COMMON_H */ diff --git a/third_party/FreeRDP/channels/disp/server/CMakeLists.txt b/third_party/FreeRDP/channels/disp/server/CMakeLists.txt new file mode 100644 index 0000000..e39f886 --- /dev/null +++ b/third_party/FreeRDP/channels/disp/server/CMakeLists.txt @@ -0,0 +1,25 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2019 Kobi Mizrachi +# +# 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. + +define_channel_server("disp") + +set(${MODULE_PREFIX}_SRCS disp_main.c disp_main.h ../disp_common.c ../disp_common.h) + +set(${MODULE_PREFIX}_LIBS freerdp) +include_directories(..) + +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry") diff --git a/third_party/FreeRDP/channels/disp/server/disp_main.c b/third_party/FreeRDP/channels/disp/server/disp_main.c new file mode 100644 index 0000000..d100bbb --- /dev/null +++ b/third_party/FreeRDP/channels/disp/server/disp_main.c @@ -0,0 +1,639 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RDPEDISP Virtual Channel Extension + * + * Copyright 2019 Kobi Mizrachi + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "disp_main.h" +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "../disp_common.h" + +#define TAG CHANNELS_TAG("rdpedisp.server") + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ + +static wStream* disp_server_single_packet_new(UINT32 type, UINT32 length) +{ + UINT error = 0; + DISPLAY_CONTROL_HEADER header; + wStream* s = Stream_New(nullptr, DISPLAY_CONTROL_HEADER_LENGTH + length); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + goto error; + } + + header.type = type; + header.length = DISPLAY_CONTROL_HEADER_LENGTH + length; + + if ((error = disp_write_header(s, &header))) + { + WLog_ERR(TAG, "Failed to write header with error %" PRIu32 "!", error); + goto error; + } + + return s; +error: + Stream_Free(s, TRUE); + return nullptr; +} + +static void disp_server_sanitize_monitor_layout(DISPLAY_CONTROL_MONITOR_LAYOUT* monitor) +{ + if (monitor->PhysicalWidth < DISPLAY_CONTROL_MIN_PHYSICAL_MONITOR_WIDTH || + monitor->PhysicalWidth > DISPLAY_CONTROL_MAX_PHYSICAL_MONITOR_WIDTH || + monitor->PhysicalHeight < DISPLAY_CONTROL_MIN_PHYSICAL_MONITOR_HEIGHT || + monitor->PhysicalHeight > DISPLAY_CONTROL_MAX_PHYSICAL_MONITOR_HEIGHT) + { + if (monitor->PhysicalWidth != 0 || monitor->PhysicalHeight != 0) + WLog_DBG( + TAG, + "Sanitizing invalid physical monitor size. Old physical monitor size: [%" PRIu32 + ", %" PRIu32 "]", + monitor->PhysicalWidth, monitor->PhysicalHeight); + + monitor->PhysicalWidth = monitor->PhysicalHeight = 0; + } +} + +static BOOL disp_server_is_monitor_layout_valid(const DISPLAY_CONTROL_MONITOR_LAYOUT* monitor) +{ + WINPR_ASSERT(monitor); + + if (monitor->Width < DISPLAY_CONTROL_MIN_MONITOR_WIDTH || + monitor->Width > DISPLAY_CONTROL_MAX_MONITOR_WIDTH) + { + WLog_WARN(TAG, "Received invalid value for monitor->Width: %" PRIu32 "", monitor->Width); + return FALSE; + } + + if (monitor->Height < DISPLAY_CONTROL_MIN_MONITOR_HEIGHT || + monitor->Height > DISPLAY_CONTROL_MAX_MONITOR_HEIGHT) + { + WLog_WARN(TAG, "Received invalid value for monitor->Height: %" PRIu32 "", monitor->Height); + return FALSE; + } + + switch (monitor->Orientation) + { + case ORIENTATION_LANDSCAPE: + case ORIENTATION_PORTRAIT: + case ORIENTATION_LANDSCAPE_FLIPPED: + case ORIENTATION_PORTRAIT_FLIPPED: + break; + + default: + WLog_WARN(TAG, "Received incorrect value for monitor->Orientation: %" PRIu32 "", + monitor->Orientation); + return FALSE; + } + + return TRUE; +} + +static UINT disp_recv_display_control_monitor_layout_pdu(wStream* s, DispServerContext* context) +{ + UINT32 error = CHANNEL_RC_OK; + DISPLAY_CONTROL_MONITOR_LAYOUT_PDU pdu = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(s); + WINPR_ASSERT(context); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, pdu.MonitorLayoutSize); /* MonitorLayoutSize (4 bytes) */ + + if (pdu.MonitorLayoutSize != DISPLAY_CONTROL_MONITOR_LAYOUT_SIZE) + { + WLog_ERR(TAG, "MonitorLayoutSize is set to %" PRIu32 ". expected %d", pdu.MonitorLayoutSize, + DISPLAY_CONTROL_MONITOR_LAYOUT_SIZE); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT32(s, pdu.NumMonitors); /* NumMonitors (4 bytes) */ + + if (pdu.NumMonitors > context->MaxNumMonitors) + { + WLog_ERR(TAG, "NumMonitors (%" PRIu32 ")> server MaxNumMonitors (%" PRIu32 ")", + pdu.NumMonitors, context->MaxNumMonitors); + return ERROR_INVALID_DATA; + } + + if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, pdu.NumMonitors, + DISPLAY_CONTROL_MONITOR_LAYOUT_SIZE)) + return ERROR_INVALID_DATA; + + pdu.Monitors = (DISPLAY_CONTROL_MONITOR_LAYOUT*)calloc(pdu.NumMonitors, + sizeof(DISPLAY_CONTROL_MONITOR_LAYOUT)); + + if (!pdu.Monitors) + { + WLog_ERR(TAG, "disp_recv_display_control_monitor_layout_pdu(): calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + WLog_DBG(TAG, "disp_recv_display_control_monitor_layout_pdu: NumMonitors=%" PRIu32 "", + pdu.NumMonitors); + + for (UINT32 index = 0; index < pdu.NumMonitors; index++) + { + DISPLAY_CONTROL_MONITOR_LAYOUT* monitor = &(pdu.Monitors[index]); + + Stream_Read_UINT32(s, monitor->Flags); /* Flags (4 bytes) */ + Stream_Read_INT32(s, monitor->Left); /* Left (4 bytes) */ + Stream_Read_INT32(s, monitor->Top); /* Top (4 bytes) */ + Stream_Read_UINT32(s, monitor->Width); /* Width (4 bytes) */ + Stream_Read_UINT32(s, monitor->Height); /* Height (4 bytes) */ + Stream_Read_UINT32(s, monitor->PhysicalWidth); /* PhysicalWidth (4 bytes) */ + Stream_Read_UINT32(s, monitor->PhysicalHeight); /* PhysicalHeight (4 bytes) */ + Stream_Read_UINT32(s, monitor->Orientation); /* Orientation (4 bytes) */ + Stream_Read_UINT32(s, monitor->DesktopScaleFactor); /* DesktopScaleFactor (4 bytes) */ + Stream_Read_UINT32(s, monitor->DeviceScaleFactor); /* DeviceScaleFactor (4 bytes) */ + + disp_server_sanitize_monitor_layout(monitor); + WLog_DBG(TAG, + "\t%" PRIu32 " : Flags: 0x%08" PRIX32 " Left/Top: (%" PRId32 ",%" PRId32 + ") W/H=%" PRIu32 "x%" PRIu32 ")", + index, monitor->Flags, monitor->Left, monitor->Top, monitor->Width, + monitor->Height); + WLog_DBG(TAG, + "\t PhysicalWidth: %" PRIu32 " PhysicalHeight: %" PRIu32 " Orientation: %" PRIu32 + "", + monitor->PhysicalWidth, monitor->PhysicalHeight, monitor->Orientation); + + if (!disp_server_is_monitor_layout_valid(monitor)) + { + error = ERROR_INVALID_DATA; + goto out; + } + } + + if (context) + IFCALLRET(context->DispMonitorLayout, error, context, &pdu); + +out: + free(pdu.Monitors); + return error; +} + +static UINT disp_server_receive_pdu(DispServerContext* context, wStream* s) +{ + UINT error = CHANNEL_RC_OK; + size_t beg = 0; + size_t end = 0; + DISPLAY_CONTROL_HEADER header = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(s); + WINPR_ASSERT(context); + + beg = Stream_GetPosition(s); + + if ((error = disp_read_header(s, &header))) + { + WLog_ERR(TAG, "disp_read_header failed with error %" PRIu32 "!", error); + return error; + } + + switch (header.type) + { + case DISPLAY_CONTROL_PDU_TYPE_MONITOR_LAYOUT: + if ((error = disp_recv_display_control_monitor_layout_pdu(s, context))) + WLog_ERR(TAG, + "disp_recv_display_control_monitor_layout_pdu " + "failed with error %" PRIu32 "!", + error); + + break; + + default: + error = CHANNEL_RC_BAD_PROC; + WLog_WARN(TAG, "Received unknown PDU type: %" PRIu32 "", header.type); + break; + } + + end = Stream_GetPosition(s); + + if (end != (beg + header.length)) + { + WLog_ERR(TAG, "Unexpected DISP pdu end: Actual: %" PRIuz ", Expected: %" PRIuz "", end, + (beg + header.length)); + Stream_SetPosition(s, (beg + header.length)); + } + + return error; +} + +static UINT disp_server_handle_messages(DispServerContext* context) +{ + DWORD BytesReturned = 0; + void* buffer = nullptr; + UINT ret = CHANNEL_RC_OK; + DispServerPrivate* priv = nullptr; + wStream* s = nullptr; + + WINPR_ASSERT(context); + + priv = context->priv; + WINPR_ASSERT(priv); + + s = priv->input_stream; + WINPR_ASSERT(s); + + /* Check whether the dynamic channel is ready */ + if (!priv->isReady) + { + if (WTSVirtualChannelQuery(priv->disp_channel, WTSVirtualChannelReady, &buffer, + &BytesReturned) == FALSE) + { + if (GetLastError() == ERROR_NO_DATA) + return ERROR_NO_DATA; + + WLog_ERR(TAG, "WTSVirtualChannelQuery failed"); + return ERROR_INTERNAL_ERROR; + } + + priv->isReady = *((BOOL*)buffer); + WTSFreeMemory(buffer); + } + + /* Consume channel event only after the disp dynamic channel is ready */ + Stream_ResetPosition(s); + + if (!WTSVirtualChannelRead(priv->disp_channel, 0, nullptr, 0, &BytesReturned)) + { + if (GetLastError() == ERROR_NO_DATA) + return ERROR_NO_DATA; + + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (BytesReturned < 1) + return CHANNEL_RC_OK; + + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + const size_t cap = Stream_Capacity(s); + if (cap > UINT32_MAX) + return CHANNEL_RC_NO_BUFFER; + + if (WTSVirtualChannelRead(priv->disp_channel, 0, Stream_BufferAs(s, char), (ULONG)cap, + &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_SetLength(s, BytesReturned); + Stream_ResetPosition(s); + + while (Stream_GetPosition(s) < Stream_Length(s)) + { + if ((ret = disp_server_receive_pdu(context, s))) + { + WLog_ERR(TAG, + "disp_server_receive_pdu " + "failed with error %" PRIu32 "!", + ret); + return ret; + } + } + + return ret; +} + +static DWORD WINAPI disp_server_thread_func(LPVOID arg) +{ + DispServerContext* context = (DispServerContext*)arg; + DispServerPrivate* priv = nullptr; + DWORD status = 0; + DWORD nCount = 0; + HANDLE events[8] = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + + priv = context->priv; + WINPR_ASSERT(priv); + + events[nCount++] = priv->stopEvent; + events[nCount++] = priv->channelEvent; + + /* Main virtual channel loop. RDPEDISP do not need version negotiation */ + while (TRUE) + { + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "", error); + break; + } + + /* Stop Event */ + if (status == WAIT_OBJECT_0) + break; + + if ((error = disp_server_handle_messages(context))) + { + WLog_ERR(TAG, "disp_server_handle_messages failed with error %" PRIu32 "", error); + break; + } + } + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT disp_server_open(DispServerContext* context) +{ + UINT rc = ERROR_INTERNAL_ERROR; + DispServerPrivate* priv = nullptr; + DWORD BytesReturned = 0; + PULONG pSessionId = nullptr; + void* buffer = nullptr; + UINT32 channelId = 0; + BOOL status = TRUE; + + WINPR_ASSERT(context); + + priv = context->priv; + WINPR_ASSERT(priv); + + priv->SessionId = WTS_CURRENT_SESSION; + + if (WTSQuerySessionInformationA(context->vcm, WTS_CURRENT_SESSION, WTSSessionId, + (LPSTR*)&pSessionId, &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSQuerySessionInformationA failed!"); + rc = ERROR_INTERNAL_ERROR; + goto out_close; + } + + priv->SessionId = (DWORD)*pSessionId; + WTSFreeMemory(pSessionId); + priv->disp_channel = + WTSVirtualChannelOpenEx(priv->SessionId, DISP_DVC_CHANNEL_NAME, WTS_CHANNEL_OPTION_DYNAMIC); + + if (!priv->disp_channel) + { + WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed!"); + rc = GetLastError(); + goto out_close; + } + + channelId = WTSChannelGetIdByHandle(priv->disp_channel); + + IFCALLRET(context->ChannelIdAssigned, status, context, channelId); + if (!status) + { + WLog_ERR(TAG, "context->ChannelIdAssigned failed!"); + rc = ERROR_INTERNAL_ERROR; + goto out_close; + } + + /* Query for channel event handle */ + if (!WTSVirtualChannelQuery(priv->disp_channel, WTSVirtualEventHandle, &buffer, + &BytesReturned) || + (BytesReturned != sizeof(HANDLE))) + { + WLog_ERR(TAG, + "WTSVirtualChannelQuery failed " + "or invalid returned size(%" PRIu32 ")", + BytesReturned); + + if (buffer) + WTSFreeMemory(buffer); + + rc = ERROR_INTERNAL_ERROR; + goto out_close; + } + + priv->channelEvent = *(HANDLE*)buffer; + WTSFreeMemory(buffer); + + if (priv->thread == nullptr) + { + if (!(priv->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + rc = ERROR_INTERNAL_ERROR; + goto out_close; + } + + if (!(priv->thread = + CreateThread(nullptr, 0, disp_server_thread_func, (void*)context, 0, nullptr))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + (void)CloseHandle(priv->stopEvent); + priv->stopEvent = nullptr; + rc = ERROR_INTERNAL_ERROR; + goto out_close; + } + } + + return CHANNEL_RC_OK; +out_close: + (void)WTSVirtualChannelClose(priv->disp_channel); + priv->disp_channel = nullptr; + priv->channelEvent = nullptr; + return rc; +} + +static UINT disp_server_packet_send(DispServerContext* context, wStream* s) +{ + UINT ret = 0; + ULONG written = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(s); + + const size_t pos = Stream_GetPosition(s); + + WINPR_ASSERT(pos <= UINT32_MAX); + if (!WTSVirtualChannelWrite(context->priv->disp_channel, Stream_BufferAs(s, char), (UINT32)pos, + &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + ret = ERROR_INTERNAL_ERROR; + goto out; + } + + if (written < Stream_GetPosition(s)) + { + WLog_WARN(TAG, "Unexpected bytes written: %" PRIu32 "/%" PRIuz "", written, + Stream_GetPosition(s)); + } + + ret = CHANNEL_RC_OK; +out: + Stream_Free(s, TRUE); + return ret; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT disp_server_send_caps_pdu(DispServerContext* context) +{ + wStream* s = nullptr; + + WINPR_ASSERT(context); + + s = disp_server_single_packet_new(DISPLAY_CONTROL_PDU_TYPE_CAPS, 12); + + if (!s) + { + WLog_ERR(TAG, "disp_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT32(s, context->MaxNumMonitors); /* MaxNumMonitors (4 bytes) */ + Stream_Write_UINT32(s, context->MaxMonitorAreaFactorA); /* MaxMonitorAreaFactorA (4 bytes) */ + Stream_Write_UINT32(s, context->MaxMonitorAreaFactorB); /* MaxMonitorAreaFactorB (4 bytes) */ + return disp_server_packet_send(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT disp_server_close(DispServerContext* context) +{ + UINT error = CHANNEL_RC_OK; + DispServerPrivate* priv = nullptr; + + WINPR_ASSERT(context); + + priv = context->priv; + WINPR_ASSERT(priv); + + if (priv->thread) + { + (void)SetEvent(priv->stopEvent); + + if (WaitForSingleObject(priv->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + return error; + } + + (void)CloseHandle(priv->thread); + (void)CloseHandle(priv->stopEvent); + priv->thread = nullptr; + priv->stopEvent = nullptr; + } + + if (priv->disp_channel) + { + (void)WTSVirtualChannelClose(priv->disp_channel); + priv->disp_channel = nullptr; + } + + return error; +} + +DispServerContext* disp_server_context_new(HANDLE vcm) +{ + DispServerContext* context = nullptr; + DispServerPrivate* priv = nullptr; + context = (DispServerContext*)calloc(1, sizeof(DispServerContext)); + + if (!context) + { + WLog_ERR(TAG, "disp_server_context_new(): calloc DispServerContext failed!"); + goto fail; + } + + priv = context->priv = (DispServerPrivate*)calloc(1, sizeof(DispServerPrivate)); + + if (!context->priv) + { + WLog_ERR(TAG, "disp_server_context_new(): calloc DispServerPrivate failed!"); + goto fail; + } + + priv->input_stream = Stream_New(nullptr, 4); + + if (!priv->input_stream) + { + WLog_ERR(TAG, "Stream_New failed!"); + goto fail; + } + + context->vcm = vcm; + context->Open = disp_server_open; + context->Close = disp_server_close; + context->DisplayControlCaps = disp_server_send_caps_pdu; + priv->isReady = FALSE; + return context; +fail: + WINPR_PRAGMA_DIAG_PUSH + WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC + disp_server_context_free(context); + WINPR_PRAGMA_DIAG_POP + return nullptr; +} + +void disp_server_context_free(DispServerContext* context) +{ + if (!context) + return; + + if (context->priv) + { + disp_server_close(context); + Stream_Free(context->priv->input_stream, TRUE); + free(context->priv); + } + + free(context); +} diff --git a/third_party/FreeRDP/channels/disp/server/disp_main.h b/third_party/FreeRDP/channels/disp/server/disp_main.h new file mode 100644 index 0000000..920a508 --- /dev/null +++ b/third_party/FreeRDP/channels/disp/server/disp_main.h @@ -0,0 +1,37 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RDPEDISP Virtual Channel Extension + * + * Copyright 2019 Kobi Mizrachi + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_DISP_SERVER_MAIN_H +#define FREERDP_CHANNEL_DISP_SERVER_MAIN_H + +#include + +struct s_disp_server_private +{ + BOOL isReady; + wStream* input_stream; + HANDLE channelEvent; + HANDLE thread; + HANDLE stopEvent; + DWORD SessionId; + + void* disp_channel; +}; + +#endif /* FREERDP_CHANNEL_DISP_SERVER_MAIN_H */ diff --git a/third_party/FreeRDP/channels/drdynvc/CMakeLists.txt b/third_party/FreeRDP/channels/drdynvc/CMakeLists.txt new file mode 100644 index 0000000..eca79c7 --- /dev/null +++ b/third_party/FreeRDP/channels/drdynvc/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel("drdynvc") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/third_party/FreeRDP/channels/drdynvc/ChannelOptions.cmake b/third_party/FreeRDP/channels/drdynvc/ChannelOptions.cmake new file mode 100644 index 0000000..22652bb --- /dev/null +++ b/third_party/FreeRDP/channels/drdynvc/ChannelOptions.cmake @@ -0,0 +1,20 @@ +set(OPTION_DEFAULT ON) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options( + NAME + "drdynvc" + TYPE + "static" + DESCRIPTION + "Dynamic Virtual Channel Extension" + SPECIFICATIONS + "[MS-RDPEDYC]" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/drdynvc/client/CMakeLists.txt b/third_party/FreeRDP/channels/drdynvc/client/CMakeLists.txt new file mode 100644 index 0000000..b9cc206 --- /dev/null +++ b/third_party/FreeRDP/channels/drdynvc/client/CMakeLists.txt @@ -0,0 +1,22 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_client("drdynvc") + +set(${MODULE_PREFIX}_SRCS drdynvc_main.c drdynvc_main.h) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntryEx") diff --git a/third_party/FreeRDP/channels/drdynvc/client/drdynvc_main.c b/third_party/FreeRDP/channels/drdynvc/client/drdynvc_main.c new file mode 100644 index 0000000..fee3f69 --- /dev/null +++ b/third_party/FreeRDP/channels/drdynvc/client/drdynvc_main.c @@ -0,0 +1,2149 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Dynamic Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "drdynvc_main.h" + +#define TAG CHANNELS_TAG("drdynvc.client") + +static const char* channel_state2str(DVC_CHANNEL_STATE state) +{ + switch (state) + { + case DVC_CHANNEL_INIT: + return "DVC_CHANNEL_INIT"; + case DVC_CHANNEL_RUNNING: + return "DVC_CHANNEL_RUNNING"; + case DVC_CHANNEL_CLOSED: + return "DVC_CHANNEL_CLOSED"; + default: + return "DVC_CHANNEL_UNKNOWN"; + } +} + +static void dvcman_channel_free(DVCMAN_CHANNEL* channel); +static UINT dvcman_channel_close(DVCMAN_CHANNEL* channel, BOOL perRequest, BOOL fromHashTableFn); +static void dvcman_free(drdynvcPlugin* drdynvc, IWTSVirtualChannelManager* pChannelMgr); +static UINT drdynvc_write_data(drdynvcPlugin* drdynvc, UINT32 ChannelId, const BYTE* data, + UINT32 dataSize, BOOL* close); +static UINT drdynvc_send(drdynvcPlugin* drdynvc, wStream* s); + +static void dvcman_wtslistener_free(DVCMAN_LISTENER* listener) +{ + if (listener) + free(listener->channel_name); + free(listener); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT dvcman_get_configuration(IWTSListener* pListener, void** ppPropertyBag) +{ + WINPR_ASSERT(ppPropertyBag); + WINPR_UNUSED(pListener); + *ppPropertyBag = nullptr; + return ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT dvcman_create_listener(IWTSVirtualChannelManager* pChannelMgr, + const char* pszChannelName, ULONG ulFlags, + IWTSListenerCallback* pListenerCallback, + IWTSListener** ppListener) +{ + DVCMAN* dvcman = (DVCMAN*)pChannelMgr; + DVCMAN_LISTENER* listener = nullptr; + + WINPR_ASSERT(dvcman); + WLog_DBG(TAG, "create_listener: %" PRIuz ".%s.", HashTable_Count(dvcman->listeners) + 1, + pszChannelName); + listener = (DVCMAN_LISTENER*)calloc(1, sizeof(DVCMAN_LISTENER)); + + if (!listener) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + listener->iface.GetConfiguration = dvcman_get_configuration; + listener->iface.pInterface = nullptr; + listener->dvcman = dvcman; + listener->channel_name = _strdup(pszChannelName); + + if (!listener->channel_name) + { + WLog_ERR(TAG, "_strdup failed!"); + dvcman_wtslistener_free(listener); + return CHANNEL_RC_NO_MEMORY; + } + + listener->flags = ulFlags; + listener->listener_callback = pListenerCallback; + + if (ppListener) + *ppListener = (IWTSListener*)listener; + + if (!HashTable_Insert(dvcman->listeners, listener->channel_name, listener)) + { + dvcman_wtslistener_free(listener); + return ERROR_INTERNAL_ERROR; + } + + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert takes ownership of listener + return CHANNEL_RC_OK; +} + +static UINT dvcman_destroy_listener(IWTSVirtualChannelManager* pChannelMgr, IWTSListener* pListener) +{ + DVCMAN_LISTENER* listener = (DVCMAN_LISTENER*)pListener; + + WINPR_UNUSED(pChannelMgr); + + if (listener) + { + DVCMAN* dvcman = listener->dvcman; + if (dvcman) + HashTable_Remove(dvcman->listeners, listener->channel_name); + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT dvcman_register_plugin(IDRDYNVC_ENTRY_POINTS* pEntryPoints, const char* name, + IWTSPlugin* pPlugin) +{ + WINPR_ASSERT(pEntryPoints); + DVCMAN* dvcman = ((DVCMAN_ENTRY_POINTS*)pEntryPoints)->dvcman; + + WINPR_ASSERT(dvcman); + if (!ArrayList_Append(dvcman->plugin_names, name)) + return ERROR_INTERNAL_ERROR; + if (!ArrayList_Append(dvcman->plugins, pPlugin)) + return ERROR_INTERNAL_ERROR; + + WLog_DBG(TAG, "register_plugin: num_plugins %" PRIuz, ArrayList_Count(dvcman->plugins)); + return CHANNEL_RC_OK; +} + +static IWTSPlugin* dvcman_get_plugin(IDRDYNVC_ENTRY_POINTS* pEntryPoints, const char* name) +{ + IWTSPlugin* plugin = nullptr; + size_t nc = 0; + size_t pc = 0; + WINPR_ASSERT(pEntryPoints); + DVCMAN* dvcman = ((DVCMAN_ENTRY_POINTS*)pEntryPoints)->dvcman; + if (!dvcman || !pEntryPoints || !name) + return nullptr; + + nc = ArrayList_Count(dvcman->plugin_names); + pc = ArrayList_Count(dvcman->plugins); + if (nc != pc) + return nullptr; + + ArrayList_Lock(dvcman->plugin_names); + ArrayList_Lock(dvcman->plugins); + for (size_t i = 0; i < pc; i++) + { + const char* cur = ArrayList_GetItem(dvcman->plugin_names, i); + if (strcmp(cur, name) == 0) + { + plugin = ArrayList_GetItem(dvcman->plugins, i); + break; + } + } + ArrayList_Unlock(dvcman->plugin_names); + ArrayList_Unlock(dvcman->plugins); + return plugin; +} + +static const ADDIN_ARGV* dvcman_get_plugin_data(IDRDYNVC_ENTRY_POINTS* pEntryPoints) +{ + WINPR_ASSERT(pEntryPoints); + return ((DVCMAN_ENTRY_POINTS*)pEntryPoints)->args; +} + +static rdpContext* dvcman_get_rdp_context(IDRDYNVC_ENTRY_POINTS* pEntryPoints) +{ + DVCMAN_ENTRY_POINTS* entry = (DVCMAN_ENTRY_POINTS*)pEntryPoints; + WINPR_ASSERT(entry); + return entry->context; +} + +static rdpSettings* dvcman_get_rdp_settings(IDRDYNVC_ENTRY_POINTS* pEntryPoints) +{ + rdpContext* context = dvcman_get_rdp_context(pEntryPoints); + WINPR_ASSERT(context); + + return context->settings; +} + +static UINT32 dvcman_get_channel_id(IWTSVirtualChannel* channel) +{ + DVCMAN_CHANNEL* dvc = (DVCMAN_CHANNEL*)channel; + WINPR_ASSERT(dvc); + return dvc->channel_id; +} + +static const char* dvcman_get_channel_name(IWTSVirtualChannel* channel) +{ + DVCMAN_CHANNEL* dvc = (DVCMAN_CHANNEL*)channel; + WINPR_ASSERT(dvc); + return dvc->channel_name; +} + +static DVCMAN_CHANNEL* dvcman_get_channel_by_id(IWTSVirtualChannelManager* pChannelMgr, + UINT32 ChannelId, BOOL doRef) +{ + DVCMAN* dvcman = (DVCMAN*)pChannelMgr; + DVCMAN_CHANNEL* dvcChannel = nullptr; + + WINPR_ASSERT(dvcman); + HashTable_Lock(dvcman->channelsById); + dvcChannel = HashTable_GetItemValue(dvcman->channelsById, &ChannelId); + if (dvcChannel) + { + if (doRef) + InterlockedIncrement(&dvcChannel->refCounter); + } + + HashTable_Unlock(dvcman->channelsById); + return dvcChannel; +} + +static IWTSVirtualChannel* dvcman_find_channel_by_id(IWTSVirtualChannelManager* pChannelMgr, + UINT32 ChannelId) +{ + DVCMAN_CHANNEL* channel = dvcman_get_channel_by_id(pChannelMgr, ChannelId, FALSE); + if (!channel) + return nullptr; + + return &channel->iface; +} + +static void dvcman_plugin_terminate(void* plugin) +{ + IWTSPlugin* pPlugin = plugin; + + WINPR_ASSERT(pPlugin); + UINT error = IFCALLRESULT(CHANNEL_RC_OK, pPlugin->Terminated, pPlugin); + if (error != CHANNEL_RC_OK) + WLog_ERR(TAG, "Terminated failed with error %" PRIu32 "!", error); +} + +static void wts_listener_free(void* arg) +{ + DVCMAN_LISTENER* listener = (DVCMAN_LISTENER*)arg; + dvcman_wtslistener_free(listener); +} + +static BOOL channelIdMatch(const void* k1, const void* k2) +{ + WINPR_ASSERT(k1); + WINPR_ASSERT(k2); + return *((const UINT32*)k1) == *((const UINT32*)k2); +} + +static UINT32 channelIdHash(const void* id) +{ + WINPR_ASSERT(id); + return *((const UINT32*)id); +} + +static void channelByIdCleanerFn(void* value) +{ + DVCMAN_CHANNEL* channel = (DVCMAN_CHANNEL*)value; + if (channel) + { + dvcman_channel_close(channel, FALSE, TRUE); + dvcman_channel_free(channel); + } +} + +static IWTSVirtualChannelManager* dvcman_new(drdynvcPlugin* plugin) +{ + wObject* obj = nullptr; + DVCMAN* dvcman = (DVCMAN*)calloc(1, sizeof(DVCMAN)); + + if (!dvcman) + return nullptr; + + dvcman->iface.CreateListener = dvcman_create_listener; + dvcman->iface.DestroyListener = dvcman_destroy_listener; + dvcman->iface.FindChannelById = dvcman_find_channel_by_id; + dvcman->iface.GetChannelId = dvcman_get_channel_id; + dvcman->iface.GetChannelName = dvcman_get_channel_name; + dvcman->drdynvc = plugin; + dvcman->channelsById = HashTable_New(TRUE); + + if (!dvcman->channelsById) + goto fail; + + HashTable_SetHashFunction(dvcman->channelsById, channelIdHash); + obj = HashTable_KeyObject(dvcman->channelsById); + WINPR_ASSERT(obj); + obj->fnObjectEquals = channelIdMatch; + + obj = HashTable_ValueObject(dvcman->channelsById); + WINPR_ASSERT(obj); + obj->fnObjectFree = channelByIdCleanerFn; + + dvcman->pool = StreamPool_New(TRUE, 10); + if (!dvcman->pool) + goto fail; + + dvcman->listeners = HashTable_New(TRUE); + if (!dvcman->listeners) + goto fail; + HashTable_SetHashFunction(dvcman->listeners, HashTable_StringHash); + + obj = HashTable_KeyObject(dvcman->listeners); + obj->fnObjectEquals = HashTable_StringCompare; + + obj = HashTable_ValueObject(dvcman->listeners); + obj->fnObjectFree = wts_listener_free; + + dvcman->plugin_names = ArrayList_New(TRUE); + if (!dvcman->plugin_names) + goto fail; + obj = ArrayList_Object(dvcman->plugin_names); + obj->fnObjectNew = winpr_ObjectStringClone; + obj->fnObjectFree = winpr_ObjectStringFree; + + dvcman->plugins = ArrayList_New(TRUE); + if (!dvcman->plugins) + goto fail; + obj = ArrayList_Object(dvcman->plugins); + obj->fnObjectFree = dvcman_plugin_terminate; + return &dvcman->iface; +fail: + dvcman_free(plugin, &dvcman->iface); + return nullptr; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT dvcman_load_addin(drdynvcPlugin* drdynvc, IWTSVirtualChannelManager* pChannelMgr, + const ADDIN_ARGV* args, rdpContext* context) +{ + WINPR_ASSERT(drdynvc); + WINPR_ASSERT(pChannelMgr); + WINPR_ASSERT(args); + WINPR_ASSERT(context); + + WLog_Print(drdynvc->log, WLOG_INFO, "Loading Dynamic Virtual Channel %s", args->argv[0]); + + PVIRTUALCHANNELENTRY pvce = freerdp_load_channel_addin_entry(args->argv[0], nullptr, nullptr, + FREERDP_ADDIN_CHANNEL_DYNAMIC); + PDVC_PLUGIN_ENTRY pDVCPluginEntry = WINPR_FUNC_PTR_CAST(pvce, PDVC_PLUGIN_ENTRY); + + if (pDVCPluginEntry) + { + DVCMAN_ENTRY_POINTS entryPoints = WINPR_C_ARRAY_INIT; + + entryPoints.iface.RegisterPlugin = dvcman_register_plugin; + entryPoints.iface.GetPlugin = dvcman_get_plugin; + entryPoints.iface.GetPluginData = dvcman_get_plugin_data; + entryPoints.iface.GetRdpSettings = dvcman_get_rdp_settings; + entryPoints.iface.GetRdpContext = dvcman_get_rdp_context; + entryPoints.dvcman = (DVCMAN*)pChannelMgr; + entryPoints.args = args; + entryPoints.context = context; + return pDVCPluginEntry(&entryPoints.iface); + } + + return ERROR_INVALID_FUNCTION; +} + +static void dvcman_channel_free(DVCMAN_CHANNEL* channel) +{ + if (!channel) + return; + + if (channel->dvc_data) + Stream_Release(channel->dvc_data); + + zgfx_context_free(channel->decompressor); + DeleteCriticalSection(&(channel->lock)); + free(channel->channel_name); + free(channel); +} + +static void dvcman_channel_unref(DVCMAN_CHANNEL* channel) +{ + WINPR_ASSERT(channel); + if (InterlockedDecrement(&channel->refCounter)) + return; + + DVCMAN* dvcman = channel->dvcman; + if (dvcman) + HashTable_Remove(dvcman->channelsById, &channel->channel_id); +} + +static UINT dvcchannel_send_close(DVCMAN_CHANNEL* channel) +{ + WINPR_ASSERT(channel); + DVCMAN* dvcman = channel->dvcman; + drdynvcPlugin* drdynvc = dvcman->drdynvc; + wStream* s = StreamPool_Take(dvcman->pool, 5); + + if (!s) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "StreamPool_Take failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT8(s, (CLOSE_REQUEST_PDU << 4) | 0x02); + Stream_Write_UINT32(s, channel->channel_id); + return drdynvc_send(drdynvc, s); +} + +static void check_open_close_receive(DVCMAN_CHANNEL* channel) +{ + WINPR_ASSERT(channel); + + IWTSVirtualChannelCallback* cb = channel->channel_callback; + const char* name = channel->channel_name; + const UINT32 id = channel->channel_id; + + WINPR_ASSERT(cb); + if (!cb->OnOpen || !cb->OnClose || !cb->OnDataReceived) + WLog_VRB(TAG, "{%s:%" PRIu32 "} OnOpen=%p, OnClose=%p, OnDataReceived=%p", name, id, + WINPR_CXX_COMPAT_CAST(const void*, cb->OnOpen), + WINPR_CXX_COMPAT_CAST(const void*, cb->OnClose), + WINPR_CXX_COMPAT_CAST(const void*, cb->OnDataReceived)); +} + +static UINT dvcman_call_on_receive(DVCMAN_CHANNEL* channel, wStream* data) +{ + WINPR_ASSERT(channel); + WINPR_ASSERT(data); + + IWTSVirtualChannelCallback* cb = channel->channel_callback; + WINPR_ASSERT(cb); + + check_open_close_receive(channel); + WINPR_ASSERT(cb->OnDataReceived); + return cb->OnDataReceived(cb, data); +} + +static UINT dvcman_channel_close(DVCMAN_CHANNEL* channel, BOOL perRequest, BOOL fromHashTableFn) +{ + UINT error = CHANNEL_RC_OK; + DrdynvcClientContext* context = nullptr; + + WINPR_ASSERT(channel); + switch (channel->state) + { + case DVC_CHANNEL_INIT: + break; + case DVC_CHANNEL_RUNNING: + if (channel->dvcman) + { + drdynvcPlugin* drdynvc = channel->dvcman->drdynvc; + WINPR_ASSERT(drdynvc); + context = drdynvc->context; + if (perRequest) + WLog_Print(drdynvc->log, WLOG_DEBUG, "sending close confirm for '%s'", + channel->channel_name); + + error = dvcchannel_send_close(channel); + if (error != CHANNEL_RC_OK) + { + if (perRequest) + WLog_Print(drdynvc->log, WLOG_DEBUG, + "error when sending closeRequest for '%s'", + channel->channel_name); + else + WLog_Print(drdynvc->log, WLOG_DEBUG, + "error when sending close confirm for '%s'", + channel->channel_name); + } + WLog_Print(drdynvc->log, WLOG_DEBUG, "listener %s destroyed channel %" PRIu32 "", + channel->channel_name, channel->channel_id); + } + + channel->state = DVC_CHANNEL_CLOSED; + + { + check_open_close_receive(channel); + + IWTSVirtualChannelCallback* cb = channel->channel_callback; + channel->channel_callback = nullptr; + if (cb) + error = IFCALLRESULT(CHANNEL_RC_OK, cb->OnClose, cb); + } + + if (channel->dvcman && channel->dvcman->drdynvc) + { + if (context) + { + IFCALLRET(context->OnChannelDisconnected, error, context, channel->channel_name, + channel->pInterface); + } + } + + if (!fromHashTableFn) + dvcman_channel_unref(channel); + break; + case DVC_CHANNEL_CLOSED: + break; + default: + break; + } + + return error; +} + +static DVCMAN_CHANNEL* dvcman_channel_new(WINPR_ATTR_UNUSED drdynvcPlugin* drdynvc, + IWTSVirtualChannelManager* pChannelMgr, UINT32 ChannelId, + const char* ChannelName) +{ + DVCMAN_CHANNEL* channel = nullptr; + + WINPR_ASSERT(drdynvc); + WINPR_ASSERT(pChannelMgr); + channel = (DVCMAN_CHANNEL*)calloc(1, sizeof(DVCMAN_CHANNEL)); + + if (!channel) + return nullptr; + + channel->dvcman = (DVCMAN*)pChannelMgr; + channel->channel_id = ChannelId; + channel->refCounter = 1; + channel->state = DVC_CHANNEL_INIT; + channel->channel_name = _strdup(ChannelName); + if (!channel->channel_name) + goto fail; + + channel->decompressor = zgfx_context_new(FALSE); + if (!channel->decompressor) + goto fail; + + if (!InitializeCriticalSectionEx(&(channel->lock), 0, 0)) + goto fail; + + return channel; +fail: + dvcman_channel_free(channel); + return nullptr; +} + +static void dvcman_clear(drdynvcPlugin* drdynvc, IWTSVirtualChannelManager* pChannelMgr) +{ + DVCMAN* dvcman = (DVCMAN*)pChannelMgr; + + WINPR_ASSERT(dvcman); + WINPR_UNUSED(drdynvc); + + HashTable_Clear(dvcman->channelsById); + ArrayList_Clear(dvcman->plugins); + ArrayList_Clear(dvcman->plugin_names); + HashTable_Clear(dvcman->listeners); +} +static void dvcman_free(drdynvcPlugin* drdynvc, IWTSVirtualChannelManager* pChannelMgr) +{ + DVCMAN* dvcman = (DVCMAN*)pChannelMgr; + + WINPR_ASSERT(dvcman); + WINPR_UNUSED(drdynvc); + + HashTable_Free(dvcman->channelsById); + ArrayList_Free(dvcman->plugins); + ArrayList_Free(dvcman->plugin_names); + HashTable_Free(dvcman->listeners); + + StreamPool_Free(dvcman->pool); + free(dvcman); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT dvcman_init(drdynvcPlugin* drdynvc, IWTSVirtualChannelManager* pChannelMgr) +{ + DVCMAN* dvcman = (DVCMAN*)pChannelMgr; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(dvcman); + ArrayList_Lock(dvcman->plugins); + for (size_t i = 0; i < ArrayList_Count(dvcman->plugins); i++) + { + IWTSPlugin* pPlugin = ArrayList_GetItem(dvcman->plugins, i); + + error = IFCALLRESULT(CHANNEL_RC_OK, pPlugin->Initialize, pPlugin, pChannelMgr); + if (error != CHANNEL_RC_OK) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "Initialize failed with error %" PRIu32 "!", + error); + goto fail; + } + } + +fail: + ArrayList_Unlock(dvcman->plugins); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT dvcman_write_channel(IWTSVirtualChannel* pChannel, ULONG cbSize, const BYTE* pBuffer, + void* pReserved) +{ + BOOL close = FALSE; + UINT status = 0; + DVCMAN_CHANNEL* channel = (DVCMAN_CHANNEL*)pChannel; + + WINPR_UNUSED(pReserved); + if (!channel || !channel->dvcman) + return CHANNEL_RC_BAD_CHANNEL; + + EnterCriticalSection(&(channel->lock)); + status = + drdynvc_write_data(channel->dvcman->drdynvc, channel->channel_id, pBuffer, cbSize, &close); + LeaveCriticalSection(&(channel->lock)); + /* Close delayed, it removes the channel struct */ + if (close) + dvcman_channel_close(channel, FALSE, FALSE); + + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT dvcman_close_channel_iface(IWTSVirtualChannel* pChannel) +{ + DVCMAN_CHANNEL* channel = (DVCMAN_CHANNEL*)pChannel; + + if (!channel) + return CHANNEL_RC_BAD_CHANNEL; + + WLog_DBG(TAG, "close_channel_iface: id=%" PRIu32 "", channel->channel_id); + return dvcman_channel_close(channel, FALSE, FALSE); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static DVCMAN_CHANNEL* dvcman_create_channel(drdynvcPlugin* drdynvc, + IWTSVirtualChannelManager* pChannelMgr, + UINT32 ChannelId, const char* ChannelName, UINT* res) +{ + BOOL bAccept = 0; + DVCMAN_CHANNEL* channel = nullptr; + DrdynvcClientContext* context = nullptr; + DVCMAN* dvcman = (DVCMAN*)pChannelMgr; + DVCMAN_LISTENER* listener = nullptr; + IWTSVirtualChannelCallback* pCallback = nullptr; + + WINPR_ASSERT(dvcman); + WINPR_ASSERT(res); + + HashTable_Lock(dvcman->listeners); + listener = (DVCMAN_LISTENER*)HashTable_GetItemValue(dvcman->listeners, ChannelName); + if (!listener) + { + *res = ERROR_NOT_FOUND; + goto out; + } + + channel = dvcman_get_channel_by_id(pChannelMgr, ChannelId, FALSE); + if (channel) + { + switch (channel->state) + { + case DVC_CHANNEL_RUNNING: + WLog_Print(drdynvc->log, WLOG_ERROR, + "Protocol error: Duplicated ChannelId %" PRIu32 " (%s)!", ChannelId, + ChannelName); + *res = CHANNEL_RC_ALREADY_OPEN; + goto out; + + case DVC_CHANNEL_CLOSED: + case DVC_CHANNEL_INIT: + default: + { + WLog_Print(drdynvc->log, WLOG_ERROR, "not expecting a createChannel from state %s", + channel_state2str(channel->state)); + *res = CHANNEL_RC_INITIALIZATION_ERROR; + goto out; + } + } + } + else + { + if (!(channel = dvcman_channel_new(drdynvc, pChannelMgr, ChannelId, ChannelName))) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "dvcman_channel_new failed!"); + *res = CHANNEL_RC_NO_MEMORY; + goto out; + } + } + + if (!HashTable_Insert(dvcman->channelsById, &channel->channel_id, channel)) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "unable to register channel in our channel list"); + *res = ERROR_INTERNAL_ERROR; + dvcman_channel_free(channel); + channel = nullptr; + goto out; + } + + channel->iface.Write = dvcman_write_channel; + channel->iface.Close = dvcman_close_channel_iface; + bAccept = TRUE; + + *res = listener->listener_callback->OnNewChannelConnection( + listener->listener_callback, &channel->iface, nullptr, &bAccept, &pCallback); + + if (*res != CHANNEL_RC_OK) + { + WLog_Print(drdynvc->log, WLOG_ERROR, + "OnNewChannelConnection failed with error %" PRIu32 "!", *res); + *res = ERROR_INTERNAL_ERROR; + dvcman_channel_unref(channel); + goto out; + } + + if (!bAccept) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "OnNewChannelConnection returned with bAccept FALSE!"); + *res = ERROR_INTERNAL_ERROR; + dvcman_channel_unref(channel); + channel = nullptr; + goto out; + } + + WLog_Print(drdynvc->log, WLOG_DEBUG, "listener %s created new channel %" PRIu32 "", + listener->channel_name, channel->channel_id); + channel->state = DVC_CHANNEL_RUNNING; + channel->channel_callback = pCallback; + channel->pInterface = listener->iface.pInterface; + context = dvcman->drdynvc->context; + + IFCALLRET(context->OnChannelConnected, *res, context, ChannelName, listener->iface.pInterface); + if (*res != CHANNEL_RC_OK) + { + WLog_Print(drdynvc->log, WLOG_ERROR, + "context.OnChannelConnected failed with error %" PRIu32 "", *res); + } + +out: + HashTable_Unlock(dvcman->listeners); + + return channel; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT dvcman_open_channel(drdynvcPlugin* drdynvc, DVCMAN_CHANNEL* channel) +{ + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(drdynvc); + WINPR_ASSERT(channel); + if (channel->state == DVC_CHANNEL_RUNNING) + { + IWTSVirtualChannelCallback* pCallback = channel->channel_callback; + + if (pCallback->OnOpen) + { + check_open_close_receive(channel); + error = pCallback->OnOpen(pCallback); + if (error) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "OnOpen failed with error %" PRIu32 "!", + error); + goto out; + } + } + + WLog_Print(drdynvc->log, WLOG_DEBUG, "open_channel: ChannelId %" PRIu32 "", + channel->channel_id); + } + +out: + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT dvcman_receive_channel_data_first(DVCMAN_CHANNEL* channel, UINT32 length) +{ + WINPR_ASSERT(channel); + WINPR_ASSERT(channel->dvcman); + if (channel->dvc_data) + Stream_Release(channel->dvc_data); + + channel->dvc_data = StreamPool_Take(channel->dvcman->pool, length); + + if (!channel->dvc_data) + { + drdynvcPlugin* drdynvc = channel->dvcman->drdynvc; + WLog_Print(drdynvc->log, WLOG_ERROR, "StreamPool_Take failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + channel->dvc_data_length = length; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT dvcman_receive_channel_data(DVCMAN_CHANNEL* channel, wStream* data, + WINPR_ATTR_UNUSED UINT32 ThreadingFlags) +{ + UINT status = CHANNEL_RC_OK; + size_t dataSize = Stream_GetRemainingLength(data); + + WINPR_ASSERT(channel); + WINPR_ASSERT(channel->dvcman); + if (channel->dvc_data) + { + drdynvcPlugin* drdynvc = channel->dvcman->drdynvc; + + /* Fragmented data */ + if (Stream_GetPosition(channel->dvc_data) + dataSize > channel->dvc_data_length) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "data exceeding declared length!"); + Stream_Release(channel->dvc_data); + channel->dvc_data = nullptr; + status = ERROR_INVALID_DATA; + goto out; + } + + Stream_Copy(data, channel->dvc_data, dataSize); + + if (Stream_GetPosition(channel->dvc_data) >= channel->dvc_data_length) + { + Stream_SealLength(channel->dvc_data); + Stream_ResetPosition(channel->dvc_data); + + status = dvcman_call_on_receive(channel, channel->dvc_data); + Stream_Release(channel->dvc_data); + channel->dvc_data = nullptr; + } + } + else + status = dvcman_call_on_receive(channel, data); + +out: + return status; +} + +static UINT8 drdynvc_write_variable_uint(wStream* s, UINT32 val) +{ + UINT8 cb = 0; + + if (val <= 0xFF) + { + cb = 0; + Stream_Write_UINT8(s, (UINT8)val); + } + else if (val <= 0xFFFF) + { + cb = 1; + Stream_Write_UINT16(s, (UINT16)val); + } + else + { + cb = 2; + Stream_Write_UINT32(s, val); + } + + return cb; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_send(drdynvcPlugin* drdynvc, wStream* s) +{ + UINT status = 0; + + if (!drdynvc) + status = CHANNEL_RC_BAD_CHANNEL_HANDLE; + else + { + WINPR_ASSERT(drdynvc->channelEntryPoints.pVirtualChannelWriteEx); + status = drdynvc->channelEntryPoints.pVirtualChannelWriteEx( + drdynvc->InitHandle, drdynvc->OpenHandle, Stream_Buffer(s), + (UINT32)Stream_GetPosition(s), s); + } + + switch (status) + { + case CHANNEL_RC_OK: + return CHANNEL_RC_OK; + + case CHANNEL_RC_NOT_CONNECTED: + Stream_Release(s); + return CHANNEL_RC_OK; + + case CHANNEL_RC_BAD_CHANNEL_HANDLE: + Stream_Release(s); + WLog_ERR(TAG, "VirtualChannelWriteEx failed with CHANNEL_RC_BAD_CHANNEL_HANDLE"); + return status; + + default: + Stream_Release(s); + WLog_Print(drdynvc->log, WLOG_ERROR, + "VirtualChannelWriteEx failed with %s [%08" PRIX32 "]", + WTSErrorToString(status), status); + return status; + } +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_write_data(drdynvcPlugin* drdynvc, UINT32 ChannelId, const BYTE* data, + UINT32 dataSize, BOOL* close) +{ + wStream* data_out = nullptr; + size_t pos = 0; + UINT8 cbChId = 0; + UINT8 cbLen = 0; + UINT status = CHANNEL_RC_BAD_INIT_HANDLE; + DVCMAN* dvcman = nullptr; + + if (!drdynvc) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + dvcman = (DVCMAN*)drdynvc->channel_mgr; + WINPR_ASSERT(dvcman); + + WLog_Print(drdynvc->log, WLOG_TRACE, "write_data: ChannelId=%" PRIu32 " size=%" PRIu32 "", + ChannelId, dataSize); + data_out = StreamPool_Take(dvcman->pool, CHANNEL_CHUNK_LENGTH); + + if (!data_out) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "StreamPool_Take failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_SetPosition(data_out, 1); + cbChId = drdynvc_write_variable_uint(data_out, ChannelId); + pos = Stream_GetPosition(data_out); + + if (dataSize == 0) + { + /* TODO: shall treat that case with write(0) that do a close */ + *close = TRUE; + Stream_Release(data_out); + } + else if (dataSize <= CHANNEL_CHUNK_LENGTH - pos) + { + Stream_ResetPosition(data_out); + Stream_Write_UINT8(data_out, (DATA_PDU << 4) | cbChId); + Stream_SetPosition(data_out, pos); + Stream_Write(data_out, data, dataSize); + status = drdynvc_send(drdynvc, data_out); + } + else + { + /* Fragment the data */ + cbLen = drdynvc_write_variable_uint(data_out, dataSize); + pos = Stream_GetPosition(data_out); + Stream_ResetPosition(data_out); + + const INT32 pdu = (DATA_FIRST_PDU << 4) | cbChId | (cbLen << 2); + Stream_Write_UINT8(data_out, WINPR_ASSERTING_INT_CAST(UINT8, pdu)); + Stream_SetPosition(data_out, pos); + + { + WINPR_ASSERT(pos <= CHANNEL_CHUNK_LENGTH); + const uint32_t chunkLength = + CHANNEL_CHUNK_LENGTH - WINPR_ASSERTING_INT_CAST(uint32_t, pos); + Stream_Write(data_out, data, chunkLength); + + data += chunkLength; + dataSize -= chunkLength; + } + status = drdynvc_send(drdynvc, data_out); + + while (status == CHANNEL_RC_OK && dataSize > 0) + { + data_out = StreamPool_Take(dvcman->pool, CHANNEL_CHUNK_LENGTH); + + if (!data_out) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "StreamPool_Take failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_SetPosition(data_out, 1); + cbChId = drdynvc_write_variable_uint(data_out, ChannelId); + pos = Stream_GetPosition(data_out); + Stream_ResetPosition(data_out); + Stream_Write_UINT8(data_out, (DATA_PDU << 4) | cbChId); + Stream_SetPosition(data_out, pos); + + uint32_t chunkLength = dataSize; + + WINPR_ASSERT(pos <= CHANNEL_CHUNK_LENGTH); + if (chunkLength > CHANNEL_CHUNK_LENGTH - pos) + chunkLength = CHANNEL_CHUNK_LENGTH - WINPR_ASSERTING_INT_CAST(uint32_t, pos); + + Stream_Write(data_out, data, chunkLength); + data += chunkLength; + dataSize -= chunkLength; + status = drdynvc_send(drdynvc, data_out); + } + } + + if (status != CHANNEL_RC_OK) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "VirtualChannelWriteEx failed with %s [%08" PRIX32 "]", + WTSErrorToString(status), status); + return status; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_send_capability_response(drdynvcPlugin* drdynvc) +{ + UINT status = 0; + wStream* s = nullptr; + DVCMAN* dvcman = nullptr; + + if (!drdynvc) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + dvcman = (DVCMAN*)drdynvc->channel_mgr; + WINPR_ASSERT(dvcman); + + WLog_Print(drdynvc->log, WLOG_TRACE, "capability_response"); + s = StreamPool_Take(dvcman->pool, 4); + + if (!s) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "Stream_Ndrdynvc_write_variable_uintew failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, 0x0050); /* Cmd+Sp+cbChId+Pad. Note: MSTSC sends 0x005c */ + Stream_Write_UINT16(s, drdynvc->version); + status = drdynvc_send(drdynvc, s); + + if (status != CHANNEL_RC_OK) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "VirtualChannelWriteEx failed with %s [%08" PRIX32 "]", + WTSErrorToString(status), status); + } + + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_process_capability_request(drdynvcPlugin* drdynvc, int Sp, int cbChId, + wStream* s) +{ + UINT status = 0; + + if (!drdynvc) + return CHANNEL_RC_BAD_INIT_HANDLE; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 3)) + return ERROR_INVALID_DATA; + + WLog_Print(drdynvc->log, WLOG_TRACE, "capability_request Sp=%d cbChId=%d", Sp, cbChId); + Stream_Seek(s, 1); /* pad */ + Stream_Read_UINT16(s, drdynvc->version); + + /* RDP8 servers offer version 3, though Microsoft forgot to document it + * in their early documents. It behaves the same as version 2. + */ + if ((drdynvc->version == 2) || (drdynvc->version == 3)) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, drdynvc->PriorityCharge0); + Stream_Read_UINT16(s, drdynvc->PriorityCharge1); + Stream_Read_UINT16(s, drdynvc->PriorityCharge2); + Stream_Read_UINT16(s, drdynvc->PriorityCharge3); + } + + status = drdynvc_send_capability_response(drdynvc); + drdynvc->state = DRDYNVC_STATE_READY; + return status; +} + +static UINT32 drdynvc_cblen_to_bytes(int cbLen) +{ + switch (cbLen) + { + case 0: + return 1; + + case 1: + return 2; + + default: + return 4; + } +} + +static UINT32 drdynvc_read_variable_uint(wStream* s, int cbLen) +{ + UINT32 val = 0; + + switch (cbLen) + { + case 0: + Stream_Read_UINT8(s, val); + break; + + case 1: + Stream_Read_UINT16(s, val); + break; + + default: + Stream_Read_UINT32(s, val); + break; + } + + return val; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_process_create_request(drdynvcPlugin* drdynvc, UINT8 Sp, UINT8 cbChId, + wStream* s) +{ + UINT status = 0; + wStream* data_out = nullptr; + UINT channel_status = 0; + DVCMAN* dvcman = nullptr; + DVCMAN_CHANNEL* channel = nullptr; + INT32 retStatus = 0; + + WINPR_UNUSED(Sp); + if (!drdynvc) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + dvcman = (DVCMAN*)drdynvc->channel_mgr; + WINPR_ASSERT(dvcman); + + if (drdynvc->state == DRDYNVC_STATE_CAPABILITIES) + { + /** + * For some reason the server does not always send the + * capabilities pdu as it should. When this happens, + * send a capabilities response. + */ + drdynvc->version = 3; + + if ((status = drdynvc_send_capability_response(drdynvc))) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "drdynvc_send_capability_response failed!"); + return status; + } + + drdynvc->state = DRDYNVC_STATE_READY; + } + + if (!Stream_CheckAndLogRequiredLength(TAG, s, drdynvc_cblen_to_bytes(cbChId))) + return ERROR_INVALID_DATA; + + const UINT32 ChannelId = drdynvc_read_variable_uint(s, cbChId); + const size_t pos = Stream_GetPosition(s); + const char* name = Stream_ConstPointer(s); + const size_t length = Stream_GetRemainingLength(s); + + if (strnlen(name, length) >= length) + return ERROR_INVALID_DATA; + + WLog_Print(drdynvc->log, WLOG_DEBUG, + "process_create_request: ChannelId=%" PRIu32 " ChannelName=%s", ChannelId, name); + + data_out = StreamPool_Take(dvcman->pool, pos + 4); + if (!data_out) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "StreamPool_Take failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT8(data_out, (CREATE_REQUEST_PDU << 4) | cbChId); + Stream_SetPosition(s, 1); + Stream_Copy(s, data_out, pos - 1); + + channel = + dvcman_create_channel(drdynvc, drdynvc->channel_mgr, ChannelId, name, &channel_status); + switch (channel_status) + { + case CHANNEL_RC_OK: + WLog_Print(drdynvc->log, WLOG_DEBUG, "channel created"); + retStatus = 0; + break; + case CHANNEL_RC_NO_MEMORY: + WLog_Print(drdynvc->log, WLOG_DEBUG, "not enough memory for channel creation"); + retStatus = STATUS_NO_MEMORY; + break; + case ERROR_NOT_FOUND: + WLog_Print(drdynvc->log, WLOG_DEBUG, "no listener for '%s'", name); + retStatus = STATUS_NOT_FOUND; /* same code used by mstsc, STATUS_UNSUCCESSFUL */ + break; + default: + WLog_Print(drdynvc->log, WLOG_DEBUG, "channel creation error"); + retStatus = STATUS_UNSUCCESSFUL; /* same code used by mstsc, STATUS_UNSUCCESSFUL */ + break; + } + Stream_Write_INT32(data_out, retStatus); + + status = drdynvc_send(drdynvc, data_out); + if (status != CHANNEL_RC_OK) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "VirtualChannelWriteEx failed with %s [%08" PRIX32 "]", + WTSErrorToString(status), status); + dvcman_channel_unref(channel); + return status; + } + + if (channel_status == CHANNEL_RC_OK) + { + if ((status = dvcman_open_channel(drdynvc, channel))) + { + WLog_Print(drdynvc->log, WLOG_ERROR, + "dvcman_open_channel failed with error %" PRIu32 "!", status); + return status; + } + } + + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_process_data_first(drdynvcPlugin* drdynvc, int Sp, int cbChId, wStream* s, + BOOL compressed, UINT32 ThreadingFlags) +{ + WINPR_ASSERT(drdynvc); + if (!Stream_CheckAndLogRequiredLength( + TAG, s, drdynvc_cblen_to_bytes(cbChId) + drdynvc_cblen_to_bytes(Sp))) + return ERROR_INVALID_DATA; + + UINT32 ChannelId = drdynvc_read_variable_uint(s, cbChId); + UINT32 Length = drdynvc_read_variable_uint(s, Sp); + WLog_Print(drdynvc->log, WLOG_TRACE, + "process_data_first: Sp=%d cbChId=%d, ChannelId=%" PRIu32 " Length=%" PRIu32 "", Sp, + cbChId, ChannelId, Length); + + DVCMAN_CHANNEL* channel = dvcman_get_channel_by_id(drdynvc->channel_mgr, ChannelId, TRUE); + if (!channel) + { + /** + * Windows Server 2012 R2 can send some messages over + * Microsoft::Windows::RDS::Geometry::v08.01 even if the dynamic virtual channel wasn't + * registered on our side. Ignoring it works. + */ + WLog_Print(drdynvc->log, WLOG_ERROR, "ChannelId %" PRIu32 " not found!", ChannelId); + return CHANNEL_RC_OK; + } + + UINT status = CHANNEL_RC_OK; + BOOL shouldFree = FALSE; + if (channel->state != DVC_CHANNEL_RUNNING) + goto out; + + if (compressed) + { + BYTE* data = nullptr; + UINT32 dataSize = 0; + if (zgfx_decompress(channel->decompressor, Stream_Pointer(s), + WINPR_ASSERTING_INT_CAST(UINT32, Stream_GetRemainingLength(s)), &data, + &dataSize, 0) < 0) + { + status = ERROR_INVALID_DATA; + WLog_Print(drdynvc->log, WLOG_ERROR, "error de-compressing first packet"); + goto out; + } + + s = Stream_New(data, dataSize); + if (!s) + { + status = CHANNEL_RC_NO_MEMORY; + WLog_Print(drdynvc->log, WLOG_ERROR, "error allocating new Stream(len=%" PRIu32 ")", + dataSize); + free(data); + goto out; + } + shouldFree = TRUE; + } + + status = dvcman_receive_channel_data_first(channel, Length); + + if (status == CHANNEL_RC_OK) + status = dvcman_receive_channel_data(channel, s, ThreadingFlags); + + if (status != CHANNEL_RC_OK) + status = dvcman_channel_close(channel, FALSE, FALSE); + +out: + if (shouldFree) + Stream_Free(s, TRUE); + dvcman_channel_unref(channel); + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_process_data(drdynvcPlugin* drdynvc, int Sp, int cbChId, wStream* s, + BOOL compressed, UINT32 ThreadingFlags) +{ + WINPR_ASSERT(drdynvc); + if (!Stream_CheckAndLogRequiredLength(TAG, s, drdynvc_cblen_to_bytes(cbChId))) + return ERROR_INVALID_DATA; + + UINT32 ChannelId = drdynvc_read_variable_uint(s, cbChId); + WLog_Print(drdynvc->log, WLOG_TRACE, "process_data: Sp=%d cbChId=%d, ChannelId=%" PRIu32 "", Sp, + cbChId, ChannelId); + + DVCMAN_CHANNEL* channel = dvcman_get_channel_by_id(drdynvc->channel_mgr, ChannelId, TRUE); + if (!channel) + { + /** + * Windows Server 2012 R2 can send some messages over + * Microsoft::Windows::RDS::Geometry::v08.01 even if the dynamic virtual channel wasn't + * registered on our side. Ignoring it works. + */ + WLog_Print(drdynvc->log, WLOG_ERROR, "ChannelId %" PRIu32 " not found!", ChannelId); + return CHANNEL_RC_OK; + } + + BOOL shouldFree = FALSE; + UINT status = CHANNEL_RC_OK; + if (channel->state != DVC_CHANNEL_RUNNING) + goto out; + + if (compressed) + { + BYTE* data = nullptr; + UINT32 dataSize = 0; + + if (zgfx_decompress(channel->decompressor, Stream_Pointer(s), + WINPR_ASSERTING_INT_CAST(UINT32, Stream_GetRemainingLength(s)), &data, + &dataSize, 0) < 0) + { + status = ERROR_INVALID_DATA; + WLog_Print(drdynvc->log, WLOG_ERROR, "error de-compressing data packet"); + goto out; + } + + s = Stream_New(data, dataSize); + if (!s) + { + status = CHANNEL_RC_NO_MEMORY; + WLog_Print(drdynvc->log, WLOG_ERROR, "error allocating new Stream(len=%" PRIu32 ")", + dataSize); + free(data); + goto out; + } + shouldFree = TRUE; + } + + status = dvcman_receive_channel_data(channel, s, ThreadingFlags); + if (status != CHANNEL_RC_OK) + status = dvcman_channel_close(channel, FALSE, FALSE); + +out: + if (shouldFree) + Stream_Free(s, TRUE); + dvcman_channel_unref(channel); + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_process_close_request(drdynvcPlugin* drdynvc, int Sp, int cbChId, wStream* s) +{ + UINT32 ChannelId = 0; + DVCMAN_CHANNEL* channel = nullptr; + + WINPR_ASSERT(drdynvc); + if (!Stream_CheckAndLogRequiredLength(TAG, s, drdynvc_cblen_to_bytes(cbChId))) + return ERROR_INVALID_DATA; + + ChannelId = drdynvc_read_variable_uint(s, cbChId); + WLog_Print(drdynvc->log, WLOG_DEBUG, + "process_close_request: Sp=%d cbChId=%d, ChannelId=%" PRIu32 "", Sp, cbChId, + ChannelId); + + channel = dvcman_get_channel_by_id(drdynvc->channel_mgr, ChannelId, TRUE); + if (!channel) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "dvcman_close_request channel %" PRIu32 " not present", + ChannelId); + return CHANNEL_RC_OK; + } + + dvcman_channel_close(channel, TRUE, FALSE); + dvcman_channel_unref(channel); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_order_recv(drdynvcPlugin* drdynvc, wStream* s, UINT32 ThreadingFlags) +{ + WINPR_ASSERT(drdynvc); + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return ERROR_INVALID_DATA; + + UINT8 value = Stream_Get_UINT8(s); + const UINT8 Cmd = (value & 0xf0) >> 4; + const UINT8 Sp = (value & 0x0c) >> 2; + const UINT8 cbChId = (value & 0x03) >> 0; + WLog_Print(drdynvc->log, WLOG_TRACE, "order_recv: Cmd=%s, Sp=%" PRIu8 " cbChId=%" PRIu8, + drdynvc_get_packet_type(Cmd), Sp, cbChId); + + switch (Cmd) + { + case CAPABILITY_REQUEST_PDU: + return drdynvc_process_capability_request(drdynvc, Sp, cbChId, s); + + case CREATE_REQUEST_PDU: + return drdynvc_process_create_request(drdynvc, Sp, cbChId, s); + + case DATA_FIRST_PDU: + case DATA_FIRST_COMPRESSED_PDU: + return drdynvc_process_data_first(drdynvc, Sp, cbChId, s, + (Cmd == DATA_FIRST_COMPRESSED_PDU), ThreadingFlags); + + case DATA_PDU: + case DATA_COMPRESSED_PDU: + return drdynvc_process_data(drdynvc, Sp, cbChId, s, (Cmd == DATA_COMPRESSED_PDU), + ThreadingFlags); + + case CLOSE_REQUEST_PDU: + return drdynvc_process_close_request(drdynvc, Sp, cbChId, s); + + case SOFT_SYNC_RESPONSE_PDU: + WLog_Print(drdynvc->log, WLOG_ERROR, + "not expecting a SOFT_SYNC_RESPONSE_PDU as a client"); + return ERROR_INTERNAL_ERROR; + + default: + WLog_Print(drdynvc->log, WLOG_ERROR, "unknown drdynvc cmd 0x%x", Cmd); + return ERROR_INTERNAL_ERROR; + } +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_virtual_channel_event_data_received(drdynvcPlugin* drdynvc, void* pData, + UINT32 dataLength, UINT32 totalLength, + UINT32 dataFlags) +{ + wStream* data_in = nullptr; + + WINPR_ASSERT(drdynvc); + if ((dataFlags & CHANNEL_FLAG_SUSPEND) || (dataFlags & CHANNEL_FLAG_RESUME)) + { + return CHANNEL_RC_OK; + } + + if (dataFlags & CHANNEL_FLAG_FIRST) + { + DVCMAN* mgr = (DVCMAN*)drdynvc->channel_mgr; + if (drdynvc->data_in) + Stream_Release(drdynvc->data_in); + + drdynvc->data_in = StreamPool_Take(mgr->pool, totalLength); + } + + if (!(data_in = drdynvc->data_in)) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "StreamPool_Take failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if (!Stream_EnsureRemainingCapacity(data_in, dataLength)) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!"); + Stream_Release(drdynvc->data_in); + drdynvc->data_in = nullptr; + return ERROR_INTERNAL_ERROR; + } + + Stream_Write(data_in, pData, dataLength); + + if (dataFlags & CHANNEL_FLAG_LAST) + { + const size_t cap = Stream_Capacity(data_in); + const size_t pos = Stream_GetPosition(data_in); + if (cap < pos) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "drdynvc_plugin_process_received: read error"); + return ERROR_INVALID_DATA; + } + + drdynvc->data_in = nullptr; + Stream_SealLength(data_in); + Stream_ResetPosition(data_in); + + if (drdynvc->async) + { + if (!MessageQueue_Post(drdynvc->queue, nullptr, 0, (void*)data_in, nullptr)) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "MessageQueue_Post failed!"); + return ERROR_INTERNAL_ERROR; + } + } + else + { + UINT error = drdynvc_order_recv(drdynvc, data_in, TRUE); + Stream_Release(data_in); + + if (error) + { + WLog_Print(drdynvc->log, WLOG_WARN, + "drdynvc_order_recv failed with error %" PRIu32 "!", error); + return error; + } + } + } + + return CHANNEL_RC_OK; +} + +static void VCAPITYPE drdynvc_virtual_channel_open_event_ex(LPVOID lpUserParam, DWORD openHandle, + UINT event, LPVOID pData, + UINT32 dataLength, UINT32 totalLength, + UINT32 dataFlags) +{ + UINT error = CHANNEL_RC_OK; + drdynvcPlugin* drdynvc = (drdynvcPlugin*)lpUserParam; + + WINPR_ASSERT(drdynvc); + switch (event) + { + case CHANNEL_EVENT_DATA_RECEIVED: + if (!drdynvc || (drdynvc->OpenHandle != openHandle)) + { + WLog_ERR(TAG, "drdynvc_virtual_channel_open_event: error no match"); + return; + } + if ((error = drdynvc_virtual_channel_event_data_received(drdynvc, pData, dataLength, + totalLength, dataFlags))) + WLog_Print(drdynvc->log, WLOG_ERROR, + "drdynvc_virtual_channel_event_data_received failed with error %" PRIu32 + "", + error); + + break; + + case CHANNEL_EVENT_WRITE_CANCELLED: + case CHANNEL_EVENT_WRITE_COMPLETE: + { + wStream* s = (wStream*)pData; + Stream_Release(s); + } + break; + + case CHANNEL_EVENT_USER: + break; + default: + break; + } + + if (error && drdynvc && drdynvc->rdpcontext) + setChannelError(drdynvc->rdpcontext, error, + "drdynvc_virtual_channel_open_event reported an error"); +} + +static DWORD WINAPI drdynvc_virtual_channel_client_thread(LPVOID arg) +{ + /* TODO: rewrite this */ + wStream* data = nullptr; + wMessage message = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + drdynvcPlugin* drdynvc = (drdynvcPlugin*)arg; + + if (!drdynvc) + { + ExitThread((DWORD)CHANNEL_RC_BAD_CHANNEL_HANDLE); + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + } + + while (1) + { + if (!MessageQueue_Wait(drdynvc->queue)) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "MessageQueue_Wait failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (!MessageQueue_Peek(drdynvc->queue, &message, TRUE)) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "MessageQueue_Peek failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (message.id == WMQ_QUIT) + break; + + if (message.id == 0) + { + UINT32 ThreadingFlags = TRUE; + data = (wStream*)message.wParam; + + if ((error = drdynvc_order_recv(drdynvc, data, ThreadingFlags))) + { + WLog_Print(drdynvc->log, WLOG_WARN, + "drdynvc_order_recv failed with error %" PRIu32 "!", error); + } + + Stream_Release(data); + } + } + + { + /* Disconnect remaining dynamic channels that the server did not. + * This is required to properly shut down channels by calling the appropriate + * event handlers. */ + DVCMAN* drdynvcMgr = (DVCMAN*)drdynvc->channel_mgr; + + HashTable_Clear(drdynvcMgr->channelsById); + } + + if (error && drdynvc->rdpcontext) + setChannelError(drdynvc->rdpcontext, error, + "drdynvc_virtual_channel_client_thread reported an error"); + + ExitThread((DWORD)error); + return error; +} + +static void drdynvc_queue_object_free(void* obj) +{ + wStream* s = nullptr; + wMessage* msg = (wMessage*)obj; + + if (!msg || (msg->id != 0)) + return; + + s = (wStream*)msg->wParam; + + if (s) + Stream_Release(s); +} + +static UINT drdynvc_virtual_channel_event_initialized(drdynvcPlugin* drdynvc, LPVOID pData, + UINT32 dataLength) +{ + wObject* obj = nullptr; + WINPR_UNUSED(pData); + WINPR_UNUSED(dataLength); + + if (!drdynvc) + goto error; + + drdynvc->queue = MessageQueue_New(nullptr); + + if (!drdynvc->queue) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "MessageQueue_New failed!"); + goto error; + } + + obj = MessageQueue_Object(drdynvc->queue); + obj->fnObjectFree = drdynvc_queue_object_free; + drdynvc->channel_mgr = dvcman_new(drdynvc); + + if (!drdynvc->channel_mgr) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "dvcman_new failed!"); + goto error; + } + + return CHANNEL_RC_OK; +error: + return ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_virtual_channel_event_connected(drdynvcPlugin* drdynvc, LPVOID pData, + UINT32 dataLength) +{ + UINT error = 0; + UINT32 status = 0; + rdpSettings* settings = nullptr; + + WINPR_ASSERT(drdynvc); + WINPR_UNUSED(pData); + WINPR_UNUSED(dataLength); + + if (!drdynvc) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + WINPR_ASSERT(drdynvc->channelEntryPoints.pVirtualChannelOpenEx); + status = drdynvc->channelEntryPoints.pVirtualChannelOpenEx( + drdynvc->InitHandle, &drdynvc->OpenHandle, drdynvc->channelDef.name, + drdynvc_virtual_channel_open_event_ex); + + if (status != CHANNEL_RC_OK) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "pVirtualChannelOpen failed with %s [%08" PRIX32 "]", + WTSErrorToString(status), status); + return status; + } + + WINPR_ASSERT(drdynvc->rdpcontext); + settings = drdynvc->rdpcontext->settings; + WINPR_ASSERT(settings); + + for (UINT32 index = 0; + index < freerdp_settings_get_uint32(settings, FreeRDP_DynamicChannelCount); index++) + { + const ADDIN_ARGV* args = + freerdp_settings_get_pointer_array(settings, FreeRDP_DynamicChannelArray, index); + error = dvcman_load_addin(drdynvc, drdynvc->channel_mgr, args, drdynvc->rdpcontext); + + if (CHANNEL_RC_OK != error) + goto error; + } + + if ((error = dvcman_init(drdynvc, drdynvc->channel_mgr))) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "dvcman_init failed with error %" PRIu32 "!", error); + goto error; + } + + drdynvc->state = DRDYNVC_STATE_CAPABILITIES; + + if (drdynvc->async) + { + if (!(drdynvc->thread = CreateThread(nullptr, 0, drdynvc_virtual_channel_client_thread, + (void*)drdynvc, 0, nullptr))) + { + error = ERROR_INTERNAL_ERROR; + WLog_Print(drdynvc->log, WLOG_ERROR, "CreateThread failed!"); + goto error; + } + + if (!SetThreadPriority(drdynvc->thread, THREAD_PRIORITY_HIGHEST)) + WLog_Print(drdynvc->log, WLOG_WARN, "SetThreadPriority failed, ignoring."); + } + +error: + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_virtual_channel_event_disconnected(drdynvcPlugin* drdynvc) +{ + UINT status = 0; + + if (!drdynvc) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + if (drdynvc->OpenHandle == 0) + return CHANNEL_RC_OK; + + if (drdynvc->queue) + { + if (!MessageQueue_PostQuit(drdynvc->queue, 0)) + { + status = GetLastError(); + WLog_Print(drdynvc->log, WLOG_ERROR, + "MessageQueue_PostQuit failed with error %" PRIu32 "", status); + return status; + } + } + + if (drdynvc->thread) + { + if (WaitForSingleObject(drdynvc->thread, INFINITE) != WAIT_OBJECT_0) + { + status = GetLastError(); + WLog_Print(drdynvc->log, WLOG_ERROR, + "WaitForSingleObject failed with error %" PRIu32 "", status); + return status; + } + + (void)CloseHandle(drdynvc->thread); + drdynvc->thread = nullptr; + } + else + { + { + /* Disconnect remaining dynamic channels that the server did not. + * This is required to properly shut down channels by calling the appropriate + * event handlers. */ + DVCMAN* drdynvcMgr = (DVCMAN*)drdynvc->channel_mgr; + + HashTable_Clear(drdynvcMgr->channelsById); + } + } + + WINPR_ASSERT(drdynvc->channelEntryPoints.pVirtualChannelCloseEx); + status = drdynvc->channelEntryPoints.pVirtualChannelCloseEx(drdynvc->InitHandle, + drdynvc->OpenHandle); + + if (status != CHANNEL_RC_OK) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "pVirtualChannelClose failed with %s [%08" PRIX32 "]", + WTSErrorToString(status), status); + } + + dvcman_clear(drdynvc, drdynvc->channel_mgr); + if (drdynvc->queue) + MessageQueue_Clear(drdynvc->queue); + drdynvc->OpenHandle = 0; + + if (drdynvc->data_in) + { + Stream_Release(drdynvc->data_in); + drdynvc->data_in = nullptr; + } + + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_virtual_channel_event_terminated(drdynvcPlugin* drdynvc) +{ + if (!drdynvc) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + MessageQueue_Free(drdynvc->queue); + drdynvc->queue = nullptr; + + if (drdynvc->channel_mgr) + { + dvcman_free(drdynvc, drdynvc->channel_mgr); + drdynvc->channel_mgr = nullptr; + } + drdynvc->InitHandle = nullptr; + free(drdynvc->context); + free(drdynvc); + return CHANNEL_RC_OK; +} + +static UINT drdynvc_virtual_channel_event_attached(drdynvcPlugin* drdynvc) +{ + UINT error = CHANNEL_RC_OK; + DVCMAN* dvcman = nullptr; + + if (!drdynvc) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + dvcman = (DVCMAN*)drdynvc->channel_mgr; + + if (!dvcman) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + ArrayList_Lock(dvcman->plugins); + for (size_t i = 0; i < ArrayList_Count(dvcman->plugins); i++) + { + IWTSPlugin* pPlugin = ArrayList_GetItem(dvcman->plugins, i); + + error = IFCALLRESULT(CHANNEL_RC_OK, pPlugin->Attached, pPlugin); + if (error != CHANNEL_RC_OK) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "Attach failed with error %" PRIu32 "!", error); + goto fail; + } + } + +fail: + ArrayList_Unlock(dvcman->plugins); + return error; +} + +static UINT drdynvc_virtual_channel_event_detached(drdynvcPlugin* drdynvc) +{ + UINT error = CHANNEL_RC_OK; + DVCMAN* dvcman = nullptr; + + if (!drdynvc) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + dvcman = (DVCMAN*)drdynvc->channel_mgr; + + if (!dvcman) + return CHANNEL_RC_BAD_CHANNEL_HANDLE; + + ArrayList_Lock(dvcman->plugins); + for (size_t i = 0; i < ArrayList_Count(dvcman->plugins); i++) + { + IWTSPlugin* pPlugin = ArrayList_GetItem(dvcman->plugins, i); + + error = IFCALLRESULT(CHANNEL_RC_OK, pPlugin->Detached, pPlugin); + if (error != CHANNEL_RC_OK) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "Detach failed with error %" PRIu32 "!", error); + goto fail; + } + } + +fail: + ArrayList_Unlock(dvcman->plugins); + + return error; +} + +static VOID VCAPITYPE drdynvc_virtual_channel_init_event_ex(LPVOID lpUserParam, LPVOID pInitHandle, + UINT event, LPVOID pData, + UINT dataLength) +{ + UINT error = CHANNEL_RC_OK; + drdynvcPlugin* drdynvc = (drdynvcPlugin*)lpUserParam; + + if (!drdynvc || (drdynvc->InitHandle != pInitHandle)) + { + WLog_ERR(TAG, "drdynvc_virtual_channel_init_event: error no match"); + return; + } + + switch (event) + { + case CHANNEL_EVENT_INITIALIZED: + error = drdynvc_virtual_channel_event_initialized(drdynvc, pData, dataLength); + break; + case CHANNEL_EVENT_CONNECTED: + if ((error = drdynvc_virtual_channel_event_connected(drdynvc, pData, dataLength))) + WLog_Print(drdynvc->log, WLOG_ERROR, + "drdynvc_virtual_channel_event_connected failed with error %" PRIu32 "", + error); + + break; + + case CHANNEL_EVENT_DISCONNECTED: + if ((error = drdynvc_virtual_channel_event_disconnected(drdynvc))) + WLog_Print(drdynvc->log, WLOG_ERROR, + "drdynvc_virtual_channel_event_disconnected failed with error %" PRIu32 + "", + error); + + break; + + case CHANNEL_EVENT_TERMINATED: + if ((error = drdynvc_virtual_channel_event_terminated(drdynvc))) + WLog_Print(drdynvc->log, WLOG_ERROR, + "drdynvc_virtual_channel_event_terminated failed with error %" PRIu32 "", + error); + + break; + + case CHANNEL_EVENT_ATTACHED: + if ((error = drdynvc_virtual_channel_event_attached(drdynvc))) + WLog_Print(drdynvc->log, WLOG_ERROR, + "drdynvc_virtual_channel_event_attached failed with error %" PRIu32 "", + error); + + break; + + case CHANNEL_EVENT_DETACHED: + if ((error = drdynvc_virtual_channel_event_detached(drdynvc))) + WLog_Print(drdynvc->log, WLOG_ERROR, + "drdynvc_virtual_channel_event_detached failed with error %" PRIu32 "", + error); + + break; + + default: + break; + } + + if (error && drdynvc->rdpcontext) + setChannelError(drdynvc->rdpcontext, error, + "drdynvc_virtual_channel_init_event_ex reported an error"); +} + +/** + * Channel Client Interface + */ + +static int drdynvc_get_version(DrdynvcClientContext* context) +{ + WINPR_ASSERT(context); + drdynvcPlugin* drdynvc = (drdynvcPlugin*)context->handle; + WINPR_ASSERT(drdynvc); + return drdynvc->version; +} + +/* drdynvc is always built-in */ +#define VirtualChannelEntryEx drdynvc_VirtualChannelEntryEx + +FREERDP_ENTRY_POINT(BOOL VCAPITYPE VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS_EX pEntryPoints, + PVOID pInitHandle)) +{ + UINT rc = 0; + drdynvcPlugin* drdynvc = nullptr; + DrdynvcClientContext* context = nullptr; + CHANNEL_ENTRY_POINTS_FREERDP_EX* pEntryPointsEx = nullptr; + drdynvc = (drdynvcPlugin*)calloc(1, sizeof(drdynvcPlugin)); + + WINPR_ASSERT(pEntryPoints); + if (!drdynvc) + { + WLog_ERR(TAG, "calloc failed!"); + return FALSE; + } + + drdynvc->channelDef.options = + CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP | CHANNEL_OPTION_COMPRESS_RDP; + (void)sprintf_s(drdynvc->channelDef.name, ARRAYSIZE(drdynvc->channelDef.name), + DRDYNVC_SVC_CHANNEL_NAME); + drdynvc->state = DRDYNVC_STATE_INITIAL; + pEntryPointsEx = (CHANNEL_ENTRY_POINTS_FREERDP_EX*)pEntryPoints; + + if ((pEntryPointsEx->cbSize >= sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)) && + (pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER)) + { + context = (DrdynvcClientContext*)calloc(1, sizeof(DrdynvcClientContext)); + + if (!context) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "calloc failed!"); + free(drdynvc); + return FALSE; + } + + context->handle = (void*)drdynvc; + context->custom = nullptr; + drdynvc->context = context; + context->GetVersion = drdynvc_get_version; + drdynvc->rdpcontext = pEntryPointsEx->context; + if (!freerdp_settings_get_bool(drdynvc->rdpcontext->settings, + FreeRDP_TransportDumpReplay) && + !freerdp_settings_get_bool(drdynvc->rdpcontext->settings, + FreeRDP_SynchronousDynamicChannels)) + drdynvc->async = TRUE; + } + + drdynvc->log = WLog_Get(TAG); + WLog_Print(drdynvc->log, WLOG_DEBUG, "VirtualChannelEntryEx"); + CopyMemory(&(drdynvc->channelEntryPoints), pEntryPoints, + sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)); + drdynvc->InitHandle = pInitHandle; + + WINPR_ASSERT(drdynvc->channelEntryPoints.pVirtualChannelInitEx); + rc = drdynvc->channelEntryPoints.pVirtualChannelInitEx( + drdynvc, context, pInitHandle, &drdynvc->channelDef, 1, VIRTUAL_CHANNEL_VERSION_WIN2000, + drdynvc_virtual_channel_init_event_ex); + + if (CHANNEL_RC_OK != rc) + { + WLog_Print(drdynvc->log, WLOG_ERROR, "pVirtualChannelInit failed with %s [%08" PRIX32 "]", + WTSErrorToString(rc), rc); + free(drdynvc->context); + free(drdynvc); + return FALSE; + } + + drdynvc->channelEntryPoints.pInterface = context; + return TRUE; +} diff --git a/third_party/FreeRDP/channels/drdynvc/client/drdynvc_main.h b/third_party/FreeRDP/channels/drdynvc/client/drdynvc_main.h new file mode 100644 index 0000000..5b1f733 --- /dev/null +++ b/third_party/FreeRDP/channels/drdynvc/client/drdynvc_main.h @@ -0,0 +1,135 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Dynamic Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_DRDYNVC_CLIENT_MAIN_H +#define FREERDP_CHANNEL_DRDYNVC_CLIENT_MAIN_H + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct drdynvc_plugin drdynvcPlugin; + +typedef struct +{ + IWTSVirtualChannelManager iface; + + drdynvcPlugin* drdynvc; + + wArrayList* plugin_names; + wArrayList* plugins; + + wHashTable* listeners; + wHashTable* channelsById; + wStreamPool* pool; +} DVCMAN; + +typedef struct +{ + IWTSListener iface; + + DVCMAN* dvcman; + char* channel_name; + UINT32 flags; + IWTSListenerCallback* listener_callback; +} DVCMAN_LISTENER; + +typedef struct +{ + IDRDYNVC_ENTRY_POINTS iface; + + DVCMAN* dvcman; + const ADDIN_ARGV* args; + rdpContext* context; +} DVCMAN_ENTRY_POINTS; + +typedef enum +{ + DVC_CHANNEL_INIT, + DVC_CHANNEL_RUNNING, + DVC_CHANNEL_CLOSED +} DVC_CHANNEL_STATE; + +typedef struct +{ + IWTSVirtualChannel iface; + + volatile LONG refCounter; + DVC_CHANNEL_STATE state; + DVCMAN* dvcman; + void* pInterface; + UINT32 channel_id; + char* channel_name; + IWTSVirtualChannelCallback* channel_callback; + + wStream* dvc_data; + UINT32 dvc_data_length; + ZGFX_CONTEXT* decompressor; + CRITICAL_SECTION lock; +} DVCMAN_CHANNEL; + +typedef enum +{ + DRDYNVC_STATE_INITIAL, + DRDYNVC_STATE_CAPABILITIES, + DRDYNVC_STATE_READY, + DRDYNVC_STATE_OPENING_CHANNEL, + DRDYNVC_STATE_SEND_RECEIVE, + DRDYNVC_STATE_FINAL +} DRDYNVC_STATE; + +struct drdynvc_plugin +{ + CHANNEL_DEF channelDef; + CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints; + + wLog* log; + HANDLE thread; + BOOL async; + wStream* data_in; + void* InitHandle; + DWORD OpenHandle; + wMessageQueue* queue; + + DRDYNVC_STATE state; + DrdynvcClientContext* context; + + UINT16 version; + int PriorityCharge0; + int PriorityCharge1; + int PriorityCharge2; + int PriorityCharge3; + rdpContext* rdpcontext; + + IWTSVirtualChannelManager* channel_mgr; +}; + +#endif /* FREERDP_CHANNEL_DRDYNVC_CLIENT_MAIN_H */ diff --git a/third_party/FreeRDP/channels/drdynvc/server/CMakeLists.txt b/third_party/FreeRDP/channels/drdynvc/server/CMakeLists.txt new file mode 100644 index 0000000..bea05db --- /dev/null +++ b/third_party/FreeRDP/channels/drdynvc/server/CMakeLists.txt @@ -0,0 +1,23 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_server("drdynvc") + +set(${MODULE_PREFIX}_SRCS drdynvc_main.c drdynvc_main.h) + +set(${MODULE_PREFIX}_LIBS freerdp) +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntry") diff --git a/third_party/FreeRDP/channels/drdynvc/server/drdynvc_main.c b/third_party/FreeRDP/channels/drdynvc/server/drdynvc_main.c new file mode 100644 index 0000000..b1f2bc1 --- /dev/null +++ b/third_party/FreeRDP/channels/drdynvc/server/drdynvc_main.c @@ -0,0 +1,204 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Dynamic Virtual Channel Extension + * + * Copyright 2013 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include +#include + +#include "drdynvc_main.h" + +#define TAG CHANNELS_TAG("drdynvc.server") + +static DWORD WINAPI drdynvc_server_thread(WINPR_ATTR_UNUSED LPVOID arg) +{ +#if 0 + wStream* s; + DWORD status; + DWORD nCount; + void* buffer; + HANDLE events[8]; + HANDLE ChannelEvent; + DWORD BytesReturned; + DrdynvcServerContext* context; + UINT error = ERROR_INTERNAL_ERROR; + context = (DrdynvcServerContext*) arg; + buffer = nullptr; + BytesReturned = 0; + ChannelEvent = nullptr; + + s = Stream_New(nullptr, 4096); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + ExitThread((DWORD) CHANNEL_RC_NO_MEMORY); + return CHANNEL_RC_NO_MEMORY; + } + + if (WTSVirtualChannelQuery(context->priv->ChannelHandle, WTSVirtualEventHandle, + &buffer, &BytesReturned) == TRUE) + { + if (BytesReturned == sizeof(HANDLE)) + CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE)); + + WTSFreeMemory(buffer); + } + + nCount = 0; + events[nCount++] = ChannelEvent; + events[nCount++] = context->priv->StopEvent; + + while (1) + { + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + + if (WaitForSingleObject(context->priv->StopEvent, 0) == WAIT_OBJECT_0) + { + error = CHANNEL_RC_OK; + break; + } + + if (!WTSVirtualChannelRead(context->priv->ChannelHandle, 0, nullptr, 0, + &BytesReturned)) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + break; + } + + if (BytesReturned < 1) + continue; + + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + break; + } + + if (!WTSVirtualChannelRead(context->priv->ChannelHandle, 0, + Stream_BufferAs(s, char), Stream_Capacity(s), &BytesReturned)) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + break; + } + } + + Stream_Free(s, TRUE); + ExitThread((DWORD) error); +#endif + // WTF ... this code only reads data into the stream until there is no more memory + ExitThread(0); + return 0; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_server_start(DrdynvcServerContext* context) +{ + context->priv->ChannelHandle = + WTSVirtualChannelOpen(context->vcm, WTS_CURRENT_SESSION, DRDYNVC_SVC_CHANNEL_NAME); + + if (!context->priv->ChannelHandle) + { + WLog_ERR(TAG, "WTSVirtualChannelOpen failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if (!(context->priv->StopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (!(context->priv->Thread = + CreateThread(nullptr, 0, drdynvc_server_thread, (void*)context, 0, nullptr))) + { + WLog_ERR(TAG, "CreateThread failed!"); + (void)CloseHandle(context->priv->StopEvent); + context->priv->StopEvent = nullptr; + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drdynvc_server_stop(DrdynvcServerContext* context) +{ + UINT error = 0; + (void)SetEvent(context->priv->StopEvent); + + if (WaitForSingleObject(context->priv->Thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error); + return error; + } + + (void)CloseHandle(context->priv->Thread); + return CHANNEL_RC_OK; +} + +DrdynvcServerContext* drdynvc_server_context_new(HANDLE vcm) +{ + DrdynvcServerContext* context = nullptr; + context = (DrdynvcServerContext*)calloc(1, sizeof(DrdynvcServerContext)); + + if (context) + { + context->vcm = vcm; + context->Start = drdynvc_server_start; + context->Stop = drdynvc_server_stop; + context->priv = (DrdynvcServerPrivate*)calloc(1, sizeof(DrdynvcServerPrivate)); + + if (!context->priv) + { + WLog_ERR(TAG, "calloc failed!"); + free(context); + return nullptr; + } + } + else + { + WLog_ERR(TAG, "calloc failed!"); + } + + return context; +} + +void drdynvc_server_context_free(DrdynvcServerContext* context) +{ + if (context) + { + free(context->priv); + free(context); + } +} diff --git a/third_party/FreeRDP/channels/drdynvc/server/drdynvc_main.h b/third_party/FreeRDP/channels/drdynvc/server/drdynvc_main.h new file mode 100644 index 0000000..4456d95 --- /dev/null +++ b/third_party/FreeRDP/channels/drdynvc/server/drdynvc_main.h @@ -0,0 +1,37 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Dynamic Virtual Channel Extension + * + * Copyright 2013 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_DRDYNVC_SERVER_MAIN_H +#define FREERDP_CHANNEL_DRDYNVC_SERVER_MAIN_H + +#include +#include +#include + +#include +#include + +struct s_drdynvc_server_private +{ + HANDLE Thread; + HANDLE StopEvent; + void* ChannelHandle; +}; + +#endif /* FREERDP_CHANNEL_DRDYNVC_SERVER_MAIN_H */ diff --git a/third_party/FreeRDP/channels/drive/CMakeLists.txt b/third_party/FreeRDP/channels/drive/CMakeLists.txt new file mode 100644 index 0000000..b6af794 --- /dev/null +++ b/third_party/FreeRDP/channels/drive/CMakeLists.txt @@ -0,0 +1,22 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel("drive") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/third_party/FreeRDP/channels/drive/ChannelOptions.cmake b/third_party/FreeRDP/channels/drive/ChannelOptions.cmake new file mode 100644 index 0000000..3d11f1f --- /dev/null +++ b/third_party/FreeRDP/channels/drive/ChannelOptions.cmake @@ -0,0 +1,20 @@ +set(OPTION_DEFAULT ON) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT OFF) + +define_channel_options( + NAME + "drive" + TYPE + "device" + DESCRIPTION + "Drive Redirection Virtual Channel Extension" + SPECIFICATIONS + "[MS-RDPEFS]" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/drive/client/CMakeLists.txt b/third_party/FreeRDP/channels/drive/client/CMakeLists.txt new file mode 100644 index 0000000..4e23982 --- /dev/null +++ b/third_party/FreeRDP/channels/drive/client/CMakeLists.txt @@ -0,0 +1,23 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_client("drive") + +set(${MODULE_PREFIX}_SRCS drive_file.c drive_file.h drive_main.c) + +set(${MODULE_PREFIX}_LIBS winpr freerdp) +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DeviceServiceEntry") diff --git a/third_party/FreeRDP/channels/drive/client/drive_file.c b/third_party/FreeRDP/channels/drive/client/drive_file.c new file mode 100644 index 0000000..8ea7552 --- /dev/null +++ b/third_party/FreeRDP/channels/drive/client/drive_file.c @@ -0,0 +1,1087 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * File System Virtual Channel + * + * Copyright 2010-2012 Marc-Andre Moreau + * Copyright 2010-2011 Vic Lee + * Copyright 2012 Gerald Richter + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 Inuvika Inc. + * Copyright 2016 David PHAM-VAN + * Copyright 2017 Armin Novak + * Copyright 2017 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 + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "drive_file.h" + +#ifdef WITH_DEBUG_RDPDR +#define DEBUG_WSTR(msg, wstr) \ + do \ + { \ + char lpstr[1024] = WINPR_C_ARRAY_INIT; \ + (void)ConvertWCharToUtf8(wstr, lpstr, ARRAYSIZE(lpstr)); \ + WLog_DBG(TAG, msg, lpstr); \ + } while (0) +#else +#define DEBUG_WSTR(msg, wstr) \ + do \ + { \ + } while (0) +#endif + +static BOOL drive_file_fix_path(WCHAR* path, size_t length) +{ + if ((length == 0) || (length > UINT32_MAX)) + return FALSE; + + WINPR_ASSERT(path); + + for (size_t i = 0; i < length; i++) + { + if (path[i] == L'\\') + path[i] = L'/'; + } + +#ifdef WIN32 + + if ((length == 3) && (path[1] == L':') && (path[2] == L'/')) + return FALSE; + +#else + + if ((length == 1) && (path[0] == L'/')) + return FALSE; + +#endif + + if ((length > 0) && (path[length - 1] == L'/')) + path[length - 1] = L'\0'; + + return TRUE; +} + +static BOOL contains_dotdot(const WCHAR* path, size_t base_length, size_t path_length) +{ + WCHAR dotdotbuffer[6] = WINPR_C_ARRAY_INIT; + const WCHAR* dotdot = InitializeConstWCharFromUtf8("..", dotdotbuffer, ARRAYSIZE(dotdotbuffer)); + const WCHAR* tst = path; + + if (path_length < 2) + return FALSE; + + do + { + tst = _wcsstr(tst, dotdot); + if (!tst) + return FALSE; + + /* Filter .. sequences in file or directory names */ + if ((base_length == 0) || (*(tst - 1) == L'/') || (*(tst - 1) == L'\\')) + { + if (tst + 2 < path + path_length) + { + if ((tst[2] == '/') || (tst[2] == '\\')) + return TRUE; + } + } + tst += 2; + } while (TRUE); + + return FALSE; +} + +static WCHAR* drive_file_combine_fullpath(const WCHAR* base_path, const WCHAR* path, + size_t PathWCharLength) +{ + BOOL ok = FALSE; + WCHAR* fullpath = nullptr; + + if (!base_path || (!path && (PathWCharLength > 0))) + goto fail; + + { + const size_t base_path_length = _wcsnlen(base_path, MAX_PATH); + const size_t length = base_path_length + PathWCharLength + 1; + fullpath = (WCHAR*)calloc(length, sizeof(WCHAR)); + + if (!fullpath) + goto fail; + + CopyMemory(fullpath, base_path, base_path_length * sizeof(WCHAR)); + if (path) + CopyMemory(&fullpath[base_path_length], path, PathWCharLength * sizeof(WCHAR)); + + if (!drive_file_fix_path(fullpath, length)) + goto fail; + + /* Ensure the path does not contain sequences like '..' */ + if (contains_dotdot(&fullpath[base_path_length], base_path_length, PathWCharLength)) + { + char abuffer[MAX_PATH] = WINPR_C_ARRAY_INIT; + (void)ConvertWCharToUtf8(&fullpath[base_path_length], abuffer, ARRAYSIZE(abuffer)); + + WLog_WARN(TAG, "[rdpdr] received invalid file path '%s' from server, aborting!", + &abuffer[base_path_length]); + goto fail; + } + } + + ok = TRUE; +fail: + if (!ok) + { + free(fullpath); + fullpath = nullptr; + } + return fullpath; +} + +static BOOL drive_file_set_fullpath(DRIVE_FILE* file, const WCHAR* fullpath) +{ + if (!file || !fullpath) + return FALSE; + + const size_t len = _wcslen(fullpath); + free(file->fullpath); + file->fullpath = nullptr; + + if (len == 0) + return TRUE; + + file->fullpath = _wcsdup(fullpath); + if (!file->fullpath) + return FALSE; + + const WCHAR sep[] = { PathGetSeparatorW(PATH_STYLE_NATIVE), '\0' }; + WCHAR* filename = _wcsrchr(file->fullpath, *sep); + if (filename && _wcsncmp(filename, sep, ARRAYSIZE(sep)) == 0) + *filename = '\0'; + + return TRUE; +} + +static BOOL drive_file_init(DRIVE_FILE* file) +{ + UINT CreateDisposition = 0; + DWORD dwAttr = GetFileAttributesW(file->fullpath); + + if (dwAttr != INVALID_FILE_ATTRIBUTES) + { + /* The file exists */ + file->is_dir = (dwAttr & FILE_ATTRIBUTE_DIRECTORY) != 0; + + if (file->is_dir) + { + if (file->CreateDisposition == FILE_CREATE) + { + SetLastError(ERROR_ALREADY_EXISTS); + return FALSE; + } + + if (file->CreateOptions & FILE_NON_DIRECTORY_FILE) + { + SetLastError(ERROR_ACCESS_DENIED); + return FALSE; + } + + return TRUE; + } + else + { + if (file->CreateOptions & FILE_DIRECTORY_FILE) + { + SetLastError(ERROR_DIRECTORY); + return FALSE; + } + } + } + else + { + file->is_dir = ((file->CreateOptions & FILE_DIRECTORY_FILE) != 0); + + if (file->is_dir) + { + /* Should only create the directory if the disposition allows for it */ + if ((file->CreateDisposition == FILE_OPEN_IF) || + (file->CreateDisposition == FILE_CREATE)) + { + if (CreateDirectoryW(file->fullpath, nullptr) != 0) + { + return TRUE; + } + } + + SetLastError(ERROR_FILE_NOT_FOUND); + return FALSE; + } + } + + if (file->file_handle == INVALID_HANDLE_VALUE) + { + switch (file->CreateDisposition) + { + case FILE_SUPERSEDE: /* If the file already exists, replace it with the given file. If + it does not, create the given file. */ + CreateDisposition = CREATE_ALWAYS; + break; + + case FILE_OPEN: /* If the file already exists, open it instead of creating a new file. + If it does not, fail the request and do not create a new file. */ + CreateDisposition = OPEN_EXISTING; + break; + + case FILE_CREATE: /* If the file already exists, fail the request and do not create or + open the given file. If it does not, create the given file. */ + CreateDisposition = CREATE_NEW; + break; + + case FILE_OPEN_IF: /* If the file already exists, open it. If it does not, create the + given file. */ + CreateDisposition = OPEN_ALWAYS; + break; + + case FILE_OVERWRITE: /* If the file already exists, open it and overwrite it. If it does + not, fail the request. */ + CreateDisposition = TRUNCATE_EXISTING; + break; + + case FILE_OVERWRITE_IF: /* If the file already exists, open it and overwrite it. If it + does not, create the given file. */ + CreateDisposition = CREATE_ALWAYS; + break; + + default: + break; + } + +#ifndef WIN32 + file->SharedAccess = 0; +#endif + file->file_handle = CreateFileW(file->fullpath, file->DesiredAccess, file->SharedAccess, + nullptr, CreateDisposition, file->FileAttributes, nullptr); + } + +#ifdef WIN32 + if (file->file_handle == INVALID_HANDLE_VALUE) + { + /* Get the error message, if any. */ + DWORD errorMessageID = GetLastError(); + + if (errorMessageID != 0) + { + LPSTR messageBuffer = nullptr; + size_t size = + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&messageBuffer, 0, nullptr); + char fullpath[MAX_PATH] = WINPR_C_ARRAY_INIT; + (void)ConvertWCharToUtf8(file->fullpath, fullpath, sizeof(fullpath)); + WLog_ERR(TAG, "Error in drive_file_init: %s %s", messageBuffer, fullpath); + /* Free the buffer. */ + LocalFree(messageBuffer); + /* restore original error code */ + SetLastError(errorMessageID); + } + } +#endif + + return file->file_handle != INVALID_HANDLE_VALUE; +} + +DRIVE_FILE* drive_file_new(const WCHAR* base_path, const WCHAR* path, UINT32 PathWCharLength, + UINT32 id, UINT32 DesiredAccess, UINT32 CreateDisposition, + UINT32 CreateOptions, UINT32 FileAttributes, UINT32 SharedAccess) +{ + if (!base_path || (!path && (PathWCharLength > 0))) + return nullptr; + + DRIVE_FILE* file = (DRIVE_FILE*)calloc(1, sizeof(DRIVE_FILE)); + + if (!file) + { + WLog_ERR(TAG, "calloc failed!"); + return nullptr; + } + + file->file_handle = INVALID_HANDLE_VALUE; + file->find_handle = INVALID_HANDLE_VALUE; + file->id = id; + file->basepath = base_path; + file->FileAttributes = FileAttributes; + file->DesiredAccess = DesiredAccess; + file->CreateDisposition = CreateDisposition; + file->CreateOptions = CreateOptions; + file->SharedAccess = SharedAccess; + + WCHAR* p = drive_file_combine_fullpath(base_path, path, PathWCharLength); + (void)drive_file_set_fullpath(file, p); + free(p); + + if (!drive_file_init(file)) + { + DWORD lastError = GetLastError(); + drive_file_free(file); + SetLastError(lastError); + return nullptr; + } + + return file; +} + +BOOL drive_file_free(DRIVE_FILE* file) +{ + BOOL rc = FALSE; + + if (!file) + return FALSE; + + if (file->file_handle != INVALID_HANDLE_VALUE) + { + (void)CloseHandle(file->file_handle); + file->file_handle = INVALID_HANDLE_VALUE; + } + + if (file->find_handle != INVALID_HANDLE_VALUE) + { + FindClose(file->find_handle); + file->find_handle = INVALID_HANDLE_VALUE; + } + + if (file->CreateOptions & FILE_DELETE_ON_CLOSE) + file->delete_pending = TRUE; + + if (file->delete_pending) + { + if (file->is_dir) + { + if (!winpr_RemoveDirectory_RecursiveW(file->fullpath)) + goto fail; + } + else if (!DeleteFileW(file->fullpath)) + goto fail; + } + + rc = TRUE; +fail: + DEBUG_WSTR("Free %s", file->fullpath); + free(file->fullpath); + free(file); + return rc; +} + +BOOL drive_file_seek(DRIVE_FILE* file, UINT64 Offset) +{ + LARGE_INTEGER loffset = WINPR_C_ARRAY_INIT; + + if (!file) + return FALSE; + + if (Offset > INT64_MAX) + return FALSE; + + loffset.QuadPart = (LONGLONG)Offset; + return SetFilePointerEx(file->file_handle, loffset, nullptr, FILE_BEGIN); +} + +BOOL drive_file_read(DRIVE_FILE* file, BYTE* buffer, UINT32* Length) +{ + DWORD read = 0; + + if (!file || !buffer || !Length) + return FALSE; + + DEBUG_WSTR("Read file %s", file->fullpath); + + if (ReadFile(file->file_handle, buffer, *Length, &read, nullptr)) + { + *Length = read; + return TRUE; + } + + return FALSE; +} + +BOOL drive_file_write(DRIVE_FILE* file, const BYTE* buffer, UINT32 Length) +{ + DWORD written = 0; + + if (!file || !buffer) + return FALSE; + + DEBUG_WSTR("Write file %s", file->fullpath); + + while (Length > 0) + { + if (!WriteFile(file->file_handle, buffer, Length, &written, nullptr)) + return FALSE; + + Length -= written; + buffer += written; + } + + return TRUE; +} + +static BOOL drive_file_query_from_handle_information(const DRIVE_FILE* file, + const BY_HANDLE_FILE_INFORMATION* info, + UINT32 FsInformationClass, wStream* output) +{ + switch (FsInformationClass) + { + case FileBasicInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232094.aspx */ + if (!Stream_EnsureRemainingCapacity(output, 4 + 36)) + return FALSE; + + Stream_Write_UINT32(output, 36); /* Length */ + Stream_Write_UINT32(output, info->ftCreationTime.dwLowDateTime); /* CreationTime */ + Stream_Write_UINT32(output, info->ftCreationTime.dwHighDateTime); /* CreationTime */ + Stream_Write_UINT32(output, info->ftLastAccessTime.dwLowDateTime); /* LastAccessTime */ + Stream_Write_UINT32(output, info->ftLastAccessTime.dwHighDateTime); /* LastAccessTime */ + Stream_Write_UINT32(output, info->ftLastWriteTime.dwLowDateTime); /* LastWriteTime */ + Stream_Write_UINT32(output, info->ftLastWriteTime.dwHighDateTime); /* LastWriteTime */ + Stream_Write_UINT32(output, info->ftLastWriteTime.dwLowDateTime); /* ChangeTime */ + Stream_Write_UINT32(output, info->ftLastWriteTime.dwHighDateTime); /* ChangeTime */ + Stream_Write_UINT32(output, info->dwFileAttributes); /* FileAttributes */ + /* Reserved(4), MUST NOT be added! */ + break; + + case FileStandardInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232088.aspx */ + if (!Stream_EnsureRemainingCapacity(output, 4 + 22)) + return FALSE; + + Stream_Write_UINT32(output, 22); /* Length */ + Stream_Write_UINT32(output, info->nFileSizeLow); /* AllocationSize */ + Stream_Write_UINT32(output, info->nFileSizeHigh); /* AllocationSize */ + Stream_Write_UINT32(output, info->nFileSizeLow); /* EndOfFile */ + Stream_Write_UINT32(output, info->nFileSizeHigh); /* EndOfFile */ + Stream_Write_UINT32(output, info->nNumberOfLinks); /* NumberOfLinks */ + Stream_Write_UINT8(output, file->delete_pending ? 1 : 0); /* DeletePending */ + Stream_Write_UINT8(output, (info->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != + 0); /* Directory */ + /* Reserved(2), MUST NOT be added! */ + break; + + case FileAttributeTagInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232093.aspx */ + if (!Stream_EnsureRemainingCapacity(output, 4 + 8)) + return FALSE; + + Stream_Write_UINT32(output, 8); /* Length */ + Stream_Write_UINT32(output, info->dwFileAttributes); /* FileAttributes */ + Stream_Write_UINT32(output, 0); /* ReparseTag */ + break; + + default: + /* Unhandled FsInformationClass */ + WLog_WARN(TAG, "Unhandled FSInformationClass %s [0x%08" PRIx32 "]", + FSInformationClass2Tag(FsInformationClass), FsInformationClass); + return FALSE; + } + + return TRUE; +} + +static BOOL drive_file_query_from_attributes(const DRIVE_FILE* file, + const WIN32_FILE_ATTRIBUTE_DATA* attrib, + UINT32 FsInformationClass, wStream* output) +{ + switch (FsInformationClass) + { + case FileBasicInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232094.aspx */ + if (!Stream_EnsureRemainingCapacity(output, 4 + 36)) + return FALSE; + + Stream_Write_UINT32(output, 36); /* Length */ + Stream_Write_UINT32(output, attrib->ftCreationTime.dwLowDateTime); /* CreationTime */ + Stream_Write_UINT32(output, attrib->ftCreationTime.dwHighDateTime); /* CreationTime */ + Stream_Write_UINT32(output, + attrib->ftLastAccessTime.dwLowDateTime); /* LastAccessTime */ + Stream_Write_UINT32(output, + attrib->ftLastAccessTime.dwHighDateTime); /* LastAccessTime */ + Stream_Write_UINT32(output, attrib->ftLastWriteTime.dwLowDateTime); /* LastWriteTime */ + Stream_Write_UINT32(output, attrib->ftLastWriteTime.dwHighDateTime); /* LastWriteTime */ + Stream_Write_UINT32(output, attrib->ftLastWriteTime.dwLowDateTime); /* ChangeTime */ + Stream_Write_UINT32(output, attrib->ftLastWriteTime.dwHighDateTime); /* ChangeTime */ + Stream_Write_UINT32(output, attrib->dwFileAttributes); /* FileAttributes */ + /* Reserved(4), MUST NOT be added! */ + break; + + case FileStandardInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232088.aspx */ + if (!Stream_EnsureRemainingCapacity(output, 4 + 22)) + return FALSE; + + Stream_Write_UINT32(output, 22); /* Length */ + Stream_Write_UINT32(output, attrib->nFileSizeLow); /* AllocationSize */ + Stream_Write_UINT32(output, attrib->nFileSizeHigh); /* AllocationSize */ + Stream_Write_UINT32(output, attrib->nFileSizeLow); /* EndOfFile */ + Stream_Write_UINT32(output, attrib->nFileSizeHigh); /* EndOfFile */ + Stream_Write_UINT32(output, 0); /* NumberOfLinks */ + Stream_Write_UINT8(output, file->delete_pending ? 1 : 0); /* DeletePending */ + Stream_Write_UINT8(output, (attrib->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != + 0); /* Directory */ + /* Reserved(2), MUST NOT be added! */ + break; + + case FileAttributeTagInformation: + + /* http://msdn.microsoft.com/en-us/library/cc232093.aspx */ + if (!Stream_EnsureRemainingCapacity(output, 4 + 8)) + return FALSE; + + Stream_Write_UINT32(output, 8); /* Length */ + Stream_Write_UINT32(output, attrib->dwFileAttributes); /* FileAttributes */ + Stream_Write_UINT32(output, 0); /* ReparseTag */ + break; + + default: + /* Unhandled FsInformationClass */ + WLog_WARN(TAG, "Unhandled FSInformationClass %s [0x%08" PRIx32 "]", + FSInformationClass2Tag(FsInformationClass), FsInformationClass); + return FALSE; + } + + return TRUE; +} + +BOOL drive_file_query_information(DRIVE_FILE* file, UINT32 FsInformationClass, wStream* output) +{ + BY_HANDLE_FILE_INFORMATION fileInformation = WINPR_C_ARRAY_INIT; + BOOL status = 0; + + if (!file || !output) + return FALSE; + + if ((file->file_handle != INVALID_HANDLE_VALUE) && + GetFileInformationByHandle(file->file_handle, &fileInformation)) + return drive_file_query_from_handle_information(file, &fileInformation, FsInformationClass, + output); + + if (!file->is_dir) + { + HANDLE hFile = CreateFileW(file->fullpath, 0, FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, nullptr); + if (hFile != INVALID_HANDLE_VALUE) + { + status = GetFileInformationByHandle(hFile, &fileInformation); + (void)CloseHandle(hFile); + if (!status) + goto out_fail; + + if (!drive_file_query_from_handle_information(file, &fileInformation, + FsInformationClass, output)) + goto out_fail; + + return TRUE; + } + } + + /* If we failed before (i.e. if information for a drive is queried) fall back to + * GetFileAttributesExW */ + { + WIN32_FILE_ATTRIBUTE_DATA fileAttributes = WINPR_C_ARRAY_INIT; + if (!GetFileAttributesExW(file->fullpath, GetFileExInfoStandard, &fileAttributes)) + goto out_fail; + + if (!drive_file_query_from_attributes(file, &fileAttributes, FsInformationClass, output)) + goto out_fail; + } + + return TRUE; +out_fail: + Stream_Write_UINT32(output, 0); /* Length */ + return FALSE; +} + +static BOOL drive_file_set_basic_information(DRIVE_FILE* file, UINT32 Length, wStream* input) +{ + WINPR_ASSERT(file); + + const uint32_t expect = 36; + if (Length != expect) + { + WLog_WARN(TAG, "Unexpected Length=%" PRIu32 ", expected %" PRIu32, Length, expect); + return FALSE; + } + + /* http://msdn.microsoft.com/en-us/library/cc232094.aspx */ + const ULARGE_INTEGER liCreationTime = { .QuadPart = Stream_Get_UINT64(input) }; + const ULARGE_INTEGER liLastAccessTime = { .QuadPart = Stream_Get_UINT64(input) }; + const ULARGE_INTEGER liLastWriteTime = { .QuadPart = Stream_Get_UINT64(input) }; + const ULARGE_INTEGER liChangeTime = { .QuadPart = Stream_Get_UINT64(input) }; + const uint32_t FileAttributes = Stream_Get_UINT32(input); + + if (!PathFileExistsW(file->fullpath)) + return FALSE; + + if (file->file_handle == INVALID_HANDLE_VALUE) + { + char fullpath[MAX_PATH] = WINPR_C_ARRAY_INIT; + (void)ConvertWCharToUtf8(file->fullpath, fullpath, sizeof(fullpath) - 1); + + WLog_ERR(TAG, "Unable to set file time %s (%" PRIu32 ")", fullpath, GetLastError()); + return FALSE; + } + + FILETIME ftCreationTime = WINPR_C_ARRAY_INIT; + FILETIME ftLastAccessTime = WINPR_C_ARRAY_INIT; + FILETIME ftLastWriteTime = WINPR_C_ARRAY_INIT; + FILETIME* pftCreationTime = nullptr; + FILETIME* pftLastAccessTime = nullptr; + FILETIME* pftLastWriteTime = nullptr; + if (liCreationTime.QuadPart != 0) + { + ftCreationTime.dwHighDateTime = liCreationTime.u.HighPart; + ftCreationTime.dwLowDateTime = liCreationTime.u.LowPart; + pftCreationTime = &ftCreationTime; + } + + if (liLastAccessTime.QuadPart != 0) + { + ftLastAccessTime.dwHighDateTime = liLastAccessTime.u.HighPart; + ftLastAccessTime.dwLowDateTime = liLastAccessTime.u.LowPart; + pftLastAccessTime = &ftLastAccessTime; + } + + if (liLastWriteTime.QuadPart != 0) + { + ftLastWriteTime.dwHighDateTime = liLastWriteTime.u.HighPart; + ftLastWriteTime.dwLowDateTime = liLastWriteTime.u.LowPart; + pftLastWriteTime = &ftLastWriteTime; + } + + if (liChangeTime.QuadPart != 0 && liChangeTime.QuadPart > liLastWriteTime.QuadPart) + { + ftLastWriteTime.dwHighDateTime = liChangeTime.u.HighPart; + ftLastWriteTime.dwLowDateTime = liChangeTime.u.LowPart; + pftLastWriteTime = &ftLastWriteTime; + } + + DEBUG_WSTR("SetFileTime %s", file->fullpath); + + if (!SetFileAttributesW(file->fullpath, FileAttributes)) + { + char fullpath[MAX_PATH] = WINPR_C_ARRAY_INIT; + (void)ConvertWCharToUtf8(file->fullpath, fullpath, sizeof(fullpath)); + WLog_ERR(TAG, "Unable to set file attributes for %s", fullpath); + return FALSE; + } + + if (!SetFileTime(file->file_handle, pftCreationTime, pftLastAccessTime, pftLastWriteTime)) + { + char fullpath[MAX_PATH] = WINPR_C_ARRAY_INIT; + (void)ConvertWCharToUtf8(file->fullpath, fullpath, sizeof(fullpath)); + WLog_ERR(TAG, "Unable to set file time for %s", fullpath); + return FALSE; + } + return TRUE; +} + +static BOOL drive_file_set_alloc_information(DRIVE_FILE* file, UINT32 Length, wStream* input) +{ + WINPR_ASSERT(file); + const uint32_t expect = 8; + if (Length != expect) + { + WLog_WARN(TAG, "Unexpected Length=%" PRIu32 ", expected %" PRIu32, Length, expect); + return FALSE; + } + + /* http://msdn.microsoft.com/en-us/library/cc232076.aspx */ + const int64_t size = Stream_Get_INT64(input); + + if (file->file_handle == INVALID_HANDLE_VALUE) + { + char fullpath[MAX_PATH] = WINPR_C_ARRAY_INIT; + (void)ConvertWCharToUtf8(file->fullpath, fullpath, sizeof(fullpath)); + WLog_ERR(TAG, "Unable to truncate %s to %" PRId64 " (%" PRIu32 ")", fullpath, size, + GetLastError()); + return FALSE; + } + + LARGE_INTEGER liSize = { .QuadPart = size }; + + if (!SetFilePointerEx(file->file_handle, liSize, nullptr, FILE_BEGIN)) + { + char fullpath[MAX_PATH] = WINPR_C_ARRAY_INIT; + (void)ConvertWCharToUtf8(file->fullpath, fullpath, sizeof(fullpath)); + WLog_ERR(TAG, "Unable to truncate %s to %" PRId64 " (%" PRIu32 ")", fullpath, size, + GetLastError()); + return FALSE; + } + + DEBUG_WSTR("Truncate %s", file->fullpath); + + if (SetEndOfFile(file->file_handle) == 0) + { + char fullpath[MAX_PATH] = WINPR_C_ARRAY_INIT; + (void)ConvertWCharToUtf8(file->fullpath, fullpath, sizeof(fullpath)); + WLog_ERR(TAG, "Unable to truncate %s to %" PRId64 " (%" PRIu32 ")", fullpath, size, + GetLastError()); + return FALSE; + } + + return TRUE; +} + +static BOOL drive_file_set_disposition_information(DRIVE_FILE* file, UINT32 Length, wStream* input) +{ + WINPR_ASSERT(file); + uint8_t delete_pending = 0; + /* http://msdn.microsoft.com/en-us/library/cc232098.aspx */ + /* http://msdn.microsoft.com/en-us/library/cc241371.aspx */ + if (file->is_dir && !PathIsDirectoryEmptyW(file->fullpath)) + { + SetLastError(ERROR_DIR_NOT_EMPTY); + return FALSE; + } + + if (Length) + { + const uint32_t expect = 1; + if (Length != expect) + WLog_DBG(TAG, "Unexpected Length=%" PRIu32 ", expected %" PRIu32, Length, expect); + + delete_pending = Stream_Get_UINT8(input); + } + else + delete_pending = 1; + + if (delete_pending) + { + DEBUG_WSTR("SetDeletePending %s", file->fullpath); + const uint32_t attr = GetFileAttributesW(file->fullpath); + + if (attr & FILE_ATTRIBUTE_READONLY) + { + SetLastError(ERROR_ACCESS_DENIED); + return FALSE; + } + } + + file->delete_pending = delete_pending; + return TRUE; +} + +static BOOL drive_file_set_rename_information(DRIVE_FILE* file, UINT32 Length, wStream* input) +{ + WINPR_ASSERT(file); + + const uint32_t expect = 6; + if (Length < expect) + { + WLog_WARN(TAG, "Unexpected Length=%" PRIu32 ", expected at least %" PRIu32, Length, expect); + return FALSE; + } + + /* http://msdn.microsoft.com/en-us/library/cc232085.aspx */ + const uint8_t ReplaceIfExists = Stream_Get_UINT8(input); + Stream_Seek_UINT8(input); /* RootDirectory */ + const uint32_t FileNameLength = Stream_Get_UINT32(input); + + if (Length != expect + FileNameLength) + { + WLog_WARN(TAG, "Unexpected Length=%" PRIu32 ", expected %" PRIu32, Length, + expect + FileNameLength); + return FALSE; + } + + WCHAR* fullpath = drive_file_combine_fullpath(file->basepath, Stream_ConstPointer(input), + FileNameLength / sizeof(WCHAR)); + + if (!fullpath) + return FALSE; + +#ifdef _WIN32 + + if (file->file_handle != INVALID_HANDLE_VALUE) + { + (void)CloseHandle(file->file_handle); + file->file_handle = INVALID_HANDLE_VALUE; + } + +#endif + DEBUG_WSTR("MoveFileExW %s", file->fullpath); + + if (MoveFileExW(file->fullpath, fullpath, + MOVEFILE_COPY_ALLOWED | (ReplaceIfExists ? MOVEFILE_REPLACE_EXISTING : 0))) + { + const BOOL rc = drive_file_set_fullpath(file, fullpath); + free(fullpath); + if (!rc) + return FALSE; + } + else + { + free(fullpath); + return FALSE; + } + +#ifdef _WIN32 + drive_file_init(file); +#endif + return TRUE; +} + +BOOL drive_file_set_information(DRIVE_FILE* file, UINT32 FsInformationClass, UINT32 Length, + wStream* input) +{ + if (!file || !input) + return FALSE; + + if (!Stream_CheckAndLogRequiredLength(TAG, input, Length)) + return FALSE; + + switch (FsInformationClass) + { + case FileBasicInformation: + return drive_file_set_basic_information(file, Length, input); + + case FileEndOfFileInformation: + /* http://msdn.microsoft.com/en-us/library/cc232067.aspx */ + case FileAllocationInformation: + return drive_file_set_alloc_information(file, Length, input); + + case FileDispositionInformation: + return drive_file_set_disposition_information(file, Length, input); + + case FileRenameInformation: + return drive_file_set_rename_information(file, Length, input); + + default: + WLog_WARN(TAG, "Unhandled FSInformationClass %s [0x%08" PRIx32 "]", + FSInformationClass2Tag(FsInformationClass), FsInformationClass); + return FALSE; + } + + return TRUE; +} + +static BOOL drive_file_query_dir_info(DRIVE_FILE* file, wStream* output, size_t length) +{ + WINPR_ASSERT(file); + WINPR_ASSERT(output); + + /* http://msdn.microsoft.com/en-us/library/cc232097.aspx */ + if (!Stream_EnsureRemainingCapacity(output, 4 + 64 + length)) + return FALSE; + + if (length > UINT32_MAX - 64) + return FALSE; + + Stream_Write_UINT32(output, (UINT32)(64 + length)); /* Length */ + Stream_Write_UINT32(output, 0); /* NextEntryOffset */ + Stream_Write_UINT32(output, 0); /* FileIndex */ + Stream_Write_UINT32(output, file->find_data.ftCreationTime.dwLowDateTime); /* CreationTime */ + Stream_Write_UINT32(output, file->find_data.ftCreationTime.dwHighDateTime); /* CreationTime */ + Stream_Write_UINT32(output, + file->find_data.ftLastAccessTime.dwLowDateTime); /* LastAccessTime */ + Stream_Write_UINT32(output, + file->find_data.ftLastAccessTime.dwHighDateTime); /* LastAccessTime */ + Stream_Write_UINT32(output, file->find_data.ftLastWriteTime.dwLowDateTime); /* LastWriteTime */ + Stream_Write_UINT32(output, file->find_data.ftLastWriteTime.dwHighDateTime); /* LastWriteTime */ + Stream_Write_UINT32(output, file->find_data.ftLastWriteTime.dwLowDateTime); /* ChangeTime */ + Stream_Write_UINT32(output, file->find_data.ftLastWriteTime.dwHighDateTime); /* ChangeTime */ + Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* EndOfFile */ + Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* EndOfFile */ + Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* AllocationSize */ + Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* AllocationSize */ + Stream_Write_UINT32(output, file->find_data.dwFileAttributes); /* FileAttributes */ + Stream_Write_UINT32(output, (UINT32)length); /* FileNameLength */ + Stream_Write(output, file->find_data.cFileName, length); + return TRUE; +} + +static BOOL drive_file_query_full_dir_info(DRIVE_FILE* file, wStream* output, size_t length) +{ + WINPR_ASSERT(file); + WINPR_ASSERT(output); + /* http://msdn.microsoft.com/en-us/library/cc232068.aspx */ + if (!Stream_EnsureRemainingCapacity(output, 4 + 68 + length)) + return FALSE; + + if (length > UINT32_MAX - 68) + return FALSE; + + Stream_Write_UINT32(output, (UINT32)(68 + length)); /* Length */ + Stream_Write_UINT32(output, 0); /* NextEntryOffset */ + Stream_Write_UINT32(output, 0); /* FileIndex */ + Stream_Write_UINT32(output, file->find_data.ftCreationTime.dwLowDateTime); /* CreationTime */ + Stream_Write_UINT32(output, file->find_data.ftCreationTime.dwHighDateTime); /* CreationTime */ + Stream_Write_UINT32(output, + file->find_data.ftLastAccessTime.dwLowDateTime); /* LastAccessTime */ + Stream_Write_UINT32(output, + file->find_data.ftLastAccessTime.dwHighDateTime); /* LastAccessTime */ + Stream_Write_UINT32(output, file->find_data.ftLastWriteTime.dwLowDateTime); /* LastWriteTime */ + Stream_Write_UINT32(output, file->find_data.ftLastWriteTime.dwHighDateTime); /* LastWriteTime */ + Stream_Write_UINT32(output, file->find_data.ftLastWriteTime.dwLowDateTime); /* ChangeTime */ + Stream_Write_UINT32(output, file->find_data.ftLastWriteTime.dwHighDateTime); /* ChangeTime */ + Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* EndOfFile */ + Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* EndOfFile */ + Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* AllocationSize */ + Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* AllocationSize */ + Stream_Write_UINT32(output, file->find_data.dwFileAttributes); /* FileAttributes */ + Stream_Write_UINT32(output, (UINT32)length); /* FileNameLength */ + Stream_Write_UINT32(output, 0); /* EaSize */ + Stream_Write(output, file->find_data.cFileName, length); + return TRUE; +} + +static BOOL drive_file_query_both_dir_info(DRIVE_FILE* file, wStream* output, size_t length) +{ + WINPR_ASSERT(file); + WINPR_ASSERT(output); + /* http://msdn.microsoft.com/en-us/library/cc232095.aspx */ + if (!Stream_EnsureRemainingCapacity(output, 4 + 93 + length)) + return FALSE; + + if (length > UINT32_MAX - 93) + return FALSE; + + Stream_Write_UINT32(output, (UINT32)(93 + length)); /* Length */ + Stream_Write_UINT32(output, 0); /* NextEntryOffset */ + Stream_Write_UINT32(output, 0); /* FileIndex */ + Stream_Write_UINT32(output, file->find_data.ftCreationTime.dwLowDateTime); /* CreationTime */ + Stream_Write_UINT32(output, file->find_data.ftCreationTime.dwHighDateTime); /* CreationTime */ + Stream_Write_UINT32(output, + file->find_data.ftLastAccessTime.dwLowDateTime); /* LastAccessTime */ + Stream_Write_UINT32(output, + file->find_data.ftLastAccessTime.dwHighDateTime); /* LastAccessTime */ + Stream_Write_UINT32(output, file->find_data.ftLastWriteTime.dwLowDateTime); /* LastWriteTime */ + Stream_Write_UINT32(output, file->find_data.ftLastWriteTime.dwHighDateTime); /* LastWriteTime */ + Stream_Write_UINT32(output, file->find_data.ftLastWriteTime.dwLowDateTime); /* ChangeTime */ + Stream_Write_UINT32(output, file->find_data.ftLastWriteTime.dwHighDateTime); /* ChangeTime */ + Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* EndOfFile */ + Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* EndOfFile */ + Stream_Write_UINT32(output, file->find_data.nFileSizeLow); /* AllocationSize */ + Stream_Write_UINT32(output, file->find_data.nFileSizeHigh); /* AllocationSize */ + Stream_Write_UINT32(output, file->find_data.dwFileAttributes); /* FileAttributes */ + Stream_Write_UINT32(output, (UINT32)length); /* FileNameLength */ + Stream_Write_UINT32(output, 0); /* EaSize */ + Stream_Write_UINT8(output, 0); /* ShortNameLength */ + /* Reserved(1), MUST NOT be added! */ + Stream_Zero(output, 24); /* ShortName */ + Stream_Write(output, file->find_data.cFileName, length); + return TRUE; +} + +static BOOL drive_file_query_names_info(DRIVE_FILE* file, wStream* output, size_t length) +{ + WINPR_ASSERT(file); + WINPR_ASSERT(output); + /* http://msdn.microsoft.com/en-us/library/cc232077.aspx */ + if (!Stream_EnsureRemainingCapacity(output, 4 + 12 + length)) + return FALSE; + + if (length > UINT32_MAX - 12) + return FALSE; + + Stream_Write_UINT32(output, (UINT32)(12 + length)); /* Length */ + Stream_Write_UINT32(output, 0); /* NextEntryOffset */ + Stream_Write_UINT32(output, 0); /* FileIndex */ + Stream_Write_UINT32(output, (UINT32)length); /* FileNameLength */ + Stream_Write(output, file->find_data.cFileName, length); + return TRUE; +} + +BOOL drive_file_query_directory(DRIVE_FILE* file, UINT32 FsInformationClass, BYTE InitialQuery, + const WCHAR* path, UINT32 PathWCharLength, wStream* output) +{ + BOOL rc = FALSE; + size_t length = 0; + WCHAR* ent_path = nullptr; + + if (!file || !path || !output) + return FALSE; + + if (InitialQuery != 0) + { + /* release search handle */ + if (file->find_handle != INVALID_HANDLE_VALUE) + FindClose(file->find_handle); + + ent_path = drive_file_combine_fullpath(file->basepath, path, PathWCharLength); + /* open new search handle and retrieve the first entry */ + file->find_handle = FindFirstFileW(ent_path, &file->find_data); + free(ent_path); + + if (file->find_handle == INVALID_HANDLE_VALUE) + goto out_fail; + } + else if (!FindNextFileW(file->find_handle, &file->find_data)) + goto out_fail; + + length = _wcslen(file->find_data.cFileName) * sizeof(WCHAR); + + switch (FsInformationClass) + { + case FileDirectoryInformation: + rc = drive_file_query_dir_info(file, output, length); + break; + + case FileFullDirectoryInformation: + rc = drive_file_query_full_dir_info(file, output, length); + break; + + case FileBothDirectoryInformation: + rc = drive_file_query_both_dir_info(file, output, length); + break; + + case FileNamesInformation: + rc = drive_file_query_names_info(file, output, length); + break; + + default: + WLog_WARN(TAG, "Unhandled FSInformationClass %s [0x%08" PRIx32 "]", + FSInformationClass2Tag(FsInformationClass), FsInformationClass); + /* Unhandled FsInformationClass */ + goto out_fail; + } + +out_fail: + if (!rc) + { + Stream_Write_UINT32(output, 0); /* Length */ + Stream_Write_UINT8(output, 0); /* Padding */ + } + return rc; +} diff --git a/third_party/FreeRDP/channels/drive/client/drive_file.h b/third_party/FreeRDP/channels/drive/client/drive_file.h new file mode 100644 index 0000000..9c5c8f1 --- /dev/null +++ b/third_party/FreeRDP/channels/drive/client/drive_file.h @@ -0,0 +1,84 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * File System Virtual Channel + * + * Copyright 2010-2012 Marc-Andre Moreau + * Copyright 2010-2011 Vic Lee + * Copyright 2012 Gerald Richter + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 Inuvika Inc. + * Copyright 2016 David PHAM-VAN + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_DRIVE_CLIENT_FILE_H +#define FREERDP_CHANNEL_DRIVE_CLIENT_FILE_H + +#include +#include + +#include +#include + +#define TAG CHANNELS_TAG("drive.client") + +typedef struct +{ + UINT32 id; + BOOL is_dir; + HANDLE file_handle; + HANDLE find_handle; + WIN32_FIND_DATAW find_data; + const WCHAR* basepath; + WCHAR* fullpath; + BOOL delete_pending; + UINT32 FileAttributes; + UINT32 SharedAccess; + UINT32 DesiredAccess; + UINT32 CreateDisposition; + UINT32 CreateOptions; +} DRIVE_FILE; + +FREERDP_LOCAL BOOL drive_file_free(DRIVE_FILE* file); + +WINPR_ATTR_MALLOC(drive_file_free, 1) +WINPR_ATTR_NODISCARD FREERDP_LOCAL DRIVE_FILE* +drive_file_new(const WCHAR* base_path, const WCHAR* path, UINT32 PathWCharLength, UINT32 id, + UINT32 DesiredAccess, UINT32 CreateDisposition, UINT32 CreateOptions, + UINT32 FileAttributes, UINT32 SharedAccess); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL drive_file_open(DRIVE_FILE* file); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL drive_file_seek(DRIVE_FILE* file, UINT64 Offset); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL drive_file_read(DRIVE_FILE* file, BYTE* buffer, + UINT32* Length); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL drive_file_write(DRIVE_FILE* file, const BYTE* buffer, + UINT32 Length); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL drive_file_query_information(DRIVE_FILE* file, + UINT32 FsInformationClass, + wStream* output); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL drive_file_set_information(DRIVE_FILE* file, + UINT32 FsInformationClass, + UINT32 Length, wStream* input); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL +drive_file_query_directory(DRIVE_FILE* file, UINT32 FsInformationClass, BYTE InitialQuery, + const WCHAR* path, UINT32 PathWCharLength, wStream* output); + +#endif /* FREERDP_CHANNEL_DRIVE_FILE_H */ diff --git a/third_party/FreeRDP/channels/drive/client/drive_main.c b/third_party/FreeRDP/channels/drive/client/drive_main.c new file mode 100644 index 0000000..94b8b7f --- /dev/null +++ b/third_party/FreeRDP/channels/drive/client/drive_main.c @@ -0,0 +1,1184 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * File System Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2012 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 David PHAM-VAN + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "drive_file.h" + +typedef struct +{ + DEVICE device; + + WCHAR* path; + BOOL automount; + UINT32 PathLength; + wListDictionary* files; + + HANDLE thread; + BOOL async; + wMessageQueue* IrpQueue; + + DEVMAN* devman; + + rdpContext* rdpcontext; +} DRIVE_DEVICE; + +static NTSTATUS drive_map_windows_err(DWORD fs_errno) +{ + NTSTATUS rc = 0; + + /* try to return NTSTATUS version of error code */ + + switch (fs_errno) + { + case STATUS_SUCCESS: + rc = STATUS_SUCCESS; + break; + + case ERROR_ACCESS_DENIED: + case ERROR_SHARING_VIOLATION: + rc = STATUS_ACCESS_DENIED; + break; + + case ERROR_FILE_NOT_FOUND: + rc = STATUS_NO_SUCH_FILE; + break; + + case ERROR_BUSY_DRIVE: + rc = STATUS_DEVICE_BUSY; + break; + + case ERROR_INVALID_DRIVE: + rc = STATUS_NO_SUCH_DEVICE; + break; + + case ERROR_NOT_READY: + rc = STATUS_NO_SUCH_DEVICE; + break; + + case ERROR_FILE_EXISTS: + case ERROR_ALREADY_EXISTS: + rc = STATUS_OBJECT_NAME_COLLISION; + break; + + case ERROR_INVALID_NAME: + rc = STATUS_NO_SUCH_FILE; + break; + + case ERROR_INVALID_HANDLE: + rc = STATUS_INVALID_HANDLE; + break; + + case ERROR_NO_MORE_FILES: + rc = STATUS_NO_MORE_FILES; + break; + + case ERROR_DIRECTORY: + rc = STATUS_NOT_A_DIRECTORY; + break; + + case ERROR_PATH_NOT_FOUND: + rc = STATUS_OBJECT_PATH_NOT_FOUND; + break; + + case ERROR_DIR_NOT_EMPTY: + rc = STATUS_DIRECTORY_NOT_EMPTY; + break; + + default: + rc = STATUS_UNSUCCESSFUL; + WLog_ERR(TAG, "Error code not found: %" PRIu32 "", fs_errno); + break; + } + + return rc; +} + +static DRIVE_FILE* drive_get_file_by_id(DRIVE_DEVICE* drive, UINT32 id) +{ + DRIVE_FILE* file = nullptr; + void* key = (void*)(size_t)id; + + if (!drive) + return nullptr; + + file = (DRIVE_FILE*)ListDictionary_GetItemValue(drive->files, key); + return file; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_create(DRIVE_DEVICE* drive, IRP* irp) +{ + BYTE Information = 0; + + WINPR_ASSERT(drive); + WINPR_ASSERT(irp); + if (!irp->devman) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 6 * 4 + 8)) + return ERROR_INVALID_DATA; + + const uint32_t DesiredAccess = Stream_Get_UINT32(irp->input); + const uint64_t allocationSize = Stream_Get_UINT64(irp->input); + const uint32_t FileAttributes = Stream_Get_UINT32(irp->input); + const uint32_t SharedAccess = Stream_Get_UINT32(irp->input); + const uint32_t CreateDisposition = Stream_Get_UINT32(irp->input); + const uint32_t CreateOptions = Stream_Get_UINT32(irp->input); + const uint32_t PathLength = Stream_Get_UINT32(irp->input); + + if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, PathLength)) + return ERROR_INVALID_DATA; + + const WCHAR* path = Stream_ConstPointer(irp->input); + UINT32 FileId = irp->devman->id_sequence++; + DRIVE_FILE* file = + drive_file_new(drive->path, path, PathLength / sizeof(WCHAR), FileId, DesiredAccess, + CreateDisposition, CreateOptions, FileAttributes, SharedAccess); + + if (!file) + { + irp->IoStatus = drive_map_windows_err(GetLastError()); + FileId = 0; + Information = 0; + } + else + { + void* key = (void*)(size_t)file->id; + + if (!ListDictionary_Add(drive->files, key, file)) + { + WLog_ERR(TAG, "ListDictionary_Add failed!"); + return ERROR_INTERNAL_ERROR; + } + + switch (CreateDisposition) + { + case FILE_SUPERSEDE: + case FILE_OPEN: + case FILE_CREATE: + case FILE_OVERWRITE: + Information = FILE_SUPERSEDED; + break; + + case FILE_OPEN_IF: + Information = FILE_OPENED; + break; + + case FILE_OVERWRITE_IF: + Information = FILE_OVERWRITTEN; + break; + + default: + Information = 0; + break; + } + + if (allocationSize > 0) + { + const BYTE buffer[] = { '\0' }; + if (!drive_file_seek(file, allocationSize - sizeof(buffer))) + return ERROR_INTERNAL_ERROR; + if (!drive_file_write(file, buffer, sizeof(buffer))) + return ERROR_INTERNAL_ERROR; + } + } + + Stream_Write_UINT32(irp->output, FileId); + Stream_Write_UINT8(irp->output, Information); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_close(DRIVE_DEVICE* drive, IRP* irp) +{ + WINPR_ASSERT(drive); + WINPR_ASSERT(irp); + if (!irp->output) + return ERROR_INVALID_PARAMETER; + + DRIVE_FILE* file = drive_get_file_by_id(drive, irp->FileId); + void* key = (void*)(size_t)irp->FileId; + + if (!file) + irp->IoStatus = STATUS_UNSUCCESSFUL; + else + { + ListDictionary_Take(drive->files, key); + + if (drive_file_free(file)) + irp->IoStatus = STATUS_SUCCESS; + else + irp->IoStatus = drive_map_windows_err(GetLastError()); + } + + Stream_Zero(irp->output, 5); /* Padding(5) */ + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_read(DRIVE_DEVICE* drive, IRP* irp) +{ + DRIVE_FILE* file = nullptr; + UINT32 Length = 0; + UINT64 Offset = 0; + + WINPR_ASSERT(drive); + WINPR_ASSERT(irp); + if (!irp->output) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 12)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, Length); + Stream_Read_UINT64(irp->input, Offset); + file = drive_get_file_by_id(drive, irp->FileId); + + if (!file) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + Length = 0; + } + else if (!drive_file_seek(file, Offset)) + { + irp->IoStatus = drive_map_windows_err(GetLastError()); + Length = 0; + } + + if (!Stream_EnsureRemainingCapacity(irp->output, 4ull + Length)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return ERROR_INTERNAL_ERROR; + } + else if (Length == 0) + Stream_Write_UINT32(irp->output, 0); + else + { + BYTE* buffer = Stream_PointerAs(irp->output, BYTE) + sizeof(UINT32); + + if (!drive_file_read(file, buffer, &Length)) + { + irp->IoStatus = drive_map_windows_err(GetLastError()); + Stream_Write_UINT32(irp->output, 0); + } + else + { + Stream_Write_UINT32(irp->output, Length); + Stream_Seek(irp->output, Length); + } + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_write(DRIVE_DEVICE* drive, IRP* irp) +{ + DRIVE_FILE* file = nullptr; + UINT32 Length = 0; + UINT64 Offset = 0; + + WINPR_ASSERT(drive); + WINPR_ASSERT(irp); + if (!irp->input || !irp->output) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, Length); + Stream_Read_UINT64(irp->input, Offset); + Stream_Seek(irp->input, 20); /* Padding */ + const void* ptr = Stream_ConstPointer(irp->input); + if (!Stream_SafeSeek(irp->input, Length)) + return ERROR_INVALID_DATA; + file = drive_get_file_by_id(drive, irp->FileId); + + if (!file) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + Length = 0; + } + else if (!drive_file_seek(file, Offset)) + { + irp->IoStatus = drive_map_windows_err(GetLastError()); + Length = 0; + } + else if (!drive_file_write(file, ptr, Length)) + { + irp->IoStatus = drive_map_windows_err(GetLastError()); + Length = 0; + } + + Stream_Write_UINT32(irp->output, Length); + Stream_Write_UINT8(irp->output, 0); /* Padding */ + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_query_information(DRIVE_DEVICE* drive, IRP* irp) +{ + DRIVE_FILE* file = nullptr; + UINT32 FsInformationClass = 0; + + WINPR_ASSERT(drive); + WINPR_ASSERT(irp); + + if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, FsInformationClass); + file = drive_get_file_by_id(drive, irp->FileId); + + if (!file) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + } + else if (!drive_file_query_information(file, FsInformationClass, irp->output)) + { + irp->IoStatus = drive_map_windows_err(GetLastError()); + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_set_information(DRIVE_DEVICE* drive, IRP* irp) +{ + DRIVE_FILE* file = nullptr; + UINT32 FsInformationClass = 0; + UINT32 Length = 0; + + WINPR_ASSERT(drive); + WINPR_ASSERT(irp); + if (!irp->input || !irp->output) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, FsInformationClass); + Stream_Read_UINT32(irp->input, Length); + Stream_Seek(irp->input, 24); /* Padding */ + file = drive_get_file_by_id(drive, irp->FileId); + + if (!file) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + } + else if (!drive_file_set_information(file, FsInformationClass, Length, irp->input)) + { + irp->IoStatus = drive_map_windows_err(GetLastError()); + } + + Stream_Write_UINT32(irp->output, Length); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_query_volume_information(DRIVE_DEVICE* drive, IRP* irp) +{ + UINT32 FsInformationClass = 0; + DWORD lpSectorsPerCluster = 0; + DWORD lpBytesPerSector = 0; + DWORD lpNumberOfFreeClusters = 0; + DWORD lpTotalNumberOfClusters = 0; + WIN32_FILE_ATTRIBUTE_DATA wfad = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(drive); + WINPR_ASSERT(irp); + + wStream* output = irp->output; + + if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, FsInformationClass); + GetDiskFreeSpaceW(drive->path, &lpSectorsPerCluster, &lpBytesPerSector, &lpNumberOfFreeClusters, + &lpTotalNumberOfClusters); + + switch (FsInformationClass) + { + case FileFsVolumeInformation: + { + /* http://msdn.microsoft.com/en-us/library/cc232108.aspx */ + const WCHAR* volumeLabel = freerdp_getApplicationDetailsStringW(); + const size_t volumeLabelLen = (_wcslen(volumeLabel) + 1) * sizeof(WCHAR); + const size_t length = 17ul + volumeLabelLen; + + if ((length > UINT32_MAX) || (volumeLabelLen > UINT32_MAX)) + return CHANNEL_RC_NO_BUFFER; + + Stream_Write_UINT32(output, (UINT32)length); /* Length */ + + if (!Stream_EnsureRemainingCapacity(output, length)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + GetFileAttributesExW(drive->path, GetFileExInfoStandard, &wfad); + Stream_Write_UINT32(output, wfad.ftCreationTime.dwLowDateTime); /* VolumeCreationTime */ + Stream_Write_UINT32(output, + wfad.ftCreationTime.dwHighDateTime); /* VolumeCreationTime */ + Stream_Write_UINT32(output, lpNumberOfFreeClusters & 0xffff); /* VolumeSerialNumber */ + Stream_Write_UINT32(output, (UINT32)volumeLabelLen); /* VolumeLabelLength */ + Stream_Write_UINT8(output, 0); /* SupportsObjects */ + /* Reserved(1), MUST NOT be added! */ + Stream_Write(output, volumeLabel, volumeLabelLen); /* VolumeLabel (Unicode) */ + } + break; + + case FileFsSizeInformation: + /* http://msdn.microsoft.com/en-us/library/cc232107.aspx */ + Stream_Write_UINT32(output, 24); /* Length */ + + if (!Stream_EnsureRemainingCapacity(output, 24)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT64(output, lpTotalNumberOfClusters); /* TotalAllocationUnits */ + Stream_Write_UINT64(output, lpNumberOfFreeClusters); /* AvailableAllocationUnits */ + Stream_Write_UINT32(output, lpSectorsPerCluster); /* SectorsPerAllocationUnit */ + Stream_Write_UINT32(output, lpBytesPerSector); /* BytesPerSector */ + break; + + case FileFsAttributeInformation: + { + WCHAR LabelBuffer[32] = WINPR_C_ARRAY_INIT; + /* http://msdn.microsoft.com/en-us/library/cc232101.aspx */ + const WCHAR* diskType = + InitializeConstWCharFromUtf8("FAT32", LabelBuffer, ARRAYSIZE(LabelBuffer)); + const size_t diskTypeLen = (_wcslen(diskType) + 1) * sizeof(WCHAR); + const size_t length = 12ul + diskTypeLen; + + if ((length > UINT32_MAX) || (diskTypeLen > UINT32_MAX)) + return CHANNEL_RC_NO_BUFFER; + + Stream_Write_UINT32(output, (UINT32)length); /* Length */ + + if (!Stream_EnsureRemainingCapacity(output, length)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT32(output, FILE_CASE_SENSITIVE_SEARCH | FILE_CASE_PRESERVED_NAMES | + FILE_UNICODE_ON_DISK); /* FileSystemAttributes */ + Stream_Write_UINT32(output, MAX_PATH); /* MaximumComponentNameLength */ + Stream_Write_UINT32(output, (UINT32)diskTypeLen); /* FileSystemNameLength */ + Stream_Write(output, diskType, diskTypeLen); /* FileSystemName (Unicode) */ + } + break; + + case FileFsFullSizeInformation: + /* http://msdn.microsoft.com/en-us/library/cc232104.aspx */ + Stream_Write_UINT32(output, 32); /* Length */ + + if (!Stream_EnsureRemainingCapacity(output, 32)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT64(output, lpTotalNumberOfClusters); /* TotalAllocationUnits */ + Stream_Write_UINT64(output, + lpNumberOfFreeClusters); /* CallerAvailableAllocationUnits */ + Stream_Write_UINT64(output, lpNumberOfFreeClusters); /* AvailableAllocationUnits */ + Stream_Write_UINT32(output, lpSectorsPerCluster); /* SectorsPerAllocationUnit */ + Stream_Write_UINT32(output, lpBytesPerSector); /* BytesPerSector */ + break; + + case FileFsDeviceInformation: + /* http://msdn.microsoft.com/en-us/library/cc232109.aspx */ + Stream_Write_UINT32(output, 8); /* Length */ + + if (!Stream_EnsureRemainingCapacity(output, 8)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT32(output, FILE_DEVICE_DISK); /* DeviceType */ + Stream_Write_UINT32(output, 0); /* Characteristics */ + break; + + default: + WLog_WARN(TAG, "Unhandled FSInformationClass %s [0x%08" PRIx32 "]", + FSInformationClass2Tag(FsInformationClass), FsInformationClass); + irp->IoStatus = STATUS_UNSUCCESSFUL; + Stream_Write_UINT32(output, 0); /* Length */ + break; + } + + return CHANNEL_RC_OK; +} + +/* http://msdn.microsoft.com/en-us/library/cc241518.aspx */ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_silent_ignore(WINPR_ATTR_UNUSED DRIVE_DEVICE* drive, IRP* irp) +{ + WINPR_ASSERT(drive); + WINPR_ASSERT(irp); + if (!irp->output) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 4)) + return ERROR_INVALID_DATA; + + const uint32_t FsInformationClass = Stream_Get_UINT32(irp->input); + WLog_VRB(TAG, "Silently ignore FSInformationClass %s [0x%08" PRIx32 "]", + FSInformationClass2Tag(FsInformationClass), FsInformationClass); + Stream_Write_UINT32(irp->output, 0); /* Length */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_query_directory(DRIVE_DEVICE* drive, IRP* irp) +{ + const WCHAR* path = nullptr; + DRIVE_FILE* file = nullptr; + BYTE InitialQuery = 0; + UINT32 PathLength = 0; + UINT32 FsInformationClass = 0; + + WINPR_ASSERT(drive); + WINPR_ASSERT(irp); + + if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, FsInformationClass); + Stream_Read_UINT8(irp->input, InitialQuery); + Stream_Read_UINT32(irp->input, PathLength); + Stream_Seek(irp->input, 23); /* Padding */ + path = Stream_ConstPointer(irp->input); + if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, PathLength)) + return ERROR_INVALID_DATA; + + file = drive_get_file_by_id(drive, irp->FileId); + + if (file == nullptr) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + Stream_Write_UINT32(irp->output, 0); /* Length */ + } + else if (!drive_file_query_directory(file, FsInformationClass, InitialQuery, path, + PathLength / sizeof(WCHAR), irp->output)) + { + irp->IoStatus = drive_map_windows_err(GetLastError()); + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_directory_control(DRIVE_DEVICE* drive, IRP* irp) +{ + WINPR_ASSERT(drive); + WINPR_ASSERT(irp); + + switch (irp->MinorFunction) + { + case IRP_MN_QUERY_DIRECTORY: + return drive_process_irp_query_directory(drive, irp); + + case IRP_MN_NOTIFY_CHANGE_DIRECTORY: /* TODO */ + irp->IoStatus = STATUS_NOT_SUPPORTED; + Stream_Write_UINT32(irp->output, 0); /* Length */ + break; + + default: + irp->IoStatus = STATUS_NOT_SUPPORTED; + Stream_Write_UINT32(irp->output, 0); /* Length */ + break; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp_device_control(WINPR_ATTR_UNUSED DRIVE_DEVICE* drive, IRP* irp) +{ + WINPR_ASSERT(drive); + WINPR_ASSERT(irp); + + Stream_Write_UINT32(irp->output, 0); /* OutputBufferLength */ + return CHANNEL_RC_OK; +} + +static UINT drive_evaluate(UINT error, IRP* irp) +{ + WINPR_ASSERT(irp); + if (error == CHANNEL_RC_OK) + { + WINPR_ASSERT(irp->Complete); + return irp->Complete(irp); + } + + WLog_ERR(TAG, "IRP %s failed with %" PRIu32, rdpdr_irp_string(irp->MajorFunction), error); + WINPR_ASSERT(irp->Discard); + irp->Discard(irp); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_process_irp(DRIVE_DEVICE* drive, IRP* irp) +{ + UINT error = CHANNEL_RC_OK; + WINPR_ASSERT(drive); + WINPR_ASSERT(irp); + + irp->IoStatus = STATUS_SUCCESS; + + switch (irp->MajorFunction) + { + case IRP_MJ_CREATE: + error = drive_process_irp_create(drive, irp); + break; + + case IRP_MJ_CLOSE: + error = drive_process_irp_close(drive, irp); + break; + + case IRP_MJ_READ: + error = drive_process_irp_read(drive, irp); + break; + + case IRP_MJ_WRITE: + error = drive_process_irp_write(drive, irp); + break; + + case IRP_MJ_QUERY_INFORMATION: + error = drive_process_irp_query_information(drive, irp); + break; + + case IRP_MJ_SET_INFORMATION: + error = drive_process_irp_set_information(drive, irp); + break; + + case IRP_MJ_QUERY_VOLUME_INFORMATION: + error = drive_process_irp_query_volume_information(drive, irp); + break; + + case IRP_MJ_LOCK_CONTROL: + error = drive_process_irp_silent_ignore(drive, irp); + break; + + case IRP_MJ_DIRECTORY_CONTROL: + error = drive_process_irp_directory_control(drive, irp); + break; + + case IRP_MJ_DEVICE_CONTROL: + error = drive_process_irp_device_control(drive, irp); + break; + + default: + irp->IoStatus = STATUS_NOT_SUPPORTED; + break; + } + + return drive_evaluate(error, irp); +} + +static BOOL drive_poll_run(DRIVE_DEVICE* drive, IRP* irp) +{ + WINPR_ASSERT(drive); + + if (irp) + { + const UINT error = drive_process_irp(drive, irp); + if (error) + { + WLog_ERR(TAG, "drive_process_irp failed with error %" PRIu32 "!", error); + return FALSE; + } + } + + return TRUE; +} + +static DWORD WINAPI drive_thread_func(LPVOID arg) +{ + DRIVE_DEVICE* drive = (DRIVE_DEVICE*)arg; + UINT error = CHANNEL_RC_OK; + + if (!drive) + { + error = ERROR_INVALID_PARAMETER; + goto fail; + } + + while (1) + { + if (!MessageQueue_Wait(drive->IrpQueue)) + { + WLog_ERR(TAG, "MessageQueue_Wait failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (MessageQueue_Size(drive->IrpQueue) < 1) + continue; + + wMessage message = WINPR_C_ARRAY_INIT; + if (!MessageQueue_Peek(drive->IrpQueue, &message, TRUE)) + { + WLog_ERR(TAG, "MessageQueue_Peek failed!"); + continue; + } + + if (message.id == WMQ_QUIT) + break; + + IRP* irp = (IRP*)message.wParam; + if (!drive_poll_run(drive, irp)) + break; + } + +fail: + + if (error && drive && drive->rdpcontext) + setChannelError(drive->rdpcontext, error, "drive_thread_func reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_irp_request(DEVICE* device, IRP* irp) +{ + DRIVE_DEVICE* drive = (DRIVE_DEVICE*)device; + + if (!drive) + return ERROR_INVALID_PARAMETER; + + if (drive->async) + { + if (!MessageQueue_Post(drive->IrpQueue, nullptr, 0, (void*)irp, nullptr)) + { + WLog_ERR(TAG, "MessageQueue_Post failed!"); + return ERROR_INTERNAL_ERROR; + } + } + else + { + if (!drive_poll_run(drive, irp)) + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +static UINT drive_free_int(DRIVE_DEVICE* drive) +{ + UINT error = CHANNEL_RC_OK; + + if (!drive) + return ERROR_INVALID_PARAMETER; + + (void)CloseHandle(drive->thread); + ListDictionary_Free(drive->files); + MessageQueue_Free(drive->IrpQueue); + Stream_Free(drive->device.data, TRUE); + free(drive->path); + free(drive); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_free(DEVICE* device) +{ + DRIVE_DEVICE* drive = (DRIVE_DEVICE*)device; + UINT error = CHANNEL_RC_OK; + + if (!drive) + return ERROR_INVALID_PARAMETER; + + if (MessageQueue_PostQuit(drive->IrpQueue, 0) && + (WaitForSingleObject(drive->thread, INFINITE) == WAIT_FAILED)) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + return error; + } + + return drive_free_int(drive); +} + +/** + * Helper function used for freeing list dictionary value object + */ +static void drive_file_objfree(void* obj) +{ + drive_file_free((DRIVE_FILE*)obj); +} + +static void drive_message_free(void* obj) +{ + wMessage* msg = obj; + if (!msg) + return; + if (msg->id != 0) + return; + + IRP* irp = (IRP*)msg->wParam; + if (!irp) + return; + WINPR_ASSERT(irp->Discard); + irp->Discard(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_register_drive_path(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints, const char* name, + const char* path, BOOL automount, uint32_t* pid) +{ + WINPR_ASSERT(pid); + + size_t length = 0; + DRIVE_DEVICE* drive = nullptr; + UINT error = ERROR_INTERNAL_ERROR; + + if (!pEntryPoints || !name || !path) + { + WLog_ERR(TAG, "Invalid parameters: pEntryPoints=%p, name=%p, path=%p", + WINPR_CXX_COMPAT_CAST(const void*, pEntryPoints), + WINPR_CXX_COMPAT_CAST(const void*, name), + WINPR_CXX_COMPAT_CAST(const void*, path)); + return ERROR_INVALID_PARAMETER; + } + + if (name[0] && path[0]) + { + size_t pathLength = strnlen(path, MAX_PATH); + drive = (DRIVE_DEVICE*)calloc(1, sizeof(DRIVE_DEVICE)); + + if (!drive) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + drive->device.type = RDPDR_DTYP_FILESYSTEM; + drive->device.IRPRequest = drive_irp_request; + drive->device.Free = drive_free; + drive->rdpcontext = pEntryPoints->rdpcontext; + drive->automount = automount; + length = strlen(name); + drive->device.data = Stream_New(nullptr, length + 1); + + if (!drive->device.data) + { + WLog_ERR(TAG, "Stream_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out_error; + } + + for (size_t i = 0; i < length; i++) + { + /* Filter 2.2.1.3 Device Announce Header (DEVICE_ANNOUNCE) forbidden symbols */ + switch (name[i]) + { + case ':': + case '<': + case '>': + case '\"': + case '/': + case '\\': + case '|': + case ' ': + Stream_Write_UINT8(drive->device.data, '_'); + break; + default: + Stream_Write_UINT8(drive->device.data, (BYTE)name[i]); + break; + } + } + Stream_Write_UINT8(drive->device.data, '\0'); + + drive->device.name = Stream_BufferAs(drive->device.data, char); + if (!drive->device.name) + goto out_error; + + if ((pathLength > 1) && (path[pathLength - 1] == '/')) + pathLength--; + + drive->path = ConvertUtf8NToWCharAlloc(path, pathLength, nullptr); + if (!drive->path) + { + error = CHANNEL_RC_NO_MEMORY; + goto out_error; + } + + drive->files = ListDictionary_New(TRUE); + + if (!drive->files) + { + WLog_ERR(TAG, "ListDictionary_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out_error; + } + + ListDictionary_ValueObject(drive->files)->fnObjectFree = drive_file_objfree; + drive->IrpQueue = MessageQueue_New(nullptr); + + if (!drive->IrpQueue) + { + WLog_ERR(TAG, "ListDictionary_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out_error; + } + + wObject* obj = MessageQueue_Object(drive->IrpQueue); + WINPR_ASSERT(obj); + obj->fnObjectFree = drive_message_free; + + if ((error = pEntryPoints->RegisterDevice(pEntryPoints->devman, &drive->device))) + { + WLog_ERR(TAG, "RegisterDevice failed with error %" PRIu32 "!", error); + goto out_error; + } + *pid = drive->device.id; + + drive->async = !freerdp_settings_get_bool(drive->rdpcontext->settings, + FreeRDP_SynchronousStaticChannels); + if (drive->async) + { + if (!(drive->thread = CreateThread(nullptr, 0, drive_thread_func, drive, + CREATE_SUSPENDED, nullptr))) + { + WLog_ERR(TAG, "CreateThread failed!"); + goto out_error; + } + + ResumeThread(drive->thread); + } + } + + return CHANNEL_RC_OK; +out_error: + drive_free_int(drive); + return error; +} + +static BOOL drive_filtered(const WCHAR* drive) +{ + const WCHAR a[] = { 'A', '\0' }; + const WCHAR b[] = { 'B', '\0' }; + const WCHAR la[] = { 'a', '\0' }; + const WCHAR lb[] = { 'b', '\0' }; + const WCHAR* list[] = { a, b, la, lb }; + + for (size_t x = 0; x < ARRAYSIZE(list); x++) + { + const WCHAR* cur = list[x]; + if (_wcsncmp(drive, cur, 2) == 0) + return TRUE; + } + return FALSE; +} + +static UINT handle_all_drives(RDPDR_DRIVE* drive, PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints) +{ + UINT error = ERROR_INTERNAL_ERROR; + + WINPR_ASSERT(drive); + WINPR_ASSERT(pEntryPoints); + + /* Enumerate all devices: */ + const DWORD dlen = GetLogicalDriveStringsW(0, nullptr); + + WCHAR* devlist = calloc(dlen, sizeof(WCHAR)); + if (!devlist) + return ERROR_OUTOFMEMORY; + + const DWORD rc = GetLogicalDriveStringsW(dlen, devlist); + if (rc >= dlen) + goto fail; + + for (size_t offset = 0, len = 0; offset < rc; offset += len + 1) + { + len = _wcsnlen(&devlist[offset], rc - offset); + + const WCHAR* dev = &devlist[offset]; + if (!drive_filtered(dev)) + { + char* bufdup = nullptr; + char* devdup = ConvertWCharNToUtf8Alloc(dev, len, nullptr); + if (!devdup) + { + error = ERROR_OUTOFMEMORY; + goto fail; + } + size_t size = 0; + winpr_asprintf(&bufdup, &size, "%s_%s", drive->device.Name, devdup); + + error = + drive_register_drive_path(pEntryPoints, bufdup, devdup, TRUE, &drive->device.Id); + free(devdup); + free(bufdup); + if (error != CHANNEL_RC_OK) + goto fail; + } + } + + error = CHANNEL_RC_OK; + +fail: + free(devlist); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT( + UINT VCAPITYPE drive_DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints)) +{ + UINT error = 0; + + WINPR_ASSERT(pEntryPoints); + + RDPDR_DRIVE* drive = (RDPDR_DRIVE*)pEntryPoints->device; + WINPR_ASSERT(drive); + + const char all[] = "*"; + const char home[] = "%"; + if (strncmp(drive->Path, all, sizeof(all)) == 0) + { + error = handle_all_drives(drive, pEntryPoints); + } + else if (strncmp(drive->Path, home, sizeof(home)) == 0) + { + free(drive->Path); + drive->Path = GetKnownPath(KNOWN_PATH_HOME); + + if (!drive->Path) + { + WLog_ERR(TAG, "_strdup failed!"); + return CHANNEL_RC_NO_MEMORY; + } + error = drive_register_drive_path(pEntryPoints, drive->device.Name, drive->Path, + drive->automount, &drive->device.Id); + } + else + { + error = drive_register_drive_path(pEntryPoints, drive->device.Name, drive->Path, + drive->automount, &drive->device.Id); + } + return error; +} diff --git a/third_party/FreeRDP/channels/echo/CMakeLists.txt b/third_party/FreeRDP/channels/echo/CMakeLists.txt new file mode 100644 index 0000000..35b45a7 --- /dev/null +++ b/third_party/FreeRDP/channels/echo/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel("echo") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/third_party/FreeRDP/channels/echo/ChannelOptions.cmake b/third_party/FreeRDP/channels/echo/ChannelOptions.cmake new file mode 100644 index 0000000..d805b58 --- /dev/null +++ b/third_party/FreeRDP/channels/echo/ChannelOptions.cmake @@ -0,0 +1,20 @@ +set(OPTION_DEFAULT ON) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options( + NAME + "echo" + TYPE + "dynamic" + DESCRIPTION + "Echo Virtual Channel Extension" + SPECIFICATIONS + "[MS-RDPEECO]" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/echo/client/CMakeLists.txt b/third_party/FreeRDP/channels/echo/client/CMakeLists.txt new file mode 100644 index 0000000..777beaf --- /dev/null +++ b/third_party/FreeRDP/channels/echo/client/CMakeLists.txt @@ -0,0 +1,25 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_client("echo") + +set(${MODULE_PREFIX}_SRCS echo_main.c echo_main.h) + +set(${MODULE_PREFIX}_LIBS winpr) +include_directories(..) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") diff --git a/third_party/FreeRDP/channels/echo/client/echo_main.c b/third_party/FreeRDP/channels/echo/client/echo_main.c new file mode 100644 index 0000000..f64b1c6 --- /dev/null +++ b/third_party/FreeRDP/channels/echo/client/echo_main.c @@ -0,0 +1,93 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Echo Virtual Channel Extension + * + * Copyright 2013 Christian Hofstaedtler + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +#include +#include +#include + +#include "echo_main.h" +#include +#include +#include + +#define TAG CHANNELS_TAG("echo.client") + +typedef struct +{ + GENERIC_DYNVC_PLUGIN baseDynPlugin; +} ECHO_PLUGIN; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT echo_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data) +{ + GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + const BYTE* pBuffer = Stream_ConstPointer(data); + const size_t cbSize = Stream_GetRemainingLength(data); + + WINPR_ASSERT(callback); + WINPR_ASSERT(callback->channel); + WINPR_ASSERT(callback->channel->Write); + + if (cbSize > UINT32_MAX) + return ERROR_INVALID_PARAMETER; + + /* echo back what we have received. ECHO does not have any message IDs. */ + return callback->channel->Write(callback->channel, (ULONG)cbSize, pBuffer, nullptr); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT echo_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + + free(callback); + + return CHANNEL_RC_OK; +} + +static const IWTSVirtualChannelCallback echo_callbacks = { echo_on_data_received, + nullptr, /* Open */ + echo_on_close, nullptr }; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE echo_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)) +{ + return freerdp_generic_DVCPluginEntry(pEntryPoints, TAG, ECHO_DVC_CHANNEL_NAME, + sizeof(ECHO_PLUGIN), sizeof(GENERIC_CHANNEL_CALLBACK), + &echo_callbacks, nullptr, nullptr); +} diff --git a/third_party/FreeRDP/channels/echo/client/echo_main.h b/third_party/FreeRDP/channels/echo/client/echo_main.h new file mode 100644 index 0000000..1286371 --- /dev/null +++ b/third_party/FreeRDP/channels/echo/client/echo_main.h @@ -0,0 +1,40 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Echo Virtual Channel Extension + * + * Copyright 2013 Christian Hofstaedtler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_ECHO_CLIENT_MAIN_H +#define FREERDP_CHANNEL_ECHO_CLIENT_MAIN_H + +#include + +#include +#include +#include +#include + +#define DVC_TAG CHANNELS_TAG("echo.client") +#ifdef WITH_DEBUG_DVC +#define DEBUG_DVC(...) WLog_DBG(DVC_TAG, __VA_ARGS__) +#else +#define DEBUG_DVC(...) \ + do \ + { \ + } while (0) +#endif + +#endif /* FREERDP_CHANNEL_ECHO_CLIENT_MAIN_H */ diff --git a/third_party/FreeRDP/channels/echo/server/CMakeLists.txt b/third_party/FreeRDP/channels/echo/server/CMakeLists.txt new file mode 100644 index 0000000..b894fb0 --- /dev/null +++ b/third_party/FreeRDP/channels/echo/server/CMakeLists.txt @@ -0,0 +1,23 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_server("echo") + +set(${MODULE_PREFIX}_SRCS echo_main.c) + +set(${MODULE_PREFIX}_LIBS freerdp) +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry") diff --git a/third_party/FreeRDP/channels/echo/server/echo_main.c b/third_party/FreeRDP/channels/echo/server/echo_main.c new file mode 100644 index 0000000..5a92d61 --- /dev/null +++ b/third_party/FreeRDP/channels/echo/server/echo_main.c @@ -0,0 +1,386 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Echo Virtual Channel Extension + * + * Copyright 2014 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define TAG CHANNELS_TAG("echo.server") + +typedef struct +{ + echo_server_context context; + + BOOL opened; + + HANDLE stopEvent; + + HANDLE thread; + void* echo_channel; + + DWORD SessionId; + +} echo_server; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT echo_server_open_channel(echo_server* echo) +{ + DWORD Error = 0; + HANDLE hEvent = nullptr; + DWORD StartTick = 0; + DWORD BytesReturned = 0; + PULONG pSessionId = nullptr; + + if (WTSQuerySessionInformationA(echo->context.vcm, WTS_CURRENT_SESSION, WTSSessionId, + (LPSTR*)&pSessionId, &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSQuerySessionInformationA failed!"); + return ERROR_INTERNAL_ERROR; + } + + echo->SessionId = (DWORD)*pSessionId; + WTSFreeMemory(pSessionId); + hEvent = WTSVirtualChannelManagerGetEventHandle(echo->context.vcm); + StartTick = GetTickCount(); + + while (echo->echo_channel == nullptr) + { + if (WaitForSingleObject(hEvent, 1000) == WAIT_FAILED) + { + Error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", Error); + return Error; + } + + echo->echo_channel = WTSVirtualChannelOpenEx(echo->SessionId, ECHO_DVC_CHANNEL_NAME, + WTS_CHANNEL_OPTION_DYNAMIC); + + if (echo->echo_channel) + { + UINT32 channelId = 0; + BOOL status = TRUE; + + channelId = WTSChannelGetIdByHandle(echo->echo_channel); + + IFCALLRET(echo->context.ChannelIdAssigned, status, &echo->context, channelId); + if (!status) + { + WLog_ERR(TAG, "context->ChannelIdAssigned failed!"); + return ERROR_INTERNAL_ERROR; + } + + break; + } + + Error = GetLastError(); + + if (Error == ERROR_NOT_FOUND) + break; + + if (GetTickCount() - StartTick > 5000) + break; + } + + return echo->echo_channel ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +static DWORD WINAPI echo_server_thread_func(LPVOID arg) +{ + wStream* s = nullptr; + void* buffer = nullptr; + DWORD nCount = 0; + HANDLE events[8]; + BOOL ready = FALSE; + HANDLE ChannelEvent = nullptr; + DWORD BytesReturned = 0; + echo_server* echo = (echo_server*)arg; + UINT error = 0; + DWORD status = 0; + + if ((error = echo_server_open_channel(echo))) + { + UINT error2 = 0; + WLog_ERR(TAG, "echo_server_open_channel failed with error %" PRIu32 "!", error); + IFCALLRET(echo->context.OpenResult, error2, &echo->context, + ECHO_SERVER_OPEN_RESULT_NOTSUPPORTED); + + if (error2) + WLog_ERR(TAG, "echo server's OpenResult callback failed with error %" PRIu32 "", + error2); + + goto out; + } + + buffer = nullptr; + BytesReturned = 0; + ChannelEvent = nullptr; + + if (WTSVirtualChannelQuery(echo->echo_channel, WTSVirtualEventHandle, &buffer, + &BytesReturned) == TRUE) + { + if (BytesReturned == sizeof(HANDLE)) + ChannelEvent = *(HANDLE*)buffer; + + WTSFreeMemory(buffer); + } + + nCount = 0; + events[nCount++] = echo->stopEvent; + events[nCount++] = ChannelEvent; + + /* Wait for the client to confirm that the Graphics Pipeline dynamic channel is ready */ + + while (1) + { + status = WaitForMultipleObjects(nCount, events, FALSE, 100); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "", error); + break; + } + + if (status == WAIT_OBJECT_0) + { + IFCALLRET(echo->context.OpenResult, error, &echo->context, + ECHO_SERVER_OPEN_RESULT_CLOSED); + + if (error) + WLog_ERR(TAG, "OpenResult failed with error %" PRIu32 "!", error); + + break; + } + + if (WTSVirtualChannelQuery(echo->echo_channel, WTSVirtualChannelReady, &buffer, + &BytesReturned) == FALSE) + { + IFCALLRET(echo->context.OpenResult, error, &echo->context, + ECHO_SERVER_OPEN_RESULT_ERROR); + + if (error) + WLog_ERR(TAG, "OpenResult failed with error %" PRIu32 "!", error); + + break; + } + + ready = *((BOOL*)buffer); + WTSFreeMemory(buffer); + + if (ready) + { + IFCALLRET(echo->context.OpenResult, error, &echo->context, ECHO_SERVER_OPEN_RESULT_OK); + + if (error) + WLog_ERR(TAG, "OpenResult failed with error %" PRIu32 "!", error); + + break; + } + } + + s = Stream_New(nullptr, 4096); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + (void)WTSVirtualChannelClose(echo->echo_channel); + ExitThread(ERROR_NOT_ENOUGH_MEMORY); + return ERROR_NOT_ENOUGH_MEMORY; + } + + while (ready) + { + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "", error); + break; + } + + if (status == WAIT_OBJECT_0) + break; + + Stream_ResetPosition(s); + if (!WTSVirtualChannelRead(echo->echo_channel, 0, nullptr, 0, &BytesReturned)) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (BytesReturned < 1) + continue; + + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + error = CHANNEL_RC_NO_MEMORY; + break; + } + + if (WTSVirtualChannelRead(echo->echo_channel, 0, Stream_BufferAs(s, char), + (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + IFCALLRET(echo->context.Response, error, &echo->context, Stream_Buffer(s), BytesReturned); + + if (error) + { + WLog_ERR(TAG, "Response failed with error %" PRIu32 "!", error); + break; + } + } + + Stream_Free(s, TRUE); + (void)WTSVirtualChannelClose(echo->echo_channel); + echo->echo_channel = nullptr; +out: + + if (error && echo->context.rdpcontext) + setChannelError(echo->context.rdpcontext, error, + "echo_server_thread_func reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT echo_server_open(echo_server_context* context) +{ + echo_server* echo = (echo_server*)context; + + if (echo->thread == nullptr) + { + if (!(echo->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (!(echo->thread = + CreateThread(nullptr, 0, echo_server_thread_func, (void*)echo, 0, nullptr))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + (void)CloseHandle(echo->stopEvent); + echo->stopEvent = nullptr; + return ERROR_INTERNAL_ERROR; + } + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT echo_server_close(echo_server_context* context) +{ + UINT error = CHANNEL_RC_OK; + echo_server* echo = (echo_server*)context; + + if (echo->thread) + { + (void)SetEvent(echo->stopEvent); + + if (WaitForSingleObject(echo->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + return error; + } + + (void)CloseHandle(echo->thread); + (void)CloseHandle(echo->stopEvent); + echo->thread = nullptr; + echo->stopEvent = nullptr; + } + + return error; +} + +static BOOL echo_server_request(echo_server_context* context, const BYTE* buffer, UINT32 length) +{ + union + { + const BYTE* cpv; + CHAR* pv; + } cnv; + cnv.cpv = buffer; + echo_server* echo = (echo_server*)context; + WINPR_ASSERT(echo); + + return WTSVirtualChannelWrite(echo->echo_channel, cnv.pv, length, nullptr); +} + +echo_server_context* echo_server_context_new(HANDLE vcm) +{ + echo_server* echo = nullptr; + echo = (echo_server*)calloc(1, sizeof(echo_server)); + + if (echo) + { + echo->context.vcm = vcm; + echo->context.Open = echo_server_open; + echo->context.Close = echo_server_close; + echo->context.Request = echo_server_request; + } + else + WLog_ERR(TAG, "calloc failed!"); + + return (echo_server_context*)echo; +} + +void echo_server_context_free(echo_server_context* context) +{ + echo_server* echo = (echo_server*)context; + echo_server_close(context); + free(echo); +} diff --git a/third_party/FreeRDP/channels/encomsp/CMakeLists.txt b/third_party/FreeRDP/channels/encomsp/CMakeLists.txt new file mode 100644 index 0000000..f646304 --- /dev/null +++ b/third_party/FreeRDP/channels/encomsp/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel("encomsp") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/third_party/FreeRDP/channels/encomsp/ChannelOptions.cmake b/third_party/FreeRDP/channels/encomsp/ChannelOptions.cmake new file mode 100644 index 0000000..974df5b --- /dev/null +++ b/third_party/FreeRDP/channels/encomsp/ChannelOptions.cmake @@ -0,0 +1,20 @@ +set(OPTION_DEFAULT ON) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options( + NAME + "encomsp" + TYPE + "static" + DESCRIPTION + "Multiparty Virtual Channel Extension" + SPECIFICATIONS + "[MS-RDPEMC]" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/encomsp/client/CMakeLists.txt b/third_party/FreeRDP/channels/encomsp/client/CMakeLists.txt new file mode 100644 index 0000000..4bc27e2 --- /dev/null +++ b/third_party/FreeRDP/channels/encomsp/client/CMakeLists.txt @@ -0,0 +1,24 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_client("encomsp") + +include_directories(..) + +set(${MODULE_PREFIX}_SRCS encomsp_main.c encomsp_main.h) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntryEx") diff --git a/third_party/FreeRDP/channels/encomsp/client/encomsp_main.c b/third_party/FreeRDP/channels/encomsp/client/encomsp_main.c new file mode 100644 index 0000000..c24aed8 --- /dev/null +++ b/third_party/FreeRDP/channels/encomsp/client/encomsp_main.c @@ -0,0 +1,1304 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Multiparty Virtual Channel + * + * Copyright 2014 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include +#include + +#include "encomsp_main.h" + +struct encomsp_plugin +{ + CHANNEL_DEF channelDef; + CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints; + + EncomspClientContext* context; + + HANDLE thread; + wStream* data_in; + void* InitHandle; + DWORD OpenHandle; + wMessageQueue* queue; + rdpContext* rdpcontext; +}; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_read_header(wStream* s, ENCOMSP_ORDER_HEADER* header) +{ + WINPR_ASSERT(header); + if (!Stream_CheckAndLogRequiredLength(TAG, s, ENCOMSP_ORDER_HEADER_SIZE)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, header->Type); /* Type (2 bytes) */ + Stream_Read_UINT16(s, header->Length); /* Length (2 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_write_header(wStream* s, const ENCOMSP_ORDER_HEADER* header) +{ + WINPR_ASSERT(header); + Stream_Write_UINT16(s, header->Type); /* Type (2 bytes) */ + Stream_Write_UINT16(s, header->Length); /* Length (2 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_read_unicode_string(wStream* s, ENCOMSP_UNICODE_STRING* str) +{ + WINPR_ASSERT(str); + const ENCOMSP_UNICODE_STRING empty = WINPR_C_ARRAY_INIT; + *str = empty; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 2)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, str->cchString); /* cchString (2 bytes) */ + + if (str->cchString > 1024) + { + WLog_ERR(TAG, "cchString was %" PRIu16 " but has to be < 1025!", str->cchString); + return ERROR_INVALID_DATA; + } + + if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, str->cchString, sizeof(WCHAR))) + return ERROR_INVALID_DATA; + + Stream_Read(s, &(str->wString), (sizeof(WCHAR) * str->cchString)); /* String (variable) */ + return CHANNEL_RC_OK; +} + +static EncomspClientContext* encomsp_get_client_interface(encomspPlugin* encomsp) +{ + WINPR_ASSERT(encomsp); + return (EncomspClientContext*)encomsp->channelEntryPoints.pInterface; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_virtual_channel_write(encomspPlugin* encomsp, wStream* s) +{ + if (!encomsp) + { + Stream_Free(s, TRUE); + return ERROR_INVALID_HANDLE; + } + + const UINT status = encomsp->channelEntryPoints.pVirtualChannelWriteEx( + encomsp->InitHandle, encomsp->OpenHandle, Stream_Buffer(s), (UINT32)Stream_Length(s), s); + + if (status != CHANNEL_RC_OK) + { + Stream_Free(s, TRUE); + WLog_ERR(TAG, "VirtualChannelWriteEx failed with %s [%08" PRIX32 "]", + WTSErrorToString(status), status); + } + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_recv_filter_updated_pdu(encomspPlugin* encomsp, wStream* s, + const ENCOMSP_ORDER_HEADER* header) +{ + ENCOMSP_FILTER_UPDATED_PDU pdu = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + EncomspClientContext* context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + WINPR_ASSERT(header); + const size_t pos = Stream_GetPosition(s); + if (pos < ENCOMSP_ORDER_HEADER_SIZE) + return ERROR_INVALID_DATA; + const size_t beg = pos - ENCOMSP_ORDER_HEADER_SIZE; + pdu.Length = header->Length; + pdu.Type = header->Type; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT8(s, pdu.Flags); /* Flags (1 byte) */ + const size_t end = Stream_GetPosition(s); + const size_t body = beg + header->Length; + + if (body < end) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + if (body > end) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(body - end))) + return ERROR_INVALID_DATA; + + Stream_SetPosition(s, body); + } + + IFCALLRET(context->FilterUpdated, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->FilterUpdated failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_recv_application_created_pdu(encomspPlugin* encomsp, wStream* s, + const ENCOMSP_ORDER_HEADER* header) +{ + ENCOMSP_APPLICATION_CREATED_PDU pdu = WINPR_C_ARRAY_INIT; + EncomspClientContext* context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 6)) + return ERROR_INVALID_DATA; + + const size_t pos = Stream_GetPosition(s); + if (pos < ENCOMSP_ORDER_HEADER_SIZE) + return ERROR_INVALID_DATA; + const size_t beg = pos - ENCOMSP_ORDER_HEADER_SIZE; + + WINPR_ASSERT(header); + pdu.Length = header->Length; + pdu.Type = header->Type; + + Stream_Read_UINT16(s, pdu.Flags); /* Flags (2 bytes) */ + Stream_Read_UINT32(s, pdu.AppId); /* AppId (4 bytes) */ + + UINT error = encomsp_read_unicode_string(s, &(pdu.Name)); + if (error != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "encomsp_read_unicode_string failed with error %" PRIu32 "", error); + return error; + } + + const size_t end = Stream_GetPosition(s); + const size_t body = beg + header->Length; + + if (body < end) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + if (body > end) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(body - end))) + return ERROR_INVALID_DATA; + + Stream_SetPosition(s, body); + } + + IFCALLRET(context->ApplicationCreated, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->ApplicationCreated failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_recv_application_removed_pdu(encomspPlugin* encomsp, wStream* s, + const ENCOMSP_ORDER_HEADER* header) +{ + ENCOMSP_APPLICATION_REMOVED_PDU pdu = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + EncomspClientContext* context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + const size_t pos = Stream_GetPosition(s); + if (pos < ENCOMSP_ORDER_HEADER_SIZE) + return ERROR_INVALID_DATA; + const size_t beg = pos - ENCOMSP_ORDER_HEADER_SIZE; + + WINPR_ASSERT(header); + pdu.Length = header->Length; + pdu.Type = header->Type; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, pdu.AppId); /* AppId (4 bytes) */ + const size_t end = Stream_GetPosition(s); + const size_t body = beg + header->Length; + + if (body < end) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + if (body > end) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(body - end))) + return ERROR_INVALID_DATA; + + Stream_SetPosition(s, body); + } + + IFCALLRET(context->ApplicationRemoved, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->ApplicationRemoved failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_recv_window_created_pdu(encomspPlugin* encomsp, wStream* s, + const ENCOMSP_ORDER_HEADER* header) +{ + ENCOMSP_WINDOW_CREATED_PDU pdu = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + EncomspClientContext* context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + const size_t pos = Stream_GetPosition(s); + if (pos < ENCOMSP_ORDER_HEADER_SIZE) + return ERROR_INVALID_DATA; + const size_t beg = pos - ENCOMSP_ORDER_HEADER_SIZE; + + WINPR_ASSERT(header); + pdu.Length = header->Length; + pdu.Type = header->Type; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 10)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, pdu.Flags); /* Flags (2 bytes) */ + Stream_Read_UINT32(s, pdu.AppId); /* AppId (4 bytes) */ + Stream_Read_UINT32(s, pdu.WndId); /* WndId (4 bytes) */ + + if ((error = encomsp_read_unicode_string(s, &(pdu.Name)))) + { + WLog_ERR(TAG, "encomsp_read_unicode_string failed with error %" PRIu32 "", error); + return error; + } + + const size_t end = Stream_GetPosition(s); + const size_t body = beg + header->Length; + + if (body < end) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + if (body > end) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(body - end))) + return ERROR_INVALID_DATA; + + Stream_SetPosition(s, body); + } + + IFCALLRET(context->WindowCreated, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->WindowCreated failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_recv_window_removed_pdu(encomspPlugin* encomsp, wStream* s, + const ENCOMSP_ORDER_HEADER* header) +{ + ENCOMSP_WINDOW_REMOVED_PDU pdu = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + EncomspClientContext* context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + const size_t pos = Stream_GetPosition(s); + if (pos < ENCOMSP_ORDER_HEADER_SIZE) + return ERROR_INVALID_DATA; + const size_t beg = pos - ENCOMSP_ORDER_HEADER_SIZE; + + WINPR_ASSERT(header); + pdu.Length = header->Length; + pdu.Type = header->Type; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, pdu.WndId); /* WndId (4 bytes) */ + const size_t end = Stream_GetPosition(s); + const size_t body = beg + header->Length; + + if (body < end) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + if (body > end) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(body - end))) + return ERROR_INVALID_DATA; + + Stream_SetPosition(s, body); + } + + IFCALLRET(context->WindowRemoved, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->WindowRemoved failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_recv_show_window_pdu(encomspPlugin* encomsp, wStream* s, + const ENCOMSP_ORDER_HEADER* header) +{ + ENCOMSP_SHOW_WINDOW_PDU pdu = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + EncomspClientContext* context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + const size_t pos = Stream_GetPosition(s); + if (pos < ENCOMSP_ORDER_HEADER_SIZE) + return ERROR_INVALID_DATA; + const size_t beg = pos - ENCOMSP_ORDER_HEADER_SIZE; + + WINPR_ASSERT(header); + pdu.Length = header->Length; + pdu.Type = header->Type; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, pdu.WndId); /* WndId (4 bytes) */ + const size_t end = Stream_GetPosition(s); + const size_t body = beg + header->Length; + + if (body < end) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + if (body > end) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(body - end))) + return ERROR_INVALID_DATA; + + Stream_SetPosition(s, body); + } + + IFCALLRET(context->ShowWindow, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->ShowWindow failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_recv_participant_created_pdu(encomspPlugin* encomsp, wStream* s, + const ENCOMSP_ORDER_HEADER* header) +{ + ENCOMSP_PARTICIPANT_CREATED_PDU pdu = WINPR_C_ARRAY_INIT; + EncomspClientContext* context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + const size_t pos = Stream_GetPosition(s); + if (pos < ENCOMSP_ORDER_HEADER_SIZE) + return ERROR_INVALID_DATA; + const size_t beg = pos - ENCOMSP_ORDER_HEADER_SIZE; + + WINPR_ASSERT(header); + pdu.Length = header->Length; + pdu.Type = header->Type; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 10)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, pdu.ParticipantId); /* ParticipantId (4 bytes) */ + Stream_Read_UINT32(s, pdu.GroupId); /* GroupId (4 bytes) */ + Stream_Read_UINT16(s, pdu.Flags); /* Flags (2 bytes) */ + + UINT error = encomsp_read_unicode_string(s, &(pdu.FriendlyName)); + if (error != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "encomsp_read_unicode_string failed with error %" PRIu32 "", error); + return error; + } + + const size_t end = Stream_GetPosition(s); + const size_t body = beg + header->Length; + + if (body < end) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + if (body > end) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(body - end))) + return ERROR_INVALID_DATA; + + Stream_SetPosition(s, body); + } + + IFCALLRET(context->ParticipantCreated, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->ParticipantCreated failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_recv_participant_removed_pdu(encomspPlugin* encomsp, wStream* s, + const ENCOMSP_ORDER_HEADER* header) +{ + ENCOMSP_PARTICIPANT_REMOVED_PDU pdu = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + EncomspClientContext* context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 12)) + return ERROR_INVALID_DATA; + + const size_t beg = (Stream_GetPosition(s)) - ENCOMSP_ORDER_HEADER_SIZE; + + WINPR_ASSERT(header); + pdu.Length = header->Length; + pdu.Type = header->Type; + + Stream_Read_UINT32(s, pdu.ParticipantId); /* ParticipantId (4 bytes) */ + Stream_Read_UINT32(s, pdu.DiscType); /* DiscType (4 bytes) */ + Stream_Read_UINT32(s, pdu.DiscCode); /* DiscCode (4 bytes) */ + const size_t end = Stream_GetPosition(s); + const size_t body = beg + header->Length; + + if (body < end) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + if (body > end) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(body - end))) + return ERROR_INVALID_DATA; + + Stream_SetPosition(s, body); + } + + IFCALLRET(context->ParticipantRemoved, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->ParticipantRemoved failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_recv_change_participant_control_level_pdu(encomspPlugin* encomsp, wStream* s, + const ENCOMSP_ORDER_HEADER* header) +{ + ENCOMSP_CHANGE_PARTICIPANT_CONTROL_LEVEL_PDU pdu = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + EncomspClientContext* context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + const size_t pos = Stream_GetPosition(s); + if (pos < ENCOMSP_ORDER_HEADER_SIZE) + return ERROR_INVALID_DATA; + const size_t beg = pos - ENCOMSP_ORDER_HEADER_SIZE; + + WINPR_ASSERT(header); + pdu.Length = header->Length; + pdu.Type = header->Type; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 6)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, pdu.Flags); /* Flags (2 bytes) */ + Stream_Read_UINT32(s, pdu.ParticipantId); /* ParticipantId (4 bytes) */ + const size_t end = Stream_GetPosition(s); + const size_t body = beg + header->Length; + + if (body < end) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + if (body > end) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(body - end))) + return ERROR_INVALID_DATA; + + Stream_SetPosition(s, body); + } + + IFCALLRET(context->ChangeParticipantControlLevel, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->ChangeParticipantControlLevel failed with error %" PRIu32 "", + error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_send_change_participant_control_level_pdu( + EncomspClientContext* context, const ENCOMSP_CHANGE_PARTICIPANT_CONTROL_LEVEL_PDU* pdu) +{ + ENCOMSP_ORDER_HEADER header = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(context); + encomspPlugin* encomsp = (encomspPlugin*)context->handle; + + header.Type = ODTYPE_PARTICIPANT_CTRL_CHANGED; + header.Length = ENCOMSP_ORDER_HEADER_SIZE + 6; + + wStream* s = Stream_New(nullptr, header.Length); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + const UINT error = encomsp_write_header(s, &header); + if (error != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "encomsp_write_header failed with error %" PRIu32 "!", error); + return error; + } + + Stream_Write_UINT16(s, pdu->Flags); /* Flags (2 bytes) */ + Stream_Write_UINT32(s, pdu->ParticipantId); /* ParticipantId (4 bytes) */ + Stream_SealLength(s); + return encomsp_virtual_channel_write(encomsp, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_recv_graphics_stream_paused_pdu(encomspPlugin* encomsp, wStream* s, + const ENCOMSP_ORDER_HEADER* header) +{ + ENCOMSP_GRAPHICS_STREAM_PAUSED_PDU pdu = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + EncomspClientContext* context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + const size_t pos = Stream_GetPosition(s); + if (pos < ENCOMSP_ORDER_HEADER_SIZE) + return ERROR_INVALID_DATA; + const size_t beg = pos - ENCOMSP_ORDER_HEADER_SIZE; + + WINPR_ASSERT(header); + pdu.Length = header->Length; + pdu.Type = header->Type; + + const size_t end = Stream_GetPosition(s); + const size_t body = beg + header->Length; + + if (body < end) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + if (body > end) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(body - end))) + return ERROR_INVALID_DATA; + + Stream_SetPosition(s, body); + } + + IFCALLRET(context->GraphicsStreamPaused, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->GraphicsStreamPaused failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_recv_graphics_stream_resumed_pdu(encomspPlugin* encomsp, wStream* s, + const ENCOMSP_ORDER_HEADER* header) +{ + ENCOMSP_GRAPHICS_STREAM_RESUMED_PDU pdu = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + EncomspClientContext* context = encomsp_get_client_interface(encomsp); + + if (!context) + return ERROR_INVALID_HANDLE; + + const size_t pos = Stream_GetPosition(s); + if (pos < ENCOMSP_ORDER_HEADER_SIZE) + return ERROR_INVALID_DATA; + const size_t beg = pos - ENCOMSP_ORDER_HEADER_SIZE; + + WINPR_ASSERT(header); + pdu.Length = header->Length; + pdu.Type = header->Type; + + const size_t end = Stream_GetPosition(s); + const size_t body = beg + header->Length; + + if (body < end) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + if (body > end) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(body - end))) + return ERROR_INVALID_DATA; + + Stream_SetPosition(s, body); + } + + IFCALLRET(context->GraphicsStreamResumed, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->GraphicsStreamResumed failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_process_receive(encomspPlugin* encomsp, wStream* s) +{ + UINT error = CHANNEL_RC_OK; + ENCOMSP_ORDER_HEADER header = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(encomsp); + while (Stream_GetRemainingLength(s) > 0) + { + if ((error = encomsp_read_header(s, &header))) + { + WLog_ERR(TAG, "encomsp_read_header failed with error %" PRIu32 "!", error); + return error; + } + + // WLog_DBG(TAG, "EncomspReceive: Type: %"PRIu16" Length: %"PRIu16"", header.Type, + // header.Length); + + switch (header.Type) + { + case ODTYPE_FILTER_STATE_UPDATED: + if ((error = encomsp_recv_filter_updated_pdu(encomsp, s, &header))) + { + WLog_ERR(TAG, "encomsp_recv_filter_updated_pdu failed with error %" PRIu32 "!", + error); + return error; + } + + break; + + case ODTYPE_APP_REMOVED: + if ((error = encomsp_recv_application_removed_pdu(encomsp, s, &header))) + { + WLog_ERR(TAG, + "encomsp_recv_application_removed_pdu failed with error %" PRIu32 "!", + error); + return error; + } + + break; + + case ODTYPE_APP_CREATED: + if ((error = encomsp_recv_application_created_pdu(encomsp, s, &header))) + { + WLog_ERR(TAG, + "encomsp_recv_application_removed_pdu failed with error %" PRIu32 "!", + error); + return error; + } + + break; + + case ODTYPE_WND_REMOVED: + if ((error = encomsp_recv_window_removed_pdu(encomsp, s, &header))) + { + WLog_ERR(TAG, "encomsp_recv_window_removed_pdu failed with error %" PRIu32 "!", + error); + return error; + } + + break; + + case ODTYPE_WND_CREATED: + if ((error = encomsp_recv_window_created_pdu(encomsp, s, &header))) + { + WLog_ERR(TAG, "encomsp_recv_window_created_pdu failed with error %" PRIu32 "!", + error); + return error; + } + + break; + + case ODTYPE_WND_SHOW: + if ((error = encomsp_recv_show_window_pdu(encomsp, s, &header))) + { + WLog_ERR(TAG, "encomsp_recv_show_window_pdu failed with error %" PRIu32 "!", + error); + return error; + } + + break; + + case ODTYPE_PARTICIPANT_REMOVED: + if ((error = encomsp_recv_participant_removed_pdu(encomsp, s, &header))) + { + WLog_ERR(TAG, + "encomsp_recv_participant_removed_pdu failed with error %" PRIu32 "!", + error); + return error; + } + + break; + + case ODTYPE_PARTICIPANT_CREATED: + if ((error = encomsp_recv_participant_created_pdu(encomsp, s, &header))) + { + WLog_ERR(TAG, + "encomsp_recv_participant_created_pdu failed with error %" PRIu32 "!", + error); + return error; + } + + break; + + case ODTYPE_PARTICIPANT_CTRL_CHANGED: + if ((error = + encomsp_recv_change_participant_control_level_pdu(encomsp, s, &header))) + { + WLog_ERR(TAG, + "encomsp_recv_change_participant_control_level_pdu failed with error " + "%" PRIu32 "!", + error); + return error; + } + + break; + + case ODTYPE_GRAPHICS_STREAM_PAUSED: + if ((error = encomsp_recv_graphics_stream_paused_pdu(encomsp, s, &header))) + { + WLog_ERR(TAG, + "encomsp_recv_graphics_stream_paused_pdu failed with error %" PRIu32 + "!", + error); + return error; + } + + break; + + case ODTYPE_GRAPHICS_STREAM_RESUMED: + if ((error = encomsp_recv_graphics_stream_resumed_pdu(encomsp, s, &header))) + { + WLog_ERR(TAG, + "encomsp_recv_graphics_stream_resumed_pdu failed with error %" PRIu32 + "!", + error); + return error; + } + + break; + + default: + WLog_ERR(TAG, "header.Type %" PRIu16 " not found", header.Type); + return ERROR_INVALID_DATA; + } + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_virtual_channel_event_data_received(encomspPlugin* encomsp, const void* pData, + UINT32 dataLength, UINT32 totalLength, + UINT32 dataFlags) +{ + WINPR_ASSERT(encomsp); + + if ((dataFlags & CHANNEL_FLAG_SUSPEND) || (dataFlags & CHANNEL_FLAG_RESUME)) + return CHANNEL_RC_OK; + + if (dataFlags & CHANNEL_FLAG_FIRST) + { + if (encomsp->data_in) + Stream_Free(encomsp->data_in, TRUE); + + encomsp->data_in = Stream_New(nullptr, totalLength); + + if (!encomsp->data_in) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + } + + wStream* data_in = encomsp->data_in; + + if (!Stream_EnsureRemainingCapacity(data_in, dataLength)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_Write(data_in, pData, dataLength); + + if (dataFlags & CHANNEL_FLAG_LAST) + { + if (Stream_Capacity(data_in) != Stream_GetPosition(data_in)) + { + WLog_ERR(TAG, "encomsp_plugin_process_received: read error"); + return ERROR_INVALID_DATA; + } + + encomsp->data_in = nullptr; + Stream_SealLength(data_in); + Stream_ResetPosition(data_in); + + if (!MessageQueue_Post(encomsp->queue, nullptr, 0, (void*)data_in, nullptr)) + { + WLog_ERR(TAG, "MessageQueue_Post failed!"); + return ERROR_INTERNAL_ERROR; + } + } + + return CHANNEL_RC_OK; +} + +static VOID VCAPITYPE encomsp_virtual_channel_open_event_ex(LPVOID lpUserParam, DWORD openHandle, + UINT event, LPVOID pData, + UINT32 dataLength, UINT32 totalLength, + UINT32 dataFlags) +{ + UINT error = CHANNEL_RC_OK; + encomspPlugin* encomsp = (encomspPlugin*)lpUserParam; + + switch (event) + { + case CHANNEL_EVENT_DATA_RECEIVED: + if (!encomsp || (encomsp->OpenHandle != openHandle)) + { + WLog_ERR(TAG, "error no match"); + return; + } + if ((error = encomsp_virtual_channel_event_data_received(encomsp, pData, dataLength, + totalLength, dataFlags))) + WLog_ERR(TAG, + "encomsp_virtual_channel_event_data_received failed with error %" PRIu32 + "", + error); + + break; + + case CHANNEL_EVENT_WRITE_CANCELLED: + case CHANNEL_EVENT_WRITE_COMPLETE: + { + wStream* s = (wStream*)pData; + Stream_Free(s, TRUE); + } + break; + + case CHANNEL_EVENT_USER: + break; + default: + break; + } + + if (error && encomsp && encomsp->rdpcontext) + setChannelError(encomsp->rdpcontext, error, + "encomsp_virtual_channel_open_event reported an error"); +} + +static DWORD WINAPI encomsp_virtual_channel_client_thread(LPVOID arg) +{ + wStream* data = nullptr; + wMessage message = WINPR_C_ARRAY_INIT; + encomspPlugin* encomsp = (encomspPlugin*)arg; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(encomsp); + while (1) + { + if (!MessageQueue_Wait(encomsp->queue)) + { + WLog_ERR(TAG, "MessageQueue_Wait failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (!MessageQueue_Peek(encomsp->queue, &message, TRUE)) + { + WLog_ERR(TAG, "MessageQueue_Peek failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (message.id == WMQ_QUIT) + break; + + if (message.id == 0) + { + data = (wStream*)message.wParam; + + if ((error = encomsp_process_receive(encomsp, data))) + { + WLog_ERR(TAG, "encomsp_process_receive failed with error %" PRIu32 "!", error); + Stream_Free(data, TRUE); + break; + } + + Stream_Free(data, TRUE); + } + } + + if (error && encomsp->rdpcontext) + setChannelError(encomsp->rdpcontext, error, + "encomsp_virtual_channel_client_thread reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_virtual_channel_event_connected(encomspPlugin* encomsp, + WINPR_ATTR_UNUSED LPVOID pData, + WINPR_ATTR_UNUSED UINT32 dataLength) +{ + WINPR_ASSERT(encomsp); + + encomsp->queue = MessageQueue_New(nullptr); + + if (!encomsp->queue) + { + WLog_ERR(TAG, "MessageQueue_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if (!(encomsp->thread = CreateThread(nullptr, 0, encomsp_virtual_channel_client_thread, + (void*)encomsp, 0, nullptr))) + { + WLog_ERR(TAG, "CreateThread failed!"); + MessageQueue_Free(encomsp->queue); + return ERROR_INTERNAL_ERROR; + } + + return encomsp->channelEntryPoints.pVirtualChannelOpenEx( + encomsp->InitHandle, &encomsp->OpenHandle, encomsp->channelDef.name, + encomsp_virtual_channel_open_event_ex); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_virtual_channel_event_disconnected(encomspPlugin* encomsp) +{ + WINPR_ASSERT(encomsp); + if (encomsp->OpenHandle == 0) + return CHANNEL_RC_OK; + + if (encomsp->queue && encomsp->thread) + { + if (MessageQueue_PostQuit(encomsp->queue, 0) && + (WaitForSingleObject(encomsp->thread, INFINITE) == WAIT_FAILED)) + { + const UINT rc = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", rc); + return rc; + } + } + + MessageQueue_Free(encomsp->queue); + (void)CloseHandle(encomsp->thread); + encomsp->queue = nullptr; + encomsp->thread = nullptr; + + WINPR_ASSERT(encomsp->channelEntryPoints.pVirtualChannelCloseEx); + const UINT rc = encomsp->channelEntryPoints.pVirtualChannelCloseEx(encomsp->InitHandle, + encomsp->OpenHandle); + + if (CHANNEL_RC_OK != rc) + { + WLog_ERR(TAG, "pVirtualChannelClose failed with %s [%08" PRIX32 "]", WTSErrorToString(rc), + rc); + return rc; + } + + encomsp->OpenHandle = 0; + + if (encomsp->data_in) + { + Stream_Free(encomsp->data_in, TRUE); + encomsp->data_in = nullptr; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_virtual_channel_event_terminated(encomspPlugin* encomsp) +{ + WINPR_ASSERT(encomsp); + + encomsp->InitHandle = nullptr; + free(encomsp->context); + free(encomsp); + return CHANNEL_RC_OK; +} + +static VOID VCAPITYPE encomsp_virtual_channel_init_event_ex(LPVOID lpUserParam, LPVOID pInitHandle, + UINT event, LPVOID pData, + UINT dataLength) +{ + UINT error = CHANNEL_RC_OK; + encomspPlugin* encomsp = (encomspPlugin*)lpUserParam; + + if (!encomsp || (encomsp->InitHandle != pInitHandle)) + { + WLog_ERR(TAG, "error no match"); + return; + } + + switch (event) + { + case CHANNEL_EVENT_INITIALIZED: + break; + + case CHANNEL_EVENT_CONNECTED: + if ((error = encomsp_virtual_channel_event_connected(encomsp, pData, dataLength))) + WLog_ERR(TAG, + "encomsp_virtual_channel_event_connected failed with error %" PRIu32 "", + error); + + break; + + case CHANNEL_EVENT_DISCONNECTED: + if ((error = encomsp_virtual_channel_event_disconnected(encomsp))) + WLog_ERR(TAG, + "encomsp_virtual_channel_event_disconnected failed with error %" PRIu32 "", + error); + + break; + + case CHANNEL_EVENT_TERMINATED: + encomsp_virtual_channel_event_terminated(encomsp); + break; + + default: + break; + } + + if (error && encomsp->rdpcontext) + setChannelError(encomsp->rdpcontext, error, + "encomsp_virtual_channel_init_event reported an error"); +} + +/* encomsp is always built-in */ +#define VirtualChannelEntryEx encomsp_VirtualChannelEntryEx + +FREERDP_ENTRY_POINT(BOOL VCAPITYPE VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS_EX pEntryPoints, + PVOID pInitHandle)) +{ + BOOL isFreerdp = FALSE; + encomspPlugin* encomsp = (encomspPlugin*)calloc(1, sizeof(encomspPlugin)); + + if (!encomsp) + { + WLog_ERR(TAG, "calloc failed!"); + return FALSE; + } + + encomsp->channelDef.options = CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP | + CHANNEL_OPTION_COMPRESS_RDP | CHANNEL_OPTION_SHOW_PROTOCOL; + (void)sprintf_s(encomsp->channelDef.name, ARRAYSIZE(encomsp->channelDef.name), + ENCOMSP_SVC_CHANNEL_NAME); + CHANNEL_ENTRY_POINTS_FREERDP_EX* pEntryPointsEx = + (CHANNEL_ENTRY_POINTS_FREERDP_EX*)pEntryPoints; + WINPR_ASSERT(pEntryPointsEx); + + EncomspClientContext* context = nullptr; + if ((pEntryPointsEx->cbSize >= sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)) && + (pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER)) + { + context = (EncomspClientContext*)calloc(1, sizeof(EncomspClientContext)); + + if (!context) + { + WLog_ERR(TAG, "calloc failed!"); + goto error_out; + } + + context->handle = (void*)encomsp; + context->FilterUpdated = nullptr; + context->ApplicationCreated = nullptr; + context->ApplicationRemoved = nullptr; + context->WindowCreated = nullptr; + context->WindowRemoved = nullptr; + context->ShowWindow = nullptr; + context->ParticipantCreated = nullptr; + context->ParticipantRemoved = nullptr; + context->ChangeParticipantControlLevel = encomsp_send_change_participant_control_level_pdu; + context->GraphicsStreamPaused = nullptr; + context->GraphicsStreamResumed = nullptr; + encomsp->context = context; + encomsp->rdpcontext = pEntryPointsEx->context; + isFreerdp = TRUE; + } + + CopyMemory(&(encomsp->channelEntryPoints), pEntryPoints, + sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)); + encomsp->InitHandle = pInitHandle; + + { + const UINT rc = encomsp->channelEntryPoints.pVirtualChannelInitEx( + encomsp, context, pInitHandle, &encomsp->channelDef, 1, VIRTUAL_CHANNEL_VERSION_WIN2000, + encomsp_virtual_channel_init_event_ex); + + if (CHANNEL_RC_OK != rc) + { + WLog_ERR(TAG, "failed with %s [%08" PRIX32 "]", WTSErrorToString(rc), rc); + goto error_out; + } + } + + encomsp->channelEntryPoints.pInterface = context; + return TRUE; +error_out: + + if (isFreerdp) + free(encomsp->context); + + free(encomsp); + return FALSE; +} diff --git a/third_party/FreeRDP/channels/encomsp/client/encomsp_main.h b/third_party/FreeRDP/channels/encomsp/client/encomsp_main.h new file mode 100644 index 0000000..ad43cea --- /dev/null +++ b/third_party/FreeRDP/channels/encomsp/client/encomsp_main.h @@ -0,0 +1,42 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Multiparty Virtual Channel + * + * Copyright 2014 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_ENCOMSP_CLIENT_MAIN_H +#define FREERDP_CHANNEL_ENCOMSP_CLIENT_MAIN_H + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#define TAG CHANNELS_TAG("encomsp.client") + +typedef struct encomsp_plugin encomspPlugin; + +#endif /* FREERDP_CHANNEL_ENCOMSP_CLIENT_MAIN_H */ diff --git a/third_party/FreeRDP/channels/encomsp/server/CMakeLists.txt b/third_party/FreeRDP/channels/encomsp/server/CMakeLists.txt new file mode 100644 index 0000000..c23795a --- /dev/null +++ b/third_party/FreeRDP/channels/encomsp/server/CMakeLists.txt @@ -0,0 +1,25 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_server("encomsp") + +include_directories(..) + +set(${MODULE_PREFIX}_SRCS encomsp_main.c encomsp_main.h) + +set(${MODULE_PREFIX}_LIBS winpr) +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntry") diff --git a/third_party/FreeRDP/channels/encomsp/server/encomsp_main.c b/third_party/FreeRDP/channels/encomsp/server/encomsp_main.c new file mode 100644 index 0000000..0bfb4a0 --- /dev/null +++ b/third_party/FreeRDP/channels/encomsp/server/encomsp_main.c @@ -0,0 +1,352 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Multiparty Virtual Channel + * + * Copyright 2014 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include + +#include "encomsp_main.h" + +#define TAG CHANNELS_TAG("encomsp.server") + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_read_header(wStream* s, ENCOMSP_ORDER_HEADER* header) +{ + if (!Stream_CheckAndLogRequiredLength(TAG, s, ENCOMSP_ORDER_HEADER_SIZE)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, header->Type); /* Type (2 bytes) */ + Stream_Read_UINT16(s, header->Length); /* Length (2 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_recv_change_participant_control_level_pdu(EncomspServerContext* context, + wStream* s, + const ENCOMSP_ORDER_HEADER* header) +{ + ENCOMSP_CHANGE_PARTICIPANT_CONTROL_LEVEL_PDU pdu = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + + const size_t pos = Stream_GetPosition(s); + if (pos < ENCOMSP_ORDER_HEADER_SIZE) + return ERROR_INVALID_PARAMETER; + + const size_t beg = pos - ENCOMSP_ORDER_HEADER_SIZE; + CopyMemory(&pdu, header, sizeof(ENCOMSP_ORDER_HEADER)); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 6)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, pdu.Flags); /* Flags (2 bytes) */ + Stream_Read_UINT32(s, pdu.ParticipantId); /* ParticipantId (4 bytes) */ + const size_t end = Stream_GetPosition(s); + + if ((beg + header->Length) < end) + { + WLog_ERR(TAG, "Not enough data!"); + return ERROR_INVALID_DATA; + } + + if ((beg + header->Length) > end) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)((beg + header->Length) - end))) + return ERROR_INVALID_DATA; + + Stream_SetPosition(s, (beg + header->Length)); + } + + IFCALLRET(context->ChangeParticipantControlLevel, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context->ChangeParticipantControlLevel failed with error %" PRIu32 "", + error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_server_receive_pdu(EncomspServerContext* context, wStream* s) +{ + UINT error = CHANNEL_RC_OK; + + while (Stream_GetRemainingLength(s) > 0) + { + ENCOMSP_ORDER_HEADER header = WINPR_C_ARRAY_INIT; + if ((error = encomsp_read_header(s, &header))) + { + WLog_ERR(TAG, "encomsp_read_header failed with error %" PRIu32 "!", error); + return error; + } + + WLog_INFO(TAG, "EncomspReceive: Type: %" PRIu16 " Length: %" PRIu16 "", header.Type, + header.Length); + + switch (header.Type) + { + case ODTYPE_PARTICIPANT_CTRL_CHANGED: + if ((error = + encomsp_recv_change_participant_control_level_pdu(context, s, &header))) + { + WLog_ERR(TAG, + "encomsp_recv_change_participant_control_level_pdu failed with error " + "%" PRIu32 "!", + error); + return error; + } + + break; + + default: + WLog_ERR(TAG, "header.Type unknown %" PRIu16 "!", header.Type); + return ERROR_INVALID_DATA; + } + } + + return error; +} + +static DWORD WINAPI encomsp_server_thread(LPVOID arg) +{ + wStream* s = nullptr; + DWORD nCount = 0; + void* buffer = nullptr; + HANDLE events[8]; + HANDLE ChannelEvent = nullptr; + DWORD BytesReturned = 0; + ENCOMSP_ORDER_HEADER* header = nullptr; + EncomspServerContext* context = nullptr; + UINT error = CHANNEL_RC_OK; + DWORD status = 0; + context = (EncomspServerContext*)arg; + + buffer = nullptr; + BytesReturned = 0; + ChannelEvent = nullptr; + s = Stream_New(nullptr, 4096); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + if (WTSVirtualChannelQuery(context->priv->ChannelHandle, WTSVirtualEventHandle, &buffer, + &BytesReturned) == TRUE) + { + if (BytesReturned == sizeof(HANDLE)) + ChannelEvent = *(HANDLE*)buffer; + + WTSFreeMemory(buffer); + } + + nCount = 0; + events[nCount++] = ChannelEvent; + events[nCount++] = context->priv->StopEvent; + + while (1) + { + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "", error); + break; + } + + status = WaitForSingleObject(context->priv->StopEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + break; + } + + if (status == WAIT_OBJECT_0) + { + break; + } + + if (!WTSVirtualChannelRead(context->priv->ChannelHandle, 0, nullptr, 0, &BytesReturned)) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (BytesReturned < 1) + continue; + + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + error = CHANNEL_RC_NO_MEMORY; + break; + } + + const size_t cap = Stream_Capacity(s); + if ((cap > UINT32_MAX) || + !WTSVirtualChannelRead(context->priv->ChannelHandle, 0, Stream_BufferAs(s, char), + (ULONG)cap, &BytesReturned)) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (Stream_GetPosition(s) >= ENCOMSP_ORDER_HEADER_SIZE) + { + header = Stream_BufferAs(s, ENCOMSP_ORDER_HEADER); + + if (header->Length >= Stream_GetPosition(s)) + { + Stream_SealLength(s); + Stream_ResetPosition(s); + + if ((error = encomsp_server_receive_pdu(context, s))) + { + WLog_ERR(TAG, "encomsp_server_receive_pdu failed with error %" PRIu32 "!", + error); + break; + } + + Stream_ResetPosition(s); + } + } + } + + Stream_Free(s, TRUE); +out: + + if (error && context->rdpcontext) + setChannelError(context->rdpcontext, error, "encomsp_server_thread reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_server_start(EncomspServerContext* context) +{ + context->priv->ChannelHandle = + WTSVirtualChannelOpen(context->vcm, WTS_CURRENT_SESSION, ENCOMSP_SVC_CHANNEL_NAME); + + if (!context->priv->ChannelHandle) + return CHANNEL_RC_BAD_CHANNEL; + + if (!(context->priv->StopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (!(context->priv->Thread = + CreateThread(nullptr, 0, encomsp_server_thread, (void*)context, 0, nullptr))) + { + WLog_ERR(TAG, "CreateThread failed!"); + (void)CloseHandle(context->priv->StopEvent); + context->priv->StopEvent = nullptr; + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT encomsp_server_stop(EncomspServerContext* context) +{ + UINT error = CHANNEL_RC_OK; + (void)SetEvent(context->priv->StopEvent); + + if (WaitForSingleObject(context->priv->Thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + return error; + } + + (void)CloseHandle(context->priv->Thread); + (void)CloseHandle(context->priv->StopEvent); + return error; +} + +EncomspServerContext* encomsp_server_context_new(HANDLE vcm) +{ + EncomspServerContext* context = nullptr; + context = (EncomspServerContext*)calloc(1, sizeof(EncomspServerContext)); + + if (context) + { + context->vcm = vcm; + context->Start = encomsp_server_start; + context->Stop = encomsp_server_stop; + context->priv = (EncomspServerPrivate*)calloc(1, sizeof(EncomspServerPrivate)); + + if (!context->priv) + { + WLog_ERR(TAG, "calloc failed!"); + free(context); + return nullptr; + } + } + + return context; +} + +void encomsp_server_context_free(EncomspServerContext* context) +{ + if (context) + { + if (context->priv->ChannelHandle != INVALID_HANDLE_VALUE) + (void)WTSVirtualChannelClose(context->priv->ChannelHandle); + + free(context->priv); + free(context); + } +} diff --git a/third_party/FreeRDP/channels/encomsp/server/encomsp_main.h b/third_party/FreeRDP/channels/encomsp/server/encomsp_main.h new file mode 100644 index 0000000..37d8c71 --- /dev/null +++ b/third_party/FreeRDP/channels/encomsp/server/encomsp_main.h @@ -0,0 +1,36 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Multiparty Virtual Channel + * + * Copyright 2014 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_ENCOMSP_SERVER_MAIN_H +#define FREERDP_CHANNEL_ENCOMSP_SERVER_MAIN_H + +#include +#include +#include + +#include + +struct s_encomsp_server_private +{ + HANDLE Thread; + HANDLE StopEvent; + void* ChannelHandle; +}; + +#endif /* FREERDP_CHANNEL_ENCOMSP_SERVER_MAIN_H */ diff --git a/third_party/FreeRDP/channels/geometry/CMakeLists.txt b/third_party/FreeRDP/channels/geometry/CMakeLists.txt new file mode 100644 index 0000000..cf1e762 --- /dev/null +++ b/third_party/FreeRDP/channels/geometry/CMakeLists.txt @@ -0,0 +1,22 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2017 David Fort +# +# 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. + +define_channel("geometry") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/third_party/FreeRDP/channels/geometry/ChannelOptions.cmake b/third_party/FreeRDP/channels/geometry/ChannelOptions.cmake new file mode 100644 index 0000000..994d454 --- /dev/null +++ b/third_party/FreeRDP/channels/geometry/ChannelOptions.cmake @@ -0,0 +1,20 @@ +set(OPTION_DEFAULT ON) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT OFF) + +define_channel_options( + NAME + "geometry" + TYPE + "dynamic" + DESCRIPTION + "Geometry tracking Virtual Channel Extension" + SPECIFICATIONS + "[MS-RDPEGT]" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/geometry/client/CMakeLists.txt b/third_party/FreeRDP/channels/geometry/client/CMakeLists.txt new file mode 100644 index 0000000..0490838 --- /dev/null +++ b/third_party/FreeRDP/channels/geometry/client/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2017 David Fort +# +# 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. + +define_channel_client("geometry") + +set(${MODULE_PREFIX}_SRCS geometry_main.c geometry_main.h) + +set(${MODULE_PREFIX}_LIBS winpr) + +include_directories(..) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") diff --git a/third_party/FreeRDP/channels/geometry/client/geometry_main.c b/third_party/FreeRDP/channels/geometry/client/geometry_main.c new file mode 100644 index 0000000..1c4f96a --- /dev/null +++ b/third_party/FreeRDP/channels/geometry/client/geometry_main.c @@ -0,0 +1,409 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Geometry tracking Virtual Channel Extension + * + * Copyright 2017 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define TAG CHANNELS_TAG("geometry.client") + +#include "geometry_main.h" + +typedef struct +{ + GENERIC_DYNVC_PLUGIN base; + GeometryClientContext* context; +} GEOMETRY_PLUGIN; + +static UINT32 mappedGeometryHash(const void* v) +{ + const UINT64* g = (const UINT64*)v; + return (UINT32)((*g >> 32) + (*g & 0xffffffff)); +} + +static BOOL mappedGeometryKeyCompare(const void* v1, const void* v2) +{ + const UINT64* g1 = (const UINT64*)v1; + const UINT64* g2 = (const UINT64*)v2; + return *g1 == *g2; +} + +static void freerdp_rgndata_reset(FREERDP_RGNDATA* data) +{ + data->nRectCount = 0; +} + +static UINT32 geometry_read_RGNDATA(wLog* logger, wStream* s, UINT32 len, FREERDP_RGNDATA* rgndata) +{ + WINPR_ASSERT(rgndata); + + if (len < 32) + { + WLog_Print(logger, WLOG_ERROR, "invalid RGNDATA"); + return ERROR_INVALID_DATA; + } + + const UINT32 dwSize = Stream_Get_UINT32(s); + + if (dwSize != 32) + { + WLog_Print(logger, WLOG_ERROR, "invalid RGNDATA dwSize"); + return ERROR_INVALID_DATA; + } + + const UINT32 iType = Stream_Get_UINT32(s); + + if (iType != RDH_RECTANGLE) + { + WLog_Print(logger, WLOG_ERROR, "iType %" PRIu32 " for RGNDATA is not supported", iType); + return ERROR_UNSUPPORTED_TYPE; + } + + rgndata->nRectCount = Stream_Get_UINT32(s); + Stream_Seek_UINT32(s); /* nRgnSize IGNORED */ + { + const INT32 x = Stream_Get_INT32(s); + const INT32 y = Stream_Get_INT32(s); + const INT32 right = Stream_Get_INT32(s); + const INT32 bottom = Stream_Get_INT32(s); + if ((abs(x) > INT16_MAX) || (abs(y) > INT16_MAX)) + return ERROR_INVALID_DATA; + const INT32 w = right - x; + const INT32 h = bottom - y; + if ((abs(w) > INT16_MAX) || (abs(h) > INT16_MAX)) + return ERROR_INVALID_DATA; + rgndata->boundingRect.x = (INT16)x; + rgndata->boundingRect.y = (INT16)y; + rgndata->boundingRect.width = (INT16)w; + rgndata->boundingRect.height = (INT16)h; + } + len -= 32; + + if (len / (4 * 4) < rgndata->nRectCount) + { + WLog_Print(logger, WLOG_ERROR, "not enough data for region rectangles"); + return ERROR_INVALID_DATA; + } + + if (rgndata->nRectCount) + { + RDP_RECT* tmp = realloc(rgndata->rects, rgndata->nRectCount * sizeof(RDP_RECT)); + + if (!tmp) + { + WLog_Print(logger, WLOG_ERROR, "unable to allocate memory for %" PRIu32 " RECTs", + rgndata->nRectCount); + return CHANNEL_RC_NO_MEMORY; + } + rgndata->rects = tmp; + + for (UINT32 i = 0; i < rgndata->nRectCount; i++) + { + RDP_RECT* rect = &rgndata->rects[i]; + + if (!Stream_CheckAndLogRequiredLengthWLog(logger, s, 16)) + return CHANNEL_RC_NULL_DATA; + + const INT32 x = Stream_Get_INT32(s); + const INT32 y = Stream_Get_INT32(s); + const INT32 right = Stream_Get_INT32(s); + const INT32 bottom = Stream_Get_INT32(s); + if ((abs(x) > INT16_MAX) || (abs(y) > INT16_MAX)) + return ERROR_INVALID_DATA; + + const INT32 w = right - x; + const INT32 h = bottom - y; + if ((abs(w) > INT16_MAX) || (abs(h) > INT16_MAX)) + return ERROR_INVALID_DATA; + + rect->x = (INT16)x; + rect->y = (INT16)y; + rect->width = (INT16)w; + rect->height = (INT16)h; + } + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT geometry_recv_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + UINT ret = CHANNEL_RC_OK; + + WINPR_ASSERT(callback); + GEOMETRY_PLUGIN* geometry = (GEOMETRY_PLUGIN*)callback->plugin; + WINPR_ASSERT(geometry); + + wLog* logger = geometry->base.log; + GeometryClientContext* context = (GeometryClientContext*)geometry->base.iface.pInterface; + WINPR_ASSERT(context); + + if (!Stream_CheckAndLogRequiredLengthWLog(logger, s, 4)) + return ERROR_INVALID_DATA; + + const UINT32 length = Stream_Get_UINT32(s); /* Length (4 bytes) */ + + if (!Stream_CheckAndLogRequiredLengthWLog(logger, s, (length - 4))) + { + WLog_Print(logger, WLOG_ERROR, "invalid packet length"); + return ERROR_INVALID_DATA; + } + + if (!Stream_CheckAndLogRequiredLengthWLog(logger, s, 20)) + return ERROR_INVALID_DATA; + + context->remoteVersion = Stream_Get_UINT32(s); + const UINT64 id = Stream_Get_UINT64(s); + const UINT32 updateType = Stream_Get_UINT32(s); + Stream_Seek_UINT32(s); /* flags */ + + MAPPED_GEOMETRY* mappedGeometry = HashTable_GetItemValue(context->geometries, &id); + + if (updateType == GEOMETRY_CLEAR) + { + if (!mappedGeometry) + { + WLog_Print(logger, WLOG_ERROR, + "geometry 0x%" PRIx64 " not found here, ignoring clear command", id); + return CHANNEL_RC_OK; + } + + WLog_Print(logger, WLOG_DEBUG, "clearing geometry 0x%" PRIx64 "", id); + + if (mappedGeometry->MappedGeometryClear && + !mappedGeometry->MappedGeometryClear(mappedGeometry)) + return ERROR_INTERNAL_ERROR; + + if (!HashTable_Remove(context->geometries, &id)) + WLog_Print(logger, WLOG_ERROR, "geometry not removed from geometries"); + } + else if (updateType == GEOMETRY_UPDATE) + { + BOOL newOne = FALSE; + + if (!mappedGeometry) + { + newOne = TRUE; + WLog_Print(logger, WLOG_DEBUG, "creating geometry 0x%" PRIx64 "", id); + mappedGeometry = calloc(1, sizeof(MAPPED_GEOMETRY)); + if (!mappedGeometry) + return CHANNEL_RC_NO_MEMORY; + + mappedGeometry->refCounter = 1; + mappedGeometry->mappingId = id; + + if (!HashTable_Insert(context->geometries, &(mappedGeometry->mappingId), + mappedGeometry)) + { + WLog_Print(logger, WLOG_ERROR, + "unable to register geometry 0x%" PRIx64 " in the table", id); + free(mappedGeometry); + return CHANNEL_RC_NO_MEMORY; + } + } + else + { + WLog_Print(logger, WLOG_DEBUG, "updating geometry 0x%" PRIx64 "", id); + } + + if (!Stream_CheckAndLogRequiredLengthWLog(logger, s, 48)) + { + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert ownership mappedGeometry + return ERROR_INVALID_DATA; + } + + mappedGeometry->topLevelId = Stream_Get_UINT64(s); + + mappedGeometry->left = Stream_Get_INT32(s); + mappedGeometry->top = Stream_Get_INT32(s); + mappedGeometry->right = Stream_Get_INT32(s); + mappedGeometry->bottom = Stream_Get_INT32(s); + + mappedGeometry->topLevelLeft = Stream_Get_INT32(s); + mappedGeometry->topLevelTop = Stream_Get_INT32(s); + mappedGeometry->topLevelRight = Stream_Get_INT32(s); + mappedGeometry->topLevelBottom = Stream_Get_INT32(s); + + const UINT32 geometryType = Stream_Get_UINT32(s); + if (geometryType != 0x02) + WLog_Print(logger, WLOG_DEBUG, "geometryType should be set to 0x02 and is 0x%" PRIx32, + geometryType); + + const UINT32 cbGeometryBuffer = Stream_Get_UINT32(s); + if (!Stream_CheckAndLogRequiredLengthWLog(logger, s, cbGeometryBuffer)) + { + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert ownership mappedGeometry + return ERROR_INVALID_DATA; + } + + if (cbGeometryBuffer > 0) + { + ret = geometry_read_RGNDATA(logger, s, cbGeometryBuffer, &mappedGeometry->geometry); + if (ret != CHANNEL_RC_OK) + return ret; + } + else + { + freerdp_rgndata_reset(&mappedGeometry->geometry); + } + + if (newOne) + { + if (context->MappedGeometryAdded && + !context->MappedGeometryAdded(context, mappedGeometry)) + { + WLog_Print(logger, WLOG_ERROR, "geometry added callback failed"); + ret = ERROR_INTERNAL_ERROR; + } + } + else + { + if (mappedGeometry->MappedGeometryUpdate && + !mappedGeometry->MappedGeometryUpdate(mappedGeometry)) + { + WLog_Print(logger, WLOG_ERROR, "geometry update callback failed"); + ret = ERROR_INTERNAL_ERROR; + } + } + } + else + { + WLog_Print(logger, WLOG_ERROR, "unknown updateType=%" PRIu32 "", updateType); + ret = CHANNEL_RC_OK; + } + + return ret; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT geometry_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data) +{ + GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + return geometry_recv_pdu(callback, data); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT geometry_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + free(pChannelCallback); + return CHANNEL_RC_OK; +} + +static void mappedGeometryUnref_void(void* arg) +{ + MAPPED_GEOMETRY* g = (MAPPED_GEOMETRY*)arg; + mappedGeometryUnref(g); +} + +/** + * Channel Client Interface + */ + +static const IWTSVirtualChannelCallback geometry_callbacks = { geometry_on_data_received, + nullptr, /* Open */ + geometry_on_close, nullptr }; + +static UINT init_plugin_cb(GENERIC_DYNVC_PLUGIN* base, WINPR_ATTR_UNUSED rdpContext* rcontext, + rdpSettings* settings) +{ + GeometryClientContext* context = nullptr; + GEOMETRY_PLUGIN* geometry = (GEOMETRY_PLUGIN*)base; + + WINPR_ASSERT(base); + WINPR_UNUSED(settings); + + context = (GeometryClientContext*)calloc(1, sizeof(GeometryClientContext)); + if (!context) + { + WLog_Print(base->log, WLOG_ERROR, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + context->geometries = HashTable_New(FALSE); + if (!context->geometries) + { + WLog_Print(base->log, WLOG_ERROR, "unable to allocate geometries"); + free(context); + return CHANNEL_RC_NO_MEMORY; + } + + HashTable_SetHashFunction(context->geometries, mappedGeometryHash); + { + wObject* obj = HashTable_KeyObject(context->geometries); + obj->fnObjectEquals = mappedGeometryKeyCompare; + } + { + wObject* obj = HashTable_ValueObject(context->geometries); + obj->fnObjectFree = mappedGeometryUnref_void; + } + context->handle = (void*)geometry; + + geometry->context = context; + geometry->base.iface.pInterface = (void*)context; + + return CHANNEL_RC_OK; +} + +static void terminate_plugin_cb(GENERIC_DYNVC_PLUGIN* base) +{ + GEOMETRY_PLUGIN* geometry = (GEOMETRY_PLUGIN*)base; + + if (geometry->context) + HashTable_Free(geometry->context->geometries); + free(geometry->context); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE geometry_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)) +{ + return freerdp_generic_DVCPluginEntry(pEntryPoints, TAG, GEOMETRY_DVC_CHANNEL_NAME, + sizeof(GEOMETRY_PLUGIN), sizeof(GENERIC_CHANNEL_CALLBACK), + &geometry_callbacks, init_plugin_cb, terminate_plugin_cb); +} diff --git a/third_party/FreeRDP/channels/geometry/client/geometry_main.h b/third_party/FreeRDP/channels/geometry/client/geometry_main.h new file mode 100644 index 0000000..dc914b6 --- /dev/null +++ b/third_party/FreeRDP/channels/geometry/client/geometry_main.h @@ -0,0 +1,30 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Geometry tracking virtual channel extension + * + * Copyright 2017 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_GEOMETRY_CLIENT_MAIN_H +#define FREERDP_CHANNEL_GEOMETRY_CLIENT_MAIN_H + +#include + +#include +#include +#include +#include + +#endif /* FREERDP_CHANNEL_GEOMETRY_CLIENT_MAIN_H */ diff --git a/third_party/FreeRDP/channels/gfxredir/CMakeLists.txt b/third_party/FreeRDP/channels/gfxredir/CMakeLists.txt new file mode 100644 index 0000000..edf396a --- /dev/null +++ b/third_party/FreeRDP/channels/gfxredir/CMakeLists.txt @@ -0,0 +1,27 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2020 Microsoft +# +# 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. + +define_channel("gfxredir") + +if(WITH_SERVER_CHANNELS OR WITH_CLIENT_CHANNELS) + include_directories(common) + add_subdirectory(common) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/third_party/FreeRDP/channels/gfxredir/ChannelOptions.cmake b/third_party/FreeRDP/channels/gfxredir/ChannelOptions.cmake new file mode 100644 index 0000000..e8bd4c7 --- /dev/null +++ b/third_party/FreeRDP/channels/gfxredir/ChannelOptions.cmake @@ -0,0 +1,20 @@ +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options( + NAME + "gfxredir" + TYPE + "dynamic" + DESCRIPTION + "Graphics Redirection Virtual Channel Extension" + SPECIFICATIONS + "[MS-RDPXXXX]" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/gfxredir/common/CMakeLists.txt b/third_party/FreeRDP/channels/gfxredir/common/CMakeLists.txt new file mode 100644 index 0000000..7776192 --- /dev/null +++ b/third_party/FreeRDP/channels/gfxredir/common/CMakeLists.txt @@ -0,0 +1,24 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2024 Armin Novak +# 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 gfxredir_common.h gfxredir_common.c) + +# Library currently header only +add_library(gfxredir-common STATIC ${SRCS}) + +channel_install(gfxredir-common ${FREERDP_ADDIN_PATH} "FreeRDPTargets") diff --git a/third_party/FreeRDP/channels/gfxredir/common/gfxredir_common.c b/third_party/FreeRDP/channels/gfxredir/common/gfxredir_common.c new file mode 100644 index 0000000..88ee88b --- /dev/null +++ b/third_party/FreeRDP/channels/gfxredir/common/gfxredir_common.c @@ -0,0 +1,58 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RDPXXXX Remote App Graphics Redirection Virtual Channel Extension + * + * Copyright 2020 Microsoft + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include + +#define TAG CHANNELS_TAG("gfxredir.common") + +#include "gfxredir_common.h" + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT gfxredir_read_header(wStream* s, GFXREDIR_HEADER* header) +{ + WINPR_ASSERT(header); + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, header->cmdId); + Stream_Read_UINT32(s, header->length); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT gfxredir_write_header(wStream* s, const GFXREDIR_HEADER* header) +{ + WINPR_ASSERT(header); + Stream_Write_UINT32(s, header->cmdId); + Stream_Write_UINT32(s, header->length); + return CHANNEL_RC_OK; +} diff --git a/third_party/FreeRDP/channels/gfxredir/common/gfxredir_common.h b/third_party/FreeRDP/channels/gfxredir/common/gfxredir_common.h new file mode 100644 index 0000000..506172f --- /dev/null +++ b/third_party/FreeRDP/channels/gfxredir/common/gfxredir_common.h @@ -0,0 +1,35 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RDPXXXX Remote App Graphics Redirection Virtual Channel Extension + * + * Copyright 2020 Microsoft + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_GFXREDIR_COMMON_H +#define FREERDP_CHANNEL_GFXREDIR_COMMON_H + +#include +#include + +#include +#include + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT gfxredir_read_header(wStream* s, GFXREDIR_HEADER* header); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT gfxredir_write_header(wStream* s, const GFXREDIR_HEADER* header); + +#endif /* FREERDP_CHANNEL_GFXREDIR_COMMON_H */ diff --git a/third_party/FreeRDP/channels/gfxredir/server/CMakeLists.txt b/third_party/FreeRDP/channels/gfxredir/server/CMakeLists.txt new file mode 100644 index 0000000..5f88a2f --- /dev/null +++ b/third_party/FreeRDP/channels/gfxredir/server/CMakeLists.txt @@ -0,0 +1,24 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2020 Microsoft +# +# 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. + +define_channel_server("gfxredir") + +set(${MODULE_PREFIX}_SRCS gfxredir_main.c gfxredir_main.h) + +set(${MODULE_PREFIX}_LIBS freerdp gfxredir-common) + +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry") diff --git a/third_party/FreeRDP/channels/gfxredir/server/gfxredir_main.c b/third_party/FreeRDP/channels/gfxredir/server/gfxredir_main.c new file mode 100644 index 0000000..93dd38f --- /dev/null +++ b/third_party/FreeRDP/channels/gfxredir/server/gfxredir_main.c @@ -0,0 +1,829 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RDPXXXX Remote App Graphics Redirection Virtual Channel Extension + * + * Copyright 2020 Microsoft + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "gfxredir_main.h" +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define TAG CHANNELS_TAG("gfxredir.server") + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT gfxredir_recv_legacy_caps_pdu(wStream* s, GfxRedirServerContext* context) +{ + UINT32 error = CHANNEL_RC_OK; + GFXREDIR_LEGACY_CAPS_PDU pdu = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(context); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 2)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, pdu.version); /* version (2 bytes) */ + + if (context) + IFCALLRET(context->GraphicsRedirectionLegacyCaps, error, context, &pdu); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT gfxredir_recv_caps_advertise_pdu(wStream* s, UINT32 length, + GfxRedirServerContext* context) +{ + UINT32 error = CHANNEL_RC_OK; + GFXREDIR_CAPS_ADVERTISE_PDU pdu = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(context); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, length)) + return ERROR_INVALID_DATA; + + pdu.length = length; + Stream_GetPointer(s, pdu.caps); + Stream_Seek(s, length); + + if (!context->GraphicsRedirectionCapsAdvertise) + { + WLog_ERR(TAG, "server does not support CapsAdvertise PDU!"); + return ERROR_NOT_SUPPORTED; + } + else if (context) + { + IFCALLRET(context->GraphicsRedirectionCapsAdvertise, error, context, &pdu); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT gfxredir_recv_present_buffer_ack_pdu(wStream* s, GfxRedirServerContext* context) +{ + UINT32 error = CHANNEL_RC_OK; + GFXREDIR_PRESENT_BUFFER_ACK_PDU pdu = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(context); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 16)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT64(s, pdu.windowId); /* windowId (8 bytes) */ + Stream_Read_UINT64(s, pdu.presentId); /* presentId (8 bytes) */ + + if (context) + { + IFCALLRET(context->PresentBufferAck, error, context, &pdu); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT gfxredir_server_receive_pdu(GfxRedirServerContext* context, wStream* s) +{ + UINT error = CHANNEL_RC_OK; + GFXREDIR_HEADER header = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(context); + + const size_t beg = Stream_GetPosition(s); + + if ((error = gfxredir_read_header(s, &header))) + { + WLog_ERR(TAG, "gfxredir_read_header failed with error %" PRIu32 "!", error); + return error; + } + + switch (header.cmdId) + { + case GFXREDIR_CMDID_LEGACY_CAPS: + if ((error = gfxredir_recv_legacy_caps_pdu(s, context))) + WLog_ERR(TAG, + "gfxredir_recv_legacy_caps_pdu " + "failed with error %" PRIu32 "!", + error); + + break; + + case GFXREDIR_CMDID_CAPS_ADVERTISE: + if ((error = gfxredir_recv_caps_advertise_pdu(s, header.length - 8, context))) + WLog_ERR(TAG, + "gfxredir_recv_caps_advertise " + "failed with error %" PRIu32 "!", + error); + break; + + case GFXREDIR_CMDID_PRESENT_BUFFER_ACK: + if ((error = gfxredir_recv_present_buffer_ack_pdu(s, context))) + WLog_ERR(TAG, + "gfxredir_recv_present_buffer_ask_pdu " + "failed with error %" PRIu32 "!", + error); + break; + + default: + error = CHANNEL_RC_BAD_PROC; + WLog_WARN(TAG, "Received unknown PDU type: %" PRIu32 "", header.cmdId); + break; + } + + const size_t end = Stream_GetPosition(s); + + if (end != (beg + header.length)) + { + WLog_ERR(TAG, "Unexpected GFXREDIR pdu end: Actual: %" PRIuz ", Expected: %" PRIuz "", end, + (beg + header.length)); + Stream_SetPosition(s, (beg + header.length)); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT gfxredir_server_handle_messages(GfxRedirServerContext* context) +{ + UINT ret = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + + GfxRedirServerPrivate* priv = context->priv; + WINPR_ASSERT(priv); + + wStream* s = priv->input_stream; + + /* Check whether the dynamic channel is ready */ + if (!priv->isReady) + { + void* buffer = nullptr; + DWORD BytesReturned = 0; + if (WTSVirtualChannelQuery(priv->gfxredir_channel, WTSVirtualChannelReady, &buffer, + &BytesReturned) == FALSE) + { + if (GetLastError() == ERROR_NO_DATA) + return ERROR_NO_DATA; + + WLog_ERR(TAG, "WTSVirtualChannelQuery failed"); + return ERROR_INTERNAL_ERROR; + } + + priv->isReady = *((BOOL*)buffer); + WTSFreeMemory(buffer); + } + + /* Consume channel event only after the dynamic channel is ready */ + if (priv->isReady) + { + Stream_ResetPosition(s); + + DWORD BytesReturned = 0; + if (!WTSVirtualChannelRead(priv->gfxredir_channel, 0, nullptr, 0, &BytesReturned)) + { + if (GetLastError() == ERROR_NO_DATA) + return ERROR_NO_DATA; + + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (BytesReturned < 1) + return CHANNEL_RC_OK; + + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + const ULONG cap = WINPR_ASSERTING_INT_CAST(ULONG, Stream_Capacity(s)); + if (WTSVirtualChannelRead(priv->gfxredir_channel, 0, (PCHAR)Stream_Buffer(s), cap, + &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_SetLength(s, BytesReturned); + Stream_ResetPosition(s); + + while (Stream_GetPosition(s) < Stream_Length(s)) + { + if ((ret = gfxredir_server_receive_pdu(context, s))) + { + WLog_ERR(TAG, + "gfxredir_server_receive_pdu " + "failed with error %" PRIu32 "!", + ret); + return ret; + } + } + } + + return ret; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static DWORD WINAPI gfxredir_server_thread_func(LPVOID arg) +{ + GfxRedirServerContext* context = (GfxRedirServerContext*)arg; + WINPR_ASSERT(context); + + GfxRedirServerPrivate* priv = context->priv; + WINPR_ASSERT(priv); + + HANDLE events[8] = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + DWORD nCount = 0; + events[nCount++] = priv->stopEvent; + events[nCount++] = priv->channelEvent; + + while (TRUE) + { + const DWORD status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "", error); + break; + } + + /* Stop Event */ + if (status == WAIT_OBJECT_0) + break; + + if ((error = gfxredir_server_handle_messages(context))) + { + WLog_ERR(TAG, "gfxredir_server_handle_messages failed with error %" PRIu32 "", error); + break; + } + } + + ExitThread(error); + return error; +} + +/** + * Function description + * Create new stream for single gfxredir packet. The new stream length + * would be required data length + header. The header will be written + * to the stream before return. + * + * @param cmdId the command identifier + * @param length data length without header + * + * @return new stream + */ +static wStream* gfxredir_server_single_packet_new(UINT32 cmdId, size_t length) +{ + GFXREDIR_HEADER header = WINPR_C_ARRAY_INIT; + wStream* s = Stream_New(nullptr, GFXREDIR_HEADER_SIZE + length); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + goto error; + } + + header.cmdId = cmdId; + header.length = WINPR_ASSERTING_INT_CAST(UINT32, GFXREDIR_HEADER_SIZE + length); + + const UINT error = gfxredir_write_header(s, &header); + if (error) + { + WLog_ERR(TAG, "Failed to write header with error %" PRIu32 "!", error); + goto error; + } + + return s; +error: + Stream_Free(s, TRUE); + return nullptr; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT gfxredir_server_packet_send(GfxRedirServerContext* context, wStream* s) +{ + UINT ret = ERROR_INTERNAL_ERROR; + ULONG written = 0; + + WINPR_ASSERT(context); + + const ULONG cap = WINPR_ASSERTING_INT_CAST(ULONG, Stream_GetPosition(s)); + if (!WTSVirtualChannelWrite(context->priv->gfxredir_channel, (PCHAR)Stream_Buffer(s), cap, + &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + ret = ERROR_INTERNAL_ERROR; + goto out; + } + + if (written < Stream_GetPosition(s)) + { + WLog_WARN(TAG, "Unexpected bytes written: %" PRIu32 "/%" PRIuz "", written, + Stream_GetPosition(s)); + } + + ret = CHANNEL_RC_OK; +out: + Stream_Free(s, TRUE); + return ret; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT gfxredir_send_error(GfxRedirServerContext* context, const GFXREDIR_ERROR_PDU* error) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(error); + + wStream* s = gfxredir_server_single_packet_new(GFXREDIR_CMDID_ERROR, 4); + + if (!s) + { + WLog_ERR(TAG, "gfxredir_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT32(s, error->errorCode); + return gfxredir_server_packet_send(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT gfxredir_send_caps_confirm(GfxRedirServerContext* context, + const GFXREDIR_CAPS_CONFIRM_PDU* graphicsCapsConfirm) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(graphicsCapsConfirm); + + if (graphicsCapsConfirm->length < GFXREDIR_CAPS_HEADER_SIZE) + { + WLog_ERR(TAG, "length must be greater than header size, failed!"); + return ERROR_INVALID_DATA; + } + + wStream* s = + gfxredir_server_single_packet_new(GFXREDIR_CMDID_CAPS_CONFIRM, graphicsCapsConfirm->length); + + if (!s) + { + WLog_ERR(TAG, "gfxredir_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT32(s, GFXREDIR_CAPS_SIGNATURE); + Stream_Write_UINT32(s, graphicsCapsConfirm->version); + Stream_Write_UINT32(s, graphicsCapsConfirm->length); + if (graphicsCapsConfirm->length > GFXREDIR_CAPS_HEADER_SIZE) + Stream_Write(s, graphicsCapsConfirm->capsData, + graphicsCapsConfirm->length - GFXREDIR_CAPS_HEADER_SIZE); + const UINT ret = gfxredir_server_packet_send(context, s); + if (ret == CHANNEL_RC_OK) + context->confirmedCapsVersion = graphicsCapsConfirm->version; + return ret; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT gfxredir_send_open_pool(GfxRedirServerContext* context, + const GFXREDIR_OPEN_POOL_PDU* openPool) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(openPool); + + if (context->confirmedCapsVersion != GFXREDIR_CAPS_VERSION2_0) + { + WLog_ERR(TAG, "open_pool is called with invalid version!"); + return ERROR_INTERNAL_ERROR; + } + + if (openPool->sectionNameLength == 0 || openPool->sectionName == nullptr) + { + WLog_ERR(TAG, "section name must be provided!"); + return ERROR_INVALID_DATA; + } + + /* make sure null-terminate */ + if (openPool->sectionName[openPool->sectionNameLength - 1] != 0) + { + WLog_ERR(TAG, "section name must be terminated with nullptr!"); + return ERROR_INVALID_DATA; + } + + wStream* s = gfxredir_server_single_packet_new(GFXREDIR_CMDID_OPEN_POOL, + 20 + (openPool->sectionNameLength * 2)); + + if (!s) + { + WLog_ERR(TAG, "gfxredir_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT64(s, openPool->poolId); + Stream_Write_UINT64(s, openPool->poolSize); + Stream_Write_UINT32(s, openPool->sectionNameLength); + Stream_Write(s, openPool->sectionName, (2ULL * openPool->sectionNameLength)); + return gfxredir_server_packet_send(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT gfxredir_send_close_pool(GfxRedirServerContext* context, + const GFXREDIR_CLOSE_POOL_PDU* closePool) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(closePool); + + if (context->confirmedCapsVersion != GFXREDIR_CAPS_VERSION2_0) + { + WLog_ERR(TAG, "close_pool is called with invalid version!"); + return ERROR_INTERNAL_ERROR; + } + + wStream* s = gfxredir_server_single_packet_new(GFXREDIR_CMDID_CLOSE_POOL, 8); + + if (!s) + { + WLog_ERR(TAG, "gfxredir_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT64(s, closePool->poolId); + return gfxredir_server_packet_send(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT gfxredir_send_create_buffer(GfxRedirServerContext* context, + const GFXREDIR_CREATE_BUFFER_PDU* createBuffer) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(createBuffer); + + if (context->confirmedCapsVersion != GFXREDIR_CAPS_VERSION2_0) + { + WLog_ERR(TAG, "create_buffer is called with invalid version!"); + return ERROR_INTERNAL_ERROR; + } + + wStream* s = gfxredir_server_single_packet_new(GFXREDIR_CMDID_CREATE_BUFFER, 40); + + if (!s) + { + WLog_ERR(TAG, "gfxredir_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT64(s, createBuffer->poolId); + Stream_Write_UINT64(s, createBuffer->bufferId); + Stream_Write_UINT64(s, createBuffer->offset); + Stream_Write_UINT32(s, createBuffer->stride); + Stream_Write_UINT32(s, createBuffer->width); + Stream_Write_UINT32(s, createBuffer->height); + Stream_Write_UINT32(s, createBuffer->format); + return gfxredir_server_packet_send(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT gfxredir_send_destroy_buffer(GfxRedirServerContext* context, + const GFXREDIR_DESTROY_BUFFER_PDU* destroyBuffer) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(destroyBuffer); + + if (context->confirmedCapsVersion != GFXREDIR_CAPS_VERSION2_0) + { + WLog_ERR(TAG, "destroy_buffer is called with invalid version!"); + return ERROR_INTERNAL_ERROR; + } + + wStream* s = gfxredir_server_single_packet_new(GFXREDIR_CMDID_DESTROY_BUFFER, 8); + + if (!s) + { + WLog_ERR(TAG, "gfxredir_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT64(s, destroyBuffer->bufferId); + return gfxredir_server_packet_send(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT gfxredir_send_present_buffer(GfxRedirServerContext* context, + const GFXREDIR_PRESENT_BUFFER_PDU* presentBuffer) +{ + RECTANGLE_32 dummyRect = { 0, 0, 0, 0 }; + + WINPR_ASSERT(context); + WINPR_ASSERT(presentBuffer); + + if (context->confirmedCapsVersion != GFXREDIR_CAPS_VERSION2_0) + { + WLog_ERR(TAG, "present_buffer is called with invalid version!"); + return ERROR_INTERNAL_ERROR; + } + + if (presentBuffer->numOpaqueRects > GFXREDIR_MAX_OPAQUE_RECTS) + { + WLog_ERR(TAG, "numOpaqueRects is larger than its limit!"); + return ERROR_INVALID_DATA; + } + + const size_t length = + 64ULL + ((presentBuffer->numOpaqueRects ? presentBuffer->numOpaqueRects : 1) * + sizeof(RECTANGLE_32)); + + wStream* s = gfxredir_server_single_packet_new(GFXREDIR_CMDID_PRESENT_BUFFER, length); + + if (!s) + { + WLog_ERR(TAG, "gfxredir_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT64(s, presentBuffer->timestamp); + Stream_Write_UINT64(s, presentBuffer->presentId); + Stream_Write_UINT64(s, presentBuffer->windowId); + Stream_Write_UINT64(s, presentBuffer->bufferId); + Stream_Write_UINT32(s, presentBuffer->orientation); + Stream_Write_UINT32(s, presentBuffer->targetWidth); + Stream_Write_UINT32(s, presentBuffer->targetHeight); + Stream_Write_UINT32(s, presentBuffer->dirtyRect.left); + Stream_Write_UINT32(s, presentBuffer->dirtyRect.top); + Stream_Write_UINT32(s, presentBuffer->dirtyRect.width); + Stream_Write_UINT32(s, presentBuffer->dirtyRect.height); + Stream_Write_UINT32(s, presentBuffer->numOpaqueRects); + if (presentBuffer->numOpaqueRects) + Stream_Write(s, presentBuffer->opaqueRects, + (presentBuffer->numOpaqueRects * sizeof(RECTANGLE_32))); + else + Stream_Write(s, &dummyRect, sizeof(RECTANGLE_32)); + return gfxredir_server_packet_send(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT gfxredir_server_open(GfxRedirServerContext* context) +{ + UINT rc = ERROR_INTERNAL_ERROR; + WINPR_ASSERT(context); + + GfxRedirServerPrivate* priv = context->priv; + WINPR_ASSERT(priv); + + DWORD BytesReturned = 0; + PULONG pSessionId = nullptr; + void* buffer = nullptr; + priv->SessionId = WTS_CURRENT_SESSION; + + if (WTSQuerySessionInformationA(context->vcm, WTS_CURRENT_SESSION, WTSSessionId, + (LPSTR*)&pSessionId, &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSQuerySessionInformationA failed!"); + rc = ERROR_INTERNAL_ERROR; + goto out_close; + } + + priv->SessionId = (DWORD)*pSessionId; + WTSFreeMemory(pSessionId); + priv->gfxredir_channel = WTSVirtualChannelOpenEx(priv->SessionId, GFXREDIR_DVC_CHANNEL_NAME, + WTS_CHANNEL_OPTION_DYNAMIC); + + if (!priv->gfxredir_channel) + { + WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed!"); + rc = GetLastError(); + goto out_close; + } + + /* Query for channel event handle */ + if (!WTSVirtualChannelQuery(priv->gfxredir_channel, WTSVirtualEventHandle, &buffer, + &BytesReturned) || + (BytesReturned != sizeof(HANDLE))) + { + WLog_ERR(TAG, + "WTSVirtualChannelQuery failed " + "or invalid returned size(%" PRIu32 ")", + BytesReturned); + + if (buffer) + WTSFreeMemory(buffer); + + rc = ERROR_INTERNAL_ERROR; + goto out_close; + } + + priv->channelEvent = *(HANDLE*)buffer; + WTSFreeMemory(buffer); + + if (priv->thread == nullptr) + { + if (!(priv->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + goto out_close; + } + + if (!(priv->thread = CreateThread(nullptr, 0, gfxredir_server_thread_func, (void*)context, + 0, nullptr))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + (void)CloseHandle(priv->stopEvent); + priv->stopEvent = nullptr; + goto out_close; + } + } + + return CHANNEL_RC_OK; +out_close: + WTSVirtualChannelClose(priv->gfxredir_channel); + priv->gfxredir_channel = nullptr; + priv->channelEvent = nullptr; + return rc; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT gfxredir_server_close(GfxRedirServerContext* context) +{ + UINT error = CHANNEL_RC_OK; + WINPR_ASSERT(context); + + GfxRedirServerPrivate* priv = context->priv; + WINPR_ASSERT(priv); + + if (priv->thread) + { + (void)SetEvent(priv->stopEvent); + + if (WaitForSingleObject(priv->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + return error; + } + + (void)CloseHandle(priv->thread); + (void)CloseHandle(priv->stopEvent); + priv->thread = nullptr; + priv->stopEvent = nullptr; + } + + if (priv->gfxredir_channel) + { + WTSVirtualChannelClose(priv->gfxredir_channel); + priv->gfxredir_channel = nullptr; + } + + return error; +} + +GfxRedirServerContext* gfxredir_server_context_new(HANDLE vcm) +{ + GfxRedirServerContext* context = + (GfxRedirServerContext*)calloc(1, sizeof(GfxRedirServerContext)); + + if (!context) + { + WLog_ERR(TAG, "gfxredir_server_context_new(): calloc GfxRedirServerContext failed!"); + return nullptr; + } + + GfxRedirServerPrivate* priv = context->priv = + (GfxRedirServerPrivate*)calloc(1, sizeof(GfxRedirServerPrivate)); + + if (!context->priv) + { + WLog_ERR(TAG, "gfxredir_server_context_new(): calloc GfxRedirServerPrivate failed!"); + goto fail; + } + + priv->input_stream = Stream_New(nullptr, 4); + + if (!priv->input_stream) + { + WLog_ERR(TAG, "Stream_New failed!"); + goto fail; + } + + context->vcm = vcm; + context->Open = gfxredir_server_open; + context->Close = gfxredir_server_close; + context->Error = gfxredir_send_error; + context->GraphicsRedirectionCapsConfirm = gfxredir_send_caps_confirm; + context->OpenPool = gfxredir_send_open_pool; + context->ClosePool = gfxredir_send_close_pool; + context->CreateBuffer = gfxredir_send_create_buffer; + context->DestroyBuffer = gfxredir_send_destroy_buffer; + context->PresentBuffer = gfxredir_send_present_buffer; + context->confirmedCapsVersion = GFXREDIR_CAPS_VERSION1; + priv->isReady = FALSE; + return context; +fail: + gfxredir_server_context_free(context); + return nullptr; +} + +void gfxredir_server_context_free(GfxRedirServerContext* context) +{ + if (!context) + return; + + gfxredir_server_close(context); + + if (context->priv) + { + Stream_Free(context->priv->input_stream, TRUE); + free(context->priv); + } + + free(context); +} diff --git a/third_party/FreeRDP/channels/gfxredir/server/gfxredir_main.h b/third_party/FreeRDP/channels/gfxredir/server/gfxredir_main.h new file mode 100644 index 0000000..45424fd --- /dev/null +++ b/third_party/FreeRDP/channels/gfxredir/server/gfxredir_main.h @@ -0,0 +1,37 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RDPXXXX Remote App Graphics Redirection Virtual Channel Extension + * + * Copyright 2020 Microsoft + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_GFXREDIR_SERVER_MAIN_H +#define FREERDP_CHANNEL_GFXREDIR_SERVER_MAIN_H + +#include + +struct s_gfxredir_server_private +{ + BOOL isReady; + wStream* input_stream; + HANDLE channelEvent; + HANDLE thread; + HANDLE stopEvent; + DWORD SessionId; + + void* gfxredir_channel; +}; + +#endif /* FREERDP_CHANNEL_GFXREDIR_SERVER_MAIN_H */ diff --git a/third_party/FreeRDP/channels/location/CMakeLists.txt b/third_party/FreeRDP/channels/location/CMakeLists.txt new file mode 100644 index 0000000..bdeceaf --- /dev/null +++ b/third_party/FreeRDP/channels/location/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2023 Pascal Nowack +# +# 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. + +define_channel("location") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/third_party/FreeRDP/channels/location/ChannelOptions.cmake b/third_party/FreeRDP/channels/location/ChannelOptions.cmake new file mode 100644 index 0000000..465173a --- /dev/null +++ b/third_party/FreeRDP/channels/location/ChannelOptions.cmake @@ -0,0 +1,20 @@ +set(OPTION_DEFAULT ON) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options( + NAME + "location" + TYPE + "dynamic" + DESCRIPTION + "Location Virtual Channel Extension" + SPECIFICATIONS + "[MS-RDPEL]" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/location/client/CMakeLists.txt b/third_party/FreeRDP/channels/location/client/CMakeLists.txt new file mode 100644 index 0000000..57fbd5a --- /dev/null +++ b/third_party/FreeRDP/channels/location/client/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2024 Armin Novak +# 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. + +define_channel_client("location") + +set(${MODULE_PREFIX}_SRCS location_main.c) + +set(${MODULE_PREFIX}_LIBS winpr) +include_directories(..) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") diff --git a/third_party/FreeRDP/channels/location/client/location_main.c b/third_party/FreeRDP/channels/location/client/location_main.c new file mode 100644 index 0000000..1cd01b2 --- /dev/null +++ b/third_party/FreeRDP/channels/location/client/location_main.c @@ -0,0 +1,492 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Location Virtual Channel Extension + * + * Copyright 2024 Armin Novak + * 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. + */ + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define TAG CHANNELS_TAG("location.client") + +/* implement [MS-RDPEL] + * https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpel/4397a0af-c821-4b75-9068-476fb579c327 + */ +typedef struct +{ + GENERIC_DYNVC_PLUGIN baseDynPlugin; + LocationClientContext context; +} LOCATION_PLUGIN; + +typedef struct +{ + GENERIC_CHANNEL_CALLBACK baseCb; + UINT32 serverVersion; + UINT32 clientVersion; + UINT32 serverFlags; + UINT32 clientFlags; +} LOCATION_CALLBACK; + +static BOOL location_read_header(wLog* log, wStream* s, UINT16* ppduType, UINT32* ppduLength) +{ + WINPR_ASSERT(log); + WINPR_ASSERT(s); + WINPR_ASSERT(ppduType); + WINPR_ASSERT(ppduLength); + + if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 6)) + return FALSE; + Stream_Read_UINT16(s, *ppduType); + Stream_Read_UINT32(s, *ppduLength); + if (*ppduLength < 6) + { + WLog_Print(log, WLOG_ERROR, + "RDPLOCATION_HEADER::pduLengh=%" PRIu16 " < sizeof(RDPLOCATION_HEADER)[6]", + *ppduLength); + return FALSE; + } + return Stream_CheckAndLogRequiredLengthWLog(log, s, *ppduLength - 6ull); +} + +static BOOL location_write_header(wStream* s, UINT16 pduType, UINT32 pduLength) +{ + if (!Stream_EnsureRemainingCapacity(s, 6)) + return FALSE; + Stream_Write_UINT16(s, pduType); + Stream_Write_UINT32(s, pduLength + 6); + return Stream_EnsureRemainingCapacity(s, pduLength); +} + +static BOOL location_read_server_ready_pdu(LOCATION_CALLBACK* callback, wStream* s, UINT32 pduSize) +{ + if (pduSize < 6 + 4) + return FALSE; // Short message + + Stream_Read_UINT32(s, callback->serverVersion); + if (pduSize >= 6 + 4 + 4) + Stream_Read_UINT32(s, callback->serverFlags); + return TRUE; +} + +static UINT location_channel_send(IWTSVirtualChannel* channel, wStream* s) +{ + const size_t len = Stream_GetPosition(s); + if (len > UINT32_MAX) + return ERROR_INTERNAL_ERROR; + + Stream_SetPosition(s, 2); + Stream_Write_UINT32(s, (UINT32)len); + + WINPR_ASSERT(channel); + WINPR_ASSERT(channel->Write); + return channel->Write(channel, (UINT32)len, Stream_Buffer(s), nullptr); +} + +static UINT location_send_client_ready_pdu(const LOCATION_CALLBACK* callback) +{ + wStream sbuffer = WINPR_C_ARRAY_INIT; + BYTE buffer[32] = WINPR_C_ARRAY_INIT; + wStream* s = Stream_StaticInit(&sbuffer, buffer, sizeof(buffer)); + WINPR_ASSERT(s); + + if (!location_write_header(s, PDUTYPE_CLIENT_READY, 8)) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(s, callback->clientVersion); + Stream_Write_UINT32(s, callback->clientFlags); + return location_channel_send(callback->baseCb.channel, s); +} + +static const char* location_version_str(UINT32 version, char* buffer, size_t size) +{ + const char* str = nullptr; + switch (version) + { + case RDPLOCATION_PROTOCOL_VERSION_100: + str = "RDPLOCATION_PROTOCOL_VERSION_100"; + break; + case RDPLOCATION_PROTOCOL_VERSION_200: + str = "RDPLOCATION_PROTOCOL_VERSION_200"; + break; + default: + str = "RDPLOCATION_PROTOCOL_VERSION_UNKNOWN"; + break; + } + + (void)_snprintf(buffer, size, "%s [0x%08" PRIx32 "]", str, version); + return buffer; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT location_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data) +{ + LOCATION_CALLBACK* callback = (LOCATION_CALLBACK*)pChannelCallback; + + WINPR_ASSERT(callback); + + LOCATION_PLUGIN* plugin = (LOCATION_PLUGIN*)callback->baseCb.plugin; + WINPR_ASSERT(plugin); + + UINT16 pduType = 0; + UINT32 pduLength = 0; + if (!location_read_header(plugin->baseDynPlugin.log, data, &pduType, &pduLength)) + return ERROR_INVALID_DATA; + + switch (pduType) + { + case PDUTYPE_SERVER_READY: + if (!location_read_server_ready_pdu(callback, data, pduLength)) + return ERROR_INVALID_DATA; + + switch (callback->serverVersion) + { + case RDPLOCATION_PROTOCOL_VERSION_200: + callback->clientVersion = RDPLOCATION_PROTOCOL_VERSION_200; + break; + case RDPLOCATION_PROTOCOL_VERSION_100: + callback->clientVersion = RDPLOCATION_PROTOCOL_VERSION_100; + break; + default: + callback->clientVersion = RDPLOCATION_PROTOCOL_VERSION_100; + if (callback->serverVersion > RDPLOCATION_PROTOCOL_VERSION_200) + callback->clientVersion = RDPLOCATION_PROTOCOL_VERSION_200; + break; + } + + { + char cbuffer[64] = WINPR_C_ARRAY_INIT; + char sbuffer[64] = WINPR_C_ARRAY_INIT; + WLog_Print(plugin->baseDynPlugin.log, WLOG_DEBUG, + "Server version %s, client version %s", + location_version_str(callback->serverVersion, sbuffer, sizeof(sbuffer)), + location_version_str(callback->clientVersion, cbuffer, sizeof(cbuffer))); + } + + if (!plugin->context.LocationStart) + { + WLog_Print(plugin->baseDynPlugin.log, WLOG_WARN, + "LocationStart=nullptr, no location data will be sent"); + return CHANNEL_RC_OK; + } + + { + const UINT res = + plugin->context.LocationStart(&plugin->context, callback->clientVersion, 0); + if (res != CHANNEL_RC_OK) + return res; + } + return location_send_client_ready_pdu(callback); + default: + WLog_WARN(TAG, "invalid pduType=%" PRIu16, pduType); + return ERROR_INVALID_DATA; + } +} + +static UINT location_send_base_location3d(IWTSVirtualChannel* channel, + const RDPLOCATION_BASE_LOCATION3D_PDU* pdu) +{ + wStream sbuffer = WINPR_C_ARRAY_INIT; + BYTE buffer[32] = WINPR_C_ARRAY_INIT; + wStream* s = Stream_StaticInit(&sbuffer, buffer, sizeof(buffer)); + WINPR_ASSERT(s); + WINPR_ASSERT(channel); + WINPR_ASSERT(pdu); + + if (pdu->source) + WLog_DBG(TAG, + "latitude=%lf, longitude=%lf, altitude=%" PRId32 + ", speed=%lf, heading=%lf, haccuracy=%lf, source=%" PRIu8, + pdu->latitude, pdu->longitude, pdu->altitude, pdu->speed ? *pdu->speed : FP_NAN, + pdu->heading ? *pdu->heading : FP_NAN, + pdu->horizontalAccuracy ? *pdu->horizontalAccuracy : FP_NAN, *pdu->source); + else + WLog_DBG(TAG, "latitude=%lf, longitude=%lf, altitude=%" PRId32, pdu->latitude, + pdu->longitude, pdu->altitude); + + if (!location_write_header(s, PDUTYPE_BASE_LOCATION3D, pdu->source ? 25 : 12)) + return ERROR_OUTOFMEMORY; + + if (!freerdp_write_four_byte_float(s, pdu->latitude) || + !freerdp_write_four_byte_float(s, pdu->longitude) || + !freerdp_write_four_byte_signed_integer(s, pdu->altitude)) + return ERROR_INTERNAL_ERROR; + + if (pdu->source) + { + if (!freerdp_write_four_byte_float(s, *pdu->speed) || + !freerdp_write_four_byte_float(s, *pdu->heading) || + !freerdp_write_four_byte_float(s, *pdu->horizontalAccuracy)) + return ERROR_INTERNAL_ERROR; + + Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(UINT8, *pdu->source)); + } + + return location_channel_send(channel, s); +} + +static UINT location_send_location2d_delta(IWTSVirtualChannel* channel, + const RDPLOCATION_LOCATION2D_DELTA_PDU* pdu) +{ + wStream sbuffer = WINPR_C_ARRAY_INIT; + BYTE buffer[32] = WINPR_C_ARRAY_INIT; + wStream* s = Stream_StaticInit(&sbuffer, buffer, sizeof(buffer)); + WINPR_ASSERT(s); + + WINPR_ASSERT(channel); + WINPR_ASSERT(pdu); + + const BOOL ext = pdu->speedDelta && pdu->headingDelta; + + if (ext) + WLog_DBG(TAG, "latitude=%lf, longitude=%lf, speed=%lf, heading=%lf", pdu->latitudeDelta, + pdu->longitudeDelta, pdu->speedDelta ? *pdu->speedDelta : FP_NAN, + pdu->headingDelta ? *pdu->headingDelta : FP_NAN); + else + WLog_DBG(TAG, "latitude=%lf, longitude=%lf", pdu->latitudeDelta, pdu->longitudeDelta); + + if (!location_write_header(s, PDUTYPE_LOCATION2D_DELTA, ext ? 16 : 8)) + return ERROR_OUTOFMEMORY; + + if (!freerdp_write_four_byte_float(s, pdu->latitudeDelta) || + !freerdp_write_four_byte_float(s, pdu->longitudeDelta)) + return ERROR_INTERNAL_ERROR; + + if (ext) + { + if (!freerdp_write_four_byte_float(s, *pdu->speedDelta) || + !freerdp_write_four_byte_float(s, *pdu->headingDelta)) + return ERROR_INTERNAL_ERROR; + } + + return location_channel_send(channel, s); +} + +static UINT location_send_location3d_delta(IWTSVirtualChannel* channel, + const RDPLOCATION_LOCATION3D_DELTA_PDU* pdu) +{ + wStream sbuffer = WINPR_C_ARRAY_INIT; + BYTE buffer[32] = WINPR_C_ARRAY_INIT; + wStream* s = Stream_StaticInit(&sbuffer, buffer, sizeof(buffer)); + WINPR_ASSERT(s); + + WINPR_ASSERT(channel); + WINPR_ASSERT(pdu); + + const BOOL ext = pdu->speedDelta && pdu->headingDelta; + + if (ext) + WLog_DBG(TAG, "latitude=%lf, longitude=%lf, altitude=%" PRId32 ", speed=%lf, heading=%lf", + pdu->latitudeDelta, pdu->longitudeDelta, pdu->altitudeDelta, + pdu->speedDelta ? *pdu->speedDelta : FP_NAN, + pdu->headingDelta ? *pdu->headingDelta : FP_NAN); + else + WLog_DBG(TAG, "latitude=%lf, longitude=%lf, altitude=%" PRId32, pdu->latitudeDelta, + pdu->longitudeDelta, pdu->altitudeDelta); + + if (!location_write_header(s, PDUTYPE_LOCATION3D_DELTA, ext ? 20 : 12)) + return ERROR_OUTOFMEMORY; + + if (!freerdp_write_four_byte_float(s, pdu->latitudeDelta) || + !freerdp_write_four_byte_float(s, pdu->longitudeDelta) || + !freerdp_write_four_byte_signed_integer(s, pdu->altitudeDelta)) + return ERROR_INTERNAL_ERROR; + + if (ext) + { + if (!freerdp_write_four_byte_float(s, *pdu->speedDelta) || + !freerdp_write_four_byte_float(s, *pdu->headingDelta)) + return ERROR_INTERNAL_ERROR; + } + + return location_channel_send(channel, s); +} + +static UINT location_send(LocationClientContext* context, LOCATION_PDUTYPE type, size_t count, ...) +{ + WINPR_ASSERT(context); + + LOCATION_PLUGIN* loc = context->handle; + WINPR_ASSERT(loc); + + GENERIC_LISTENER_CALLBACK* cb = loc->baseDynPlugin.listener_callback; + WINPR_ASSERT(cb); + + IWTSVirtualChannel* channel = cb->channel; + WINPR_ASSERT(channel); + + const LOCATION_CALLBACK* callback = + (const LOCATION_CALLBACK*)loc->baseDynPlugin.channel_callbacks; + WINPR_ASSERT(callback); + + UINT32 res = ERROR_INTERNAL_ERROR; + va_list ap = WINPR_C_ARRAY_INIT; + va_start(ap, count); + switch (type) + { + case PDUTYPE_BASE_LOCATION3D: + if ((count != 3) && (count != 7)) + res = ERROR_INVALID_PARAMETER; + else + { + LOCATIONSOURCE source = LOCATIONSOURCE_IP; + double speed = FP_NAN; + double heading = FP_NAN; + double horizontalAccuracy = FP_NAN; + RDPLOCATION_BASE_LOCATION3D_PDU pdu = { .latitude = va_arg(ap, double), + .longitude = va_arg(ap, double), + .altitude = va_arg(ap, INT32), + .speed = nullptr, + .heading = nullptr, + .horizontalAccuracy = nullptr, + .source = nullptr }; + + if ((count > 3) && (callback->clientVersion >= RDPLOCATION_PROTOCOL_VERSION_200)) + { + speed = va_arg(ap, double); + heading = va_arg(ap, double); + horizontalAccuracy = va_arg(ap, double); + source = WINPR_ASSERTING_INT_CAST(LOCATIONSOURCE, va_arg(ap, int)); + pdu.speed = &speed; + pdu.heading = &heading; + pdu.horizontalAccuracy = &horizontalAccuracy; + pdu.source = &source; + } + res = location_send_base_location3d(channel, &pdu); + } + break; + case PDUTYPE_LOCATION2D_DELTA: + if ((count != 2) && (count != 4)) + res = ERROR_INVALID_PARAMETER; + else + { + RDPLOCATION_LOCATION2D_DELTA_PDU pdu = { .latitudeDelta = va_arg(ap, double), + .longitudeDelta = va_arg(ap, double), + .speedDelta = nullptr, + .headingDelta = nullptr }; + + double speedDelta = FP_NAN; + double headingDelta = FP_NAN; + if ((count > 2) && (callback->clientVersion >= RDPLOCATION_PROTOCOL_VERSION_200)) + { + speedDelta = va_arg(ap, double); + headingDelta = va_arg(ap, double); + pdu.speedDelta = &speedDelta; + pdu.headingDelta = &headingDelta; + } + res = location_send_location2d_delta(channel, &pdu); + } + break; + case PDUTYPE_LOCATION3D_DELTA: + if ((count != 3) && (count != 5)) + res = ERROR_INVALID_PARAMETER; + else + { + double speedDelta = FP_NAN; + double headingDelta = FP_NAN; + + RDPLOCATION_LOCATION3D_DELTA_PDU pdu = { .latitudeDelta = va_arg(ap, double), + .longitudeDelta = va_arg(ap, double), + .altitudeDelta = va_arg(ap, INT32), + .speedDelta = nullptr, + .headingDelta = nullptr }; + if ((count > 3) && (callback->clientVersion >= RDPLOCATION_PROTOCOL_VERSION_200)) + { + speedDelta = va_arg(ap, double); + headingDelta = va_arg(ap, double); + pdu.speedDelta = &speedDelta; + pdu.headingDelta = &headingDelta; + } + res = location_send_location3d_delta(channel, &pdu); + } + break; + default: + res = ERROR_INVALID_PARAMETER; + break; + } + va_end(ap); + return res; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT location_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + UINT res = CHANNEL_RC_OK; + GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + + if (callback) + { + LOCATION_PLUGIN* plugin = (LOCATION_PLUGIN*)callback->plugin; + WINPR_ASSERT(plugin); + + res = IFCALLRESULT(CHANNEL_RC_OK, plugin->context.LocationStop, &plugin->context); + } + free(callback); + + return res; +} + +static UINT location_init(GENERIC_DYNVC_PLUGIN* plugin, WINPR_ATTR_UNUSED rdpContext* context, + WINPR_ATTR_UNUSED rdpSettings* settings) +{ + LOCATION_PLUGIN* loc = (LOCATION_PLUGIN*)plugin; + + WINPR_ASSERT(loc); + + loc->context.LocationSend = location_send; + loc->context.handle = loc; + plugin->iface.pInterface = &loc->context; + return CHANNEL_RC_OK; +} + +static const IWTSVirtualChannelCallback location_callbacks = { location_on_data_received, + nullptr, /* Open */ + location_on_close, nullptr }; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE location_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)) +{ + return freerdp_generic_DVCPluginEntry(pEntryPoints, TAG, LOCATION_DVC_CHANNEL_NAME, + sizeof(LOCATION_PLUGIN), sizeof(LOCATION_CALLBACK), + &location_callbacks, location_init, nullptr); +} diff --git a/third_party/FreeRDP/channels/location/server/CMakeLists.txt b/third_party/FreeRDP/channels/location/server/CMakeLists.txt new file mode 100644 index 0000000..7870016 --- /dev/null +++ b/third_party/FreeRDP/channels/location/server/CMakeLists.txt @@ -0,0 +1,24 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2023 Pascal Nowack +# +# 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. + +define_channel_server("location") + +set(${MODULE_PREFIX}_SRCS location_main.c) + +set(${MODULE_PREFIX}_LIBS freerdp) + +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry") diff --git a/third_party/FreeRDP/channels/location/server/location_main.c b/third_party/FreeRDP/channels/location/server/location_main.c new file mode 100644 index 0000000..fd9429d --- /dev/null +++ b/third_party/FreeRDP/channels/location/server/location_main.c @@ -0,0 +1,689 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Location Virtual Channel Extension + * + * Copyright 2023 Pascal Nowack + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include +#include +#include +#include + +#define TAG CHANNELS_TAG("location.server") + +typedef enum +{ + LOCATION_INITIAL, + LOCATION_OPENED, +} eLocationChannelState; + +typedef struct +{ + LocationServerContext context; + + HANDLE stopEvent; + + HANDLE thread; + void* location_channel; + + DWORD SessionId; + + BOOL isOpened; + BOOL externalThread; + + /* Channel state */ + eLocationChannelState state; + + wStream* buffer; +} location_server; + +static UINT location_server_initialize(LocationServerContext* context, BOOL externalThread) +{ + UINT error = CHANNEL_RC_OK; + location_server* location = (location_server*)context; + + WINPR_ASSERT(location); + + if (location->isOpened) + { + WLog_WARN(TAG, "Application error: Location channel already initialized, " + "calling in this state is not possible!"); + return ERROR_INVALID_STATE; + } + + location->externalThread = externalThread; + + return error; +} + +static UINT location_server_open_channel(location_server* location) +{ + LocationServerContext* context = &location->context; + DWORD Error = ERROR_SUCCESS; + HANDLE hEvent = nullptr; + DWORD BytesReturned = 0; + PULONG pSessionId = nullptr; + UINT32 channelId = 0; + BOOL status = TRUE; + + WINPR_ASSERT(location); + + if (WTSQuerySessionInformationA(location->context.vcm, WTS_CURRENT_SESSION, WTSSessionId, + (LPSTR*)&pSessionId, &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSQuerySessionInformationA failed!"); + return ERROR_INTERNAL_ERROR; + } + + location->SessionId = (DWORD)*pSessionId; + WTSFreeMemory(pSessionId); + hEvent = WTSVirtualChannelManagerGetEventHandle(location->context.vcm); + + if (WaitForSingleObject(hEvent, 1000) == WAIT_FAILED) + { + Error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", Error); + return Error; + } + + location->location_channel = WTSVirtualChannelOpenEx( + location->SessionId, LOCATION_DVC_CHANNEL_NAME, WTS_CHANNEL_OPTION_DYNAMIC); + if (!location->location_channel) + { + Error = GetLastError(); + WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed with error %" PRIu32 "!", Error); + return Error; + } + + channelId = WTSChannelGetIdByHandle(location->location_channel); + + IFCALLRET(context->ChannelIdAssigned, status, context, channelId); + if (!status) + { + WLog_ERR(TAG, "context->ChannelIdAssigned failed!"); + return ERROR_INTERNAL_ERROR; + } + + return Error; +} + +static UINT location_server_recv_client_ready(LocationServerContext* context, wStream* s, + const RDPLOCATION_HEADER* header) +{ + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(s); + WINPR_ASSERT(header); + + RDPLOCATION_CLIENT_READY_PDU pdu = { .header = *header, + .protocolVersion = RDPLOCATION_PROTOCOL_VERSION_100, + .flags = 0 }; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_NO_DATA; + + { + const UINT32 version = Stream_Get_UINT32(s); + switch (version) + { + case RDPLOCATION_PROTOCOL_VERSION_100: + pdu.protocolVersion = RDPLOCATION_PROTOCOL_VERSION_100; + break; + case RDPLOCATION_PROTOCOL_VERSION_200: + pdu.protocolVersion = RDPLOCATION_PROTOCOL_VERSION_200; + break; + default: + pdu.protocolVersion = RDPLOCATION_PROTOCOL_VERSION_200; + WLog_WARN(TAG, + "Received unsupported protocol version %" PRIu32 + ", setting to highest supported %u", + version, pdu.protocolVersion); + break; + } + } + + if (Stream_GetRemainingLength(s) >= 4) + Stream_Read_UINT32(s, pdu.flags); + + IFCALLRET(context->ClientReady, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->ClientReady failed with error %" PRIu32 "", error); + + return error; +} + +static UINT location_server_recv_base_location3d(LocationServerContext* context, wStream* s, + const RDPLOCATION_HEADER* header) +{ + UINT error = CHANNEL_RC_OK; + double speed = 0.0; + double heading = 0.0; + double horizontalAccuracy = 0.0; + LOCATIONSOURCE source = LOCATIONSOURCE_IP; + + WINPR_ASSERT(context); + WINPR_ASSERT(s); + WINPR_ASSERT(header); + + RDPLOCATION_BASE_LOCATION3D_PDU pdu = { .header = *header, + .latitude = FP_NAN, + .longitude = FP_NAN, + .altitude = 0, + .speed = nullptr, + .heading = nullptr, + .horizontalAccuracy = nullptr, + .source = nullptr }; + + if (!freerdp_read_four_byte_float(s, &pdu.latitude) || + !freerdp_read_four_byte_float(s, &pdu.longitude) || + !freerdp_read_four_byte_signed_integer(s, &pdu.altitude)) + return FALSE; + + if (Stream_GetRemainingLength(s) >= 1) + { + if (!freerdp_read_four_byte_float(s, &speed) || + !freerdp_read_four_byte_float(s, &heading) || + !freerdp_read_four_byte_float(s, &horizontalAccuracy) || + !Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return FALSE; + + { + const UINT8 src = Stream_Get_UINT8(s); + switch (src) + { + case LOCATIONSOURCE_IP: + case LOCATIONSOURCE_WIFI: + case LOCATIONSOURCE_CELL: + case LOCATIONSOURCE_GNSS: + break; + default: + WLog_ERR(TAG, "Invalid LOCATIONSOURCE value %" PRIu8 "", src); + return FALSE; + } + source = (LOCATIONSOURCE)src; + } + + pdu.speed = &speed; + pdu.heading = &heading; + pdu.horizontalAccuracy = &horizontalAccuracy; + pdu.source = &source; + } + + IFCALLRET(context->BaseLocation3D, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->BaseLocation3D failed with error %" PRIu32 "", error); + + return error; +} + +static UINT location_server_recv_location2d_delta(LocationServerContext* context, wStream* s, + const RDPLOCATION_HEADER* header) +{ + UINT error = CHANNEL_RC_OK; + double speedDelta = 0.0; + double headingDelta = 0.0; + + WINPR_ASSERT(context); + WINPR_ASSERT(s); + WINPR_ASSERT(header); + + RDPLOCATION_LOCATION2D_DELTA_PDU pdu = { .header = *header, + .latitudeDelta = FP_NAN, + .longitudeDelta = FP_NAN, + .speedDelta = nullptr, + .headingDelta = nullptr }; + + if (!freerdp_read_four_byte_float(s, &pdu.latitudeDelta) || + !freerdp_read_four_byte_float(s, &pdu.longitudeDelta)) + return FALSE; + + if (Stream_GetRemainingLength(s) >= 1) + { + if (!freerdp_read_four_byte_float(s, &speedDelta) || + !freerdp_read_four_byte_float(s, &headingDelta)) + return FALSE; + + pdu.speedDelta = &speedDelta; + pdu.headingDelta = &headingDelta; + } + + IFCALLRET(context->Location2DDelta, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->Location2DDelta failed with error %" PRIu32 "", error); + + return error; +} + +static UINT location_server_recv_location3d_delta(LocationServerContext* context, wStream* s, + const RDPLOCATION_HEADER* header) +{ + UINT error = CHANNEL_RC_OK; + double speedDelta = 0.0; + double headingDelta = 0.0; + + WINPR_ASSERT(context); + WINPR_ASSERT(s); + WINPR_ASSERT(header); + + RDPLOCATION_LOCATION3D_DELTA_PDU pdu = { .header = *header, + .latitudeDelta = FP_NAN, + .longitudeDelta = FP_NAN, + .speedDelta = nullptr, + .headingDelta = nullptr }; + + if (!freerdp_read_four_byte_float(s, &pdu.latitudeDelta) || + !freerdp_read_four_byte_float(s, &pdu.longitudeDelta) || + !freerdp_read_four_byte_signed_integer(s, &pdu.altitudeDelta)) + return FALSE; + + if (Stream_GetRemainingLength(s) >= 1) + { + if (!freerdp_read_four_byte_float(s, &speedDelta) || + !freerdp_read_four_byte_float(s, &headingDelta)) + return FALSE; + + pdu.speedDelta = &speedDelta; + pdu.headingDelta = &headingDelta; + } + + IFCALLRET(context->Location3DDelta, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->Location3DDelta failed with error %" PRIu32 "", error); + + return error; +} + +static UINT location_process_message(location_server* location) +{ + UINT error = ERROR_INTERNAL_ERROR; + ULONG BytesReturned = 0; + + WINPR_ASSERT(location); + WINPR_ASSERT(location->location_channel); + + wStream* s = location->buffer; + WINPR_ASSERT(s); + + Stream_ResetPosition(s); + const BOOL rc = + WTSVirtualChannelRead(location->location_channel, 0, nullptr, 0, &BytesReturned); + if (!rc) + goto out; + + if (BytesReturned < 1) + { + error = CHANNEL_RC_OK; + goto out; + } + + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + if (WTSVirtualChannelRead(location->location_channel, 0, Stream_BufferAs(s, char), + (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + goto out; + } + + Stream_SetLength(s, BytesReturned); + if (!Stream_CheckAndLogRequiredLength(TAG, s, LOCATION_HEADER_SIZE)) + return ERROR_NO_DATA; + + { + const UINT16 pduType = Stream_Get_UINT16(s); + const RDPLOCATION_HEADER header = { .pduType = (LOCATION_PDUTYPE)pduType, + .pduLength = Stream_Get_UINT32(s) }; + + switch (pduType) + { + case PDUTYPE_CLIENT_READY: + error = location_server_recv_client_ready(&location->context, s, &header); + break; + case PDUTYPE_BASE_LOCATION3D: + error = location_server_recv_base_location3d(&location->context, s, &header); + break; + case PDUTYPE_LOCATION2D_DELTA: + error = location_server_recv_location2d_delta(&location->context, s, &header); + break; + case PDUTYPE_LOCATION3D_DELTA: + error = location_server_recv_location3d_delta(&location->context, s, &header); + break; + default: + WLog_ERR(TAG, "location_process_message: unknown or invalid pduType %" PRIu16 "", + pduType); + break; + } + } + +out: + if (error) + WLog_ERR(TAG, "Response failed with error %" PRIu32 "!", error); + + return error; +} + +static UINT location_server_context_poll_int(LocationServerContext* context) +{ + location_server* location = (location_server*)context; + UINT error = ERROR_INTERNAL_ERROR; + + WINPR_ASSERT(location); + + switch (location->state) + { + case LOCATION_INITIAL: + error = location_server_open_channel(location); + if (error) + WLog_ERR(TAG, "location_server_open_channel failed with error %" PRIu32 "!", error); + else + location->state = LOCATION_OPENED; + break; + case LOCATION_OPENED: + error = location_process_message(location); + break; + default: + break; + } + + return error; +} + +static HANDLE location_server_get_channel_handle(location_server* location) +{ + void* buffer = nullptr; + DWORD BytesReturned = 0; + HANDLE ChannelEvent = nullptr; + + WINPR_ASSERT(location); + + if (WTSVirtualChannelQuery(location->location_channel, WTSVirtualEventHandle, &buffer, + &BytesReturned) == TRUE) + { + if (BytesReturned == sizeof(HANDLE)) + ChannelEvent = *(HANDLE*)buffer; + + WTSFreeMemory(buffer); + } + + return ChannelEvent; +} + +static DWORD WINAPI location_server_thread_func(LPVOID arg) +{ + DWORD nCount = 0; + HANDLE events[2] = WINPR_C_ARRAY_INIT; + location_server* location = (location_server*)arg; + UINT error = CHANNEL_RC_OK; + DWORD status = 0; + + WINPR_ASSERT(location); + + nCount = 0; + events[nCount++] = location->stopEvent; + + while ((error == CHANNEL_RC_OK) && (WaitForSingleObject(events[0], 0) != WAIT_OBJECT_0)) + { + switch (location->state) + { + case LOCATION_INITIAL: + error = location_server_context_poll_int(&location->context); + if (error == CHANNEL_RC_OK) + { + events[1] = location_server_get_channel_handle(location); + nCount = 2; + } + break; + case LOCATION_OPENED: + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + switch (status) + { + case WAIT_OBJECT_0: + break; + case WAIT_OBJECT_0 + 1: + case WAIT_TIMEOUT: + error = location_server_context_poll_int(&location->context); + break; + + case WAIT_FAILED: + default: + error = ERROR_INTERNAL_ERROR; + break; + } + break; + default: + break; + } + } + + (void)WTSVirtualChannelClose(location->location_channel); + location->location_channel = nullptr; + + if (error && location->context.rdpcontext) + setChannelError(location->context.rdpcontext, error, + "location_server_thread_func reported an error"); + + ExitThread(error); + return error; +} + +static UINT location_server_open(LocationServerContext* context) +{ + location_server* location = (location_server*)context; + + WINPR_ASSERT(location); + + if (!location->externalThread && (location->thread == nullptr)) + { + location->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + if (!location->stopEvent) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + location->thread = + CreateThread(nullptr, 0, location_server_thread_func, location, 0, nullptr); + if (!location->thread) + { + WLog_ERR(TAG, "CreateThread failed!"); + (void)CloseHandle(location->stopEvent); + location->stopEvent = nullptr; + return ERROR_INTERNAL_ERROR; + } + } + location->isOpened = TRUE; + + return CHANNEL_RC_OK; +} + +static UINT location_server_close(LocationServerContext* context) +{ + UINT error = CHANNEL_RC_OK; + location_server* location = (location_server*)context; + + WINPR_ASSERT(location); + + if (!location->externalThread && location->thread) + { + (void)SetEvent(location->stopEvent); + + if (WaitForSingleObject(location->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + return error; + } + + (void)CloseHandle(location->thread); + (void)CloseHandle(location->stopEvent); + location->thread = nullptr; + location->stopEvent = nullptr; + } + if (location->externalThread) + { + if (location->state != LOCATION_INITIAL) + { + (void)WTSVirtualChannelClose(location->location_channel); + location->location_channel = nullptr; + location->state = LOCATION_INITIAL; + } + } + location->isOpened = FALSE; + + return error; +} + +static UINT location_server_context_poll(LocationServerContext* context) +{ + location_server* location = (location_server*)context; + + WINPR_ASSERT(location); + + if (!location->externalThread) + return ERROR_INTERNAL_ERROR; + + return location_server_context_poll_int(context); +} + +static BOOL location_server_context_handle(LocationServerContext* context, HANDLE* handle) +{ + location_server* location = (location_server*)context; + + WINPR_ASSERT(location); + WINPR_ASSERT(handle); + + if (!location->externalThread) + return FALSE; + if (location->state == LOCATION_INITIAL) + return FALSE; + + *handle = location_server_get_channel_handle(location); + + return TRUE; +} + +static UINT location_server_packet_send(LocationServerContext* context, wStream* s) +{ + location_server* location = (location_server*)context; + UINT error = CHANNEL_RC_OK; + ULONG written = 0; + + WINPR_ASSERT(location); + WINPR_ASSERT(s); + + const size_t pos = Stream_GetPosition(s); + WINPR_ASSERT(pos <= UINT32_MAX); + if (!WTSVirtualChannelWrite(location->location_channel, Stream_BufferAs(s, char), (ULONG)pos, + &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + error = ERROR_INTERNAL_ERROR; + goto out; + } + + if (written < Stream_GetPosition(s)) + { + WLog_WARN(TAG, "Unexpected bytes written: %" PRIu32 "/%" PRIuz "", written, + Stream_GetPosition(s)); + } + +out: + Stream_Free(s, TRUE); + return error; +} + +static UINT location_server_send_server_ready(LocationServerContext* context, + const RDPLOCATION_SERVER_READY_PDU* serverReady) +{ + wStream* s = nullptr; + UINT32 pduLength = 0; + UINT32 protocolVersion = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(serverReady); + + protocolVersion = serverReady->protocolVersion; + + pduLength = LOCATION_HEADER_SIZE + 4 + 4; + + s = Stream_New(nullptr, pduLength); + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return ERROR_NOT_ENOUGH_MEMORY; + } + + /* RDPLOCATION_HEADER */ + Stream_Write_UINT16(s, PDUTYPE_SERVER_READY); + Stream_Write_UINT32(s, pduLength); + + Stream_Write_UINT32(s, protocolVersion); + Stream_Write_UINT32(s, serverReady->flags); + + return location_server_packet_send(context, s); +} + +LocationServerContext* location_server_context_new(HANDLE vcm) +{ + location_server* location = (location_server*)calloc(1, sizeof(location_server)); + + if (!location) + return nullptr; + + location->context.vcm = vcm; + location->context.Initialize = location_server_initialize; + location->context.Open = location_server_open; + location->context.Close = location_server_close; + location->context.Poll = location_server_context_poll; + location->context.ChannelHandle = location_server_context_handle; + + location->context.ServerReady = location_server_send_server_ready; + + location->buffer = Stream_New(nullptr, 4096); + if (!location->buffer) + goto fail; + + return &location->context; +fail: + WINPR_PRAGMA_DIAG_PUSH + WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC + location_server_context_free(&location->context); + WINPR_PRAGMA_DIAG_POP + return nullptr; +} + +void location_server_context_free(LocationServerContext* context) +{ + location_server* location = (location_server*)context; + + if (location) + { + location_server_close(context); + Stream_Free(location->buffer, TRUE); + } + + free(location); +} diff --git a/third_party/FreeRDP/channels/parallel/CMakeLists.txt b/third_party/FreeRDP/channels/parallel/CMakeLists.txt new file mode 100644 index 0000000..9475c56 --- /dev/null +++ b/third_party/FreeRDP/channels/parallel/CMakeLists.txt @@ -0,0 +1,22 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel("parallel") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/third_party/FreeRDP/channels/parallel/ChannelOptions.cmake b/third_party/FreeRDP/channels/parallel/ChannelOptions.cmake new file mode 100644 index 0000000..1bac3ae --- /dev/null +++ b/third_party/FreeRDP/channels/parallel/ChannelOptions.cmake @@ -0,0 +1,38 @@ +set(OPTION_DEFAULT ON) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT OFF) + +if(WIN32) + set(OPTION_CLIENT_DEFAULT OFF) + set(OPTION_SERVER_DEFAULT OFF) + message("Serial redirection not supported on windows") +endif() + +if(APPLE) + set(OPTION_CLIENT_DEFAULT OFF) + set(OPTION_SERVER_DEFAULT OFF) + message("Serial redirection not supported on apple") +endif() + +if(ANDROID) + set(OPTION_CLIENT_DEFAULT OFF) + set(OPTION_SERVER_DEFAULT OFF) + message("Serial redirection not supported on android") +endif() + +define_channel_options( + NAME + "parallel" + TYPE + "device" + DESCRIPTION + "Parallel Port Virtual Channel Extension" + SPECIFICATIONS + "[MS-RDPESP]" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/parallel/client/CMakeLists.txt b/third_party/FreeRDP/channels/parallel/client/CMakeLists.txt new file mode 100644 index 0000000..35da0e6 --- /dev/null +++ b/third_party/FreeRDP/channels/parallel/client/CMakeLists.txt @@ -0,0 +1,23 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_client("parallel") + +set(${MODULE_PREFIX}_SRCS parallel_main.c) + +set(${MODULE_PREFIX}_LIBS winpr freerdp) +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DeviceServiceEntry") diff --git a/third_party/FreeRDP/channels/parallel/client/parallel_main.c b/third_party/FreeRDP/channels/parallel/client/parallel_main.c new file mode 100644 index 0000000..e8efe05 --- /dev/null +++ b/third_party/FreeRDP/channels/parallel/client/parallel_main.c @@ -0,0 +1,541 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Redirected Parallel Port Device Service + * + * Copyright 2010 O.S. Systems Software Ltda. + * Copyright 2010 Eduardo Fiss Beloni + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include + +#ifndef _WIN32 +#include +#include +#include +#endif + +#ifdef __LINUX__ +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define TAG CHANNELS_TAG("drive.client") + +typedef struct +{ + DEVICE device; + + int file; + char* path; + UINT32 id; + + HANDLE thread; + wMessageQueue* queue; + rdpContext* rdpcontext; + wLog* log; +} PARALLEL_DEVICE; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT parallel_process_irp_create(PARALLEL_DEVICE* parallel, IRP* irp) +{ + char* path = nullptr; + UINT32 PathLength = 0; + + WINPR_ASSERT(parallel); + WINPR_ASSERT(irp); + + if (!Stream_SafeSeek(irp->input, 28)) + return ERROR_INVALID_DATA; + /* DesiredAccess(4) AllocationSize(8), FileAttributes(4) */ + /* SharedAccess(4) CreateDisposition(4), CreateOptions(4) */ + if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 4)) + return ERROR_INVALID_DATA; + Stream_Read_UINT32(irp->input, PathLength); + if (PathLength < sizeof(WCHAR)) + return ERROR_INVALID_DATA; + const WCHAR* ptr = Stream_ConstPointer(irp->input); + if (!Stream_SafeSeek(irp->input, PathLength)) + return ERROR_INVALID_DATA; + path = ConvertWCharNToUtf8Alloc(ptr, PathLength / sizeof(WCHAR), nullptr); + if (!path) + return CHANNEL_RC_NO_MEMORY; + + parallel->id = irp->devman->id_sequence++; + parallel->file = open(parallel->path, O_RDWR); + + if (parallel->file < 0) + { + irp->IoStatus = STATUS_ACCESS_DENIED; + parallel->id = 0; + } + else + { + /* all read and write operations should be non-blocking */ + if (fcntl(parallel->file, F_SETFL, O_NONBLOCK) == -1) + { + } + } + + Stream_Write_UINT32(irp->output, parallel->id); + Stream_Write_UINT8(irp->output, 0); + free(path); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT parallel_process_irp_close(PARALLEL_DEVICE* parallel, IRP* irp) +{ + WINPR_ASSERT(parallel); + WINPR_ASSERT(irp); + + (void)close(parallel->file); + + Stream_Zero(irp->output, 5); /* Padding(5) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT parallel_process_irp_read(PARALLEL_DEVICE* parallel, IRP* irp) +{ + UINT32 Length = 0; + UINT64 Offset = 0; + ssize_t status = 0; + BYTE* buffer = nullptr; + + WINPR_ASSERT(parallel); + WINPR_ASSERT(irp); + + if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 12)) + return ERROR_INVALID_DATA; + Stream_Read_UINT32(irp->input, Length); + Stream_Read_UINT64(irp->input, Offset); + (void)Offset; /* [MS-RDPESP] 3.2.5.1.4 Processing a Server Read Request Message + * ignored */ + buffer = (BYTE*)calloc(Length, sizeof(BYTE)); + + if (!buffer) + { + WLog_Print(parallel->log, WLOG_ERROR, "malloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + status = read(parallel->file, buffer, Length); + + if ((status < 0) || (status > UINT32_MAX)) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + free(buffer); + buffer = nullptr; + Length = 0; + } + else + { + Length = (UINT32)status; + } + + Stream_Write_UINT32(irp->output, Length); + + if (Length > 0) + { + if (!Stream_EnsureRemainingCapacity(irp->output, Length)) + { + WLog_Print(parallel->log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!"); + free(buffer); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write(irp->output, buffer, Length); + } + + free(buffer); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT parallel_process_irp_write(PARALLEL_DEVICE* parallel, IRP* irp) +{ + UINT32 len = 0; + UINT32 Length = 0; + UINT64 Offset = 0; + + WINPR_ASSERT(parallel); + WINPR_ASSERT(irp); + + if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 12)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, Length); + Stream_Read_UINT64(irp->input, Offset); + (void)Offset; /* [MS-RDPESP] 3.2.5.1.5 Processing a Server Write Request Message + * ignore offset */ + if (!Stream_SafeSeek(irp->input, 20)) /* Padding */ + return ERROR_INVALID_DATA; + const void* ptr = Stream_ConstPointer(irp->input); + if (!Stream_SafeSeek(irp->input, Length)) + return ERROR_INVALID_DATA; + len = Length; + + while (len > 0) + { + const ssize_t status = write(parallel->file, ptr, len); + + if ((status < 0) || (status > len)) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + Length = 0; + break; + } + + Stream_Seek(irp->input, WINPR_ASSERTING_INT_CAST(size_t, status)); + len -= status; + } + + Stream_Write_UINT32(irp->output, Length); + Stream_Write_UINT8(irp->output, 0); /* Padding */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT parallel_process_irp_device_control(WINPR_ATTR_UNUSED PARALLEL_DEVICE* parallel, + IRP* irp) +{ + WINPR_ASSERT(parallel); + WINPR_ASSERT(irp); + + Stream_Write_UINT32(irp->output, 0); /* OutputBufferLength */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ + +static UINT parallel_eval(UINT error, IRP* irp) +{ + WINPR_ASSERT(irp); + if (error == CHANNEL_RC_OK) + { + WINPR_ASSERT(irp->Complete); + return irp->Complete(irp); + } + + WLog_ERR(TAG, "IRP %s failed with %" PRIu32, rdpdr_irp_string(irp->MajorFunction), error); + WINPR_ASSERT(irp->Discard); + irp->Discard(irp); + return error; +} + +static UINT parallel_process_irp(PARALLEL_DEVICE* parallel, IRP* irp) +{ + UINT error = ERROR_INTERNAL_ERROR; + + WINPR_ASSERT(parallel); + WINPR_ASSERT(irp); + + switch (irp->MajorFunction) + { + case IRP_MJ_CREATE: + error = parallel_process_irp_create(parallel, irp); + break; + + case IRP_MJ_CLOSE: + error = parallel_process_irp_close(parallel, irp); + break; + + case IRP_MJ_READ: + error = parallel_process_irp_read(parallel, irp); + break; + + case IRP_MJ_WRITE: + error = parallel_process_irp_write(parallel, irp); + break; + + case IRP_MJ_DEVICE_CONTROL: + error = parallel_process_irp_device_control(parallel, irp); + break; + + default: + irp->IoStatus = STATUS_NOT_SUPPORTED; + error = CHANNEL_RC_OK; + break; + } + + error = parallel_eval(error, irp); + + DWORD level = WLOG_TRACE; + if (error) + level = WLOG_WARN; + + WLog_Print(parallel->log, level, + "[%s|0x%08" PRIx32 "] completed with %s [0x%08" PRIx32 "] (IoStatus %s [0x%08" PRIx32 + "])", + rdpdr_irp_string(irp->MajorFunction), irp->MajorFunction, WTSErrorToString(error), + error, NtStatus2Tag(irp->IoStatus), WINPR_CXX_COMPAT_CAST(UINT32, irp->IoStatus)); + + return error; +} + +static DWORD WINAPI parallel_thread_func(LPVOID arg) +{ + PARALLEL_DEVICE* parallel = (PARALLEL_DEVICE*)arg; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(parallel); + while (1) + { + if (!MessageQueue_Wait(parallel->queue)) + { + WLog_Print(parallel->log, WLOG_ERROR, "MessageQueue_Wait failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + wMessage message = WINPR_C_ARRAY_INIT; + if (!MessageQueue_Peek(parallel->queue, &message, TRUE)) + { + WLog_Print(parallel->log, WLOG_ERROR, "MessageQueue_Peek failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (message.id == WMQ_QUIT) + break; + + IRP* irp = (IRP*)message.wParam; + + error = parallel_process_irp(parallel, irp); + if (error) + { + WLog_Print(parallel->log, WLOG_ERROR, + "parallel_process_irp failed with error %" PRIu32 "!", error); + break; + } + } + + if (error && parallel->rdpcontext) + setChannelError(parallel->rdpcontext, error, "parallel_thread_func reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT parallel_irp_request(DEVICE* device, IRP* irp) +{ + PARALLEL_DEVICE* parallel = (PARALLEL_DEVICE*)device; + + WINPR_ASSERT(parallel); + + if (!MessageQueue_Post(parallel->queue, nullptr, 0, (void*)irp, nullptr)) + { + WLog_Print(parallel->log, WLOG_ERROR, "MessageQueue_Post failed!"); + irp->Discard(irp); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT parallel_free_int(PARALLEL_DEVICE* parallel) +{ + if (parallel) + { + if (!MessageQueue_PostQuit(parallel->queue, 0) || + (WaitForSingleObject(parallel->thread, INFINITE) == WAIT_FAILED)) + { + const UINT error = GetLastError(); + WLog_Print(parallel->log, WLOG_ERROR, + "WaitForSingleObject failed with error %" PRIu32 "!", error); + } + + (void)CloseHandle(parallel->thread); + Stream_Free(parallel->device.data, TRUE); + MessageQueue_Free(parallel->queue); + } + free(parallel); + return CHANNEL_RC_OK; +} + +static UINT parallel_free(DEVICE* device) +{ + if (device) + return parallel_free_int((PARALLEL_DEVICE*)device); + return CHANNEL_RC_OK; +} + +static void parallel_message_free(void* obj) +{ + wMessage* msg = obj; + if (!msg) + return; + if (msg->id != 0) + return; + + IRP* irp = (IRP*)msg->wParam; + if (!irp) + return; + WINPR_ASSERT(irp->Discard); + irp->Discard(irp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT( + UINT VCAPITYPE parallel_DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints)) +{ + PARALLEL_DEVICE* parallel = nullptr; + UINT error = 0; + + WINPR_ASSERT(pEntryPoints); + + RDPDR_PARALLEL* device = (RDPDR_PARALLEL*)pEntryPoints->device; + WINPR_ASSERT(device); + + wLog* log = WLog_Get(TAG); + WINPR_ASSERT(log); + + char* name = device->device.Name; + char* path = device->Path; + + if (!name || (name[0] == '*') || !path) + { + /* TODO: implement auto detection of parallel ports */ + WLog_Print(log, WLOG_WARN, "Autodetection not implemented, no ports will be redirected"); + return CHANNEL_RC_INITIALIZATION_ERROR; + } + + if (name[0] && path[0]) + { + parallel = (PARALLEL_DEVICE*)calloc(1, sizeof(PARALLEL_DEVICE)); + + if (!parallel) + { + WLog_Print(log, WLOG_ERROR, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + parallel->log = log; + parallel->device.type = RDPDR_DTYP_PARALLEL; + parallel->device.name = name; + parallel->device.IRPRequest = parallel_irp_request; + parallel->device.Free = parallel_free; + parallel->rdpcontext = pEntryPoints->rdpcontext; + const size_t length = strlen(name); + parallel->device.data = Stream_New(nullptr, length + 1); + + if (!parallel->device.data) + { + WLog_Print(parallel->log, WLOG_ERROR, "Stream_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + + for (size_t i = 0; i <= length; i++) + Stream_Write_INT8(parallel->device.data, name[i] < 0 ? '_' : name[i]); + + parallel->path = path; + parallel->queue = MessageQueue_New(nullptr); + + if (!parallel->queue) + { + WLog_Print(parallel->log, WLOG_ERROR, "MessageQueue_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + + wObject* obj = MessageQueue_Object(parallel->queue); + WINPR_ASSERT(obj); + obj->fnObjectFree = parallel_message_free; + + error = pEntryPoints->RegisterDevice(pEntryPoints->devman, ¶llel->device); + if (error) + { + WLog_Print(parallel->log, WLOG_ERROR, "RegisterDevice failed with error %" PRIu32 "!", + error); + goto error_out; + } + + parallel->thread = CreateThread(nullptr, 0, parallel_thread_func, parallel, 0, nullptr); + if (!parallel->thread) + { + WLog_Print(parallel->log, WLOG_ERROR, "CreateThread failed!"); + error = ERROR_INTERNAL_ERROR; + goto error_out; + } + } + + return CHANNEL_RC_OK; +error_out: + parallel_free_int(parallel); + return error; +} diff --git a/third_party/FreeRDP/channels/printer/CMakeLists.txt b/third_party/FreeRDP/channels/printer/CMakeLists.txt new file mode 100644 index 0000000..39a2099 --- /dev/null +++ b/third_party/FreeRDP/channels/printer/CMakeLists.txt @@ -0,0 +1,22 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel("printer") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/third_party/FreeRDP/channels/printer/ChannelOptions.cmake b/third_party/FreeRDP/channels/printer/ChannelOptions.cmake new file mode 100644 index 0000000..5f315f9 --- /dev/null +++ b/third_party/FreeRDP/channels/printer/ChannelOptions.cmake @@ -0,0 +1,40 @@ +set(OPTION_DEFAULT ON) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT OFF) + +if(WIN32) + set(OPTION_CLIENT_DEFAULT ON) + set(OPTION_SERVER_DEFAULT OFF) +else() + # cups is available on mac os and linux by default, on android it is optional. + if(NOT IOS AND NOT ANDROID) + set(CUPS_DEFAULT ON) + else() + set(CUPS_DEFAULT OFF) + endif() + option(WITH_CUPS "CUPS printer support" ${CUPS_DEFAULT}) + if(WITH_CUPS) + set(OPTION_CLIENT_DEFAULT ON) + set(OPTION_SERVER_DEFAULT OFF) + else() + set(OPTION_CLIENT_DEFAULT OFF) + set(OPTION_SERVER_DEFAULT OFF) + endif() +endif() + +define_channel_options( + NAME + "printer" + TYPE + "device" + DESCRIPTION + "Print Virtual Channel Extension" + SPECIFICATIONS + "[MS-RDPEPC]" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/printer/client/CMakeLists.txt b/third_party/FreeRDP/channels/printer/client/CMakeLists.txt new file mode 100644 index 0000000..c3769a7 --- /dev/null +++ b/third_party/FreeRDP/channels/printer/client/CMakeLists.txt @@ -0,0 +1,31 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_client("printer") + +set(${MODULE_PREFIX}_SRCS printer_main.c) + +set(${MODULE_PREFIX}_LIBS winpr freerdp) +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DeviceServiceEntry") + +if(WITH_CUPS) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "cups" "") +endif() + +if(WIN32 AND NOT UWP) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "win" "") +endif() diff --git a/third_party/FreeRDP/channels/printer/client/cups/CMakeLists.txt b/third_party/FreeRDP/channels/printer/client/cups/CMakeLists.txt new file mode 100644 index 0000000..5b244fb --- /dev/null +++ b/third_party/FreeRDP/channels/printer/client/cups/CMakeLists.txt @@ -0,0 +1,29 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2019 Armin Novak +# Copyright 2019 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. +define_channel_client_subsystem("printer" "cups" "") + +find_package(Cups 2.0 REQUIRED) +freerdp_client_pc_add_requires_private("cups") +set(${MODULE_PREFIX}_SRCS printer_cups.c) + +set(${MODULE_PREFIX}_LIBS winpr freerdp ${CUPS_LIBRARIES}) + +include_directories(..) +include_directories(SYSTEM ${CUPS_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") diff --git a/third_party/FreeRDP/channels/printer/client/cups/printer_cups.c b/third_party/FreeRDP/channels/printer/client/cups/printer_cups.c new file mode 100644 index 0000000..408b783 --- /dev/null +++ b/third_party/FreeRDP/channels/printer/client/cups/printer_cups.c @@ -0,0 +1,477 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Print Virtual Channel - CUPS driver + * + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include + +#include +#define TAG CHANNELS_TAG("printer.client.cups") + +#if defined(__APPLE__) +#include +#include + +static bool is_mac_os_sonoma_or_later(void) +{ + char str[256] = WINPR_C_ARRAY_INIT; + size_t size = sizeof(str); + + errno = 0; + int ret = sysctlbyname("kern.osrelease", str, &size, nullptr, 0); + if (ret != 0) + { + char buffer[256] = WINPR_C_ARRAY_INIT; + WLog_WARN(TAG, "sysctlbyname('kern.osrelease') failed with %s [%d]", + winpr_strerror(errno, buffer, sizeof(buffer)), errno); + return false; + } + + int major = 0; + int minor = 0; + int patch = 0; + + // NOLINTNEXTLINE(cert-err34-c) + const int rc = sscanf(str, "%d.%d.%d", &major, &minor, &patch); + if (rc != 3) + { + WLog_WARN(TAG, "could not match '%s' to format '%d.%d.%d'", str, major, minor, patch); + return false; + } + + if (major < 23) + return false; + return true; +} +#endif + +typedef struct +{ + rdpPrinterDriver driver; + + size_t id_sequence; + size_t references; +} rdpCupsPrinterDriver; + +typedef struct +{ + rdpPrintJob printjob; + + http_t* printjob_object; + int printjob_id; +} rdpCupsPrintJob; + +typedef struct +{ + rdpPrinter printer; + + rdpCupsPrintJob* printjob; +} rdpCupsPrinter; + +WINPR_ATTR_MALLOC(free, 1) +static char* printer_cups_get_printjob_name(size_t id) +{ + struct tm tres = WINPR_C_ARRAY_INIT; + const time_t tt = time(nullptr); + const struct tm* t = localtime_r(&tt, &tres); + + char* str = nullptr; + size_t len = 0; + const int rc = + winpr_asprintf(&str, &len, "%s Print %04d-%02d-%02d %02d-%02d-%02d - Job %" PRIuz, + freerdp_getApplicationDetailsString(), t->tm_year + 1900, t->tm_mon + 1, + t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, id); + if (rc <= 0) + { + free(str); + return nullptr; + } + + return str; +} + +static bool http_status_ok(http_status_t status) +{ + switch (status) + { + case HTTP_OK: + case HTTP_CONTINUE: + return true; + default: + return false; + } +} + +static UINT write_printjob(rdpPrintJob* printjob, const BYTE* data, size_t size) +{ + rdpCupsPrintJob* cups_printjob = (rdpCupsPrintJob*)printjob; + + WINPR_ASSERT(cups_printjob); + + http_status_t rc = + cupsWriteRequestData(cups_printjob->printjob_object, (const char*)data, size); + if (!http_status_ok(rc)) + WLog_WARN(TAG, "cupsWriteRequestData returned %s", httpStatus(rc)); + + return CHANNEL_RC_OK; +} +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT printer_cups_write_printjob(rdpPrintJob* printjob, const BYTE* data, size_t size) +{ + return write_printjob(printjob, data, size); +} + +static void printer_cups_close_printjob(rdpPrintJob* printjob) +{ + rdpCupsPrintJob* cups_printjob = (rdpCupsPrintJob*)printjob; + rdpCupsPrinter* cups_printer = nullptr; + + WINPR_ASSERT(cups_printjob); + + ipp_status_t rc = cupsFinishDocument(cups_printjob->printjob_object, printjob->printer->name); + if (rc != IPP_OK) + WLog_WARN(TAG, "cupsFinishDocument returned %s", ippErrorString(rc)); + + cups_printjob->printjob_id = 0; + httpClose(cups_printjob->printjob_object); + + cups_printer = (rdpCupsPrinter*)printjob->printer; + WINPR_ASSERT(cups_printer); + + cups_printer->printjob = nullptr; + free(cups_printjob); +} + +static rdpPrintJob* printer_cups_create_printjob(rdpPrinter* printer, UINT32 id) +{ + rdpCupsPrinter* cups_printer = (rdpCupsPrinter*)printer; + rdpCupsPrintJob* cups_printjob = nullptr; + + WINPR_ASSERT(cups_printer); + + if (cups_printer->printjob != nullptr) + { + WLog_WARN(TAG, "printjob [printer '%s'] already existing, abort!", printer->name); + return nullptr; + } + + cups_printjob = (rdpCupsPrintJob*)calloc(1, sizeof(rdpCupsPrintJob)); + if (!cups_printjob) + return nullptr; + + cups_printjob->printjob.id = id; + cups_printjob->printjob.printer = printer; + + cups_printjob->printjob.Write = printer_cups_write_printjob; + cups_printjob->printjob.Close = printer_cups_close_printjob; + + { + cups_printjob->printjob_object = httpConnect2(cupsServer(), ippPort(), nullptr, AF_UNSPEC, + HTTP_ENCRYPT_IF_REQUESTED, 1, 10000, nullptr); + + if (!cups_printjob->printjob_object) + { + WLog_WARN(TAG, "httpConnect2 failed for '%s:%d", cupsServer(), ippPort()); + free(cups_printjob); + return nullptr; + } + + char* jobTitle = printer_cups_get_printjob_name(cups_printjob->printjob.id); + if (!jobTitle) + { + httpClose(cups_printjob->printjob_object); + free(cups_printjob); + return nullptr; + } + + cups_printjob->printjob_id = + cupsCreateJob(cups_printjob->printjob_object, printer->name, jobTitle, 0, nullptr); + + if (!cups_printjob->printjob_id) + { + WLog_WARN(TAG, "cupsCreateJob failed for printer '%s', driver '%s'", printer->name, + printer->driver); + httpClose(cups_printjob->printjob_object); + free(cups_printjob); + free(jobTitle); + return nullptr; + } + + http_status_t rc = + cupsStartDocument(cups_printjob->printjob_object, printer->name, + cups_printjob->printjob_id, jobTitle, CUPS_FORMAT_AUTO, 1); + free(jobTitle); + if (!http_status_ok(rc)) + WLog_WARN(TAG, "cupsStartDocument [printer '%s', driver '%s'] returned %s", + printer->name, printer->driver, httpStatus(rc)); + } + + cups_printer->printjob = cups_printjob; + + return &cups_printjob->printjob; +} + +static rdpPrintJob* printer_cups_find_printjob(rdpPrinter* printer, UINT32 id) +{ + rdpCupsPrinter* cups_printer = (rdpCupsPrinter*)printer; + + WINPR_ASSERT(cups_printer); + + if (cups_printer->printjob == nullptr) + return nullptr; + if (cups_printer->printjob->printjob.id != id) + return nullptr; + + return &cups_printer->printjob->printjob; +} + +static void printer_cups_free_printer(rdpPrinter* printer) +{ + rdpCupsPrinter* cups_printer = (rdpCupsPrinter*)printer; + + WINPR_ASSERT(cups_printer); + + if (cups_printer->printjob) + { + WINPR_ASSERT(cups_printer->printjob->printjob.Close); + cups_printer->printjob->printjob.Close(&cups_printer->printjob->printjob); + } + + if (printer->backend) + { + WINPR_ASSERT(printer->backend->ReleaseRef); + printer->backend->ReleaseRef(printer->backend); + } + free(printer->name); + free(printer->driver); + free(printer); +} + +static void printer_cups_add_ref_printer(rdpPrinter* printer) +{ + if (printer) + printer->references++; +} + +static void printer_cups_release_ref_printer(rdpPrinter* printer) +{ + if (!printer) + return; + if (printer->references <= 1) + printer_cups_free_printer(printer); + else + printer->references--; +} + +static rdpPrinter* printer_cups_new_printer(rdpCupsPrinterDriver* cups_driver, const char* name, + const char* driverName, BOOL is_default) +{ + rdpCupsPrinter* cups_printer = nullptr; + + cups_printer = (rdpCupsPrinter*)calloc(1, sizeof(rdpCupsPrinter)); + if (!cups_printer) + return nullptr; + + cups_printer->printer.backend = &cups_driver->driver; + + cups_printer->printer.id = cups_driver->id_sequence++; + cups_printer->printer.name = _strdup(name); + if (!cups_printer->printer.name) + goto fail; + + if (driverName) + cups_printer->printer.driver = _strdup(driverName); + else + { + const char* dname = "MS Publisher Imagesetter"; +#if defined(__APPLE__) + if (is_mac_os_sonoma_or_later()) + dname = "Microsoft Print to PDF"; +#endif + cups_printer->printer.driver = _strdup(dname); + } + if (!cups_printer->printer.driver) + goto fail; + + cups_printer->printer.is_default = is_default; + + cups_printer->printer.CreatePrintJob = printer_cups_create_printjob; + cups_printer->printer.FindPrintJob = printer_cups_find_printjob; + cups_printer->printer.AddRef = printer_cups_add_ref_printer; + cups_printer->printer.ReleaseRef = printer_cups_release_ref_printer; + + WINPR_ASSERT(cups_printer->printer.AddRef); + cups_printer->printer.AddRef(&cups_printer->printer); + + WINPR_ASSERT(cups_printer->printer.backend->AddRef); + cups_printer->printer.backend->AddRef(cups_printer->printer.backend); + + return &cups_printer->printer; + +fail: + printer_cups_free_printer(&cups_printer->printer); + return nullptr; +} + +static void printer_cups_release_enum_printers(rdpPrinter** printers) +{ + rdpPrinter** cur = printers; + + while ((cur != nullptr) && ((*cur) != nullptr)) + { + if ((*cur)->ReleaseRef) + (*cur)->ReleaseRef(*cur); + cur++; + } + free((void*)printers); +} + +static rdpPrinter** printer_cups_enum_printers(rdpPrinterDriver* driver) +{ + rdpPrinter** printers = nullptr; + int num_printers = 0; + cups_dest_t* dests = nullptr; + BOOL haveDefault = FALSE; + const int num_dests = cupsGetDests(&dests); + + WINPR_ASSERT(driver); + if (num_dests >= 0) + printers = (rdpPrinter**)calloc((size_t)num_dests + 1, sizeof(rdpPrinter*)); + if (!printers) + return nullptr; + + for (size_t i = 0; i < (size_t)num_dests; i++) + { + const cups_dest_t* dest = &dests[i]; + if (dest->instance == nullptr) + { + rdpPrinter* current = printer_cups_new_printer((rdpCupsPrinterDriver*)driver, + dest->name, nullptr, dest->is_default); + if (!current) + { + printer_cups_release_enum_printers(printers); + printers = nullptr; + break; + } + + if (current->is_default) + haveDefault = TRUE; + + printers[num_printers++] = current; + } + } + cupsFreeDests(num_dests, dests); + + if (!haveDefault && (num_dests > 0) && printers) + { + if (printers[0]) + printers[0]->is_default = TRUE; + } + + return printers; +} + +static rdpPrinter* printer_cups_get_printer(rdpPrinterDriver* driver, const char* name, + const char* driverName, BOOL isDefault) +{ + rdpCupsPrinterDriver* cups_driver = (rdpCupsPrinterDriver*)driver; + + WINPR_ASSERT(cups_driver); + return printer_cups_new_printer(cups_driver, name, driverName, isDefault); +} + +static void printer_cups_add_ref_driver(rdpPrinterDriver* driver) +{ + rdpCupsPrinterDriver* cups_driver = (rdpCupsPrinterDriver*)driver; + if (cups_driver) + cups_driver->references++; +} + +/* Singleton */ +static rdpCupsPrinterDriver* uniq_cups_driver = nullptr; + +static void printer_cups_release_ref_driver(rdpPrinterDriver* driver) +{ + rdpCupsPrinterDriver* cups_driver = (rdpCupsPrinterDriver*)driver; + + WINPR_ASSERT(cups_driver); + + if (cups_driver->references <= 1) + { + if (uniq_cups_driver == cups_driver) + uniq_cups_driver = nullptr; + free(cups_driver); + } + else + cups_driver->references--; +} + +FREERDP_ENTRY_POINT(UINT VCAPITYPE cups_freerdp_printer_client_subsystem_entry(void* arg)) +{ + rdpPrinterDriver** ppPrinter = (rdpPrinterDriver**)arg; + if (!ppPrinter) + return ERROR_INVALID_PARAMETER; + + if (!uniq_cups_driver) + { + uniq_cups_driver = (rdpCupsPrinterDriver*)calloc(1, sizeof(rdpCupsPrinterDriver)); + + if (!uniq_cups_driver) + return ERROR_OUTOFMEMORY; + + uniq_cups_driver->driver.EnumPrinters = printer_cups_enum_printers; + uniq_cups_driver->driver.ReleaseEnumPrinters = printer_cups_release_enum_printers; + uniq_cups_driver->driver.GetPrinter = printer_cups_get_printer; + + uniq_cups_driver->driver.AddRef = printer_cups_add_ref_driver; + uniq_cups_driver->driver.ReleaseRef = printer_cups_release_ref_driver; + + uniq_cups_driver->id_sequence = 1; + } + + WINPR_ASSERT(uniq_cups_driver->driver.AddRef); + uniq_cups_driver->driver.AddRef(&uniq_cups_driver->driver); + + *ppPrinter = &uniq_cups_driver->driver; + return CHANNEL_RC_OK; +} diff --git a/third_party/FreeRDP/channels/printer/client/printer_main.c b/third_party/FreeRDP/channels/printer/client/printer_main.c new file mode 100644 index 0000000..a2b2867 --- /dev/null +++ b/third_party/FreeRDP/channels/printer/client/printer_main.c @@ -0,0 +1,1220 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Print Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 Armin Novak + * Copyright 2016 David PHAM-VAN + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../printer.h" + +#include +#include +#include + +#define TAG CHANNELS_TAG("printer.client") + +typedef struct +{ + DEVICE device; + + rdpPrinter* printer; + + WINPR_PSLIST_HEADER pIrpList; + + HANDLE event; + HANDLE stopEvent; + + HANDLE thread; + rdpContext* rdpcontext; + char port[64]; + BOOL async; +} PRINTER_DEVICE; + +typedef enum +{ + PRN_CONF_PORT = 0, + PRN_CONF_PNP = 1, + PRN_CONF_DRIVER = 2, + PRN_CONF_DATA = 3 +} prn_conf_t; + +static const char* filemap[] = { "PortDosName", "PnPName", "DriverName", + "CachedPrinterConfigData" }; + +WINPR_ATTR_MALLOC(free, 1) +WINPR_ATTR_NODISCARD +static char* get_printer_hash(const WCHAR* name, size_t length) +{ + BYTE hash[WINPR_SHA256_DIGEST_LENGTH] = WINPR_C_ARRAY_INIT; + + if (!winpr_Digest(WINPR_MD_SHA256, name, length, hash, sizeof(hash))) + return nullptr; + + return winpr_BinToHexString(hash, sizeof(hash), FALSE); +} + +WINPR_ATTR_MALLOC(free, 1) +WINPR_ATTR_NODISCARD +static char* get_printer_config_path(const rdpSettings* settings, const WCHAR* name, size_t length) +{ + char* config = nullptr; + const char* path = freerdp_settings_get_string(settings, FreeRDP_ConfigPath); + char* dir = GetCombinedPath(path, "printers"); + if (!dir) + return nullptr; + char* bname = get_printer_hash(name, length); + if (!bname) + goto fail; + config = GetCombinedPath(dir, bname); + + if (config && !winpr_PathFileExists(config)) + { + if (!winpr_PathMakePath(config, nullptr)) + { + free(config); + config = nullptr; + } + } + +fail: + free(dir); + free(bname); + return config; +} + +static BOOL printer_write_setting(const char* path, prn_conf_t type, const void* data, + size_t length) +{ + if (!path) + return FALSE; + + DWORD written = 0; + BOOL rc = FALSE; + HANDLE file = nullptr; + char* base64 = nullptr; + const char* name = filemap[type]; + char* abs = GetCombinedPath(path, name); + + if (!abs || (length > INT32_MAX)) + { + free(abs); + return FALSE; + } + + file = winpr_CreateFile(abs, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, + nullptr); + free(abs); + + if (file == INVALID_HANDLE_VALUE) + return FALSE; + + if (length > 0) + { + base64 = crypto_base64_encode(data, length); + + if (!base64) + goto fail; + + /* base64 char represents 6bit -> 4*(n/3) is the length which is + * always smaller than 2*n */ + const size_t b64len = strnlen(base64, 2 * length); + rc = WriteFile(file, base64, (UINT32)b64len, &written, nullptr); + + if (b64len != written) + rc = FALSE; + } + else + rc = TRUE; + +fail: + (void)CloseHandle(file); + free(base64); + return rc; +} + +static BOOL printer_config_valid(const char* path) +{ + if (!path) + return FALSE; + + if (!winpr_PathFileExists(path)) + return FALSE; + + return TRUE; +} + +static BOOL printer_read_setting(const char* path, prn_conf_t type, void** data, UINT32* length) +{ + DWORD lowSize = 0; + DWORD highSize = 0; + DWORD read = 0; + BOOL rc = FALSE; + char* fdata = nullptr; + const char* name = filemap[type]; + + switch (type) + { + case PRN_CONF_DATA: + break; + default: + WLog_DBG(TAG, "Printer option %s ignored", name); + return FALSE; + } + + char* abs = GetCombinedPath(path, name); + if (!abs) + return FALSE; + + HANDLE file = winpr_CreateFile(abs, GENERIC_READ, 0, nullptr, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, nullptr); + free(abs); + + if (file == INVALID_HANDLE_VALUE) + return FALSE; + + lowSize = GetFileSize(file, &highSize); + + if ((lowSize == INVALID_FILE_SIZE) || (highSize != 0)) + goto fail; + + if (lowSize != 0) + { + fdata = malloc(lowSize); + + if (!fdata) + goto fail; + + rc = ReadFile(file, fdata, lowSize, &read, nullptr); + + if (lowSize != read) + rc = FALSE; + } + +fail: + (void)CloseHandle(file); + + if (rc && (lowSize <= INT_MAX)) + { + size_t blen = 0; + crypto_base64_decode(fdata, lowSize, (BYTE**)data, &blen); + + if (*data && (blen > 0)) + *length = (UINT32)blen; + else + { + rc = FALSE; + *length = 0; + } + } + else + { + *length = 0; + *data = nullptr; + } + + free(fdata); + return rc; +} + +static BOOL printer_save_to_config(const rdpSettings* settings, const char* PortDosName, + size_t PortDosNameLen, const WCHAR* PnPName, size_t PnPNameLen, + const WCHAR* DriverName, size_t DriverNameLen, + const WCHAR* PrinterName, size_t PrintNameLen, + const BYTE* CachedPrinterConfigData, size_t CacheFieldsLen) +{ + BOOL rc = FALSE; + char* path = get_printer_config_path(settings, PrinterName, PrintNameLen); + + if (!path) + goto fail; + + if (!printer_write_setting(path, PRN_CONF_PORT, PortDosName, PortDosNameLen)) + goto fail; + + if (!printer_write_setting(path, PRN_CONF_PNP, PnPName, PnPNameLen)) + goto fail; + + if (!printer_write_setting(path, PRN_CONF_DRIVER, DriverName, DriverNameLen)) + goto fail; + + if (!printer_write_setting(path, PRN_CONF_DATA, CachedPrinterConfigData, CacheFieldsLen)) + goto fail; + +fail: + free(path); + return rc; +} + +static BOOL printer_update_to_config(const rdpSettings* settings, const WCHAR* name, size_t length, + const BYTE* data, size_t datalen) +{ + BOOL rc = FALSE; + char* path = get_printer_config_path(settings, name, length); + rc = printer_write_setting(path, PRN_CONF_DATA, data, datalen); + free(path); + return rc; +} + +static BOOL printer_remove_config(const rdpSettings* settings, const WCHAR* name, size_t length) +{ + BOOL rc = FALSE; + char* path = get_printer_config_path(settings, name, length); + + if (!printer_config_valid(path)) + goto fail; + + rc = winpr_RemoveDirectory(path); +fail: + free(path); + return rc; +} + +static BOOL printer_move_config(const rdpSettings* settings, const WCHAR* oldName, size_t oldLength, + const WCHAR* newName, size_t newLength) +{ + BOOL rc = FALSE; + char* oldPath = get_printer_config_path(settings, oldName, oldLength); + char* newPath = get_printer_config_path(settings, newName, newLength); + + if (printer_config_valid(oldPath)) + rc = winpr_MoveFile(oldPath, newPath); + + free(oldPath); + free(newPath); + return rc; +} + +static BOOL printer_load_from_config(const rdpSettings* settings, rdpPrinter* printer, + PRINTER_DEVICE* printer_dev) +{ + BOOL res = FALSE; + WCHAR* wname = nullptr; + size_t wlen = 0; + char* path = nullptr; + UINT32 flags = 0; + void* DriverName = nullptr; + UINT32 DriverNameLen = 0; + void* PnPName = nullptr; + UINT32 PnPNameLen = 0; + void* CachedPrinterConfigData = nullptr; + UINT32 CachedFieldsLen = 0; + UINT32 PrinterNameLen = 0; + + if (!settings || !printer || !printer->name) + return FALSE; + + wname = ConvertUtf8ToWCharAlloc(printer->name, &wlen); + + if (!wname) + goto fail; + + wlen++; + path = get_printer_config_path(settings, wname, wlen * sizeof(WCHAR)); + { + const size_t plen = wlen * sizeof(WCHAR); + if (plen > UINT32_MAX) + goto fail; + PrinterNameLen = (UINT32)plen; + } + + if (!path) + goto fail; + + if (printer->is_default) + flags |= RDPDR_PRINTER_ANNOUNCE_FLAG_DEFAULTPRINTER; + + if (!printer_read_setting(path, PRN_CONF_PNP, &PnPName, &PnPNameLen)) + { + } + + if (!printer_read_setting(path, PRN_CONF_DRIVER, &DriverName, &DriverNameLen)) + { + size_t len = 0; + DriverName = ConvertUtf8ToWCharAlloc(printer->driver, &len); + if (!DriverName) + goto fail; + const size_t dlen = (len + 1) * sizeof(WCHAR); + if (dlen > UINT32_MAX) + goto fail; + DriverNameLen = (UINT32)dlen; + } + + if (!printer_read_setting(path, PRN_CONF_DATA, &CachedPrinterConfigData, &CachedFieldsLen)) + { + } + + Stream_ResetPosition(printer_dev->device.data); + + if (!Stream_EnsureRemainingCapacity(printer_dev->device.data, 24)) + goto fail; + + Stream_Write_UINT32(printer_dev->device.data, flags); + Stream_Write_UINT32(printer_dev->device.data, 0); /* CodePage, reserved */ + Stream_Write_UINT32(printer_dev->device.data, PnPNameLen); /* PnPNameLen */ + Stream_Write_UINT32(printer_dev->device.data, DriverNameLen); + Stream_Write_UINT32(printer_dev->device.data, PrinterNameLen); + Stream_Write_UINT32(printer_dev->device.data, CachedFieldsLen); + + if (!Stream_EnsureRemainingCapacity(printer_dev->device.data, PnPNameLen)) + goto fail; + + if (PnPNameLen > 0) + Stream_Write(printer_dev->device.data, PnPName, PnPNameLen); + + if (!Stream_EnsureRemainingCapacity(printer_dev->device.data, DriverNameLen)) + goto fail; + + Stream_Write(printer_dev->device.data, DriverName, DriverNameLen); + + if (!Stream_EnsureRemainingCapacity(printer_dev->device.data, PrinterNameLen)) + goto fail; + + union + { + char c[2]; + WCHAR w; + } backslash; + backslash.c[0] = '\\'; + backslash.c[1] = '\0'; + + for (WCHAR* wptr = wname; (wptr = _wcschr(wptr, backslash.w));) + *wptr = L'_'; + Stream_Write(printer_dev->device.data, wname, PrinterNameLen); + + if (!Stream_EnsureRemainingCapacity(printer_dev->device.data, CachedFieldsLen)) + goto fail; + + Stream_Write(printer_dev->device.data, CachedPrinterConfigData, CachedFieldsLen); + res = TRUE; +fail: + free(path); + free(wname); + free(PnPName); + free(DriverName); + free(CachedPrinterConfigData); + return res; +} + +static BOOL printer_save_default_config(const rdpSettings* settings, rdpPrinter* printer) +{ + BOOL res = FALSE; + WCHAR* wname = nullptr; + WCHAR* driver = nullptr; + size_t wlen = 0; + size_t dlen = 0; + char* path = nullptr; + + if (!settings || !printer || !printer->name || !printer->driver) + return FALSE; + + wname = ConvertUtf8ToWCharAlloc(printer->name, nullptr); + + if (!wname) + goto fail; + + driver = ConvertUtf8ToWCharAlloc(printer->driver, nullptr); + + if (!driver) + goto fail; + + wlen = _wcslen(wname) + 1; + dlen = _wcslen(driver) + 1; + path = get_printer_config_path(settings, wname, wlen * sizeof(WCHAR)); + + if (!path) + goto fail; + + if (dlen > 1) + { + if (!printer_write_setting(path, PRN_CONF_DRIVER, driver, dlen * sizeof(WCHAR))) + goto fail; + } + + res = TRUE; +fail: + free(path); + free(wname); + free(driver); + return res; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT printer_process_irp_create(PRINTER_DEVICE* printer_dev, IRP* irp) +{ + rdpPrintJob* printjob = nullptr; + + WINPR_ASSERT(printer_dev); + WINPR_ASSERT(irp); + + if (printer_dev->printer) + { + WINPR_ASSERT(printer_dev->printer->CreatePrintJob); + printjob = + printer_dev->printer->CreatePrintJob(printer_dev->printer, irp->devman->id_sequence++); + } + + if (printjob) + { + Stream_Write_UINT32(irp->output, printjob->id); /* FileId */ + } + else + { + Stream_Write_UINT32(irp->output, 0); /* FileId */ + irp->IoStatus = STATUS_PRINT_QUEUE_FULL; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT printer_process_irp_close(PRINTER_DEVICE* printer_dev, IRP* irp) +{ + rdpPrintJob* printjob = nullptr; + + WINPR_ASSERT(printer_dev); + WINPR_ASSERT(irp); + + if (printer_dev->printer) + { + WINPR_ASSERT(printer_dev->printer->FindPrintJob); + printjob = printer_dev->printer->FindPrintJob(printer_dev->printer, irp->FileId); + } + + if (!printjob) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + } + else + { + printjob->Close(printjob); + } + + Stream_Zero(irp->output, 4); /* Padding(4) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT printer_process_irp_write(PRINTER_DEVICE* printer_dev, IRP* irp) +{ + UINT32 Length = 0; + UINT64 Offset = 0; + rdpPrintJob* printjob = nullptr; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(printer_dev); + WINPR_ASSERT(irp); + + if (!Stream_CheckAndLogRequiredLength(TAG, irp->input, 32)) + return ERROR_INVALID_DATA; + Stream_Read_UINT32(irp->input, Length); + Stream_Read_UINT64(irp->input, Offset); + (void)Offset; /* [MS-RDPEPC] 2.2.2.9 Server Printer Write Request (DR_PRN_WRITE_REQ) + * reserved for future use, ignore */ + Stream_Seek(irp->input, 20); /* Padding */ + const void* ptr = Stream_ConstPointer(irp->input); + if (!Stream_SafeSeek(irp->input, Length)) + return ERROR_INVALID_DATA; + if (printer_dev->printer) + { + WINPR_ASSERT(printer_dev->printer->FindPrintJob); + printjob = printer_dev->printer->FindPrintJob(printer_dev->printer, irp->FileId); + } + + if (!printjob) + { + irp->IoStatus = STATUS_UNSUCCESSFUL; + Length = 0; + } + else + { + error = printjob->Write(printjob, ptr, Length); + } + + if (error) + { + WLog_ERR(TAG, "printjob->Write failed with error %" PRIu32 "!", error); + return error; + } + + Stream_Write_UINT32(irp->output, Length); + Stream_Write_UINT8(irp->output, 0); /* Padding */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT printer_process_irp_device_control(WINPR_ATTR_UNUSED PRINTER_DEVICE* printer_dev, + IRP* irp) +{ + WINPR_ASSERT(printer_dev); + WINPR_ASSERT(irp); + + Stream_Write_UINT32(irp->output, 0); /* OutputBufferLength */ + return CHANNEL_RC_OK; +} + +static UINT printer_evaluate(UINT error, IRP* irp) +{ + WINPR_ASSERT(irp); + if (error == CHANNEL_RC_OK) + { + WINPR_ASSERT(irp->Complete); + return irp->Complete(irp); + } + + WLog_ERR(TAG, "IRP %s failed with %" PRIu32, rdpdr_irp_string(irp->MajorFunction), error); + WINPR_ASSERT(irp->Discard); + irp->Discard(irp); + return error; +} +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT printer_process_irp(PRINTER_DEVICE* printer_dev, IRP* irp) +{ + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(printer_dev); + WINPR_ASSERT(irp); + + switch (irp->MajorFunction) + { + case IRP_MJ_CREATE: + error = printer_process_irp_create(printer_dev, irp); + break; + + case IRP_MJ_CLOSE: + error = printer_process_irp_close(printer_dev, irp); + break; + + case IRP_MJ_WRITE: + error = printer_process_irp_write(printer_dev, irp); + break; + + case IRP_MJ_DEVICE_CONTROL: + error = printer_process_irp_device_control(printer_dev, irp); + break; + + default: + irp->IoStatus = STATUS_NOT_SUPPORTED; + break; + } + + return printer_evaluate(error, irp); +} + +static DWORD WINAPI printer_thread_func(LPVOID arg) +{ + IRP* irp = nullptr; + PRINTER_DEVICE* printer_dev = (PRINTER_DEVICE*)arg; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(printer_dev); + + while (1) + { + HANDLE obj[] = { printer_dev->event, printer_dev->stopEvent }; + DWORD rc = WaitForMultipleObjects(ARRAYSIZE(obj), obj, FALSE, INFINITE); + + if (rc == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "!", error); + break; + } + + if (rc == WAIT_OBJECT_0 + 1) + break; + else if (rc != WAIT_OBJECT_0) + continue; + + (void)ResetEvent(printer_dev->event); + irp = (IRP*)InterlockedPopEntrySList(printer_dev->pIrpList); + + if (irp == nullptr) + { + WLog_ERR(TAG, "InterlockedPopEntrySList failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if ((error = printer_process_irp(printer_dev, irp))) + { + WLog_ERR(TAG, "printer_process_irp failed with error %" PRIu32 "!", error); + break; + } + } + + if (error && printer_dev->rdpcontext) + setChannelError(printer_dev->rdpcontext, error, "printer_thread_func reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT printer_irp_request(DEVICE* device, IRP* irp) +{ + PRINTER_DEVICE* printer_dev = (PRINTER_DEVICE*)device; + + WINPR_ASSERT(printer_dev); + WINPR_ASSERT(irp); + + if (printer_dev->async) + { + InterlockedPushEntrySList(printer_dev->pIrpList, &(irp->ItemEntry)); + (void)SetEvent(printer_dev->event); + } + else + { + UINT error = printer_process_irp(printer_dev, irp); + if (error) + { + WLog_ERR(TAG, "printer_process_irp failed with error %" PRIu32 "!", error); + return error; + } + } + + return CHANNEL_RC_OK; +} + +static UINT printer_custom_component(DEVICE* device, UINT16 component, UINT16 packetId, wStream* s) +{ + UINT32 eventID = 0; + PRINTER_DEVICE* printer_dev = (PRINTER_DEVICE*)device; + + WINPR_ASSERT(printer_dev); + WINPR_ASSERT(printer_dev->rdpcontext); + + const rdpSettings* settings = printer_dev->rdpcontext->settings; + WINPR_ASSERT(settings); + + if (component != RDPDR_CTYP_PRN) + return ERROR_INVALID_DATA; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, eventID); + + switch (packetId) + { + case PAKID_PRN_CACHE_DATA: + switch (eventID) + { + case RDPDR_ADD_PRINTER_EVENT: + { + char PortDosName[8]; + UINT32 PnPNameLen = 0; + UINT32 DriverNameLen = 0; + UINT32 PrintNameLen = 0; + UINT32 CacheFieldsLen = 0; + const WCHAR* PnPName = nullptr; + const WCHAR* DriverName = nullptr; + const WCHAR* PrinterName = nullptr; + const BYTE* CachedPrinterConfigData = nullptr; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 24)) + return ERROR_INVALID_DATA; + + Stream_Read(s, PortDosName, sizeof(PortDosName)); + Stream_Read_UINT32(s, PnPNameLen); + Stream_Read_UINT32(s, DriverNameLen); + Stream_Read_UINT32(s, PrintNameLen); + Stream_Read_UINT32(s, CacheFieldsLen); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, PnPNameLen)) + return ERROR_INVALID_DATA; + + PnPName = Stream_ConstPointer(s); + Stream_Seek(s, PnPNameLen); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, DriverNameLen)) + return ERROR_INVALID_DATA; + + DriverName = Stream_ConstPointer(s); + Stream_Seek(s, DriverNameLen); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, PrintNameLen)) + return ERROR_INVALID_DATA; + + PrinterName = Stream_ConstPointer(s); + Stream_Seek(s, PrintNameLen); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, CacheFieldsLen)) + return ERROR_INVALID_DATA; + + CachedPrinterConfigData = Stream_ConstPointer(s); + Stream_Seek(s, CacheFieldsLen); + + if (!printer_save_to_config(settings, PortDosName, sizeof(PortDosName), PnPName, + PnPNameLen, DriverName, DriverNameLen, PrinterName, + PrintNameLen, CachedPrinterConfigData, + CacheFieldsLen)) + return ERROR_INTERNAL_ERROR; + } + break; + + case RDPDR_UPDATE_PRINTER_EVENT: + { + UINT32 PrinterNameLen = 0; + UINT32 ConfigDataLen = 0; + const WCHAR* PrinterName = nullptr; + const BYTE* ConfigData = nullptr; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, PrinterNameLen); + Stream_Read_UINT32(s, ConfigDataLen); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, PrinterNameLen)) + return ERROR_INVALID_DATA; + + PrinterName = Stream_ConstPointer(s); + Stream_Seek(s, PrinterNameLen); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, ConfigDataLen)) + return ERROR_INVALID_DATA; + + ConfigData = Stream_ConstPointer(s); + Stream_Seek(s, ConfigDataLen); + + if (!printer_update_to_config(settings, PrinterName, PrinterNameLen, ConfigData, + ConfigDataLen)) + return ERROR_INTERNAL_ERROR; + } + break; + + case RDPDR_DELETE_PRINTER_EVENT: + { + UINT32 PrinterNameLen = 0; + const WCHAR* PrinterName = nullptr; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, PrinterNameLen); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, PrinterNameLen)) + return ERROR_INVALID_DATA; + + PrinterName = Stream_ConstPointer(s); + Stream_Seek(s, PrinterNameLen); + printer_remove_config(settings, PrinterName, PrinterNameLen); + } + break; + + case RDPDR_RENAME_PRINTER_EVENT: + { + UINT32 OldPrinterNameLen = 0; + UINT32 NewPrinterNameLen = 0; + const WCHAR* OldPrinterName = nullptr; + const WCHAR* NewPrinterName = nullptr; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, OldPrinterNameLen); + Stream_Read_UINT32(s, NewPrinterNameLen); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, OldPrinterNameLen)) + return ERROR_INVALID_DATA; + + OldPrinterName = Stream_ConstPointer(s); + Stream_Seek(s, OldPrinterNameLen); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, NewPrinterNameLen)) + return ERROR_INVALID_DATA; + + NewPrinterName = Stream_ConstPointer(s); + Stream_Seek(s, NewPrinterNameLen); + + if (!printer_move_config(settings, OldPrinterName, OldPrinterNameLen, + NewPrinterName, NewPrinterNameLen)) + return ERROR_INTERNAL_ERROR; + } + break; + + default: + WLog_ERR(TAG, "Unknown cache data eventID: 0x%08" PRIX32 "", eventID); + return ERROR_INVALID_DATA; + } + + break; + + case PAKID_PRN_USING_XPS: + { + UINT32 flags = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, flags); + WLog_ERR(TAG, + "Ignoring unhandled message PAKID_PRN_USING_XPS [printerID=%08" PRIx32 + ", flags=%08" PRIx32 "]", + eventID, flags); + } + break; + + default: + WLog_ERR(TAG, "Unknown printing component packetID: 0x%04" PRIX16 "", packetId); + return ERROR_INVALID_DATA; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT printer_free(DEVICE* device) +{ + IRP* irp = nullptr; + PRINTER_DEVICE* printer_dev = (PRINTER_DEVICE*)device; + UINT error = 0; + + WINPR_ASSERT(printer_dev); + + if (printer_dev->async) + { + (void)SetEvent(printer_dev->stopEvent); + + if (WaitForSingleObject(printer_dev->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + + /* The analyzer is confused by this premature return value. + * Since this case can not be handled gracefully silence the + * analyzer here. */ +#ifndef __clang_analyzer__ + return error; +#endif + } + + while ((irp = (IRP*)InterlockedPopEntrySList(printer_dev->pIrpList)) != nullptr) + { + WINPR_ASSERT(irp->Discard); + irp->Discard(irp); + } + + (void)CloseHandle(printer_dev->thread); + (void)CloseHandle(printer_dev->stopEvent); + (void)CloseHandle(printer_dev->event); + winpr_aligned_free(printer_dev->pIrpList); + } + + if (printer_dev->printer) + { + WINPR_ASSERT(printer_dev->printer->ReleaseRef); + printer_dev->printer->ReleaseRef(printer_dev->printer); + } + + Stream_Free(printer_dev->device.data, TRUE); + free(printer_dev); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT printer_register(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints, rdpPrinter* printer) +{ + PRINTER_DEVICE* printer_dev = nullptr; + UINT error = ERROR_INTERNAL_ERROR; + + WINPR_ASSERT(pEntryPoints); + WINPR_ASSERT(printer); + + printer_dev = (PRINTER_DEVICE*)calloc(1, sizeof(PRINTER_DEVICE)); + + if (!printer_dev) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + printer_dev->device.data = Stream_New(nullptr, 1024); + + if (!printer_dev->device.data) + goto error_out; + + (void)sprintf_s(printer_dev->port, sizeof(printer_dev->port), "PRN%" PRIuz, printer->id); + printer_dev->device.type = RDPDR_DTYP_PRINT; + printer_dev->device.name = printer_dev->port; + printer_dev->device.IRPRequest = printer_irp_request; + printer_dev->device.CustomComponentRequest = printer_custom_component; + printer_dev->device.Free = printer_free; + printer_dev->rdpcontext = pEntryPoints->rdpcontext; + printer_dev->printer = printer; + + if (!freerdp_settings_get_bool(pEntryPoints->rdpcontext->settings, + FreeRDP_SynchronousStaticChannels)) + printer_dev->async = TRUE; + + if (!printer_load_from_config(pEntryPoints->rdpcontext->settings, printer, printer_dev)) + goto error_out; + + if (printer_dev->async) + { + printer_dev->pIrpList = (WINPR_PSLIST_HEADER)winpr_aligned_malloc( + sizeof(WINPR_SLIST_HEADER), MEMORY_ALLOCATION_ALIGNMENT); + + if (!printer_dev->pIrpList) + { + WLog_ERR(TAG, "_aligned_malloc failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + + InitializeSListHead(printer_dev->pIrpList); + + printer_dev->event = CreateEvent(nullptr, TRUE, FALSE, nullptr); + if (!printer_dev->event) + { + WLog_ERR(TAG, "CreateEvent failed!"); + error = ERROR_INTERNAL_ERROR; + goto error_out; + } + + printer_dev->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + if (!printer_dev->stopEvent) + { + WLog_ERR(TAG, "CreateEvent failed!"); + error = ERROR_INTERNAL_ERROR; + goto error_out; + } + } + + error = pEntryPoints->RegisterDevice(pEntryPoints->devman, &printer_dev->device); + if (error) + { + WLog_ERR(TAG, "RegisterDevice failed with error %" PRIu32 "!", error); + goto error_out; + } + + if (printer_dev->async) + { + printer_dev->thread = + CreateThread(nullptr, 0, printer_thread_func, (void*)printer_dev, 0, nullptr); + if (!printer_dev->thread) + { + WLog_ERR(TAG, "CreateThread failed!"); + error = ERROR_INTERNAL_ERROR; + goto error_out; + } + } + + WINPR_ASSERT(printer->AddRef); + printer->AddRef(printer); + return CHANNEL_RC_OK; +error_out: + printer_free(&printer_dev->device); + return error; +} + +static rdpPrinterDriver* printer_load_backend(const char* backend) +{ + typedef UINT(VCAPITYPE * backend_load_t)(rdpPrinterDriver**); + PVIRTUALCHANNELENTRY entry = freerdp_load_channel_addin_entry("printer", backend, nullptr, 0); + backend_load_t func = WINPR_FUNC_PTR_CAST(entry, backend_load_t); + if (!func) + return nullptr; + + rdpPrinterDriver* printer = nullptr; + const UINT rc = func(&printer); + if (rc != CHANNEL_RC_OK) + return nullptr; + + return printer; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT( + UINT VCAPITYPE printer_DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints)) +{ + char* name = nullptr; + char* driver_name = nullptr; + BOOL default_backend = TRUE; + RDPDR_PRINTER* device = nullptr; + rdpPrinterDriver* driver = nullptr; + UINT error = CHANNEL_RC_OK; + + if (!pEntryPoints || !pEntryPoints->device) + return ERROR_INVALID_PARAMETER; + + device = (RDPDR_PRINTER*)pEntryPoints->device; + name = device->device.Name; + driver_name = _strdup(device->DriverName); + + /* Secondary argument is one of the following: + * + * ... name of a printer driver + * : ... name of a printer driver and local printer backend to use + */ + if (driver_name) + { + char* sep = strstr(driver_name, ":"); + if (sep) + { + const char* backend = sep + 1; + *sep = '\0'; + driver = printer_load_backend(backend); + default_backend = FALSE; + } + } + + if (!driver && default_backend) + { + const char* backend = +#if defined(WITH_CUPS) + "cups" +#elif defined(_WIN32) + "win" +#else + "" +#endif + ; + + driver = printer_load_backend(backend); + } + + if (!driver) + { + WLog_ERR(TAG, "Could not get a printer driver!"); + error = CHANNEL_RC_INITIALIZATION_ERROR; + goto fail; + } + + if (name && name[0]) + { + WINPR_ASSERT(driver->GetPrinter); + rdpPrinter* printer = driver->GetPrinter(driver, name, driver_name, device->IsDefault); + + if (!printer) + { + WLog_ERR(TAG, "Could not get printer %s!", name); + error = CHANNEL_RC_INITIALIZATION_ERROR; + goto fail; + } + + WINPR_ASSERT(printer->ReleaseRef); + if (!printer_save_default_config(pEntryPoints->rdpcontext->settings, printer)) + { + error = CHANNEL_RC_INITIALIZATION_ERROR; + printer->ReleaseRef(printer); + goto fail; + } + + error = printer_register(pEntryPoints, printer); + printer->ReleaseRef(printer); + if (error) + { + WLog_ERR(TAG, "printer_register failed with error %" PRIu32 "!", error); + goto fail; + } + } + else + { + WINPR_ASSERT(driver->EnumPrinters); + rdpPrinter** printers = driver->EnumPrinters(driver); + if (printers) + { + for (rdpPrinter** current = printers; *current; ++current) + { + error = printer_register(pEntryPoints, *current); + if (error) + { + WLog_ERR(TAG, "printer_register failed with error %" PRIu32 "!", error); + break; + } + } + } + else + { + WLog_ERR(TAG, "Failed to enumerate printers!"); + error = CHANNEL_RC_INITIALIZATION_ERROR; + } + + WINPR_ASSERT(driver->ReleaseEnumPrinters); + driver->ReleaseEnumPrinters(printers); + } + +fail: + free(driver_name); + if (driver) + { + WINPR_ASSERT(driver->ReleaseRef); + driver->ReleaseRef(driver); + } + + return error; +} diff --git a/third_party/FreeRDP/channels/printer/client/win/CMakeLists.txt b/third_party/FreeRDP/channels/printer/client/win/CMakeLists.txt new file mode 100644 index 0000000..454683b --- /dev/null +++ b/third_party/FreeRDP/channels/printer/client/win/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2019 Armin Novak +# Copyright 2019 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. +define_channel_client_subsystem("printer" "win" "") + +set(${MODULE_PREFIX}_SRCS printer_win.c) + +set(${MODULE_PREFIX}_LIBS winpr freerdp) + +include_directories(..) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") diff --git a/third_party/FreeRDP/channels/printer/client/win/printer_win.c b/third_party/FreeRDP/channels/printer/client/win/printer_win.c new file mode 100644 index 0000000..38b9a80 --- /dev/null +++ b/third_party/FreeRDP/channels/printer/client/win/printer_win.c @@ -0,0 +1,480 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Print Virtual Channel - WIN driver + * + * Copyright 2012 Gerald Richter + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#define WIDEN_INT(x) L##x +#define WIDEN(x) WIDEN_INT(x) +#define PRINTER_TAG CHANNELS_TAG("printer.client") +#ifdef WITH_DEBUG_WINPR +#define DEBUG_WINPR(...) WLog_DBG(PRINTER_TAG, __VA_ARGS__) +#else +#define DEBUG_WINPR(...) \ + do \ + { \ + } while (0) +#endif + +typedef struct +{ + rdpPrinterDriver driver; + + size_t id_sequence; + size_t references; +} rdpWinPrinterDriver; + +typedef struct +{ + rdpPrintJob printjob; + DOC_INFO_1 di; + DWORD handle; + + void* printjob_object; + int printjob_id; +} rdpWinPrintJob; + +typedef struct +{ + rdpPrinter printer; + HANDLE hPrinter; + rdpWinPrintJob* printjob; +} rdpWinPrinter; + +WINPR_ATTR_MALLOC(free, 1) +static WCHAR* printer_win_get_printjob_name(size_t id) +{ + struct tm tres = WINPR_C_ARRAY_INIT; + WCHAR* str = nullptr; + size_t len = 0; + + const time_t tt = time(nullptr); + const errno_t err = localtime_s(&tres, &tt); + + do + { + if (len > 0) + { + str = calloc(len + 1, sizeof(WCHAR)); + if (!str) + return nullptr; + } + + const int rc = swprintf_s( + str, len, + WIDEN("%s Print %04d-%02d-%02d %02d-%02d-%02d - Job %") WIDEN(PRIuz) WIDEN("\0"), + freerdp_getApplicationDetailsStringW(), tres.tm_year + 1900, tres.tm_mon + 1, + tres.tm_mday, tres.tm_hour, tres.tm_min, tres.tm_sec, id); + if (rc <= 0) + { + free(str); + return nullptr; + } + len = WINPR_ASSERTING_INT_CAST(size_t, rc) + 1ull; + } while (!str); + + return str; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT printer_win_write_printjob(rdpPrintJob* printjob, const BYTE* data, size_t size) +{ + LPCVOID pBuf = data; + DWORD pcWritten = 0; + + if (size > UINT32_MAX) + return ERROR_BAD_ARGUMENTS; + + if (!printjob || !data) + return ERROR_BAD_ARGUMENTS; + + rdpWinPrinter* printer = (rdpWinPrinter*)printjob->printer; + if (!printer) + return ERROR_BAD_ARGUMENTS; + + DWORD cbBuf = WINPR_ASSERTING_INT_CAST(uint32_t, size); + if (!WritePrinter(printer->hPrinter, WINPR_CAST_CONST_PTR_AWAY(pBuf, void*), cbBuf, &pcWritten)) + return ERROR_INTERNAL_ERROR; + return CHANNEL_RC_OK; +} + +static void printer_win_close_printjob(rdpPrintJob* printjob) +{ + rdpWinPrintJob* win_printjob = (rdpWinPrintJob*)printjob; + rdpWinPrinter* win_printer; + + if (!printjob) + return; + + win_printer = (rdpWinPrinter*)printjob->printer; + if (!win_printer) + return; + + if (!EndPagePrinter(win_printer->hPrinter)) + { + } + + if (!EndDocPrinter(win_printer->hPrinter)) + { + } + + win_printer->printjob = nullptr; + + free(win_printjob->di.pDocName); + free(win_printjob); +} + +static rdpPrintJob* printer_win_create_printjob(rdpPrinter* printer, UINT32 id) +{ + rdpWinPrinter* win_printer = (rdpWinPrinter*)printer; + rdpWinPrintJob* win_printjob; + + if (win_printer->printjob != nullptr) + return nullptr; + + win_printjob = (rdpWinPrintJob*)calloc(1, sizeof(rdpWinPrintJob)); + if (!win_printjob) + return nullptr; + + win_printjob->printjob.id = id; + win_printjob->printjob.printer = printer; + win_printjob->di.pDocName = printer_win_get_printjob_name(id); + win_printjob->di.pDatatype = nullptr; + win_printjob->di.pOutputFile = nullptr; + + win_printjob->handle = StartDocPrinter(win_printer->hPrinter, 1, (LPBYTE) & (win_printjob->di)); + + if (!win_printjob->handle) + { + free(win_printjob->di.pDocName); + free(win_printjob); + return nullptr; + } + + if (!StartPagePrinter(win_printer->hPrinter)) + { + free(win_printjob->di.pDocName); + free(win_printjob); + return nullptr; + } + + win_printjob->printjob.Write = printer_win_write_printjob; + win_printjob->printjob.Close = printer_win_close_printjob; + + win_printer->printjob = win_printjob; + + return &win_printjob->printjob; +} + +static rdpPrintJob* printer_win_find_printjob(rdpPrinter* printer, UINT32 id) +{ + rdpWinPrinter* win_printer = (rdpWinPrinter*)printer; + + if (!win_printer->printjob) + return nullptr; + + if (win_printer->printjob->printjob.id != id) + return nullptr; + + return (rdpPrintJob*)win_printer->printjob; +} + +static void printer_win_free_printer(rdpPrinter* printer) +{ + rdpWinPrinter* win_printer = (rdpWinPrinter*)printer; + + if (win_printer->printjob) + win_printer->printjob->printjob.Close((rdpPrintJob*)win_printer->printjob); + + if (win_printer->hPrinter) + ClosePrinter(win_printer->hPrinter); + + if (printer->backend) + printer->backend->ReleaseRef(printer->backend); + + free(printer->name); + free(printer->driver); + free(printer); +} + +static void printer_win_add_ref_printer(rdpPrinter* printer) +{ + if (printer) + printer->references++; +} + +static void printer_win_release_ref_printer(rdpPrinter* printer) +{ + if (!printer) + return; + if (printer->references <= 1) + printer_win_free_printer(printer); + else + printer->references--; +} + +static rdpPrinter* printer_win_new_printer(rdpWinPrinterDriver* win_driver, const WCHAR* name, + const WCHAR* drivername, BOOL is_default) +{ + rdpWinPrinter* win_printer; + DWORD needed = 0; + PRINTER_INFO_2* prninfo = nullptr; + + if (!name) + return nullptr; + + win_printer = (rdpWinPrinter*)calloc(1, sizeof(rdpWinPrinter)); + if (!win_printer) + return nullptr; + + win_printer->printer.backend = &win_driver->driver; + win_printer->printer.id = win_driver->id_sequence++; + win_printer->printer.name = ConvertWCharToUtf8Alloc(name, nullptr); + if (!win_printer->printer.name) + goto fail; + + if (!win_printer->printer.name) + goto fail; + win_printer->printer.is_default = is_default; + + win_printer->printer.CreatePrintJob = printer_win_create_printjob; + win_printer->printer.FindPrintJob = printer_win_find_printjob; + win_printer->printer.AddRef = printer_win_add_ref_printer; + win_printer->printer.ReleaseRef = printer_win_release_ref_printer; + + if (!OpenPrinter(WINPR_CAST_CONST_PTR_AWAY(name, WCHAR*), &(win_printer->hPrinter), nullptr)) + goto fail; + + /* How many memory should be allocated for printer data */ + GetPrinter(win_printer->hPrinter, 2, (LPBYTE)prninfo, 0, &needed); + if (needed == 0) + goto fail; + + prninfo = (PRINTER_INFO_2*)GlobalAlloc(GPTR, needed); + if (!prninfo) + goto fail; + + if (!GetPrinter(win_printer->hPrinter, 2, (LPBYTE)prninfo, needed, &needed)) + { + GlobalFree(prninfo); + goto fail; + } + + if (drivername) + win_printer->printer.driver = ConvertWCharToUtf8Alloc(drivername, nullptr); + else + win_printer->printer.driver = ConvertWCharToUtf8Alloc(prninfo->pDriverName, nullptr); + GlobalFree(prninfo); + if (!win_printer->printer.driver) + goto fail; + + win_printer->printer.AddRef(&win_printer->printer); + win_printer->printer.backend->AddRef(win_printer->printer.backend); + return &win_printer->printer; + +fail: + printer_win_free_printer(&win_printer->printer); + return nullptr; +} + +static void printer_win_release_enum_printers(rdpPrinter** printers) +{ + rdpPrinter** cur = printers; + + while ((cur != nullptr) && ((*cur) != nullptr)) + { + if ((*cur)->ReleaseRef) + (*cur)->ReleaseRef(*cur); + cur++; + } + free(printers); +} + +static rdpPrinter** printer_win_enum_printers(rdpPrinterDriver* driver) +{ + rdpPrinter** printers; + int num_printers; + PRINTER_INFO_2* prninfo = nullptr; + DWORD needed, returned; + BOOL haveDefault = FALSE; + LPWSTR defaultPrinter = nullptr; + + GetDefaultPrinter(nullptr, &needed); + if (needed) + { + defaultPrinter = (LPWSTR)calloc(needed, sizeof(WCHAR)); + + if (!defaultPrinter) + return nullptr; + + if (!GetDefaultPrinter(defaultPrinter, &needed)) + defaultPrinter[0] = '\0'; + } + + /* find required size for the buffer */ + EnumPrinters(PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS, nullptr, 2, nullptr, 0, &needed, + &returned); + + /* allocate array of PRINTER_INFO structures */ + prninfo = (PRINTER_INFO_2*)GlobalAlloc(GPTR, needed); + if (!prninfo) + { + free(defaultPrinter); + return nullptr; + } + + /* call again */ + if (!EnumPrinters(PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS, nullptr, 2, (LPBYTE)prninfo, + needed, &needed, &returned)) + { + } + + printers = (rdpPrinter**)calloc((returned + 1), sizeof(rdpPrinter*)); + if (!printers) + { + GlobalFree(prninfo); + free(defaultPrinter); + return nullptr; + } + + num_printers = 0; + + for (int i = 0; i < (int)returned; i++) + { + rdpPrinter* current = printers[num_printers]; + current = printer_win_new_printer((rdpWinPrinterDriver*)driver, prninfo[i].pPrinterName, + prninfo[i].pDriverName, + _wcscmp(prninfo[i].pPrinterName, defaultPrinter) == 0); + if (!current) + { + printer_win_release_enum_printers(printers); + printers = nullptr; + break; + } + if (current->is_default) + haveDefault = TRUE; + printers[num_printers++] = current; + } + + if (!haveDefault && (returned > 0)) + printers[0]->is_default = TRUE; + + GlobalFree(prninfo); + free(defaultPrinter); + return printers; +} + +static rdpPrinter* printer_win_get_printer(rdpPrinterDriver* driver, const char* name, + const char* driverName, BOOL isDefault) +{ + WCHAR* driverNameW = nullptr; + WCHAR* nameW = nullptr; + rdpWinPrinterDriver* win_driver = (rdpWinPrinterDriver*)driver; + rdpPrinter* myPrinter = nullptr; + + if (name) + { + nameW = ConvertUtf8ToWCharAlloc(name, nullptr); + if (!nameW) + return nullptr; + } + if (driverName) + { + driverNameW = ConvertUtf8ToWCharAlloc(driverName, nullptr); + if (!driverNameW) + return nullptr; + } + + myPrinter = printer_win_new_printer(win_driver, nameW, driverNameW, isDefault); + free(driverNameW); + free(nameW); + + return myPrinter; +} + +static void printer_win_add_ref_driver(rdpPrinterDriver* driver) +{ + rdpWinPrinterDriver* win = (rdpWinPrinterDriver*)driver; + if (win) + win->references++; +} + +/* Singleton */ +static rdpWinPrinterDriver* win_driver = nullptr; + +static void printer_win_release_ref_driver(rdpPrinterDriver* driver) +{ + rdpWinPrinterDriver* win = (rdpWinPrinterDriver*)driver; + if (win->references <= 1) + { + free(win); + win_driver = nullptr; + } + else + win->references--; +} + +FREERDP_ENTRY_POINT(UINT VCAPITYPE win_freerdp_printer_client_subsystem_entry(void* arg)) +{ + rdpPrinterDriver** ppPrinter = (rdpPrinterDriver**)arg; + if (!ppPrinter) + return ERROR_INVALID_PARAMETER; + + if (!win_driver) + { + win_driver = (rdpWinPrinterDriver*)calloc(1, sizeof(rdpWinPrinterDriver)); + + if (!win_driver) + return ERROR_OUTOFMEMORY; + + win_driver->driver.EnumPrinters = printer_win_enum_printers; + win_driver->driver.ReleaseEnumPrinters = printer_win_release_enum_printers; + win_driver->driver.GetPrinter = printer_win_get_printer; + + win_driver->driver.AddRef = printer_win_add_ref_driver; + win_driver->driver.ReleaseRef = printer_win_release_ref_driver; + + win_driver->id_sequence = 1; + } + + win_driver->driver.AddRef(&win_driver->driver); + + *ppPrinter = &win_driver->driver; + return CHANNEL_RC_OK; +} diff --git a/third_party/FreeRDP/channels/printer/printer.h b/third_party/FreeRDP/channels/printer/printer.h new file mode 100644 index 0000000..ae0902d --- /dev/null +++ b/third_party/FreeRDP/channels/printer/printer.h @@ -0,0 +1,36 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Definition for the printer channel + * + * Copyright 2016 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_PRINTER_PRINTER_H +#define FREERDP_CHANNEL_PRINTER_PRINTER_H + +/* SERVER_PRINTER_CACHE_EVENT.cachedata */ +#define RDPDR_ADD_PRINTER_EVENT 0x00000001 +#define RDPDR_UPDATE_PRINTER_EVENT 0x00000002 +#define RDPDR_DELETE_PRINTER_EVENT 0x00000003 +#define RDPDR_RENAME_PRINTER_EVENT 0x00000004 + +/* DR_PRN_DEVICE_ANNOUNCE.Flags */ +#define RDPDR_PRINTER_ANNOUNCE_FLAG_ASCII 0x00000001 +#define RDPDR_PRINTER_ANNOUNCE_FLAG_DEFAULTPRINTER 0x00000002 +#define RDPDR_PRINTER_ANNOUNCE_FLAG_NETWORKPRINTER 0x00000004 +#define RDPDR_PRINTER_ANNOUNCE_FLAG_TSPRINTER 0x00000008 +#define RDPDR_PRINTER_ANNOUNCE_FLAG_XPSFORMAT 0x00000010 + +#endif /* FREERDP_CHANNEL_PRINTER_PRINTER_H */ diff --git a/third_party/FreeRDP/channels/rail/CMakeLists.txt b/third_party/FreeRDP/channels/rail/CMakeLists.txt new file mode 100644 index 0000000..2d0c073 --- /dev/null +++ b/third_party/FreeRDP/channels/rail/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel("rail") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/third_party/FreeRDP/channels/rail/ChannelOptions.cmake b/third_party/FreeRDP/channels/rail/ChannelOptions.cmake new file mode 100644 index 0000000..8cff636 --- /dev/null +++ b/third_party/FreeRDP/channels/rail/ChannelOptions.cmake @@ -0,0 +1,20 @@ +set(OPTION_DEFAULT ON) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options( + NAME + "rail" + TYPE + "static" + DESCRIPTION + "Remote Programs Virtual Channel Extension" + SPECIFICATIONS + "[MS-RDPERP]" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/rail/client/CMakeLists.txt b/third_party/FreeRDP/channels/rail/client/CMakeLists.txt new file mode 100644 index 0000000..5227766 --- /dev/null +++ b/third_party/FreeRDP/channels/rail/client/CMakeLists.txt @@ -0,0 +1,31 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_client("rail") + +set(${MODULE_PREFIX}_SRCS + ../rail_common.h + ../rail_common.c + client_rails.c + rail_main.c + rail_main.h + rail_orders.c + rail_orders.h +) + +set(${MODULE_PREFIX}_LIBS freerdp) +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntryEx") diff --git a/third_party/FreeRDP/channels/rail/client/client_rails.c b/third_party/FreeRDP/channels/rail/client/client_rails.c new file mode 100644 index 0000000..e7ee89c --- /dev/null +++ b/third_party/FreeRDP/channels/rail/client/client_rails.c @@ -0,0 +1,104 @@ + +#include + +#include +#include + +#include "rail_main.h" + +UINT client_rail_server_start_cmd(RailClientContext* context) +{ + UINT status = 0; + char argsAndFile[520] = WINPR_C_ARRAY_INIT; + RAIL_EXEC_ORDER exec = WINPR_C_ARRAY_INIT; + RAIL_SYSPARAM_ORDER sysparam = WINPR_C_ARRAY_INIT; + RAIL_CLIENT_STATUS_ORDER clientStatus = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(context); + railPlugin* rail = context->handle; + WINPR_ASSERT(rail); + WINPR_ASSERT(rail->rdpcontext); + + const rdpSettings* settings = rail->rdpcontext->settings; + WINPR_ASSERT(settings); + + clientStatus.flags = TS_RAIL_CLIENTSTATUS_ALLOWLOCALMOVESIZE; + + if (freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled)) + clientStatus.flags |= TS_RAIL_CLIENTSTATUS_AUTORECONNECT; + + clientStatus.flags |= TS_RAIL_CLIENTSTATUS_ZORDER_SYNC; + clientStatus.flags |= TS_RAIL_CLIENTSTATUS_WINDOW_RESIZE_MARGIN_SUPPORTED; + clientStatus.flags |= TS_RAIL_CLIENTSTATUS_APPBAR_REMOTING_SUPPORTED; + clientStatus.flags |= TS_RAIL_CLIENTSTATUS_POWER_DISPLAY_REQUEST_SUPPORTED; + clientStatus.flags |= TS_RAIL_CLIENTSTATUS_BIDIRECTIONAL_CLOAK_SUPPORTED; + status = context->ClientInformation(context, &clientStatus); + + if (status != CHANNEL_RC_OK) + return status; + + if (freerdp_settings_get_bool(settings, FreeRDP_RemoteAppLanguageBarSupported)) + { + RAIL_LANGBAR_INFO_ORDER langBarInfo; + langBarInfo.languageBarStatus = 0x00000008; /* TF_SFT_HIDDEN */ + status = context->ClientLanguageBarInfo(context, &langBarInfo); + + /* We want the language bar, but the server might not support it. */ + switch (status) + { + case CHANNEL_RC_OK: + case ERROR_BAD_CONFIGURATION: + break; + default: + return status; + } + } + + sysparam.params = 0; + sysparam.params |= SPI_MASK_SET_HIGH_CONTRAST; + sysparam.highContrast.colorScheme.string = nullptr; + sysparam.highContrast.colorScheme.length = 0; + sysparam.highContrast.flags = 0x7E; + sysparam.params |= SPI_MASK_SET_MOUSE_BUTTON_SWAP; + sysparam.mouseButtonSwap = FALSE; + sysparam.params |= SPI_MASK_SET_KEYBOARD_PREF; + sysparam.keyboardPref = FALSE; + sysparam.params |= SPI_MASK_SET_DRAG_FULL_WINDOWS; + sysparam.dragFullWindows = FALSE; + sysparam.params |= SPI_MASK_SET_KEYBOARD_CUES; + sysparam.keyboardCues = FALSE; + sysparam.params |= SPI_MASK_SET_WORK_AREA; + sysparam.workArea.left = 0; + sysparam.workArea.top = 0; + + const UINT32 w = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + const UINT32 h = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + + sysparam.workArea.right = WINPR_ASSERTING_INT_CAST(UINT16, w); + sysparam.workArea.bottom = WINPR_ASSERTING_INT_CAST(UINT16, h); + sysparam.dragFullWindows = FALSE; + status = context->ClientSystemParam(context, &sysparam); + + if (status != CHANNEL_RC_OK) + return status; + + const char* RemoteApplicationFile = + freerdp_settings_get_string(settings, FreeRDP_RemoteApplicationFile); + const char* RemoteApplicationCmdLine = + freerdp_settings_get_string(settings, FreeRDP_RemoteApplicationCmdLine); + if (RemoteApplicationFile && RemoteApplicationCmdLine) + { + (void)_snprintf(argsAndFile, ARRAYSIZE(argsAndFile), "%s %s", RemoteApplicationCmdLine, + RemoteApplicationFile); + exec.RemoteApplicationArguments = argsAndFile; + } + else if (RemoteApplicationFile) + exec.RemoteApplicationArguments = RemoteApplicationFile; + else + exec.RemoteApplicationArguments = RemoteApplicationCmdLine; + exec.RemoteApplicationProgram = + freerdp_settings_get_string(settings, FreeRDP_RemoteApplicationProgram); + exec.RemoteApplicationWorkingDir = + freerdp_settings_get_string(settings, FreeRDP_ShellWorkingDirectory); + return context->ClientExecute(context, &exec); +} diff --git a/third_party/FreeRDP/channels/rail/client/rail_main.c b/third_party/FreeRDP/channels/rail/client/rail_main.c new file mode 100644 index 0000000..1a9528c --- /dev/null +++ b/third_party/FreeRDP/channels/rail/client/rail_main.c @@ -0,0 +1,761 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RAIL Virtual Channel Plugin + * + * Copyright 2011 Marc-Andre Moreau + * Copyright 2011 Roman Barabanov + * Copyright 2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2017 Armin Novak + * Copyright 2017 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 + +#include + +#include +#include +#include + +#include "rail_orders.h" +#include "rail_main.h" + +#include "../../../channels/client/addin.h" + +RailClientContext* rail_get_client_interface(railPlugin* rail) +{ + RailClientContext* pInterface = nullptr; + + if (!rail) + return nullptr; + + pInterface = (RailClientContext*)rail->channelEntryPoints.pInterface; + return pInterface; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send(railPlugin* rail, wStream* s) +{ + UINT status = 0; + + if (!rail) + { + Stream_Free(s, TRUE); + return CHANNEL_RC_BAD_INIT_HANDLE; + } + + status = rail->channelEntryPoints.pVirtualChannelWriteEx( + rail->InitHandle, rail->OpenHandle, Stream_Buffer(s), (UINT32)Stream_GetPosition(s), s); + + if (status != CHANNEL_RC_OK) + { + Stream_Free(s, TRUE); + WLog_ERR(TAG, "pVirtualChannelWriteEx failed with %s [%08" PRIX32 "]", + WTSErrorToString(status), status); + } + + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_send_channel_data(railPlugin* rail, wStream* src) +{ + if (!rail || !src) + { + Stream_Free(src, TRUE); + return ERROR_INVALID_PARAMETER; + } + + return rail_send(rail, src); +} + +/** + * Callback Interface + */ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_client_execute(RailClientContext* context, const RAIL_EXEC_ORDER* exec) +{ + const char* exeOrFile = nullptr; + UINT error = 0; + railPlugin* rail = nullptr; + UINT16 flags = 0; + RAIL_UNICODE_STRING ruExeOrFile = WINPR_C_ARRAY_INIT; + RAIL_UNICODE_STRING ruWorkingDir = WINPR_C_ARRAY_INIT; + RAIL_UNICODE_STRING ruArguments = WINPR_C_ARRAY_INIT; + + if (!context || !exec) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*)context->handle; + exeOrFile = exec->RemoteApplicationProgram; + flags = exec->flags; + + if (!exeOrFile) + return ERROR_INVALID_PARAMETER; + + if (!utf8_string_to_rail_string(exec->RemoteApplicationProgram, + &ruExeOrFile) || /* RemoteApplicationProgram */ + !utf8_string_to_rail_string(exec->RemoteApplicationWorkingDir, + &ruWorkingDir) || /* ShellWorkingDirectory */ + !utf8_string_to_rail_string(exec->RemoteApplicationArguments, + &ruArguments)) /* RemoteApplicationCmdLine */ + error = ERROR_INTERNAL_ERROR; + else + error = rail_send_client_exec_order(rail, flags, &ruExeOrFile, &ruWorkingDir, &ruArguments); + + free(ruExeOrFile.string); + free(ruWorkingDir.string); + free(ruArguments.string); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_client_activate(RailClientContext* context, const RAIL_ACTIVATE_ORDER* activate) +{ + railPlugin* rail = nullptr; + + if (!context || !activate) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*)context->handle; + return rail_send_client_activate_order(rail, activate); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send_client_sysparam(RailClientContext* context, RAIL_SYSPARAM_ORDER* sysparam) +{ + wStream* s = nullptr; + size_t length = RAIL_SYSPARAM_ORDER_LENGTH; + railPlugin* rail = nullptr; + UINT error = 0; + BOOL extendedSpiSupported = 0; + + if (!context || !sysparam) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*)context->handle; + + switch (sysparam->param) + { + case SPI_SET_DRAG_FULL_WINDOWS: + case SPI_SET_KEYBOARD_CUES: + case SPI_SET_KEYBOARD_PREF: + case SPI_SET_MOUSE_BUTTON_SWAP: + length += 1; + break; + + case SPI_SET_WORK_AREA: + case SPI_DISPLAY_CHANGE: + case SPI_TASKBAR_POS: + length += 8; + break; + + case SPI_SET_HIGH_CONTRAST: + length += sysparam->highContrast.colorSchemeLength + 10; + break; + + case SPI_SETFILTERKEYS: + length += 20; + break; + + case SPI_SETSTICKYKEYS: + case SPI_SETCARETWIDTH: + case SPI_SETTOGGLEKEYS: + length += 4; + break; + + default: + return ERROR_BAD_ARGUMENTS; + } + + s = rail_pdu_init(length); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + extendedSpiSupported = rail_is_extended_spi_supported(rail->channelFlags); + if ((error = rail_write_sysparam_order(s, sysparam, extendedSpiSupported))) + { + WLog_ERR(TAG, "rail_write_client_sysparam_order failed with error %" PRIu32 "!", error); + Stream_Free(s, TRUE); + return error; + } + + return rail_send_pdu(rail, s, TS_RAIL_ORDER_SYSPARAM); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_client_system_param(RailClientContext* context, + const RAIL_SYSPARAM_ORDER* sysInParam) +{ + UINT error = CHANNEL_RC_OK; + RAIL_SYSPARAM_ORDER sysparam; + + if (!context || !sysInParam) + return ERROR_INVALID_PARAMETER; + + sysparam = *sysInParam; + + if (sysparam.params & SPI_MASK_SET_HIGH_CONTRAST) + { + sysparam.param = SPI_SET_HIGH_CONTRAST; + + if ((error = rail_send_client_sysparam(context, &sysparam))) + { + WLog_ERR(TAG, "rail_send_client_sysparam failed with error %" PRIu32 "!", error); + return error; + } + } + + if (sysparam.params & SPI_MASK_TASKBAR_POS) + { + sysparam.param = SPI_TASKBAR_POS; + + if ((error = rail_send_client_sysparam(context, &sysparam))) + { + WLog_ERR(TAG, "rail_send_client_sysparam failed with error %" PRIu32 "!", error); + return error; + } + } + + if (sysparam.params & SPI_MASK_SET_MOUSE_BUTTON_SWAP) + { + sysparam.param = SPI_SET_MOUSE_BUTTON_SWAP; + + if ((error = rail_send_client_sysparam(context, &sysparam))) + { + WLog_ERR(TAG, "rail_send_client_sysparam failed with error %" PRIu32 "!", error); + return error; + } + } + + if (sysparam.params & SPI_MASK_SET_KEYBOARD_PREF) + { + sysparam.param = SPI_SET_KEYBOARD_PREF; + + if ((error = rail_send_client_sysparam(context, &sysparam))) + { + WLog_ERR(TAG, "rail_send_client_sysparam failed with error %" PRIu32 "!", error); + return error; + } + } + + if (sysparam.params & SPI_MASK_SET_DRAG_FULL_WINDOWS) + { + sysparam.param = SPI_SET_DRAG_FULL_WINDOWS; + + if ((error = rail_send_client_sysparam(context, &sysparam))) + { + WLog_ERR(TAG, "rail_send_client_sysparam failed with error %" PRIu32 "!", error); + return error; + } + } + + if (sysparam.params & SPI_MASK_SET_KEYBOARD_CUES) + { + sysparam.param = SPI_SET_KEYBOARD_CUES; + + if ((error = rail_send_client_sysparam(context, &sysparam))) + { + WLog_ERR(TAG, "rail_send_client_sysparam failed with error %" PRIu32 "!", error); + return error; + } + } + + if (sysparam.params & SPI_MASK_SET_WORK_AREA) + { + sysparam.param = SPI_SET_WORK_AREA; + + if ((error = rail_send_client_sysparam(context, &sysparam))) + { + WLog_ERR(TAG, "rail_send_client_sysparam failed with error %" PRIu32 "!", error); + return error; + } + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_client_system_command(RailClientContext* context, + const RAIL_SYSCOMMAND_ORDER* syscommand) +{ + railPlugin* rail = nullptr; + + if (!context || !syscommand) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*)context->handle; + return rail_send_client_syscommand_order(rail, syscommand); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_client_handshake(RailClientContext* context, const RAIL_HANDSHAKE_ORDER* handshake) +{ + railPlugin* rail = nullptr; + + if (!context || !handshake) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*)context->handle; + return rail_send_handshake_order(rail, handshake); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_client_notify_event(RailClientContext* context, + const RAIL_NOTIFY_EVENT_ORDER* notifyEvent) +{ + railPlugin* rail = nullptr; + + if (!context || !notifyEvent) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*)context->handle; + return rail_send_client_notify_event_order(rail, notifyEvent); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_client_window_move(RailClientContext* context, + const RAIL_WINDOW_MOVE_ORDER* windowMove) +{ + railPlugin* rail = nullptr; + + if (!context || !windowMove) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*)context->handle; + return rail_send_client_window_move_order(rail, windowMove); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_client_information(RailClientContext* context, + const RAIL_CLIENT_STATUS_ORDER* clientStatus) +{ + railPlugin* rail = nullptr; + + if (!context || !clientStatus) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*)context->handle; + return rail_send_client_status_order(rail, clientStatus); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_client_system_menu(RailClientContext* context, const RAIL_SYSMENU_ORDER* sysmenu) +{ + railPlugin* rail = nullptr; + + if (!context || !sysmenu) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*)context->handle; + return rail_send_client_sysmenu_order(rail, sysmenu); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_client_language_bar_info(RailClientContext* context, + const RAIL_LANGBAR_INFO_ORDER* langBarInfo) +{ + railPlugin* rail = nullptr; + + if (!context || !langBarInfo) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*)context->handle; + return rail_send_client_langbar_info_order(rail, langBarInfo); +} + +static UINT rail_client_language_ime_info(RailClientContext* context, + const RAIL_LANGUAGEIME_INFO_ORDER* langImeInfo) +{ + railPlugin* rail = nullptr; + + if (!context || !langImeInfo) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*)context->handle; + return rail_send_client_languageime_info_order(rail, langImeInfo); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_client_get_appid_request(RailClientContext* context, + const RAIL_GET_APPID_REQ_ORDER* getAppIdReq) +{ + railPlugin* rail = nullptr; + + if (!context || !getAppIdReq || !context->handle) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*)context->handle; + return rail_send_client_get_appid_req_order(rail, getAppIdReq); +} + +static UINT rail_client_compartment_info(RailClientContext* context, + const RAIL_COMPARTMENT_INFO_ORDER* compartmentInfo) +{ + railPlugin* rail = nullptr; + + if (!context || !compartmentInfo || !context->handle) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*)context->handle; + return rail_send_client_compartment_info_order(rail, compartmentInfo); +} + +static UINT rail_client_cloak(RailClientContext* context, const RAIL_CLOAK* cloak) +{ + railPlugin* rail = nullptr; + + if (!context || !cloak || !context->handle) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*)context->handle; + return rail_send_client_cloak_order(rail, cloak); +} + +static UINT rail_client_snap_arrange(RailClientContext* context, const RAIL_SNAP_ARRANGE* snap) +{ + railPlugin* rail = nullptr; + + if (!context || !snap || !context->handle) + return ERROR_INVALID_PARAMETER; + + rail = (railPlugin*)context->handle; + return rail_send_client_snap_arrange_order(rail, snap); +} + +static UINT rail_client_text_scale(RailClientContext* context, UINT32 textScale) +{ + if (!context || !context->handle) + return ERROR_INVALID_PARAMETER; + + railPlugin* rail = (railPlugin*)context->handle; + return rail_send_client_text_scale_order(rail, textScale); +} + +static UINT rail_client_caret_blink_rate(RailClientContext* context, UINT32 rate) +{ + if (!context || !context->handle) + return ERROR_INVALID_PARAMETER; + + railPlugin* rail = (railPlugin*)context->handle; + return rail_send_client_caret_blink_rate_order(rail, rate); +} + +static VOID VCAPITYPE rail_virtual_channel_open_event_ex(LPVOID lpUserParam, DWORD openHandle, + UINT event, LPVOID pData, + UINT32 dataLength, UINT32 totalLength, + UINT32 dataFlags) +{ + UINT error = CHANNEL_RC_OK; + railPlugin* rail = (railPlugin*)lpUserParam; + + switch (event) + { + case CHANNEL_EVENT_DATA_RECEIVED: + if (!rail || (rail->OpenHandle != openHandle)) + { + WLog_ERR(TAG, "error no match"); + return; + } + + if ((error = channel_client_post_message(rail->MsgsHandle, pData, dataLength, + totalLength, dataFlags))) + { + WLog_ERR(TAG, + "rail_virtual_channel_event_data_received" + " failed with error %" PRIu32 "!", + error); + } + + break; + + case CHANNEL_EVENT_WRITE_CANCELLED: + case CHANNEL_EVENT_WRITE_COMPLETE: + { + wStream* s = (wStream*)pData; + Stream_Free(s, TRUE); + } + break; + + case CHANNEL_EVENT_USER: + break; + default: + break; + } + + if (error && rail && rail->rdpcontext) + setChannelError(rail->rdpcontext, error, + "rail_virtual_channel_open_event reported an error"); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_virtual_channel_event_connected(railPlugin* rail, WINPR_ATTR_UNUSED LPVOID pData, + WINPR_ATTR_UNUSED UINT32 dataLength) +{ + RailClientContext* context = rail_get_client_interface(rail); + UINT status = CHANNEL_RC_OK; + + WINPR_ASSERT(rail); + + if (context) + { + IFCALLRET(context->OnOpen, status, context, &rail->sendHandshake); + + if (status != CHANNEL_RC_OK) + WLog_ERR(TAG, "context->OnOpen failed with %s [%08" PRIX32 "]", + WTSErrorToString(status), status); + } + rail->MsgsHandle = channel_client_create_handler(rail->rdpcontext, rail, rail_order_recv, + RAIL_SVC_CHANNEL_NAME); + if (!rail->MsgsHandle) + return ERROR_INTERNAL_ERROR; + + return rail->channelEntryPoints.pVirtualChannelOpenEx(rail->InitHandle, &rail->OpenHandle, + rail->channelDef.name, + rail_virtual_channel_open_event_ex); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_virtual_channel_event_disconnected(railPlugin* rail) +{ + UINT rc = 0; + + channel_client_quit_handler(rail->MsgsHandle); + if (rail->OpenHandle == 0) + return CHANNEL_RC_OK; + + WINPR_ASSERT(rail->channelEntryPoints.pVirtualChannelCloseEx); + rc = rail->channelEntryPoints.pVirtualChannelCloseEx(rail->InitHandle, rail->OpenHandle); + + if (CHANNEL_RC_OK != rc) + { + WLog_ERR(TAG, "pVirtualChannelCloseEx failed with %s [%08" PRIX32 "]", WTSErrorToString(rc), + rc); + return rc; + } + + rail->OpenHandle = 0; + + return CHANNEL_RC_OK; +} + +static void rail_virtual_channel_event_terminated(railPlugin* rail) +{ + rail->InitHandle = nullptr; + free(rail->context); + free(rail); +} + +static VOID VCAPITYPE rail_virtual_channel_init_event_ex(LPVOID lpUserParam, LPVOID pInitHandle, + UINT event, LPVOID pData, UINT dataLength) +{ + UINT error = CHANNEL_RC_OK; + railPlugin* rail = (railPlugin*)lpUserParam; + + if (!rail || (rail->InitHandle != pInitHandle)) + { + WLog_ERR(TAG, "error no match"); + return; + } + + switch (event) + { + case CHANNEL_EVENT_CONNECTED: + if ((error = rail_virtual_channel_event_connected(rail, pData, dataLength))) + WLog_ERR(TAG, "rail_virtual_channel_event_connected failed with error %" PRIu32 "!", + error); + + break; + + case CHANNEL_EVENT_DISCONNECTED: + if ((error = rail_virtual_channel_event_disconnected(rail))) + WLog_ERR(TAG, + "rail_virtual_channel_event_disconnected failed with error %" PRIu32 "!", + error); + + break; + + case CHANNEL_EVENT_TERMINATED: + rail_virtual_channel_event_terminated(rail); + break; + + case CHANNEL_EVENT_ATTACHED: + case CHANNEL_EVENT_DETACHED: + default: + break; + } + + if (error && rail->rdpcontext) + setChannelError(rail->rdpcontext, error, + "rail_virtual_channel_init_event_ex reported an error"); +} + +/* rail is always built-in */ +#define VirtualChannelEntryEx rail_VirtualChannelEntryEx + +FREERDP_ENTRY_POINT(BOOL VCAPITYPE VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS_EX pEntryPoints, + PVOID pInitHandle)) +{ + UINT rc = 0; + railPlugin* rail = nullptr; + RailClientContext* context = nullptr; + CHANNEL_ENTRY_POINTS_FREERDP_EX* pEntryPointsEx = nullptr; + BOOL isFreerdp = FALSE; + rail = (railPlugin*)calloc(1, sizeof(railPlugin)); + + if (!rail) + { + WLog_ERR(TAG, "calloc failed!"); + return FALSE; + } + + /* Default to automatically replying to server handshakes */ + rail->sendHandshake = TRUE; + rail->channelDef.options = CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP | + CHANNEL_OPTION_COMPRESS_RDP | CHANNEL_OPTION_SHOW_PROTOCOL; + (void)sprintf_s(rail->channelDef.name, ARRAYSIZE(rail->channelDef.name), RAIL_SVC_CHANNEL_NAME); + pEntryPointsEx = (CHANNEL_ENTRY_POINTS_FREERDP_EX*)pEntryPoints; + + if ((pEntryPointsEx->cbSize >= sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)) && + (pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER)) + { + context = (RailClientContext*)calloc(1, sizeof(RailClientContext)); + + if (!context) + { + WLog_ERR(TAG, "calloc failed!"); + free(rail); + return FALSE; + } + + context->handle = (void*)rail; + context->custom = nullptr; + context->ClientExecute = rail_client_execute; + context->ClientActivate = rail_client_activate; + context->ClientSystemParam = rail_client_system_param; + context->ClientSystemCommand = rail_client_system_command; + context->ClientHandshake = rail_client_handshake; + context->ClientNotifyEvent = rail_client_notify_event; + context->ClientWindowMove = rail_client_window_move; + context->ClientInformation = rail_client_information; + context->ClientSystemMenu = rail_client_system_menu; + context->ClientLanguageBarInfo = rail_client_language_bar_info; + context->ClientLanguageIMEInfo = rail_client_language_ime_info; + context->ClientGetAppIdRequest = rail_client_get_appid_request; + context->ClientSnapArrange = rail_client_snap_arrange; + context->ClientCloak = rail_client_cloak; + context->ClientCompartmentInfo = rail_client_compartment_info; + context->ClientTextScale = rail_client_text_scale; + context->ClientCaretBlinkRate = rail_client_caret_blink_rate; + rail->rdpcontext = pEntryPointsEx->context; + rail->context = context; + isFreerdp = TRUE; + } + + rail->log = WLog_Get("com.freerdp.channels.rail.client"); + WLog_Print(rail->log, WLOG_DEBUG, "VirtualChannelEntryEx"); + CopyMemory(&(rail->channelEntryPoints), pEntryPoints, sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)); + rail->InitHandle = pInitHandle; + rc = rail->channelEntryPoints.pVirtualChannelInitEx( + rail, context, pInitHandle, &rail->channelDef, 1, VIRTUAL_CHANNEL_VERSION_WIN2000, + rail_virtual_channel_init_event_ex); + + if (CHANNEL_RC_OK != rc) + { + WLog_ERR(TAG, "failed with %s [%08" PRIX32 "]", WTSErrorToString(rc), rc); + goto error_out; + } + + rail->channelEntryPoints.pInterface = context; + return TRUE; +error_out: + + if (isFreerdp) + free(rail->context); + + free(rail); + return FALSE; +} diff --git a/third_party/FreeRDP/channels/rail/client/rail_main.h b/third_party/FreeRDP/channels/rail/client/rail_main.h new file mode 100644 index 0000000..7c2a93e --- /dev/null +++ b/third_party/FreeRDP/channels/rail/client/rail_main.h @@ -0,0 +1,61 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RAIL Virtual Channel Plugin + * + * Copyright 2011 Marc-Andre Moreau + * Copyright 2011 Roman Barabanov + * Copyright 2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RAIL_CLIENT_MAIN_H +#define FREERDP_CHANNEL_RAIL_CLIENT_MAIN_H + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../rail_common.h" + +typedef struct +{ + CHANNEL_DEF channelDef; + CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints; + + RailClientContext* context; + + wLog* log; + void* InitHandle; + DWORD OpenHandle; + void* MsgsHandle; + rdpContext* rdpcontext; + DWORD channelBuildNumber; + DWORD channelFlags; + RAIL_CLIENT_STATUS_ORDER clientStatus; + BOOL sendHandshake; +} railPlugin; + +WINPR_ATTR_NODISCARD FREERDP_LOCAL RailClientContext* rail_get_client_interface(railPlugin* rail); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT rail_send_channel_data(railPlugin* rail, wStream* s); + +#endif /* FREERDP_CHANNEL_RAIL_CLIENT_MAIN_H */ diff --git a/third_party/FreeRDP/channels/rail/client/rail_orders.c b/third_party/FreeRDP/channels/rail/client/rail_orders.c new file mode 100644 index 0000000..4f43077 --- /dev/null +++ b/third_party/FreeRDP/channels/rail/client/rail_orders.c @@ -0,0 +1,1610 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Remote Applications Integrated Locally (RAIL) Orders + * + * Copyright 2009 Marc-Andre Moreau + * Copyright 2011 Roman Barabanov + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2017 Armin Novak + * Copyright 2017 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 + +#include +#include + +#include +#include + +#include "rail_orders.h" + +static BOOL rail_is_feature_supported(const rdpContext* context, UINT32 featureMask); + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_send_pdu(railPlugin* rail, wStream* s, UINT16 orderType) +{ + char buffer[128] = WINPR_C_ARRAY_INIT; + UINT16 orderLength = 0; + + if (!rail || !s) + { + Stream_Free(s, TRUE); + return ERROR_INVALID_PARAMETER; + } + + orderLength = (UINT16)Stream_GetPosition(s); + Stream_ResetPosition(s); + rail_write_pdu_header(s, orderType, orderLength); + Stream_SetPosition(s, orderLength); + WLog_Print(rail->log, WLOG_DEBUG, "Sending %s PDU, length: %" PRIu16 "", + rail_get_order_type_string_full(orderType, buffer, sizeof(buffer)), orderLength); + return rail_send_channel_data(rail, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_server_exec_result_order(wStream* s, RAIL_EXEC_RESULT_ORDER* execResult) +{ + if (!s || !execResult) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_EXEC_RESULT_ORDER_LENGTH)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, execResult->flags); /* flags (2 bytes) */ + Stream_Read_UINT16(s, execResult->execResult); /* execResult (2 bytes) */ + Stream_Read_UINT32(s, execResult->rawResult); /* rawResult (4 bytes) */ + Stream_Seek_UINT16(s); /* padding (2 bytes) */ + return rail_read_unicode_string(s, &execResult->exeOrFile) + ? CHANNEL_RC_OK + : ERROR_INTERNAL_ERROR; /* exeOrFile */ +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_server_minmaxinfo_order(wStream* s, RAIL_MINMAXINFO_ORDER* minmaxinfo) +{ + if (!s || !minmaxinfo) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_MINMAXINFO_ORDER_LENGTH)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, minmaxinfo->windowId); /* windowId (4 bytes) */ + Stream_Read_INT16(s, minmaxinfo->maxWidth); /* maxWidth (2 bytes) */ + Stream_Read_INT16(s, minmaxinfo->maxHeight); /* maxHeight (2 bytes) */ + Stream_Read_INT16(s, minmaxinfo->maxPosX); /* maxPosX (2 bytes) */ + Stream_Read_INT16(s, minmaxinfo->maxPosY); /* maxPosY (2 bytes) */ + Stream_Read_INT16(s, minmaxinfo->minTrackWidth); /* minTrackWidth (2 bytes) */ + Stream_Read_INT16(s, minmaxinfo->minTrackHeight); /* minTrackHeight (2 bytes) */ + Stream_Read_INT16(s, minmaxinfo->maxTrackWidth); /* maxTrackWidth (2 bytes) */ + Stream_Read_INT16(s, minmaxinfo->maxTrackHeight); /* maxTrackHeight (2 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_server_localmovesize_order(wStream* s, + RAIL_LOCALMOVESIZE_ORDER* localMoveSize) +{ + UINT16 isMoveSizeStart = 0; + + if (!s || !localMoveSize) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_LOCALMOVESIZE_ORDER_LENGTH)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, localMoveSize->windowId); /* windowId (4 bytes) */ + Stream_Read_UINT16(s, isMoveSizeStart); /* isMoveSizeStart (2 bytes) */ + localMoveSize->isMoveSizeStart = (isMoveSizeStart != 0); + Stream_Read_UINT16(s, localMoveSize->moveSizeType); /* moveSizeType (2 bytes) */ + Stream_Read_INT16(s, localMoveSize->posX); /* posX (2 bytes) */ + Stream_Read_INT16(s, localMoveSize->posY); /* posY (2 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_server_get_appid_resp_order(wStream* s, + RAIL_GET_APPID_RESP_ORDER* getAppidResp) +{ + if (!s || !getAppidResp) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_GET_APPID_RESP_ORDER_LENGTH)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, getAppidResp->windowId); /* windowId (4 bytes) */ + Stream_Read_UTF16_String( + s, getAppidResp->applicationId, + ARRAYSIZE(getAppidResp->applicationId)); /* applicationId (260 UNICODE chars) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_langbar_info_order(wStream* s, RAIL_LANGBAR_INFO_ORDER* langbarInfo) +{ + if (!s || !langbarInfo) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_LANGBAR_INFO_ORDER_LENGTH)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, langbarInfo->languageBarStatus); /* languageBarStatus (4 bytes) */ + return CHANNEL_RC_OK; +} + +static UINT rail_write_client_status_order(wStream* s, const RAIL_CLIENT_STATUS_ORDER* clientStatus) +{ + if (!s || !clientStatus) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, clientStatus->flags); /* flags (4 bytes) */ + return ERROR_SUCCESS; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_write_client_exec_order(wStream* s, UINT16 flags, + const RAIL_UNICODE_STRING* exeOrFile, + const RAIL_UNICODE_STRING* workingDir, + const RAIL_UNICODE_STRING* arguments) +{ + UINT error = 0; + + if (!s || !exeOrFile || !workingDir || !arguments) + return ERROR_INVALID_PARAMETER; + + /* [MS-RDPERP] 2.2.2.3.1 Client Execute PDU (TS_RAIL_ORDER_EXEC) + * Check argument limits */ + if ((exeOrFile->length > 520) || (workingDir->length > 520) || (arguments->length > 16000)) + { + WLog_ERR(TAG, + "TS_RAIL_ORDER_EXEC argument limits exceeded: ExeOrFile=%" PRIu16 + " [max=520], WorkingDir=%" PRIu16 " [max=520], Arguments=%" PRIu16 " [max=16000]", + exeOrFile->length, workingDir->length, arguments->length); + return ERROR_BAD_ARGUMENTS; + } + + Stream_Write_UINT16(s, flags); /* flags (2 bytes) */ + Stream_Write_UINT16(s, exeOrFile->length); /* exeOrFileLength (2 bytes) */ + Stream_Write_UINT16(s, workingDir->length); /* workingDirLength (2 bytes) */ + Stream_Write_UINT16(s, arguments->length); /* argumentsLength (2 bytes) */ + + if ((error = rail_write_unicode_string_value(s, exeOrFile))) + { + WLog_ERR(TAG, "rail_write_unicode_string_value failed with error %" PRIu32 "", error); + return error; + } + + if ((error = rail_write_unicode_string_value(s, workingDir))) + { + WLog_ERR(TAG, "rail_write_unicode_string_value failed with error %" PRIu32 "", error); + return error; + } + + if ((error = rail_write_unicode_string_value(s, arguments))) + { + WLog_ERR(TAG, "rail_write_unicode_string_value failed with error %" PRIu32 "", error); + return error; + } + + return error; +} + +static UINT rail_write_client_activate_order(wStream* s, const RAIL_ACTIVATE_ORDER* activate) +{ + BYTE enabled = 0; + + if (!s || !activate) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, activate->windowId); /* windowId (4 bytes) */ + enabled = activate->enabled ? 1 : 0; + Stream_Write_UINT8(s, enabled); /* enabled (1 byte) */ + return ERROR_SUCCESS; +} + +static UINT rail_write_client_sysmenu_order(wStream* s, const RAIL_SYSMENU_ORDER* sysmenu) +{ + if (!s || !sysmenu) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, sysmenu->windowId); /* windowId (4 bytes) */ + Stream_Write_INT16(s, sysmenu->left); /* left (2 bytes) */ + Stream_Write_INT16(s, sysmenu->top); /* top (2 bytes) */ + return ERROR_SUCCESS; +} + +static UINT rail_write_client_syscommand_order(wStream* s, const RAIL_SYSCOMMAND_ORDER* syscommand) +{ + if (!s || !syscommand) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, syscommand->windowId); /* windowId (4 bytes) */ + Stream_Write_UINT16(s, syscommand->command); /* command (2 bytes) */ + return ERROR_SUCCESS; +} + +static UINT rail_write_client_notify_event_order(wStream* s, + const RAIL_NOTIFY_EVENT_ORDER* notifyEvent) +{ + if (!s || !notifyEvent) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, notifyEvent->windowId); /* windowId (4 bytes) */ + Stream_Write_UINT32(s, notifyEvent->notifyIconId); /* notifyIconId (4 bytes) */ + Stream_Write_UINT32(s, notifyEvent->message); /* notifyIconId (4 bytes) */ + return ERROR_SUCCESS; +} + +static UINT rail_write_client_window_move_order(wStream* s, + const RAIL_WINDOW_MOVE_ORDER* windowMove) +{ + if (!s || !windowMove) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, windowMove->windowId); /* windowId (4 bytes) */ + Stream_Write_INT16(s, windowMove->left); /* left (2 bytes) */ + Stream_Write_INT16(s, windowMove->top); /* top (2 bytes) */ + Stream_Write_INT16(s, windowMove->right); /* right (2 bytes) */ + Stream_Write_INT16(s, windowMove->bottom); /* bottom (2 bytes) */ + return ERROR_SUCCESS; +} + +static UINT rail_write_client_get_appid_req_order(wStream* s, + const RAIL_GET_APPID_REQ_ORDER* getAppidReq) +{ + if (!s || !getAppidReq) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, getAppidReq->windowId); /* windowId (4 bytes) */ + return ERROR_SUCCESS; +} + +static UINT rail_write_langbar_info_order(wStream* s, const RAIL_LANGBAR_INFO_ORDER* langbarInfo) +{ + if (!s || !langbarInfo) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, langbarInfo->languageBarStatus); /* languageBarStatus (4 bytes) */ + return ERROR_SUCCESS; +} + +static UINT rail_write_languageime_info_order(wStream* s, + const RAIL_LANGUAGEIME_INFO_ORDER* langImeInfo) +{ + if (!s || !langImeInfo) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, langImeInfo->ProfileType); + Stream_Write_UINT16(s, WINPR_ASSERTING_INT_CAST(UINT16, langImeInfo->LanguageID)); + Stream_Write(s, &langImeInfo->LanguageProfileCLSID, sizeof(langImeInfo->LanguageProfileCLSID)); + Stream_Write(s, &langImeInfo->ProfileGUID, sizeof(langImeInfo->ProfileGUID)); + Stream_Write_UINT32(s, langImeInfo->KeyboardLayout); + return ERROR_SUCCESS; +} + +static UINT rail_write_compartment_info_order(wStream* s, + const RAIL_COMPARTMENT_INFO_ORDER* compartmentInfo) +{ + if (!s || !compartmentInfo) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, compartmentInfo->ImeState); + Stream_Write_UINT32(s, compartmentInfo->ImeConvMode); + Stream_Write_UINT32(s, compartmentInfo->ImeSentenceMode); + Stream_Write_UINT32(s, compartmentInfo->KanaMode); + return ERROR_SUCCESS; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_handshake_order(railPlugin* rail, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + RAIL_HANDSHAKE_ORDER serverHandshake = WINPR_C_ARRAY_INIT; + UINT error = 0; + + if (!context || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_handshake_order(s, &serverHandshake))) + { + WLog_ERR(TAG, "rail_read_handshake_order failed with error %" PRIu32 "!", error); + return error; + } + + rail->channelBuildNumber = serverHandshake.buildNumber; + + if (rail->sendHandshake) + { + RAIL_HANDSHAKE_ORDER clientHandshake = WINPR_C_ARRAY_INIT; + clientHandshake.buildNumber = 0x00001DB0; + error = context->ClientHandshake(context, &clientHandshake); + } + + if (error != CHANNEL_RC_OK) + return error; + + if (context->custom) + { + IFCALLRET(context->ServerHandshake, error, context, &serverHandshake); + + if (error) + WLog_ERR(TAG, "context.ServerHandshake failed with error %" PRIu32 "", error); + } + + return error; +} + +static UINT rail_read_compartment_info_order(wStream* s, + RAIL_COMPARTMENT_INFO_ORDER* compartmentInfo) +{ + if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_COMPARTMENT_INFO_ORDER_LENGTH)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, compartmentInfo->ImeState); /* ImeState (4 bytes) */ + Stream_Read_UINT32(s, compartmentInfo->ImeConvMode); /* ImeConvMode (4 bytes) */ + Stream_Read_UINT32(s, compartmentInfo->ImeSentenceMode); /* ImeSentenceMode (4 bytes) */ + Stream_Read_UINT32(s, compartmentInfo->KanaMode); /* KANAMode (4 bytes) */ + return CHANNEL_RC_OK; +} + +static UINT rail_recv_compartmentinfo_order(railPlugin* rail, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + RAIL_COMPARTMENT_INFO_ORDER pdu = WINPR_C_ARRAY_INIT; + UINT error = 0; + + if (!context || !s) + return ERROR_INVALID_PARAMETER; + + if (!rail_is_feature_supported(rail->rdpcontext, RAIL_LEVEL_LANGUAGE_IME_SYNC_SUPPORTED)) + return ERROR_BAD_CONFIGURATION; + + if ((error = rail_read_compartment_info_order(s, &pdu))) + return error; + + if (context->custom) + { + IFCALLRET(context->ClientCompartmentInfo, error, context, &pdu); + + if (error) + WLog_ERR(TAG, "context.ClientCompartmentInfo failed with error %" PRIu32 "", error); + } + + return error; +} + +BOOL rail_is_feature_supported(const rdpContext* context, UINT32 featureMask) +{ + UINT32 supported = 0; + UINT32 masked = 0; + + if (!context || !context->settings) + return FALSE; + + const UINT32 level = + freerdp_settings_get_uint32(context->settings, FreeRDP_RemoteApplicationSupportLevel); + const UINT32 mask = + freerdp_settings_get_uint32(context->settings, FreeRDP_RemoteApplicationSupportMask); + supported = level & mask; + masked = (supported & featureMask); + + if (masked != featureMask) + { + char maskstr[256] = WINPR_C_ARRAY_INIT; + char actualstr[256] = WINPR_C_ARRAY_INIT; + + WLog_WARN(TAG, "have %s, require %s", + freerdp_rail_support_flags_to_string(supported, actualstr, sizeof(actualstr)), + freerdp_rail_support_flags_to_string(featureMask, maskstr, sizeof(maskstr))); + return FALSE; + } + + return TRUE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_handshake_ex_order(railPlugin* rail, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + RAIL_HANDSHAKE_EX_ORDER serverHandshake = WINPR_C_ARRAY_INIT; + UINT error = 0; + + if (!rail || !context || !s) + return ERROR_INVALID_PARAMETER; + + if (!rail_is_feature_supported(rail->rdpcontext, RAIL_LEVEL_HANDSHAKE_EX_SUPPORTED)) + return ERROR_BAD_CONFIGURATION; + + if ((error = rail_read_handshake_ex_order(s, &serverHandshake))) + { + WLog_ERR(TAG, "rail_read_handshake_ex_order failed with error %" PRIu32 "!", error); + return error; + } + + rail->channelBuildNumber = serverHandshake.buildNumber; + rail->channelFlags = serverHandshake.railHandshakeFlags; + + { + char buffer[192] = WINPR_C_ARRAY_INIT; + WLog_DBG(TAG, "HandshakeFlags=%s [buildNumber=0x%08" PRIx32 "]", + rail_handshake_ex_flags_to_string(rail->channelFlags, buffer, sizeof(buffer)), + rail->channelBuildNumber); + } + + if (rail->sendHandshake) + { + RAIL_HANDSHAKE_ORDER clientHandshake = WINPR_C_ARRAY_INIT; + clientHandshake.buildNumber = 0x00001DB0; + /* 2.2.2.2.3 HandshakeEx PDU (TS_RAIL_ORDER_HANDSHAKE_EX) + * Client response is really a Handshake PDU */ + error = context->ClientHandshake(context, &clientHandshake); + } + + if (error != CHANNEL_RC_OK) + return error; + + if (context->custom) + { + IFCALLRET(context->ServerHandshakeEx, error, context, &serverHandshake); + + if (error) + WLog_ERR(TAG, "context.ServerHandshakeEx failed with error %" PRIu32 "", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_exec_result_order(railPlugin* rail, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + RAIL_EXEC_RESULT_ORDER execResult = WINPR_C_ARRAY_INIT; + UINT error = 0; + + if (!context || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_server_exec_result_order(s, &execResult))) + { + WLog_ERR(TAG, "rail_read_server_exec_result_order failed with error %" PRIu32 "!", error); + goto fail; + } + + if (context->custom) + { + IFCALLRET(context->ServerExecuteResult, error, context, &execResult); + + if (error) + WLog_ERR(TAG, "context.ServerExecuteResult failed with error %" PRIu32 "", error); + } + +fail: + free(execResult.exeOrFile.string); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_server_sysparam_order(railPlugin* rail, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + RAIL_SYSPARAM_ORDER sysparam; + UINT error = 0; + BOOL extendedSpiSupported = 0; + + if (!context || !s) + return ERROR_INVALID_PARAMETER; + + extendedSpiSupported = rail_is_extended_spi_supported(rail->channelFlags); + if ((error = rail_read_sysparam_order(s, &sysparam, extendedSpiSupported))) + { + WLog_ERR(TAG, "rail_read_sysparam_order failed with error %" PRIu32 "!", error); + return error; + } + + if (context->custom) + { + IFCALLRET(context->ServerSystemParam, error, context, &sysparam); + + if (error) + WLog_ERR(TAG, "context.ServerSystemParam failed with error %" PRIu32 "", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_server_minmaxinfo_order(railPlugin* rail, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + RAIL_MINMAXINFO_ORDER minMaxInfo = WINPR_C_ARRAY_INIT; + UINT error = 0; + + if (!context || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_server_minmaxinfo_order(s, &minMaxInfo))) + { + WLog_ERR(TAG, "rail_read_server_minmaxinfo_order failed with error %" PRIu32 "!", error); + return error; + } + + if (context->custom) + { + IFCALLRET(context->ServerMinMaxInfo, error, context, &minMaxInfo); + + if (error) + WLog_ERR(TAG, "context.ServerMinMaxInfo failed with error %" PRIu32 "", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_server_localmovesize_order(railPlugin* rail, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + RAIL_LOCALMOVESIZE_ORDER localMoveSize = WINPR_C_ARRAY_INIT; + UINT error = 0; + + if (!context || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_server_localmovesize_order(s, &localMoveSize))) + { + WLog_ERR(TAG, "rail_read_server_localmovesize_order failed with error %" PRIu32 "!", error); + return error; + } + + if (context->custom) + { + IFCALLRET(context->ServerLocalMoveSize, error, context, &localMoveSize); + + if (error) + WLog_ERR(TAG, "context.ServerLocalMoveSize failed with error %" PRIu32 "", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_server_get_appid_resp_order(railPlugin* rail, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + RAIL_GET_APPID_RESP_ORDER getAppIdResp = WINPR_C_ARRAY_INIT; + UINT error = 0; + + if (!context || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_server_get_appid_resp_order(s, &getAppIdResp))) + { + WLog_ERR(TAG, "rail_read_server_get_appid_resp_order failed with error %" PRIu32 "!", + error); + return error; + } + + if (context->custom) + { + IFCALLRET(context->ServerGetAppIdResponse, error, context, &getAppIdResp); + + if (error) + WLog_ERR(TAG, "context.ServerGetAppIdResponse failed with error %" PRIu32 "", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_langbar_info_order(railPlugin* rail, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + RAIL_LANGBAR_INFO_ORDER langBarInfo = WINPR_C_ARRAY_INIT; + UINT error = 0; + + if (!context) + return ERROR_INVALID_PARAMETER; + + if (!rail_is_feature_supported(rail->rdpcontext, RAIL_LEVEL_DOCKED_LANGBAR_SUPPORTED)) + return ERROR_BAD_CONFIGURATION; + + if ((error = rail_read_langbar_info_order(s, &langBarInfo))) + { + WLog_ERR(TAG, "rail_read_langbar_info_order failed with error %" PRIu32 "!", error); + return error; + } + + if (context->custom) + { + IFCALLRET(context->ServerLanguageBarInfo, error, context, &langBarInfo); + + if (error) + WLog_ERR(TAG, "context.ServerLanguageBarInfo failed with error %" PRIu32 "", error); + } + + return error; +} + +static UINT rail_read_taskbar_info_order(wStream* s, RAIL_TASKBAR_INFO_ORDER* taskbarInfo) +{ + if (!s || !taskbarInfo) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_TASKBAR_INFO_ORDER_LENGTH)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, taskbarInfo->TaskbarMessage); + Stream_Read_UINT32(s, taskbarInfo->WindowIdTab); + Stream_Read_UINT32(s, taskbarInfo->Body); + return CHANNEL_RC_OK; +} + +static UINT rail_recv_taskbar_info_order(railPlugin* rail, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + RAIL_TASKBAR_INFO_ORDER taskBarInfo = WINPR_C_ARRAY_INIT; + UINT error = 0; + + if (!context) + return ERROR_INVALID_PARAMETER; + + /* 2.2.2.14.1 Taskbar Tab Info PDU (TS_RAIL_ORDER_TASKBARINFO) + * server -> client message only supported if announced. */ + if (!rail_is_feature_supported(rail->rdpcontext, RAIL_LEVEL_SHELL_INTEGRATION_SUPPORTED)) + return ERROR_BAD_CONFIGURATION; + + if ((error = rail_read_taskbar_info_order(s, &taskBarInfo))) + { + WLog_ERR(TAG, "rail_read_langbar_info_order failed with error %" PRIu32 "!", error); + return error; + } + + if (context->custom) + { + IFCALLRET(context->ServerTaskBarInfo, error, context, &taskBarInfo); + + if (error) + WLog_ERR(TAG, "context.ServerTaskBarInfo failed with error %" PRIu32 "", error); + } + + return error; +} + +static UINT rail_read_zorder_sync_order(wStream* s, RAIL_ZORDER_SYNC* zorder) +{ + if (!s || !zorder) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_Z_ORDER_SYNC_ORDER_LENGTH)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, zorder->windowIdMarker); + return CHANNEL_RC_OK; +} + +static UINT rail_recv_zorder_sync_order(railPlugin* rail, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + RAIL_ZORDER_SYNC zorder = WINPR_C_ARRAY_INIT; + UINT error = 0; + + if (!context) + return ERROR_INVALID_PARAMETER; + + if ((rail->clientStatus.flags & TS_RAIL_CLIENTSTATUS_ZORDER_SYNC) == 0) + return ERROR_INVALID_DATA; + + if ((error = rail_read_zorder_sync_order(s, &zorder))) + { + WLog_ERR(TAG, "rail_read_zorder_sync_order failed with error %" PRIu32 "!", error); + return error; + } + + if (context->custom) + { + IFCALLRET(context->ServerZOrderSync, error, context, &zorder); + + if (error) + WLog_ERR(TAG, "context.ServerZOrderSync failed with error %" PRIu32 "", error); + } + + return error; +} + +static UINT rail_read_cloak_order(wStream* s, RAIL_CLOAK* cloak) +{ + BYTE cloaked = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_CLOAK_ORDER_LENGTH)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, cloak->windowId); /* WindowId (4 bytes) */ + Stream_Read_UINT8(s, cloaked); /* Cloaked (1 byte) */ + cloak->cloak = (cloaked != 0); + return CHANNEL_RC_OK; +} + +static UINT rail_recv_cloak_order(railPlugin* rail, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + RAIL_CLOAK cloak = WINPR_C_ARRAY_INIT; + UINT error = 0; + + if (!context) + return ERROR_INVALID_PARAMETER; + + /* 2.2.2.12.1 Window Cloak State Change PDU (TS_RAIL_ORDER_CLOAK) + * server -> client message only supported if announced. */ + if ((rail->clientStatus.flags & TS_RAIL_CLIENTSTATUS_BIDIRECTIONAL_CLOAK_SUPPORTED) == 0) + return ERROR_INVALID_DATA; + + if ((error = rail_read_cloak_order(s, &cloak))) + { + WLog_ERR(TAG, "rail_read_zorder_sync_order failed with error %" PRIu32 "!", error); + return error; + } + + if (context->custom) + { + IFCALLRET(context->ServerCloak, error, context, &cloak); + + if (error) + WLog_ERR(TAG, "context.ServerZOrderSync failed with error %" PRIu32 "", error); + } + + return error; +} + +static UINT rail_read_power_display_request_order(wStream* s, RAIL_POWER_DISPLAY_REQUEST* power) +{ + UINT32 active = 0; + + if (!s || !power) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_POWER_DISPLAY_REQUEST_ORDER_LENGTH)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, active); + power->active = active != 0; + return CHANNEL_RC_OK; +} + +static UINT rail_recv_power_display_request_order(railPlugin* rail, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + RAIL_POWER_DISPLAY_REQUEST power = WINPR_C_ARRAY_INIT; + UINT error = 0; + + if (!context) + return ERROR_INVALID_PARAMETER; + + /* 2.2.2.13.1 Power Display Request PDU(TS_RAIL_ORDER_POWER_DISPLAY_REQUEST) + */ + if ((rail->clientStatus.flags & TS_RAIL_CLIENTSTATUS_POWER_DISPLAY_REQUEST_SUPPORTED) == 0) + return ERROR_INVALID_DATA; + + if ((error = rail_read_power_display_request_order(s, &power))) + { + WLog_ERR(TAG, "rail_read_zorder_sync_order failed with error %" PRIu32 "!", error); + return error; + } + + if (context->custom) + { + IFCALLRET(context->ServerPowerDisplayRequest, error, context, &power); + + if (error) + WLog_ERR(TAG, "context.ServerPowerDisplayRequest failed with error %" PRIu32 "", error); + } + + return error; +} + +static UINT rail_read_get_application_id_extended_response_order(wStream* s, + RAIL_GET_APPID_RESP_EX* id) +{ + if (!s || !id) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, id->windowID); + + if (!Stream_Read_UTF16_String(s, id->applicationID, ARRAYSIZE(id->applicationID))) + return ERROR_INVALID_DATA; + + if (_wcsnlen(id->applicationID, ARRAYSIZE(id->applicationID)) >= ARRAYSIZE(id->applicationID)) + return ERROR_INVALID_DATA; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, id->processId); + + if (!Stream_Read_UTF16_String(s, id->processImageName, ARRAYSIZE(id->processImageName))) + return ERROR_INVALID_DATA; + + if (_wcsnlen(id->applicationID, ARRAYSIZE(id->processImageName)) >= + ARRAYSIZE(id->processImageName)) + return ERROR_INVALID_DATA; + + return CHANNEL_RC_OK; +} + +static UINT rail_recv_get_application_id_extended_response_order(railPlugin* rail, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + RAIL_GET_APPID_RESP_EX id = WINPR_C_ARRAY_INIT; + UINT error = 0; + + if (!context) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_get_application_id_extended_response_order(s, &id))) + { + WLog_ERR(TAG, + "rail_read_get_application_id_extended_response_order failed with error %" PRIu32 + "!", + error); + return error; + } + + if (context->custom) + { + IFCALLRET(context->ServerGetAppidResponseExtended, error, context, &id); + + if (error) + WLog_ERR(TAG, "context.ServerGetAppidResponseExtended failed with error %" PRIu32 "", + error); + } + + return error; +} + +static UINT rail_read_textscaleinfo_order(wStream* s, UINT32* pTextScaleFactor) +{ + WINPR_ASSERT(pTextScaleFactor); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, *pTextScaleFactor); + return CHANNEL_RC_OK; +} + +static UINT rail_recv_textscaleinfo_order(railPlugin* rail, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + UINT32 TextScaleFactor = 0; + UINT error = 0; + + if (!context) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_textscaleinfo_order(s, &TextScaleFactor))) + return error; + + if (context->custom) + { + IFCALLRET(context->ClientTextScale, error, context, TextScaleFactor); + + if (error) + WLog_ERR(TAG, "context.ClientTextScale failed with error %" PRIu32 "", error); + } + + return error; +} + +static UINT rail_read_caretblinkinfo_order(wStream* s, UINT32* pCaretBlinkRate) +{ + WINPR_ASSERT(pCaretBlinkRate); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, *pCaretBlinkRate); + return CHANNEL_RC_OK; +} + +static UINT rail_recv_caretblinkinfo_order(railPlugin* rail, wStream* s) +{ + RailClientContext* context = rail_get_client_interface(rail); + UINT32 CaretBlinkRate = 0; + UINT error = 0; + + if (!context) + return ERROR_INVALID_PARAMETER; + if ((error = rail_read_caretblinkinfo_order(s, &CaretBlinkRate))) + return error; + + if (context->custom) + { + IFCALLRET(context->ClientCaretBlinkRate, error, context, CaretBlinkRate); + + if (error) + WLog_ERR(TAG, "context.ClientCaretBlinkRate failed with error %" PRIu32 "", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_order_recv(LPVOID userdata, wStream* s) +{ + char buffer[128] = WINPR_C_ARRAY_INIT; + railPlugin* rail = userdata; + UINT16 orderType = 0; + UINT16 orderLength = 0; + UINT error = CHANNEL_RC_OK; + + if (!rail || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_pdu_header(s, &orderType, &orderLength))) + { + WLog_ERR(TAG, "rail_read_pdu_header failed with error %" PRIu32 "!", error); + return error; + } + + WLog_Print(rail->log, WLOG_DEBUG, "Received %s PDU, length:%" PRIu16 "", + rail_get_order_type_string_full(orderType, buffer, sizeof(buffer)), orderLength); + + switch (orderType) + { + case TS_RAIL_ORDER_HANDSHAKE: + error = rail_recv_handshake_order(rail, s); + break; + + case TS_RAIL_ORDER_COMPARTMENTINFO: + error = rail_recv_compartmentinfo_order(rail, s); + break; + + case TS_RAIL_ORDER_HANDSHAKE_EX: + error = rail_recv_handshake_ex_order(rail, s); + break; + + case TS_RAIL_ORDER_EXEC_RESULT: + error = rail_recv_exec_result_order(rail, s); + break; + + case TS_RAIL_ORDER_SYSPARAM: + error = rail_recv_server_sysparam_order(rail, s); + break; + + case TS_RAIL_ORDER_MINMAXINFO: + error = rail_recv_server_minmaxinfo_order(rail, s); + break; + + case TS_RAIL_ORDER_LOCALMOVESIZE: + error = rail_recv_server_localmovesize_order(rail, s); + break; + + case TS_RAIL_ORDER_GET_APPID_RESP: + error = rail_recv_server_get_appid_resp_order(rail, s); + break; + + case TS_RAIL_ORDER_LANGBARINFO: + error = rail_recv_langbar_info_order(rail, s); + break; + + case TS_RAIL_ORDER_TASKBARINFO: + error = rail_recv_taskbar_info_order(rail, s); + break; + + case TS_RAIL_ORDER_ZORDER_SYNC: + error = rail_recv_zorder_sync_order(rail, s); + break; + + case TS_RAIL_ORDER_CLOAK: + error = rail_recv_cloak_order(rail, s); + break; + + case TS_RAIL_ORDER_POWER_DISPLAY_REQUEST: + error = rail_recv_power_display_request_order(rail, s); + break; + + case TS_RAIL_ORDER_GET_APPID_RESP_EX: + error = rail_recv_get_application_id_extended_response_order(rail, s); + break; + + case TS_RAIL_ORDER_TEXTSCALEINFO: + error = rail_recv_textscaleinfo_order(rail, s); + break; + + case TS_RAIL_ORDER_CARETBLINKINFO: + error = rail_recv_caretblinkinfo_order(rail, s); + break; + + default: + WLog_ERR(TAG, "Unknown RAIL PDU %s received.", + rail_get_order_type_string_full(orderType, buffer, sizeof(buffer))); + return ERROR_INVALID_DATA; + } + + if (error != CHANNEL_RC_OK) + { + char ebuffer[128] = WINPR_C_ARRAY_INIT; + WLog_Print(rail->log, WLOG_ERROR, "Failed to process rail %s PDU, length:%" PRIu16 "", + rail_get_order_type_string_full(orderType, ebuffer, sizeof(ebuffer)), + orderLength); + } + + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_send_handshake_order(railPlugin* rail, const RAIL_HANDSHAKE_ORDER* handshake) +{ + if (!rail || !handshake) + return ERROR_INVALID_PARAMETER; + + wStream* s = rail_pdu_init(RAIL_HANDSHAKE_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rail_write_handshake_order(s, handshake); + return rail_send_pdu(rail, s, TS_RAIL_ORDER_HANDSHAKE); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_send_handshake_ex_order(railPlugin* rail, const RAIL_HANDSHAKE_EX_ORDER* handshakeEx) +{ + if (!rail || !handshakeEx) + return ERROR_INVALID_PARAMETER; + + wStream* s = rail_pdu_init(RAIL_HANDSHAKE_EX_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rail_write_handshake_ex_order(s, handshakeEx); + return rail_send_pdu(rail, s, TS_RAIL_ORDER_HANDSHAKE_EX); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_send_client_status_order(railPlugin* rail, const RAIL_CLIENT_STATUS_ORDER* clientStatus) +{ + wStream* s = nullptr; + UINT error = 0; + + if (!rail || !clientStatus) + return ERROR_INVALID_PARAMETER; + + rail->clientStatus = *clientStatus; + s = rail_pdu_init(RAIL_CLIENT_STATUS_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + error = rail_write_client_status_order(s, clientStatus); + + if (ERROR_SUCCESS != error) + { + + Stream_Free(s, TRUE); + return error; + } + + return rail_send_pdu(rail, s, TS_RAIL_ORDER_CLIENTSTATUS); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_send_client_exec_order(railPlugin* rail, UINT16 flags, + const RAIL_UNICODE_STRING* exeOrFile, + const RAIL_UNICODE_STRING* workingDir, + const RAIL_UNICODE_STRING* arguments) +{ + wStream* s = nullptr; + UINT error = 0; + size_t length = 0; + + if (!rail || !exeOrFile || !workingDir || !arguments) + return ERROR_INVALID_PARAMETER; + + length = RAIL_EXEC_ORDER_LENGTH + exeOrFile->length + workingDir->length + arguments->length; + s = rail_pdu_init(length); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if ((error = rail_write_client_exec_order(s, flags, exeOrFile, workingDir, arguments))) + { + WLog_ERR(TAG, "rail_write_client_exec_order failed with error %" PRIu32 "!", error); + goto out; + } + + return rail_send_pdu(rail, s, TS_RAIL_ORDER_EXEC); + +out: + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_send_client_activate_order(railPlugin* rail, const RAIL_ACTIVATE_ORDER* activate) +{ + wStream* s = nullptr; + UINT error = 0; + + if (!rail || !activate) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_ACTIVATE_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + error = rail_write_client_activate_order(s, activate); + + if (ERROR_SUCCESS != error) + { + + Stream_Free(s, TRUE); + return error; + } + + return rail_send_pdu(rail, s, TS_RAIL_ORDER_ACTIVATE); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_send_client_sysmenu_order(railPlugin* rail, const RAIL_SYSMENU_ORDER* sysmenu) +{ + wStream* s = nullptr; + UINT error = 0; + + if (!rail || !sysmenu) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_SYSMENU_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + error = rail_write_client_sysmenu_order(s, sysmenu); + + if (ERROR_SUCCESS != error) + { + + Stream_Free(s, TRUE); + return error; + } + + return rail_send_pdu(rail, s, TS_RAIL_ORDER_SYSMENU); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_send_client_syscommand_order(railPlugin* rail, const RAIL_SYSCOMMAND_ORDER* syscommand) +{ + wStream* s = nullptr; + UINT error = 0; + + if (!rail || !syscommand) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_SYSCOMMAND_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + error = rail_write_client_syscommand_order(s, syscommand); + + if (ERROR_SUCCESS != error) + { + + Stream_Free(s, TRUE); + return error; + } + + return rail_send_pdu(rail, s, TS_RAIL_ORDER_SYSCOMMAND); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_send_client_notify_event_order(railPlugin* rail, + const RAIL_NOTIFY_EVENT_ORDER* notifyEvent) +{ + wStream* s = nullptr; + UINT error = 0; + + if (!rail || !notifyEvent) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_NOTIFY_EVENT_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + error = rail_write_client_notify_event_order(s, notifyEvent); + + if (ERROR_SUCCESS != error) + { + + Stream_Free(s, TRUE); + return error; + } + + return rail_send_pdu(rail, s, TS_RAIL_ORDER_NOTIFY_EVENT); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_send_client_window_move_order(railPlugin* rail, const RAIL_WINDOW_MOVE_ORDER* windowMove) +{ + wStream* s = nullptr; + UINT error = 0; + + if (!rail || !windowMove) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_WINDOW_MOVE_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + error = rail_write_client_window_move_order(s, windowMove); + + if (ERROR_SUCCESS != error) + { + + Stream_Free(s, TRUE); + return error; + } + + return rail_send_pdu(rail, s, TS_RAIL_ORDER_WINDOWMOVE); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_send_client_get_appid_req_order(railPlugin* rail, + const RAIL_GET_APPID_REQ_ORDER* getAppIdReq) +{ + wStream* s = nullptr; + UINT error = 0; + + if (!rail || !getAppIdReq) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_GET_APPID_REQ_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + error = rail_write_client_get_appid_req_order(s, getAppIdReq); + + if (ERROR_SUCCESS != error) + { + + Stream_Free(s, TRUE); + return error; + } + return rail_send_pdu(rail, s, TS_RAIL_ORDER_GET_APPID_REQ); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_send_client_langbar_info_order(railPlugin* rail, + const RAIL_LANGBAR_INFO_ORDER* langBarInfo) +{ + wStream* s = nullptr; + UINT error = 0; + + if (!rail || !langBarInfo) + return ERROR_INVALID_PARAMETER; + + if (!rail_is_feature_supported(rail->rdpcontext, RAIL_LEVEL_DOCKED_LANGBAR_SUPPORTED)) + return ERROR_BAD_CONFIGURATION; + + s = rail_pdu_init(RAIL_LANGBAR_INFO_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + error = rail_write_langbar_info_order(s, langBarInfo); + + if (ERROR_SUCCESS != error) + { + + Stream_Free(s, TRUE); + return error; + } + return rail_send_pdu(rail, s, TS_RAIL_ORDER_LANGBARINFO); +} + +UINT rail_send_client_languageime_info_order(railPlugin* rail, + const RAIL_LANGUAGEIME_INFO_ORDER* langImeInfo) +{ + wStream* s = nullptr; + UINT error = 0; + + if (!rail || !langImeInfo) + return ERROR_INVALID_PARAMETER; + + if (!rail_is_feature_supported(rail->rdpcontext, RAIL_LEVEL_LANGUAGE_IME_SYNC_SUPPORTED)) + return ERROR_BAD_CONFIGURATION; + + s = rail_pdu_init(RAIL_LANGUAGEIME_INFO_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + error = rail_write_languageime_info_order(s, langImeInfo); + + if (ERROR_SUCCESS != error) + { + + Stream_Free(s, TRUE); + return error; + } + return rail_send_pdu(rail, s, TS_RAIL_ORDER_LANGUAGEIMEINFO); +} + +UINT rail_send_client_compartment_info_order(railPlugin* rail, + const RAIL_COMPARTMENT_INFO_ORDER* compartmentInfo) +{ + wStream* s = nullptr; + UINT error = 0; + + if (!rail || !compartmentInfo) + return ERROR_INVALID_PARAMETER; + + if (!rail_is_feature_supported(rail->rdpcontext, RAIL_LEVEL_LANGUAGE_IME_SYNC_SUPPORTED)) + return ERROR_BAD_CONFIGURATION; + + s = rail_pdu_init(RAIL_COMPARTMENT_INFO_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + error = rail_write_compartment_info_order(s, compartmentInfo); + + if (ERROR_SUCCESS != error) + { + Stream_Free(s, TRUE); + return error; + } + return rail_send_pdu(rail, s, TS_RAIL_ORDER_COMPARTMENTINFO); +} + +UINT rail_send_client_cloak_order(railPlugin* rail, const RAIL_CLOAK* cloak) +{ + if (!rail || !cloak) + return ERROR_INVALID_PARAMETER; + + wStream* s = rail_pdu_init(5); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT32(s, cloak->windowId); + Stream_Write_UINT8(s, cloak->cloak ? 1 : 0); + return rail_send_pdu(rail, s, TS_RAIL_ORDER_CLOAK); +} + +UINT rail_send_client_snap_arrange_order(railPlugin* rail, const RAIL_SNAP_ARRANGE* snap) +{ + if (!rail) + return ERROR_INVALID_PARAMETER; + + /* 2.2.2.7.5 Client Window Snap PDU (TS_RAIL_ORDER_SNAP_ARRANGE) */ + if ((rail->channelFlags & TS_RAIL_ORDER_HANDSHAKE_EX_FLAGS_SNAP_ARRANGE_SUPPORTED) == 0) + { + RAIL_WINDOW_MOVE_ORDER move = WINPR_C_ARRAY_INIT; + move.top = snap->top; + move.left = snap->left; + move.right = snap->right; + move.bottom = snap->bottom; + move.windowId = snap->windowId; + return rail_send_client_window_move_order(rail, &move); + } + + wStream* s = rail_pdu_init(12); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT32(s, snap->windowId); + Stream_Write_INT16(s, snap->left); + Stream_Write_INT16(s, snap->top); + Stream_Write_INT16(s, snap->right); + Stream_Write_INT16(s, snap->bottom); + return rail_send_pdu(rail, s, TS_RAIL_ORDER_SNAP_ARRANGE); +} + +UINT rail_send_client_text_scale_order(railPlugin* rail, UINT32 textScale) +{ + if (!rail) + return ERROR_INVALID_PARAMETER; + + wStream* s = rail_pdu_init(4); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT32(s, textScale); + return rail_send_pdu(rail, s, TS_RAIL_ORDER_TEXTSCALEINFO); +} + +UINT rail_send_client_caret_blink_rate_order(railPlugin* rail, UINT32 rate) +{ + if (!rail) + return ERROR_INVALID_PARAMETER; + + wStream* s = rail_pdu_init(4); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT32(s, rate); + return rail_send_pdu(rail, s, TS_RAIL_ORDER_CARETBLINKINFO); +} diff --git a/third_party/FreeRDP/channels/rail/client/rail_orders.h b/third_party/FreeRDP/channels/rail/client/rail_orders.h new file mode 100644 index 0000000..8f887dc --- /dev/null +++ b/third_party/FreeRDP/channels/rail/client/rail_orders.h @@ -0,0 +1,89 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Remote Applications Integrated Locally (RAIL) + * + * Copyright 2009 Marc-Andre Moreau + * Copyright 2011 Roman Barabanov + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RAIL_CLIENT_ORDERS_H +#define FREERDP_CHANNEL_RAIL_CLIENT_ORDERS_H + +#include + +#include "rail_main.h" + +#define TAG CHANNELS_TAG("rail.client") + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT rail_order_recv(LPVOID userdata, wStream* s); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT rail_send_pdu(railPlugin* rail, wStream* s, + UINT16 orderType); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT +rail_send_handshake_order(railPlugin* rail, const RAIL_HANDSHAKE_ORDER* handshake); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT +rail_send_handshake_ex_order(railPlugin* rail, const RAIL_HANDSHAKE_EX_ORDER* handshakeEx); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT +rail_send_client_status_order(railPlugin* rail, const RAIL_CLIENT_STATUS_ORDER* clientStatus); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT rail_send_client_exec_order( + railPlugin* rail, UINT16 flags, const RAIL_UNICODE_STRING* exeOrFile, + const RAIL_UNICODE_STRING* workingDir, const RAIL_UNICODE_STRING* arguments); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT +rail_send_client_activate_order(railPlugin* rail, const RAIL_ACTIVATE_ORDER* activate); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT +rail_send_client_sysmenu_order(railPlugin* rail, const RAIL_SYSMENU_ORDER* sysmenu); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT +rail_send_client_syscommand_order(railPlugin* rail, const RAIL_SYSCOMMAND_ORDER* syscommand); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT +rail_send_client_notify_event_order(railPlugin* rail, const RAIL_NOTIFY_EVENT_ORDER* notifyEvent); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT +rail_send_client_window_move_order(railPlugin* rail, const RAIL_WINDOW_MOVE_ORDER* windowMove); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT +rail_send_client_get_appid_req_order(railPlugin* rail, const RAIL_GET_APPID_REQ_ORDER* getAppIdReq); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT +rail_send_client_langbar_info_order(railPlugin* rail, const RAIL_LANGBAR_INFO_ORDER* langBarInfo); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT rail_send_client_languageime_info_order( + railPlugin* rail, const RAIL_LANGUAGEIME_INFO_ORDER* langImeInfo); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT rail_send_client_cloak_order(railPlugin* rail, + const RAIL_CLOAK* cloak); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT +rail_send_client_snap_arrange_order(railPlugin* rail, const RAIL_SNAP_ARRANGE* snap); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT rail_send_client_compartment_info_order( + railPlugin* rail, const RAIL_COMPARTMENT_INFO_ORDER* compartmentInfo); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT rail_send_client_text_scale_order(railPlugin* rail, + UINT32 textScale); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT rail_send_client_caret_blink_rate_order(railPlugin* rail, + UINT32 rate); + +#endif /* FREERDP_CHANNEL_RAIL_CLIENT_ORDERS_H */ diff --git a/third_party/FreeRDP/channels/rail/rail_common.c b/third_party/FreeRDP/channels/rail/rail_common.c new file mode 100644 index 0000000..c98afba --- /dev/null +++ b/third_party/FreeRDP/channels/rail/rail_common.c @@ -0,0 +1,618 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RAIL common functions + * + * Copyright 2011 Marc-Andre Moreau + * Copyright 2011 Roman Barabanov + * Copyright 2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * 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 "rail_common.h" + +#include +#include + +#define TAG CHANNELS_TAG("rail.common") + +const char* rail_get_order_type_string(UINT16 orderType) +{ + switch (orderType) + { + case TS_RAIL_ORDER_EXEC: + return "TS_RAIL_ORDER_EXEC"; + case TS_RAIL_ORDER_ACTIVATE: + return "TS_RAIL_ORDER_ACTIVATE"; + case TS_RAIL_ORDER_SYSPARAM: + return "TS_RAIL_ORDER_SYSPARAM"; + case TS_RAIL_ORDER_SYSCOMMAND: + return "TS_RAIL_ORDER_SYSCOMMAND"; + case TS_RAIL_ORDER_HANDSHAKE: + return "TS_RAIL_ORDER_HANDSHAKE"; + case TS_RAIL_ORDER_NOTIFY_EVENT: + return "TS_RAIL_ORDER_NOTIFY_EVENT"; + case TS_RAIL_ORDER_WINDOWMOVE: + return "TS_RAIL_ORDER_WINDOWMOVE"; + case TS_RAIL_ORDER_LOCALMOVESIZE: + return "TS_RAIL_ORDER_LOCALMOVESIZE"; + case TS_RAIL_ORDER_MINMAXINFO: + return "TS_RAIL_ORDER_MINMAXINFO"; + case TS_RAIL_ORDER_CLIENTSTATUS: + return "TS_RAIL_ORDER_CLIENTSTATUS"; + case TS_RAIL_ORDER_SYSMENU: + return "TS_RAIL_ORDER_SYSMENU"; + case TS_RAIL_ORDER_LANGBARINFO: + return "TS_RAIL_ORDER_LANGBARINFO"; + case TS_RAIL_ORDER_GET_APPID_REQ: + return "TS_RAIL_ORDER_GET_APPID_REQ"; + case TS_RAIL_ORDER_GET_APPID_RESP: + return "TS_RAIL_ORDER_GET_APPID_RESP"; + case TS_RAIL_ORDER_TASKBARINFO: + return "TS_RAIL_ORDER_TASKBARINFO"; + case TS_RAIL_ORDER_LANGUAGEIMEINFO: + return "TS_RAIL_ORDER_LANGUAGEIMEINFO"; + case TS_RAIL_ORDER_COMPARTMENTINFO: + return "TS_RAIL_ORDER_COMPARTMENTINFO"; + case TS_RAIL_ORDER_HANDSHAKE_EX: + return "TS_RAIL_ORDER_HANDSHAKE_EX"; + case TS_RAIL_ORDER_ZORDER_SYNC: + return "TS_RAIL_ORDER_ZORDER_SYNC"; + case TS_RAIL_ORDER_CLOAK: + return "TS_RAIL_ORDER_CLOAK"; + case TS_RAIL_ORDER_POWER_DISPLAY_REQUEST: + return "TS_RAIL_ORDER_POWER_DISPLAY_REQUEST"; + case TS_RAIL_ORDER_SNAP_ARRANGE: + return "TS_RAIL_ORDER_SNAP_ARRANGE"; + case TS_RAIL_ORDER_GET_APPID_RESP_EX: + return "TS_RAIL_ORDER_GET_APPID_RESP_EX"; + case TS_RAIL_ORDER_EXEC_RESULT: + return "TS_RAIL_ORDER_EXEC_RESULT"; + case TS_RAIL_ORDER_TEXTSCALEINFO: + return "TS_RAIL_ORDER_TEXTSCALEINFO"; + case TS_RAIL_ORDER_CARETBLINKINFO: + return "TS_RAIL_ORDER_CARETBLINKINFO"; + default: + return "TS_RAIL_ORDER_UNKNOWN"; + } +} + +const char* rail_get_order_type_string_full(UINT16 orderType, char* buffer, size_t length) +{ + (void)_snprintf(buffer, length, "%s[0x%04" PRIx16 "]", rail_get_order_type_string(orderType), + orderType); + return buffer; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_read_pdu_header(wStream* s, UINT16* orderType, UINT16* orderLength) +{ + if (!s || !orderType || !orderLength) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, *orderType); /* orderType (2 bytes) */ + Stream_Read_UINT16(s, *orderLength); /* orderLength (2 bytes) */ + return CHANNEL_RC_OK; +} + +void rail_write_pdu_header(wStream* s, UINT16 orderType, UINT16 orderLength) +{ + Stream_Write_UINT16(s, orderType); /* orderType (2 bytes) */ + Stream_Write_UINT16(s, orderLength); /* orderLength (2 bytes) */ +} + +wStream* rail_pdu_init(size_t length) +{ + wStream* s = Stream_New(nullptr, length + RAIL_PDU_HEADER_LENGTH); + + if (!s) + return nullptr; + + Stream_Seek(s, RAIL_PDU_HEADER_LENGTH); + return s; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_read_handshake_order(wStream* s, RAIL_HANDSHAKE_ORDER* handshake) +{ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, handshake->buildNumber); /* buildNumber (4 bytes) */ + return CHANNEL_RC_OK; +} + +void rail_write_handshake_order(wStream* s, const RAIL_HANDSHAKE_ORDER* handshake) +{ + Stream_Write_UINT32(s, handshake->buildNumber); /* buildNumber (4 bytes) */ +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_read_handshake_ex_order(wStream* s, RAIL_HANDSHAKE_EX_ORDER* handshakeEx) +{ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, handshakeEx->buildNumber); /* buildNumber (4 bytes) */ + Stream_Read_UINT32(s, handshakeEx->railHandshakeFlags); /* railHandshakeFlags (4 bytes) */ + return CHANNEL_RC_OK; +} + +void rail_write_handshake_ex_order(wStream* s, const RAIL_HANDSHAKE_EX_ORDER* handshakeEx) +{ + Stream_Write_UINT32(s, handshakeEx->buildNumber); /* buildNumber (4 bytes) */ + Stream_Write_UINT32(s, handshakeEx->railHandshakeFlags); /* railHandshakeFlags (4 bytes) */ +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_write_unicode_string(wStream* s, const RAIL_UNICODE_STRING* unicode_string) +{ + if (!s || !unicode_string) + return ERROR_INVALID_PARAMETER; + + if (!Stream_EnsureRemainingCapacity(s, 2 + unicode_string->length)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, unicode_string->length); /* cbString (2 bytes) */ + Stream_Write(s, unicode_string->string, unicode_string->length); /* string */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_write_unicode_string_value(wStream* s, const RAIL_UNICODE_STRING* unicode_string) +{ + size_t length = 0; + + if (!s || !unicode_string) + return ERROR_INVALID_PARAMETER; + + length = unicode_string->length; + + if (length > 0) + { + if (!Stream_EnsureRemainingCapacity(s, length)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write(s, unicode_string->string, length); /* string */ + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_high_contrast(wStream* s, RAIL_HIGH_CONTRAST* highContrast) +{ + if (!s || !highContrast) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, highContrast->flags); /* flags (4 bytes) */ + Stream_Read_UINT32(s, highContrast->colorSchemeLength); /* colorSchemeLength (4 bytes) */ + + if (!rail_read_unicode_string(s, &highContrast->colorScheme)) /* colorScheme */ + return ERROR_INTERNAL_ERROR; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_write_high_contrast(wStream* s, const RAIL_HIGH_CONTRAST* highContrast) +{ + UINT32 colorSchemeLength = 0; + + if (!s || !highContrast) + return ERROR_INVALID_PARAMETER; + + if (!Stream_EnsureRemainingCapacity(s, 8)) + return CHANNEL_RC_NO_MEMORY; + + colorSchemeLength = highContrast->colorScheme.length + 2; + Stream_Write_UINT32(s, highContrast->flags); /* flags (4 bytes) */ + Stream_Write_UINT32(s, colorSchemeLength); /* colorSchemeLength (4 bytes) */ + return rail_write_unicode_string(s, &highContrast->colorScheme); /* colorScheme */ +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_filterkeys(wStream* s, TS_FILTERKEYS* filterKeys) +{ + if (!s || !filterKeys) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 20)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, filterKeys->Flags); + Stream_Read_UINT32(s, filterKeys->WaitTime); + Stream_Read_UINT32(s, filterKeys->DelayTime); + Stream_Read_UINT32(s, filterKeys->RepeatTime); + Stream_Read_UINT32(s, filterKeys->BounceTime); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_write_filterkeys(wStream* s, const TS_FILTERKEYS* filterKeys) +{ + if (!s || !filterKeys) + return ERROR_INVALID_PARAMETER; + + if (!Stream_EnsureRemainingCapacity(s, 20)) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT32(s, filterKeys->Flags); + Stream_Write_UINT32(s, filterKeys->WaitTime); + Stream_Write_UINT32(s, filterKeys->DelayTime); + Stream_Write_UINT32(s, filterKeys->RepeatTime); + Stream_Write_UINT32(s, filterKeys->BounceTime); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rail_read_sysparam_order(wStream* s, RAIL_SYSPARAM_ORDER* sysparam, BOOL extendedSpiSupported) +{ + BYTE body = 0; + UINT error = CHANNEL_RC_OK; + + if (!s || !sysparam) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 5)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, sysparam->param); /* systemParam (4 bytes) */ + + sysparam->params = 0; /* bitflags of received params */ + + switch (sysparam->param) + { + /* Client sysparams */ + case SPI_SET_DRAG_FULL_WINDOWS: + sysparam->params |= SPI_MASK_SET_DRAG_FULL_WINDOWS; + Stream_Read_UINT8(s, body); /* body (1 byte) */ + sysparam->dragFullWindows = body != 0; + break; + + case SPI_SET_KEYBOARD_CUES: + sysparam->params |= SPI_MASK_SET_KEYBOARD_CUES; + Stream_Read_UINT8(s, body); /* body (1 byte) */ + sysparam->keyboardCues = body != 0; + break; + + case SPI_SET_KEYBOARD_PREF: + sysparam->params |= SPI_MASK_SET_KEYBOARD_PREF; + Stream_Read_UINT8(s, body); /* body (1 byte) */ + sysparam->keyboardPref = body != 0; + break; + + case SPI_SET_MOUSE_BUTTON_SWAP: + sysparam->params |= SPI_MASK_SET_MOUSE_BUTTON_SWAP; + Stream_Read_UINT8(s, body); /* body (1 byte) */ + sysparam->mouseButtonSwap = body != 0; + break; + + case SPI_SET_WORK_AREA: + sysparam->params |= SPI_MASK_SET_WORK_AREA; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, sysparam->workArea.left); /* left (2 bytes) */ + Stream_Read_UINT16(s, sysparam->workArea.top); /* top (2 bytes) */ + Stream_Read_UINT16(s, sysparam->workArea.right); /* right (2 bytes) */ + Stream_Read_UINT16(s, sysparam->workArea.bottom); /* bottom (2 bytes) */ + break; + + case SPI_DISPLAY_CHANGE: + sysparam->params |= SPI_MASK_DISPLAY_CHANGE; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, sysparam->displayChange.left); /* left (2 bytes) */ + Stream_Read_UINT16(s, sysparam->displayChange.top); /* top (2 bytes) */ + Stream_Read_UINT16(s, sysparam->displayChange.right); /* right (2 bytes) */ + Stream_Read_UINT16(s, sysparam->displayChange.bottom); /* bottom (2 bytes) */ + break; + + case SPI_TASKBAR_POS: + sysparam->params |= SPI_MASK_TASKBAR_POS; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, sysparam->taskbarPos.left); /* left (2 bytes) */ + Stream_Read_UINT16(s, sysparam->taskbarPos.top); /* top (2 bytes) */ + Stream_Read_UINT16(s, sysparam->taskbarPos.right); /* right (2 bytes) */ + Stream_Read_UINT16(s, sysparam->taskbarPos.bottom); /* bottom (2 bytes) */ + break; + + case SPI_SET_HIGH_CONTRAST: + sysparam->params |= SPI_MASK_SET_HIGH_CONTRAST; + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + error = rail_read_high_contrast(s, &sysparam->highContrast); + break; + + case SPI_SETCARETWIDTH: + sysparam->params |= SPI_MASK_SET_CARET_WIDTH; + + if (!extendedSpiSupported) + return ERROR_INVALID_DATA; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, sysparam->caretWidth); + + if (sysparam->caretWidth < 0x0001) + return ERROR_INVALID_DATA; + + break; + + case SPI_SETSTICKYKEYS: + sysparam->params |= SPI_MASK_SET_STICKY_KEYS; + + if (!extendedSpiSupported) + return ERROR_INVALID_DATA; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, sysparam->stickyKeys); + break; + + case SPI_SETTOGGLEKEYS: + sysparam->params |= SPI_MASK_SET_TOGGLE_KEYS; + + if (!extendedSpiSupported) + return ERROR_INVALID_DATA; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, sysparam->toggleKeys); + break; + + case SPI_SETFILTERKEYS: + sysparam->params |= SPI_MASK_SET_FILTER_KEYS; + + if (!extendedSpiSupported) + return ERROR_INVALID_DATA; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 20)) + return ERROR_INVALID_DATA; + + error = rail_read_filterkeys(s, &sysparam->filterKeys); + break; + + /* Server sysparams */ + case SPI_SETSCREENSAVEACTIVE: + sysparam->params |= SPI_MASK_SET_SCREEN_SAVE_ACTIVE; + + Stream_Read_UINT8(s, body); /* body (1 byte) */ + sysparam->setScreenSaveActive = body != 0; + break; + + case SPI_SETSCREENSAVESECURE: + sysparam->params |= SPI_MASK_SET_SET_SCREEN_SAVE_SECURE; + + Stream_Read_UINT8(s, body); /* body (1 byte) */ + sysparam->setScreenSaveSecure = body != 0; + break; + + default: + break; + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 err2or code + */ +UINT rail_write_sysparam_order(wStream* s, const RAIL_SYSPARAM_ORDER* sysparam, + BOOL extendedSpiSupported) +{ + BYTE body = 0; + UINT error = CHANNEL_RC_OK; + + if (!s || !sysparam) + return ERROR_INVALID_PARAMETER; + + if (!Stream_EnsureRemainingCapacity(s, 12)) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT32(s, sysparam->param); /* systemParam (4 bytes) */ + + switch (sysparam->param) + { + /* Client sysparams */ + case SPI_SET_DRAG_FULL_WINDOWS: + body = sysparam->dragFullWindows ? 1 : 0; + Stream_Write_UINT8(s, body); + break; + + case SPI_SET_KEYBOARD_CUES: + body = sysparam->keyboardCues ? 1 : 0; + Stream_Write_UINT8(s, body); + break; + + case SPI_SET_KEYBOARD_PREF: + body = sysparam->keyboardPref ? 1 : 0; + Stream_Write_UINT8(s, body); + break; + + case SPI_SET_MOUSE_BUTTON_SWAP: + body = sysparam->mouseButtonSwap ? 1 : 0; + Stream_Write_UINT8(s, body); + break; + + case SPI_SET_WORK_AREA: + Stream_Write_UINT16(s, sysparam->workArea.left); /* left (2 bytes) */ + Stream_Write_UINT16(s, sysparam->workArea.top); /* top (2 bytes) */ + Stream_Write_UINT16(s, sysparam->workArea.right); /* right (2 bytes) */ + Stream_Write_UINT16(s, sysparam->workArea.bottom); /* bottom (2 bytes) */ + break; + + case SPI_DISPLAY_CHANGE: + Stream_Write_UINT16(s, sysparam->displayChange.left); /* left (2 bytes) */ + Stream_Write_UINT16(s, sysparam->displayChange.top); /* top (2 bytes) */ + Stream_Write_UINT16(s, sysparam->displayChange.right); /* right (2 bytes) */ + Stream_Write_UINT16(s, sysparam->displayChange.bottom); /* bottom (2 bytes) */ + break; + + case SPI_TASKBAR_POS: + Stream_Write_UINT16(s, sysparam->taskbarPos.left); /* left (2 bytes) */ + Stream_Write_UINT16(s, sysparam->taskbarPos.top); /* top (2 bytes) */ + Stream_Write_UINT16(s, sysparam->taskbarPos.right); /* right (2 bytes) */ + Stream_Write_UINT16(s, sysparam->taskbarPos.bottom); /* bottom (2 bytes) */ + break; + + case SPI_SET_HIGH_CONTRAST: + error = rail_write_high_contrast(s, &sysparam->highContrast); + break; + + case SPI_SETCARETWIDTH: + if (!extendedSpiSupported) + return ERROR_INVALID_DATA; + + if (sysparam->caretWidth < 0x0001) + return ERROR_INVALID_DATA; + + Stream_Write_UINT32(s, sysparam->caretWidth); + break; + + case SPI_SETSTICKYKEYS: + if (!extendedSpiSupported) + return ERROR_INVALID_DATA; + + Stream_Write_UINT32(s, sysparam->stickyKeys); + break; + + case SPI_SETTOGGLEKEYS: + if (!extendedSpiSupported) + return ERROR_INVALID_DATA; + + Stream_Write_UINT32(s, sysparam->toggleKeys); + break; + + case SPI_SETFILTERKEYS: + if (!extendedSpiSupported) + return ERROR_INVALID_DATA; + + error = rail_write_filterkeys(s, &sysparam->filterKeys); + break; + + /* Server sysparams */ + case SPI_SETSCREENSAVEACTIVE: + body = sysparam->setScreenSaveActive ? 1 : 0; + Stream_Write_UINT8(s, body); + break; + + case SPI_SETSCREENSAVESECURE: + body = sysparam->setScreenSaveSecure ? 1 : 0; + Stream_Write_UINT8(s, body); + break; + + default: + return ERROR_INVALID_PARAMETER; + } + + return error; +} + +BOOL rail_is_extended_spi_supported(UINT32 channelFlags) +{ + return (channelFlags & TS_RAIL_ORDER_HANDSHAKE_EX_FLAGS_EXTENDED_SPI_SUPPORTED) != 0; +} + +const char* rail_handshake_ex_flags_to_string(UINT32 flags, char* buffer, size_t len) +{ + if (len < 1) + return nullptr; + + (void)_snprintf(buffer, len, "{"); + char* fbuffer = &buffer[1]; + len--; + + if (flags & TS_RAIL_ORDER_HANDSHAKEEX_FLAGS_HIDEF) + winpr_str_append("HIDEF", fbuffer, len, "|"); + if (flags & TS_RAIL_ORDER_HANDSHAKE_EX_FLAGS_EXTENDED_SPI_SUPPORTED) + winpr_str_append("EXTENDED_SPI_SUPPORTED", fbuffer, len, "|"); + if (flags & TS_RAIL_ORDER_HANDSHAKE_EX_FLAGS_SNAP_ARRANGE_SUPPORTED) + winpr_str_append("SNAP_ARRANGE_SUPPORTED", fbuffer, len, "|"); + if (flags & TS_RAIL_ORDER_HANDSHAKE_EX_FLAGS_TEXT_SCALE_SUPPORTED) + winpr_str_append("TEXT_SCALE_SUPPORTED", fbuffer, len, "|"); + if (flags & TS_RAIL_ORDER_HANDSHAKE_EX_FLAGS_CARET_BLINK_SUPPORTED) + winpr_str_append("CARET_BLINK_SUPPORTED", fbuffer, len, "|"); + if (flags & TS_RAIL_ORDER_HANDSHAKE_EX_FLAGS_EXTENDED_SPI_2_SUPPORTED) + winpr_str_append("EXTENDED_SPI_2_SUPPORTED", fbuffer, len, "|"); + + char number[16] = WINPR_C_ARRAY_INIT; + (void)_snprintf(number, sizeof(number), "[0x%08" PRIx32 "]", flags); + winpr_str_append(number, buffer, len, "}"); + return buffer; +} diff --git a/third_party/FreeRDP/channels/rail/rail_common.h b/third_party/FreeRDP/channels/rail/rail_common.h new file mode 100644 index 0000000..df6106f --- /dev/null +++ b/third_party/FreeRDP/channels/rail/rail_common.h @@ -0,0 +1,98 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RAIL Virtual Channel Plugin + * + * Copyright 2011 Marc-Andre Moreau + * Copyright 2011 Roman Barabanov + * Copyright 2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RAIL_COMMON_H +#define FREERDP_CHANNEL_RAIL_COMMON_H + +#include + +#define RAIL_PDU_HEADER_LENGTH 4 + +/* Fixed length of PDUs, excluding variable lengths */ +#define RAIL_HANDSHAKE_ORDER_LENGTH 4 /* fixed */ +#define RAIL_HANDSHAKE_EX_ORDER_LENGTH 8 /* fixed */ +#define RAIL_CLIENT_STATUS_ORDER_LENGTH 4 /* fixed */ +#define RAIL_EXEC_ORDER_LENGTH 8 /* variable */ +#define RAIL_EXEC_RESULT_ORDER_LENGTH 12 /* variable */ +#define RAIL_SYSPARAM_ORDER_LENGTH 4 /* variable */ +#define RAIL_MINMAXINFO_ORDER_LENGTH 20 /* fixed */ +#define RAIL_LOCALMOVESIZE_ORDER_LENGTH 12 /* fixed */ +#define RAIL_ACTIVATE_ORDER_LENGTH 5 /* fixed */ +#define RAIL_SYSMENU_ORDER_LENGTH 8 /* fixed */ +#define RAIL_SYSCOMMAND_ORDER_LENGTH 6 /* fixed */ +#define RAIL_NOTIFY_EVENT_ORDER_LENGTH 12 /* fixed */ +#define RAIL_WINDOW_MOVE_ORDER_LENGTH 12 /* fixed */ +#define RAIL_SNAP_ARRANGE_ORDER_LENGTH 12 /* fixed */ +#define RAIL_GET_APPID_REQ_ORDER_LENGTH 4 /* fixed */ +#define RAIL_LANGBAR_INFO_ORDER_LENGTH 4 /* fixed */ +#define RAIL_LANGUAGEIME_INFO_ORDER_LENGTH 42 /* fixed */ +#define RAIL_COMPARTMENT_INFO_ORDER_LENGTH 16 /* fixed */ +#define RAIL_CLOAK_ORDER_LENGTH 5 /* fixed */ +#define RAIL_TASKBAR_INFO_ORDER_LENGTH 12 /* fixed */ +#define RAIL_Z_ORDER_SYNC_ORDER_LENGTH 4 /* fixed */ +#define RAIL_POWER_DISPLAY_REQUEST_ORDER_LENGTH 4 /* fixed */ +#define RAIL_GET_APPID_RESP_ORDER_LENGTH 524 /* fixed */ +#define RAIL_GET_APPID_RESP_EX_ORDER_LENGTH 1048 /* fixed */ + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT rail_read_handshake_order(wStream* s, + RAIL_HANDSHAKE_ORDER* handshake); + +FREERDP_LOCAL +void rail_write_handshake_order(wStream* s, const RAIL_HANDSHAKE_ORDER* handshake); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT +rail_read_handshake_ex_order(wStream* s, RAIL_HANDSHAKE_EX_ORDER* handshakeEx); + +FREERDP_LOCAL +void rail_write_handshake_ex_order(wStream* s, const RAIL_HANDSHAKE_EX_ORDER* handshakeEx); + +WINPR_ATTR_MALLOC(Stream_Free, 1) +WINPR_ATTR_NODISCARD FREERDP_LOCAL wStream* rail_pdu_init(size_t length); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT rail_read_pdu_header(wStream* s, UINT16* orderType, + UINT16* orderLength); + +FREERDP_LOCAL +void rail_write_pdu_header(wStream* s, UINT16 orderType, UINT16 orderLength); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT +rail_write_unicode_string(wStream* s, const RAIL_UNICODE_STRING* unicode_string); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT +rail_write_unicode_string_value(wStream* s, const RAIL_UNICODE_STRING* unicode_string); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT rail_read_sysparam_order(wStream* s, + RAIL_SYSPARAM_ORDER* sysparam, + BOOL extendedSpiSupported); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT rail_write_sysparam_order( + wStream* s, const RAIL_SYSPARAM_ORDER* sysparam, BOOL extendedSpiSupported); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL rail_is_extended_spi_supported(UINT32 channelFlags); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL const char* rail_get_order_type_string(UINT16 orderType); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL const char* +rail_get_order_type_string_full(UINT16 orderType, char* buffer, size_t length); + +#endif /* FREERDP_CHANNEL_RAIL_COMMON_H */ diff --git a/third_party/FreeRDP/channels/rail/server/CMakeLists.txt b/third_party/FreeRDP/channels/rail/server/CMakeLists.txt new file mode 100644 index 0000000..76734b0 --- /dev/null +++ b/third_party/FreeRDP/channels/rail/server/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2019 Mati Shabtay +# +# 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. + +define_channel_server("rail") + +set(${MODULE_PREFIX}_SRCS ../rail_common.c ../rail_common.h rail_main.c rail_main.h) + +set(${MODULE_PREFIX}_LIBS freerdp) + +include_directories(..) + +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntryEx") diff --git a/third_party/FreeRDP/channels/rail/server/rail_main.c b/third_party/FreeRDP/channels/rail/server/rail_main.c new file mode 100644 index 0000000..686427f --- /dev/null +++ b/third_party/FreeRDP/channels/rail/server/rail_main.c @@ -0,0 +1,1735 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RAIL Virtual Channel Plugin + * + * Copyright 2019 Mati Shabtay + * + + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "rail_main.h" + +#define TAG CHANNELS_TAG("rail.server") + +/** + * Sends a single rail PDU on the channel + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send(RailServerContext* context, wStream* s, ULONG length) +{ + UINT status = CHANNEL_RC_OK; + ULONG written = 0; + + if (!context) + return CHANNEL_RC_BAD_INIT_HANDLE; + + if (!WTSVirtualChannelWrite(context->priv->rail_channel, Stream_BufferAs(s, char), length, + &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + status = ERROR_INTERNAL_ERROR; + } + + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_server_send_pdu(RailServerContext* context, wStream* s, UINT16 orderType) +{ + char buffer[128] = WINPR_C_ARRAY_INIT; + UINT16 orderLength = 0; + + if (!context || !s) + return ERROR_INVALID_PARAMETER; + + orderLength = (UINT16)Stream_GetPosition(s); + Stream_ResetPosition(s); + rail_write_pdu_header(s, orderType, orderLength); + Stream_SetPosition(s, orderLength); + WLog_DBG(TAG, "Sending %s PDU, length: %" PRIu16 "", + rail_get_order_type_string_full(orderType, buffer, sizeof(buffer)), orderLength); + return rail_send(context, s, orderLength); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_write_local_move_size_order(wStream* s, + const RAIL_LOCALMOVESIZE_ORDER* localMoveSize) +{ + if (!s || !localMoveSize) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, localMoveSize->windowId); /* WindowId (4 bytes) */ + Stream_Write_UINT16(s, localMoveSize->isMoveSizeStart ? 1 : 0); /* IsMoveSizeStart (2 bytes) */ + Stream_Write_UINT16(s, localMoveSize->moveSizeType); /* MoveSizeType (2 bytes) */ + Stream_Write_INT16(s, localMoveSize->posX); /* PosX (2 bytes) */ + Stream_Write_INT16(s, localMoveSize->posY); /* PosY (2 bytes) */ + return ERROR_SUCCESS; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_write_min_max_info_order(wStream* s, const RAIL_MINMAXINFO_ORDER* minMaxInfo) +{ + if (!s || !minMaxInfo) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, minMaxInfo->windowId); /* WindowId (4 bytes) */ + Stream_Write_INT16(s, minMaxInfo->maxWidth); /* MaxWidth (2 bytes) */ + Stream_Write_INT16(s, minMaxInfo->maxHeight); /* MaxHeight (2 bytes) */ + Stream_Write_INT16(s, minMaxInfo->maxPosX); /* MaxPosX (2 bytes) */ + Stream_Write_INT16(s, minMaxInfo->maxPosY); /* MaxPosY (2 bytes) */ + Stream_Write_INT16(s, minMaxInfo->minTrackWidth); /* MinTrackWidth (2 bytes) */ + Stream_Write_INT16(s, minMaxInfo->minTrackHeight); /* MinTrackHeight (2 bytes) */ + Stream_Write_INT16(s, minMaxInfo->maxTrackWidth); /* MaxTrackWidth (2 bytes) */ + Stream_Write_INT16(s, minMaxInfo->maxTrackHeight); /* MaxTrackHeight (2 bytes) */ + return ERROR_SUCCESS; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_write_taskbar_info_order(wStream* s, const RAIL_TASKBAR_INFO_ORDER* taskbarInfo) +{ + if (!s || !taskbarInfo) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, taskbarInfo->TaskbarMessage); /* TaskbarMessage (4 bytes) */ + Stream_Write_UINT32(s, taskbarInfo->WindowIdTab); /* WindowIdTab (4 bytes) */ + Stream_Write_UINT32(s, taskbarInfo->Body); /* Body (4 bytes) */ + return ERROR_SUCCESS; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_write_langbar_info_order(wStream* s, const RAIL_LANGBAR_INFO_ORDER* langbarInfo) +{ + if (!s || !langbarInfo) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, langbarInfo->languageBarStatus); /* LanguageBarStatus (4 bytes) */ + return ERROR_SUCCESS; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_write_exec_result_order(wStream* s, const RAIL_EXEC_RESULT_ORDER* execResult) +{ + if (!s || !execResult) + return ERROR_INVALID_PARAMETER; + + if (execResult->exeOrFile.length > 520 || execResult->exeOrFile.length < 1) + return ERROR_INVALID_DATA; + + Stream_Write_UINT16(s, execResult->flags); /* Flags (2 bytes) */ + Stream_Write_UINT16(s, execResult->execResult); /* ExecResult (2 bytes) */ + Stream_Write_UINT32(s, execResult->rawResult); /* RawResult (4 bytes) */ + Stream_Write_UINT16(s, 0); /* Padding (2 bytes) */ + Stream_Write_UINT16(s, execResult->exeOrFile.length); /* ExeOrFileLength (2 bytes) */ + Stream_Write(s, execResult->exeOrFile.string, + execResult->exeOrFile.length); /* ExeOrFile (variable) */ + return ERROR_SUCCESS; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_write_z_order_sync_order(wStream* s, const RAIL_ZORDER_SYNC* zOrderSync) +{ + if (!s || !zOrderSync) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, zOrderSync->windowIdMarker); /* WindowIdMarker (4 bytes) */ + return ERROR_SUCCESS; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_write_cloak_order(wStream* s, const RAIL_CLOAK* cloak) +{ + if (!s || !cloak) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, cloak->windowId); /* WindowId (4 bytes) */ + Stream_Write_UINT8(s, cloak->cloak ? 1 : 0); /* Cloaked (1 byte) */ + return ERROR_SUCCESS; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +rail_write_power_display_request_order(wStream* s, + const RAIL_POWER_DISPLAY_REQUEST* powerDisplayRequest) +{ + if (!s || !powerDisplayRequest) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, powerDisplayRequest->active ? 1 : 0); /* Active (4 bytes) */ + return ERROR_SUCCESS; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_write_get_app_id_resp_order(wStream* s, + const RAIL_GET_APPID_RESP_ORDER* getAppidResp) +{ + if (!s || !getAppidResp) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, getAppidResp->windowId); /* WindowId (4 bytes) */ + Stream_Write_UTF16_String( + s, getAppidResp->applicationId, + ARRAYSIZE(getAppidResp->applicationId)); /* ApplicationId (512 bytes) */ + return ERROR_SUCCESS; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_write_get_appid_resp_ex_order(wStream* s, + const RAIL_GET_APPID_RESP_EX* getAppidRespEx) +{ + if (!s || !getAppidRespEx) + return ERROR_INVALID_PARAMETER; + + Stream_Write_UINT32(s, getAppidRespEx->windowID); /* WindowId (4 bytes) */ + Stream_Write_UTF16_String( + s, getAppidRespEx->applicationID, + ARRAYSIZE(getAppidRespEx->applicationID)); /* ApplicationId (520 bytes) */ + Stream_Write_UINT32(s, getAppidRespEx->processId); /* ProcessId (4 bytes) */ + Stream_Write_UTF16_String( + s, getAppidRespEx->processImageName, + ARRAYSIZE(getAppidRespEx->processImageName)); /* ProcessImageName (520 bytes) */ + return ERROR_SUCCESS; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send_server_handshake(RailServerContext* context, + const RAIL_HANDSHAKE_ORDER* handshake) +{ + wStream* s = nullptr; + UINT error = 0; + + if (!context || !handshake) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_HANDSHAKE_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rail_write_handshake_order(s, handshake); + error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_HANDSHAKE); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send_server_handshake_ex(RailServerContext* context, + const RAIL_HANDSHAKE_EX_ORDER* handshakeEx) +{ + wStream* s = nullptr; + UINT error = 0; + + if (!context || !handshakeEx || !context->priv) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_HANDSHAKE_EX_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rail_server_set_handshake_ex_flags(context, handshakeEx->railHandshakeFlags); + + rail_write_handshake_ex_order(s, handshakeEx); + error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_HANDSHAKE_EX); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send_server_sysparam(RailServerContext* context, + const RAIL_SYSPARAM_ORDER* sysparam) +{ + wStream* s = nullptr; + UINT error = 0; + RailServerPrivate* priv = nullptr; + BOOL extendedSpiSupported = 0; + + if (!context || !sysparam) + return ERROR_INVALID_PARAMETER; + + priv = context->priv; + + if (!priv) + return ERROR_INVALID_PARAMETER; + + extendedSpiSupported = rail_is_extended_spi_supported(context->priv->channelFlags); + s = rail_pdu_init(RAIL_SYSPARAM_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + error = rail_write_sysparam_order(s, sysparam, extendedSpiSupported); + if (error == CHANNEL_RC_OK) + error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_SYSPARAM); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send_server_local_move_size(RailServerContext* context, + const RAIL_LOCALMOVESIZE_ORDER* localMoveSize) +{ + wStream* s = nullptr; + UINT error = 0; + + if (!context || !localMoveSize) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_LOCALMOVESIZE_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rail_write_local_move_size_order(s, localMoveSize); + error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_LOCALMOVESIZE); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send_server_min_max_info(RailServerContext* context, + const RAIL_MINMAXINFO_ORDER* minMaxInfo) +{ + wStream* s = nullptr; + UINT error = 0; + + if (!context || !minMaxInfo) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_MINMAXINFO_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rail_write_min_max_info_order(s, minMaxInfo); + error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_MINMAXINFO); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send_server_taskbar_info(RailServerContext* context, + const RAIL_TASKBAR_INFO_ORDER* taskbarInfo) +{ + wStream* s = nullptr; + UINT error = 0; + + if (!context || !taskbarInfo) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_TASKBAR_INFO_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rail_write_taskbar_info_order(s, taskbarInfo); + error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_TASKBARINFO); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send_server_langbar_info(RailServerContext* context, + const RAIL_LANGBAR_INFO_ORDER* langbarInfo) +{ + wStream* s = nullptr; + UINT error = 0; + + if (!context || !langbarInfo) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_LANGBAR_INFO_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rail_write_langbar_info_order(s, langbarInfo); + error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_LANGBARINFO); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send_server_exec_result(RailServerContext* context, + const RAIL_EXEC_RESULT_ORDER* execResult) +{ + wStream* s = nullptr; + UINT error = 0; + + if (!context || !execResult) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_EXEC_RESULT_ORDER_LENGTH + execResult->exeOrFile.length); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rail_write_exec_result_order(s, execResult); + error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_EXEC_RESULT); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send_server_z_order_sync(RailServerContext* context, + const RAIL_ZORDER_SYNC* zOrderSync) +{ + wStream* s = nullptr; + UINT error = 0; + + if (!context || !zOrderSync) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_Z_ORDER_SYNC_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rail_write_z_order_sync_order(s, zOrderSync); + error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_ZORDER_SYNC); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send_server_cloak(RailServerContext* context, const RAIL_CLOAK* cloak) +{ + wStream* s = nullptr; + UINT error = 0; + + if (!context || !cloak) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_CLOAK_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rail_write_cloak_order(s, cloak); + error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_CLOAK); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +rail_send_server_power_display_request(RailServerContext* context, + const RAIL_POWER_DISPLAY_REQUEST* powerDisplayRequest) +{ + wStream* s = nullptr; + UINT error = 0; + + if (!context || !powerDisplayRequest) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_POWER_DISPLAY_REQUEST_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rail_write_power_display_request_order(s, powerDisplayRequest); + error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_POWER_DISPLAY_REQUEST); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error coie + */ +static UINT rail_send_server_get_app_id_resp(RailServerContext* context, + const RAIL_GET_APPID_RESP_ORDER* getAppidResp) +{ + wStream* s = nullptr; + UINT error = 0; + + if (!context || !getAppidResp) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_GET_APPID_RESP_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rail_write_get_app_id_resp_order(s, getAppidResp); + error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_GET_APPID_RESP); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_send_server_get_appid_resp_ex(RailServerContext* context, + const RAIL_GET_APPID_RESP_EX* getAppidRespEx) +{ + wStream* s = nullptr; + UINT error = 0; + + if (!context || !getAppidRespEx) + return ERROR_INVALID_PARAMETER; + + s = rail_pdu_init(RAIL_GET_APPID_RESP_EX_ORDER_LENGTH); + + if (!s) + { + WLog_ERR(TAG, "rail_pdu_init failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rail_write_get_appid_resp_ex_order(s, getAppidRespEx); + error = rail_server_send_pdu(context, s, TS_RAIL_ORDER_GET_APPID_RESP_EX); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_client_status_order(wStream* s, RAIL_CLIENT_STATUS_ORDER* clientStatus) +{ + if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_CLIENT_STATUS_ORDER_LENGTH)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, clientStatus->flags); /* Flags (4 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_exec_order(wStream* s, RAIL_EXEC_ORDER* exec, char* args[]) +{ + RAIL_EXEC_ORDER order = WINPR_C_ARRAY_INIT; + UINT16 exeLen = 0; + UINT16 workLen = 0; + UINT16 argLen = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_EXEC_ORDER_LENGTH)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, exec->flags); /* Flags (2 bytes) */ + Stream_Read_UINT16(s, exeLen); /* ExeOrFileLength (2 bytes) */ + Stream_Read_UINT16(s, workLen); /* WorkingDirLength (2 bytes) */ + Stream_Read_UINT16(s, argLen); /* ArgumentsLength (2 bytes) */ + + if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)exeLen + workLen + argLen)) + return ERROR_INVALID_DATA; + + if (exeLen > 0) + { + const size_t len = exeLen / sizeof(WCHAR); + exec->RemoteApplicationProgram = args[0] = + Stream_Read_UTF16_String_As_UTF8(s, len, nullptr); + if (!exec->RemoteApplicationProgram) + goto fail; + } + if (workLen > 0) + { + const size_t len = workLen / sizeof(WCHAR); + exec->RemoteApplicationWorkingDir = args[1] = + Stream_Read_UTF16_String_As_UTF8(s, len, nullptr); + if (!exec->RemoteApplicationWorkingDir) + goto fail; + } + if (argLen > 0) + { + const size_t len = argLen / sizeof(WCHAR); + exec->RemoteApplicationArguments = args[2] = + Stream_Read_UTF16_String_As_UTF8(s, len, nullptr); + if (!exec->RemoteApplicationArguments) + goto fail; + } + + return CHANNEL_RC_OK; +fail: + free(args[0]); + free(args[1]); + free(args[2]); + *exec = order; + return ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_activate_order(wStream* s, RAIL_ACTIVATE_ORDER* activate) +{ + BYTE enabled = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_ACTIVATE_ORDER_LENGTH)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, activate->windowId); /* WindowId (4 bytes) */ + Stream_Read_UINT8(s, enabled); /* Enabled (1 byte) */ + activate->enabled = (enabled != 0); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_sysmenu_order(wStream* s, RAIL_SYSMENU_ORDER* sysmenu) +{ + if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_SYSMENU_ORDER_LENGTH)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, sysmenu->windowId); /* WindowId (4 bytes) */ + Stream_Read_INT16(s, sysmenu->left); /* Left (2 bytes) */ + Stream_Read_INT16(s, sysmenu->top); /* Top (2 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_syscommand_order(wStream* s, RAIL_SYSCOMMAND_ORDER* syscommand) +{ + if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_SYSCOMMAND_ORDER_LENGTH)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, syscommand->windowId); /* WindowId (4 bytes) */ + Stream_Read_UINT16(s, syscommand->command); /* Command (2 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_notify_event_order(wStream* s, RAIL_NOTIFY_EVENT_ORDER* notifyEvent) +{ + if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_NOTIFY_EVENT_ORDER_LENGTH)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, notifyEvent->windowId); /* WindowId (4 bytes) */ + Stream_Read_UINT32(s, notifyEvent->notifyIconId); /* NotifyIconId (4 bytes) */ + Stream_Read_UINT32(s, notifyEvent->message); /* Message (4 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_get_appid_req_order(wStream* s, RAIL_GET_APPID_REQ_ORDER* getAppidReq) +{ + if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_GET_APPID_REQ_ORDER_LENGTH)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, getAppidReq->windowId); /* WindowId (4 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_window_move_order(wStream* s, RAIL_WINDOW_MOVE_ORDER* windowMove) +{ + if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_WINDOW_MOVE_ORDER_LENGTH)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, windowMove->windowId); /* WindowId (4 bytes) */ + Stream_Read_INT16(s, windowMove->left); /* Left (2 bytes) */ + Stream_Read_INT16(s, windowMove->top); /* Top (2 bytes) */ + Stream_Read_INT16(s, windowMove->right); /* Right (2 bytes) */ + Stream_Read_INT16(s, windowMove->bottom); /* Bottom (2 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_snap_arange_order(wStream* s, RAIL_SNAP_ARRANGE* snapArrange) +{ + if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_SNAP_ARRANGE_ORDER_LENGTH)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, snapArrange->windowId); /* WindowId (4 bytes) */ + Stream_Read_INT16(s, snapArrange->left); /* Left (2 bytes) */ + Stream_Read_INT16(s, snapArrange->top); /* Top (2 bytes) */ + Stream_Read_INT16(s, snapArrange->right); /* Right (2 bytes) */ + Stream_Read_INT16(s, snapArrange->bottom); /* Bottom (2 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_langbar_info_order(wStream* s, RAIL_LANGBAR_INFO_ORDER* langbarInfo) +{ + if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_LANGBAR_INFO_ORDER_LENGTH)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, langbarInfo->languageBarStatus); /* LanguageBarStatus (4 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_language_ime_info_order(wStream* s, + RAIL_LANGUAGEIME_INFO_ORDER* languageImeInfo) +{ + if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_LANGUAGEIME_INFO_ORDER_LENGTH)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, languageImeInfo->ProfileType); /* ProfileType (4 bytes) */ + Stream_Read_UINT16(s, languageImeInfo->LanguageID); /* LanguageID (2 bytes) */ + Stream_Read( + s, &languageImeInfo->LanguageProfileCLSID, + sizeof(languageImeInfo->LanguageProfileCLSID)); /* LanguageProfileCLSID (16 bytes) */ + Stream_Read(s, &languageImeInfo->ProfileGUID, + sizeof(languageImeInfo->ProfileGUID)); /* ProfileGUID (16 bytes) */ + Stream_Read_UINT32(s, languageImeInfo->KeyboardLayout); /* KeyboardLayout (4 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_compartment_info_order(wStream* s, + RAIL_COMPARTMENT_INFO_ORDER* compartmentInfo) +{ + if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_COMPARTMENT_INFO_ORDER_LENGTH)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, compartmentInfo->ImeState); /* ImeState (4 bytes) */ + Stream_Read_UINT32(s, compartmentInfo->ImeConvMode); /* ImeConvMode (4 bytes) */ + Stream_Read_UINT32(s, compartmentInfo->ImeSentenceMode); /* ImeSentenceMode (4 bytes) */ + Stream_Read_UINT32(s, compartmentInfo->KanaMode); /* KANAMode (4 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_read_cloak_order(wStream* s, RAIL_CLOAK* cloak) +{ + BYTE cloaked = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, RAIL_CLOAK_ORDER_LENGTH)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, cloak->windowId); /* WindowId (4 bytes) */ + Stream_Read_UINT8(s, cloaked); /* Cloaked (1 byte) */ + cloak->cloak = (cloaked != 0); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_handshake_order(RailServerContext* context, + RAIL_HANDSHAKE_ORDER* handshake, wStream* s) +{ + UINT error = 0; + + if (!context || !handshake || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_handshake_order(s, handshake))) + { + WLog_ERR(TAG, "rail_read_handshake_order failed with error %" PRIu32 "!", error); + return error; + } + + IFCALLRET(context->ClientHandshake, error, context, handshake); + + if (error) + WLog_ERR(TAG, "context.ClientHandshake failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_client_status_order(RailServerContext* context, + RAIL_CLIENT_STATUS_ORDER* clientStatus, wStream* s) +{ + UINT error = 0; + + if (!context || !clientStatus || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_client_status_order(s, clientStatus))) + { + WLog_ERR(TAG, "rail_read_client_status_order failed with error %" PRIu32 "!", error); + return error; + } + + IFCALLRET(context->ClientClientStatus, error, context, clientStatus); + + if (error) + WLog_ERR(TAG, "context.ClientClientStatus failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_exec_order(RailServerContext* context, wStream* s) +{ + UINT error = 0; + char* args[3] = WINPR_C_ARRAY_INIT; + RAIL_EXEC_ORDER exec = WINPR_C_ARRAY_INIT; + + if (!context || !s) + return ERROR_INVALID_PARAMETER; + + error = rail_read_exec_order(s, &exec, args); + if (error) + { + WLog_ERR(TAG, "rail_read_client_status_order failed with error %" PRIu32 "!", error); + return error; + } + + IFCALLRET(context->ClientExec, error, context, &exec); + + if (error) + WLog_ERR(TAG, "context.Exec failed with error %" PRIu32 "", error); + + free(args[0]); + free(args[1]); + free(args[2]); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_sysparam_order(RailServerContext* context, + RAIL_SYSPARAM_ORDER* sysparam, wStream* s) +{ + UINT error = 0; + BOOL extendedSpiSupported = 0; + + if (!context || !sysparam || !s) + return ERROR_INVALID_PARAMETER; + + extendedSpiSupported = rail_is_extended_spi_supported(context->priv->channelFlags); + if ((error = rail_read_sysparam_order(s, sysparam, extendedSpiSupported))) + { + WLog_ERR(TAG, "rail_read_sysparam_order failed with error %" PRIu32 "!", error); + return error; + } + + IFCALLRET(context->ClientSysparam, error, context, sysparam); + + if (error) + WLog_ERR(TAG, "context.ClientSysparam failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_activate_order(RailServerContext* context, + RAIL_ACTIVATE_ORDER* activate, wStream* s) +{ + UINT error = 0; + + if (!context || !activate || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_activate_order(s, activate))) + { + WLog_ERR(TAG, "rail_read_activate_order failed with error %" PRIu32 "!", error); + return error; + } + + IFCALLRET(context->ClientActivate, error, context, activate); + + if (error) + WLog_ERR(TAG, "context.ClientActivate failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_sysmenu_order(RailServerContext* context, RAIL_SYSMENU_ORDER* sysmenu, + wStream* s) +{ + UINT error = 0; + + if (!context || !sysmenu || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_sysmenu_order(s, sysmenu))) + { + WLog_ERR(TAG, "rail_read_sysmenu_order failed with error %" PRIu32 "!", error); + return error; + } + + IFCALLRET(context->ClientSysmenu, error, context, sysmenu); + + if (error) + WLog_ERR(TAG, "context.ClientSysmenu failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_syscommand_order(RailServerContext* context, + RAIL_SYSCOMMAND_ORDER* syscommand, wStream* s) +{ + UINT error = 0; + + if (!context || !syscommand || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_syscommand_order(s, syscommand))) + { + WLog_ERR(TAG, "rail_read_syscommand_order failed with error %" PRIu32 "!", error); + return error; + } + + IFCALLRET(context->ClientSyscommand, error, context, syscommand); + + if (error) + WLog_ERR(TAG, "context.ClientSyscommand failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_notify_event_order(RailServerContext* context, + RAIL_NOTIFY_EVENT_ORDER* notifyEvent, wStream* s) +{ + UINT error = 0; + + if (!context || !notifyEvent || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_notify_event_order(s, notifyEvent))) + { + WLog_ERR(TAG, "rail_read_notify_event_order failed with error %" PRIu32 "!", error); + return error; + } + + IFCALLRET(context->ClientNotifyEvent, error, context, notifyEvent); + + if (error) + WLog_ERR(TAG, "context.ClientNotifyEvent failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_window_move_order(RailServerContext* context, + RAIL_WINDOW_MOVE_ORDER* windowMove, wStream* s) +{ + UINT error = 0; + + if (!context || !windowMove || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_window_move_order(s, windowMove))) + { + WLog_ERR(TAG, "rail_read_window_move_order failed with error %" PRIu32 "!", error); + return error; + } + + IFCALLRET(context->ClientWindowMove, error, context, windowMove); + + if (error) + WLog_ERR(TAG, "context.ClientWindowMove failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_snap_arrange_order(RailServerContext* context, + RAIL_SNAP_ARRANGE* snapArrange, wStream* s) +{ + UINT error = 0; + + if (!context || !snapArrange || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_snap_arange_order(s, snapArrange))) + { + WLog_ERR(TAG, "rail_read_snap_arange_order failed with error %" PRIu32 "!", error); + return error; + } + + IFCALLRET(context->ClientSnapArrange, error, context, snapArrange); + + if (error) + WLog_ERR(TAG, "context.ClientSnapArrange failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_get_appid_req_order(RailServerContext* context, + RAIL_GET_APPID_REQ_ORDER* getAppidReq, wStream* s) +{ + UINT error = 0; + + if (!context || !getAppidReq || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_get_appid_req_order(s, getAppidReq))) + { + WLog_ERR(TAG, "rail_read_get_appid_req_order failed with error %" PRIu32 "!", error); + return error; + } + + IFCALLRET(context->ClientGetAppidReq, error, context, getAppidReq); + + if (error) + WLog_ERR(TAG, "context.ClientGetAppidReq failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_langbar_info_order(RailServerContext* context, + RAIL_LANGBAR_INFO_ORDER* langbarInfo, wStream* s) +{ + UINT error = 0; + + if (!context || !langbarInfo || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_langbar_info_order(s, langbarInfo))) + { + WLog_ERR(TAG, "rail_read_langbar_info_order failed with error %" PRIu32 "!", error); + return error; + } + + IFCALLRET(context->ClientLangbarInfo, error, context, langbarInfo); + + if (error) + WLog_ERR(TAG, "context.ClientLangbarInfo failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_language_ime_info_order(RailServerContext* context, + RAIL_LANGUAGEIME_INFO_ORDER* languageImeInfo, + wStream* s) +{ + UINT error = 0; + + if (!context || !languageImeInfo || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_language_ime_info_order(s, languageImeInfo))) + { + WLog_ERR(TAG, "rail_read_language_ime_info_order failed with error %" PRIu32 "!", error); + return error; + } + + IFCALLRET(context->ClientLanguageImeInfo, error, context, languageImeInfo); + + if (error) + WLog_ERR(TAG, "context.ClientLanguageImeInfo failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_compartment_info(RailServerContext* context, + RAIL_COMPARTMENT_INFO_ORDER* compartmentInfo, + wStream* s) +{ + UINT error = 0; + + if (!context || !compartmentInfo || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_compartment_info_order(s, compartmentInfo))) + { + WLog_ERR(TAG, "rail_read_compartment_info_order failed with error %" PRIu32 "!", error); + return error; + } + + IFCALLRET(context->ClientCompartmentInfo, error, context, compartmentInfo); + + if (error) + WLog_ERR(TAG, "context.ClientCompartmentInfo failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_recv_client_cloak_order(RailServerContext* context, RAIL_CLOAK* cloak, wStream* s) +{ + UINT error = 0; + + if (!context || !cloak || !s) + return ERROR_INVALID_PARAMETER; + + if ((error = rail_read_cloak_order(s, cloak))) + { + WLog_ERR(TAG, "rail_read_cloak_order failed with error %" PRIu32 "!", error); + return error; + } + + IFCALLRET(context->ClientCloak, error, context, cloak); + + if (error) + WLog_ERR(TAG, "context.Cloak failed with error %" PRIu32 "", error); + + return error; +} + +static UINT rail_recv_client_text_scale_order(RailServerContext* context, wStream* s) +{ + UINT error = CHANNEL_RC_OK; + UINT32 TextScaleFactor = 0; + + if (!context || !s) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, TextScaleFactor); + IFCALLRET(context->ClientTextScale, error, context, TextScaleFactor); + + if (error) + WLog_ERR(TAG, "context.TextScale failed with error %" PRIu32 "", error); + + return error; +} + +static UINT rail_recv_client_caret_blink(RailServerContext* context, wStream* s) +{ + UINT error = CHANNEL_RC_OK; + UINT32 CaretBlinkRate = 0; + + if (!context || !s) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, CaretBlinkRate); + IFCALLRET(context->ClientCaretBlinkRate, error, context, CaretBlinkRate); + + if (error) + WLog_ERR(TAG, "context.CaretBlinkRate failed with error %" PRIu32 "", error); + + return error; +} + +static DWORD WINAPI rail_server_thread(LPVOID arg) +{ + RailServerContext* context = (RailServerContext*)arg; + RailServerPrivate* priv = context->priv; + DWORD status = 0; + DWORD nCount = 0; + HANDLE events[8]; + UINT error = CHANNEL_RC_OK; + events[nCount++] = priv->channelEvent; + events[nCount++] = priv->stopEvent; + + while (TRUE) + { + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "!", error); + break; + } + + status = WaitForSingleObject(context->priv->stopEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error); + break; + } + + if (status == WAIT_OBJECT_0) + break; + + status = WaitForSingleObject(context->priv->channelEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR( + TAG, + "WaitForSingleObject(context->priv->channelEvent, 0) failed with error %" PRIu32 + "!", + error); + break; + } + + if (status == WAIT_OBJECT_0) + { + if ((error = rail_server_handle_messages(context))) + { + WLog_ERR(TAG, "rail_server_handle_messages failed with error %" PRIu32 "", error); + break; + } + } + } + + if (error && context->rdpcontext) + setChannelError(context->rdpcontext, error, "rail_server_thread reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rail_server_start(RailServerContext* context) +{ + void* buffer = nullptr; + DWORD bytesReturned = 0; + RailServerPrivate* priv = context->priv; + UINT error = ERROR_INTERNAL_ERROR; + priv->rail_channel = + WTSVirtualChannelOpen(context->vcm, WTS_CURRENT_SESSION, RAIL_SVC_CHANNEL_NAME); + + if (!priv->rail_channel) + { + WLog_ERR(TAG, "WTSVirtualChannelOpen failed!"); + return error; + } + + if (!WTSVirtualChannelQuery(priv->rail_channel, WTSVirtualEventHandle, &buffer, + &bytesReturned) || + (bytesReturned != sizeof(HANDLE))) + { + WLog_ERR(TAG, + "error during WTSVirtualChannelQuery(WTSVirtualEventHandle) or invalid returned " + "size(%" PRIu32 ")", + bytesReturned); + + if (buffer) + WTSFreeMemory(buffer); + + goto out_close; + } + + priv->channelEvent = *(HANDLE*)buffer; + WTSFreeMemory(buffer); + context->priv->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + + if (!context->priv->stopEvent) + { + WLog_ERR(TAG, "CreateEvent failed!"); + goto out_close; + } + + context->priv->thread = + CreateThread(nullptr, 0, rail_server_thread, (void*)context, 0, nullptr); + + if (!context->priv->thread) + { + WLog_ERR(TAG, "CreateThread failed!"); + goto out_stop_event; + } + + return CHANNEL_RC_OK; +out_stop_event: + (void)CloseHandle(context->priv->stopEvent); + context->priv->stopEvent = nullptr; +out_close: + (void)WTSVirtualChannelClose(context->priv->rail_channel); + context->priv->rail_channel = nullptr; + return error; +} + +static BOOL rail_server_stop(RailServerContext* context) +{ + RailServerPrivate* priv = context->priv; + + if (priv->thread) + { + (void)SetEvent(priv->stopEvent); + + if (WaitForSingleObject(priv->thread, INFINITE) == WAIT_FAILED) + { + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", GetLastError()); + return FALSE; + } + + (void)CloseHandle(priv->thread); + (void)CloseHandle(priv->stopEvent); + priv->thread = nullptr; + priv->stopEvent = nullptr; + } + + if (priv->rail_channel) + { + (void)WTSVirtualChannelClose(priv->rail_channel); + priv->rail_channel = nullptr; + } + + priv->channelEvent = nullptr; + return TRUE; +} + +RailServerContext* rail_server_context_new(HANDLE vcm) +{ + RailServerContext* context = nullptr; + RailServerPrivate* priv = nullptr; + context = (RailServerContext*)calloc(1, sizeof(RailServerContext)); + + if (!context) + { + WLog_ERR(TAG, "calloc failed!"); + return nullptr; + } + + context->vcm = vcm; + context->Start = rail_server_start; + context->Stop = rail_server_stop; + context->ServerHandshake = rail_send_server_handshake; + context->ServerHandshakeEx = rail_send_server_handshake_ex; + context->ServerSysparam = rail_send_server_sysparam; + context->ServerLocalMoveSize = rail_send_server_local_move_size; + context->ServerMinMaxInfo = rail_send_server_min_max_info; + context->ServerTaskbarInfo = rail_send_server_taskbar_info; + context->ServerLangbarInfo = rail_send_server_langbar_info; + context->ServerExecResult = rail_send_server_exec_result; + context->ServerGetAppidResp = rail_send_server_get_app_id_resp; + context->ServerZOrderSync = rail_send_server_z_order_sync; + context->ServerCloak = rail_send_server_cloak; + context->ServerPowerDisplayRequest = rail_send_server_power_display_request; + context->ServerGetAppidRespEx = rail_send_server_get_appid_resp_ex; + context->priv = priv = (RailServerPrivate*)calloc(1, sizeof(RailServerPrivate)); + + if (!priv) + { + WLog_ERR(TAG, "calloc failed!"); + goto out_free; + } + + /* Create shared input stream */ + priv->input_stream = Stream_New(nullptr, 4096); + + if (!priv->input_stream) + { + WLog_ERR(TAG, "Stream_New failed!"); + goto out_free_priv; + } + + return context; +out_free_priv: + free(context->priv); +out_free: + free(context); + return nullptr; +} + +void rail_server_context_free(RailServerContext* context) +{ + if (context->priv) + Stream_Free(context->priv->input_stream, TRUE); + + free(context->priv); + free(context); +} + +void rail_server_set_handshake_ex_flags(RailServerContext* context, DWORD flags) +{ + RailServerPrivate* priv = nullptr; + + if (!context || !context->priv) + return; + + priv = context->priv; + priv->channelFlags = flags; +} + +UINT rail_server_handle_messages(RailServerContext* context) +{ + char buffer[128] = WINPR_C_ARRAY_INIT; + UINT status = CHANNEL_RC_OK; + DWORD bytesReturned = 0; + UINT16 orderType = 0; + UINT16 orderLength = 0; + RailServerPrivate* priv = context->priv; + wStream* s = priv->input_stream; + + /* Read header */ + if (!Stream_EnsureRemainingCapacity(s, RAIL_PDU_HEADER_LENGTH)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed, RAIL_PDU_HEADER_LENGTH"); + return CHANNEL_RC_NO_MEMORY; + } + + if (!WTSVirtualChannelRead(priv->rail_channel, 0, Stream_Pointer(s), RAIL_PDU_HEADER_LENGTH, + &bytesReturned)) + { + if (GetLastError() == ERROR_NO_DATA) + return ERROR_NO_DATA; + + WLog_ERR(TAG, "channel connection closed"); + return ERROR_INTERNAL_ERROR; + } + + /* Parse header */ + if ((status = rail_read_pdu_header(s, &orderType, &orderLength)) != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "rail_read_pdu_header failed with error %" PRIu32 "!", status); + return status; + } + + if (!Stream_EnsureRemainingCapacity(s, orderLength - RAIL_PDU_HEADER_LENGTH)) + { + WLog_ERR(TAG, + "Stream_EnsureRemainingCapacity failed, orderLength - RAIL_PDU_HEADER_LENGTH"); + return CHANNEL_RC_NO_MEMORY; + } + + /* Read body */ + if (!WTSVirtualChannelRead(priv->rail_channel, 0, Stream_Pointer(s), + orderLength - RAIL_PDU_HEADER_LENGTH, &bytesReturned)) + { + if (GetLastError() == ERROR_NO_DATA) + return ERROR_NO_DATA; + + WLog_ERR(TAG, "channel connection closed"); + return ERROR_INTERNAL_ERROR; + } + + WLog_DBG(TAG, "Received %s PDU, length:%" PRIu16 "", + rail_get_order_type_string_full(orderType, buffer, sizeof(buffer)), orderLength); + + switch (orderType) + { + case TS_RAIL_ORDER_HANDSHAKE: + { + RAIL_HANDSHAKE_ORDER handshake; + return rail_recv_client_handshake_order(context, &handshake, s); + } + + case TS_RAIL_ORDER_CLIENTSTATUS: + { + RAIL_CLIENT_STATUS_ORDER clientStatus; + return rail_recv_client_client_status_order(context, &clientStatus, s); + } + + case TS_RAIL_ORDER_EXEC: + return rail_recv_client_exec_order(context, s); + + case TS_RAIL_ORDER_SYSPARAM: + { + RAIL_SYSPARAM_ORDER sysparam = WINPR_C_ARRAY_INIT; + return rail_recv_client_sysparam_order(context, &sysparam, s); + } + + case TS_RAIL_ORDER_ACTIVATE: + { + RAIL_ACTIVATE_ORDER activate; + return rail_recv_client_activate_order(context, &activate, s); + } + + case TS_RAIL_ORDER_SYSMENU: + { + RAIL_SYSMENU_ORDER sysmenu; + return rail_recv_client_sysmenu_order(context, &sysmenu, s); + } + + case TS_RAIL_ORDER_SYSCOMMAND: + { + RAIL_SYSCOMMAND_ORDER syscommand; + return rail_recv_client_syscommand_order(context, &syscommand, s); + } + + case TS_RAIL_ORDER_NOTIFY_EVENT: + { + RAIL_NOTIFY_EVENT_ORDER notifyEvent; + return rail_recv_client_notify_event_order(context, ¬ifyEvent, s); + } + + case TS_RAIL_ORDER_WINDOWMOVE: + { + RAIL_WINDOW_MOVE_ORDER windowMove; + return rail_recv_client_window_move_order(context, &windowMove, s); + } + + case TS_RAIL_ORDER_SNAP_ARRANGE: + { + RAIL_SNAP_ARRANGE snapArrange; + return rail_recv_client_snap_arrange_order(context, &snapArrange, s); + } + + case TS_RAIL_ORDER_GET_APPID_REQ: + { + RAIL_GET_APPID_REQ_ORDER getAppidReq; + return rail_recv_client_get_appid_req_order(context, &getAppidReq, s); + } + + case TS_RAIL_ORDER_LANGBARINFO: + { + RAIL_LANGBAR_INFO_ORDER langbarInfo; + return rail_recv_client_langbar_info_order(context, &langbarInfo, s); + } + + case TS_RAIL_ORDER_LANGUAGEIMEINFO: + { + RAIL_LANGUAGEIME_INFO_ORDER languageImeInfo; + return rail_recv_client_language_ime_info_order(context, &languageImeInfo, s); + } + + case TS_RAIL_ORDER_COMPARTMENTINFO: + { + RAIL_COMPARTMENT_INFO_ORDER compartmentInfo; + return rail_recv_client_compartment_info(context, &compartmentInfo, s); + } + + case TS_RAIL_ORDER_CLOAK: + { + RAIL_CLOAK cloak; + return rail_recv_client_cloak_order(context, &cloak, s); + } + + case TS_RAIL_ORDER_TEXTSCALEINFO: + { + return rail_recv_client_text_scale_order(context, s); + } + + case TS_RAIL_ORDER_CARETBLINKINFO: + { + return rail_recv_client_caret_blink(context, s); + } + + default: + WLog_ERR(TAG, "Unknown RAIL PDU order received."); + return ERROR_INVALID_DATA; + } +} diff --git a/third_party/FreeRDP/channels/rail/server/rail_main.h b/third_party/FreeRDP/channels/rail/server/rail_main.h new file mode 100644 index 0000000..f15cf19 --- /dev/null +++ b/third_party/FreeRDP/channels/rail/server/rail_main.h @@ -0,0 +1,44 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RAIL Virtual Channel Plugin + * + * Copyright 2019 Mati Shabtay + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RAIL_SERVER_MAIN_H +#define FREERDP_CHANNEL_RAIL_SERVER_MAIN_H + +#include +#include + +#include +#include +#include + +#include "../rail_common.h" + +struct s_rail_server_private +{ + HANDLE thread; + HANDLE stopEvent; + HANDLE channelEvent; + void* rail_channel; + + wStream* input_stream; + + DWORD channelFlags; +}; + +#endif /* FREERDP_CHANNEL_RAIL_SERVER_MAIN_H */ diff --git a/third_party/FreeRDP/channels/rdp2tcp/CMakeLists.txt b/third_party/FreeRDP/channels/rdp2tcp/CMakeLists.txt new file mode 100644 index 0000000..3d0bf46 --- /dev/null +++ b/third_party/FreeRDP/channels/rdp2tcp/CMakeLists.txt @@ -0,0 +1,22 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel("rdp2tcp") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/third_party/FreeRDP/channels/rdp2tcp/ChannelOptions.cmake b/third_party/FreeRDP/channels/rdp2tcp/ChannelOptions.cmake new file mode 100644 index 0000000..ca21887 --- /dev/null +++ b/third_party/FreeRDP/channels/rdp2tcp/ChannelOptions.cmake @@ -0,0 +1,18 @@ +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT OFF) + +define_channel_options( + NAME + "rdp2tcp" + TYPE + "static" + DESCRIPTION + "Tunneling TCP over RDP" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/rdp2tcp/client/CMakeLists.txt b/third_party/FreeRDP/channels/rdp2tcp/client/CMakeLists.txt new file mode 100644 index 0000000..1c9c03b --- /dev/null +++ b/third_party/FreeRDP/channels/rdp2tcp/client/CMakeLists.txt @@ -0,0 +1,23 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_client("rdp2tcp") + +set(${MODULE_PREFIX}_SRCS rdp2tcp_main.c) + +set(${MODULE_PREFIX}_LIBS freerdp) +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "VirtualChannelEntryEx") diff --git a/third_party/FreeRDP/channels/rdp2tcp/client/rdp2tcp_main.c b/third_party/FreeRDP/channels/rdp2tcp/client/rdp2tcp_main.c new file mode 100644 index 0000000..2d2bb40 --- /dev/null +++ b/third_party/FreeRDP/channels/rdp2tcp/client/rdp2tcp_main.c @@ -0,0 +1,330 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * rdp2tcp Virtual Channel Extension + * + * Copyright 2017 Artur Zaprzala + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#define TAG CLIENT_TAG(RDP2TCP_DVC_CHANNEL_NAME) + +typedef struct +{ + HANDLE hStdOutputRead; + HANDLE hStdInputWrite; + HANDLE hProcess; + HANDLE copyThread; + HANDLE writeComplete; + DWORD openHandle; + void* initHandle; + CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints; + char buffer[16 * 1024]; + char* commandline; +} Plugin; + +static int init_external_addin(Plugin* plugin) +{ + int rc = -1; + SECURITY_ATTRIBUTES saAttr = WINPR_C_ARRAY_INIT; + STARTUPINFOA siStartInfo = WINPR_C_ARRAY_INIT; /* Using ANSI type to match CreateProcessA */ + PROCESS_INFORMATION procInfo = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(plugin); + + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = nullptr; + siStartInfo.cb = sizeof(STARTUPINFO); + siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); + siStartInfo.dwFlags = STARTF_USESTDHANDLES; + + // Create pipes + if (!CreatePipe(&plugin->hStdOutputRead, &siStartInfo.hStdOutput, &saAttr, 0)) + { + WLog_ERR(TAG, "stdout CreatePipe"); + goto fail; + } + + if (!SetHandleInformation(plugin->hStdOutputRead, HANDLE_FLAG_INHERIT, 0)) + { + WLog_ERR(TAG, "stdout SetHandleInformation"); + goto fail; + } + + if (!CreatePipe(&siStartInfo.hStdInput, &plugin->hStdInputWrite, &saAttr, 0)) + { + WLog_ERR(TAG, "stdin CreatePipe"); + goto fail; + } + + if (!SetHandleInformation(plugin->hStdInputWrite, HANDLE_FLAG_INHERIT, 0)) + { + WLog_ERR(TAG, "stdin SetHandleInformation"); + goto fail; + } + + // Execute plugin + const ADDIN_ARGV* args = (const ADDIN_ARGV*)plugin->channelEntryPoints.pExtendedData; + if (!args || (args->argc < 2)) + { + WLog_ERR(TAG, "missing command line options"); + goto fail; + } + + plugin->commandline = _strdup(args->argv[1]); + if (!CreateProcessA(nullptr, + plugin->commandline, // command line + nullptr, // process security attributes + nullptr, // primary thread security attributes + TRUE, // handles are inherited + 0, // creation flags + nullptr, // use parent's environment + nullptr, // use parent's current directory + &siStartInfo, // STARTUPINFO pointer + &procInfo // receives PROCESS_INFORMATION + )) + { + WLog_ERR(TAG, "fork for addin"); + goto fail; + } + + plugin->hProcess = procInfo.hProcess; + + rc = 0; +fail: + (void)CloseHandle(procInfo.hThread); + (void)CloseHandle(siStartInfo.hStdOutput); + (void)CloseHandle(siStartInfo.hStdInput); + return rc; +} + +static DWORD WINAPI copyThread(void* data) +{ + DWORD status = WAIT_OBJECT_0; + Plugin* plugin = (Plugin*)data; + size_t const bufsize = 16ULL * 1024ULL; + + WINPR_ASSERT(plugin); + + while (status == WAIT_OBJECT_0) + { + (void)ResetEvent(plugin->writeComplete); + + DWORD dwRead = 0; + char* buffer = calloc(bufsize, sizeof(char)); + + if (!buffer) + { + (void)fprintf(stderr, "rdp2tcp copyThread: malloc failed\n"); + goto fail; + } + + // if (!ReadFile(plugin->hStdOutputRead, plugin->buffer, sizeof plugin->buffer, &dwRead, + // nullptr)) + if (!ReadFile(plugin->hStdOutputRead, buffer, bufsize, &dwRead, nullptr)) + { + free(buffer); + goto fail; + } + + if (plugin->channelEntryPoints.pVirtualChannelWriteEx( + plugin->initHandle, plugin->openHandle, buffer, dwRead, buffer) != CHANNEL_RC_OK) + { + free(buffer); + (void)fprintf(stderr, "rdp2tcp copyThread failed %i\n", (int)dwRead); + goto fail; + } + + HANDLE handles[] = { plugin->writeComplete, + freerdp_abort_event(plugin->channelEntryPoints.context) }; + status = WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, INFINITE); + } + +fail: + ExitThread(0); + return 0; +} + +static void closeChannel(Plugin* plugin) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(plugin->channelEntryPoints.pVirtualChannelCloseEx); + plugin->channelEntryPoints.pVirtualChannelCloseEx(plugin->initHandle, plugin->openHandle); +} + +static void dataReceived(Plugin* plugin, void* pData, UINT32 dataLength, UINT32 totalLength, + UINT32 dataFlags) +{ + DWORD dwWritten = 0; + + WINPR_ASSERT(plugin); + + if (dataFlags & CHANNEL_FLAG_SUSPEND) + return; + + if (dataFlags & CHANNEL_FLAG_RESUME) + return; + + if (dataFlags & CHANNEL_FLAG_FIRST) + { + if (!WriteFile(plugin->hStdInputWrite, &totalLength, sizeof(totalLength), &dwWritten, + nullptr)) + closeChannel(plugin); + } + + if (!WriteFile(plugin->hStdInputWrite, pData, dataLength, &dwWritten, nullptr)) + closeChannel(plugin); +} + +static void VCAPITYPE VirtualChannelOpenEventEx(LPVOID lpUserParam, + WINPR_ATTR_UNUSED DWORD openHandle, UINT event, + LPVOID pData, UINT32 dataLength, UINT32 totalLength, + UINT32 dataFlags) +{ + Plugin* plugin = (Plugin*)lpUserParam; + + WINPR_ASSERT(plugin); + switch (event) + { + case CHANNEL_EVENT_DATA_RECEIVED: + dataReceived(plugin, pData, dataLength, totalLength, dataFlags); + break; + + case CHANNEL_EVENT_WRITE_CANCELLED: + free(pData); + break; + case CHANNEL_EVENT_WRITE_COMPLETE: + (void)SetEvent(plugin->writeComplete); + free(pData); + break; + default: + break; + } +} + +static void channel_terminated(Plugin* plugin) +{ + if (!plugin) + return; + + if (plugin->copyThread) + (void)CloseHandle(plugin->copyThread); + if (plugin->writeComplete) + (void)CloseHandle(plugin->writeComplete); + + (void)CloseHandle(plugin->hStdInputWrite); + (void)CloseHandle(plugin->hStdOutputRead); + TerminateProcess(plugin->hProcess, 0); + (void)CloseHandle(plugin->hProcess); + free(plugin->commandline); + free(plugin); +} + +static void channel_initialized(Plugin* plugin) +{ + WINPR_ASSERT(plugin); + WINPR_ASSERT(!plugin->writeComplete); + plugin->writeComplete = CreateEvent(nullptr, TRUE, FALSE, nullptr); + + WINPR_ASSERT(!plugin->copyThread); + plugin->copyThread = CreateThread(nullptr, 0, copyThread, plugin, 0, nullptr); +} + +static VOID VCAPITYPE VirtualChannelInitEventEx(LPVOID lpUserParam, LPVOID pInitHandle, UINT event, + WINPR_ATTR_UNUSED LPVOID pData, + WINPR_ATTR_UNUSED UINT dataLength) +{ + Plugin* plugin = (Plugin*)lpUserParam; + + WINPR_ASSERT(plugin); + + switch (event) + { + case CHANNEL_EVENT_INITIALIZED: + channel_initialized(plugin); + break; + + case CHANNEL_EVENT_CONNECTED: + WINPR_ASSERT(plugin); + WINPR_ASSERT(plugin->channelEntryPoints.pVirtualChannelOpenEx); + if (plugin->channelEntryPoints.pVirtualChannelOpenEx( + pInitHandle, &plugin->openHandle, RDP2TCP_DVC_CHANNEL_NAME, + VirtualChannelOpenEventEx) != CHANNEL_RC_OK) + return; + + break; + + case CHANNEL_EVENT_DISCONNECTED: + closeChannel(plugin); + break; + + case CHANNEL_EVENT_TERMINATED: + channel_terminated(plugin); + break; + default: + break; + } +} + +#define VirtualChannelEntryEx rdp2tcp_VirtualChannelEntryEx +FREERDP_ENTRY_POINT(BOOL VCAPITYPE VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS_EX pEntryPoints, + PVOID pInitHandle)) +{ + CHANNEL_ENTRY_POINTS_FREERDP_EX* pEntryPointsEx = + (CHANNEL_ENTRY_POINTS_FREERDP_EX*)pEntryPoints; + WINPR_ASSERT(pEntryPointsEx); + WINPR_ASSERT(pEntryPointsEx->cbSize >= sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX) && + pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER); + + Plugin* plugin = (Plugin*)calloc(1, sizeof(Plugin)); + + if (!plugin) + return FALSE; + + plugin->initHandle = pInitHandle; + plugin->channelEntryPoints = *pEntryPointsEx; + + if (init_external_addin(plugin) < 0) + { + channel_terminated(plugin); + return FALSE; + } + + CHANNEL_DEF channelDef = WINPR_C_ARRAY_INIT; + strncpy(channelDef.name, RDP2TCP_DVC_CHANNEL_NAME, sizeof(channelDef.name)); + channelDef.options = + CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP | CHANNEL_OPTION_COMPRESS_RDP; + + if (pEntryPointsEx->pVirtualChannelInitEx(plugin, nullptr, pInitHandle, &channelDef, 1, + VIRTUAL_CHANNEL_VERSION_WIN2000, + VirtualChannelInitEventEx) != CHANNEL_RC_OK) + { + channel_terminated(plugin); + return FALSE; + } + + return TRUE; +} diff --git a/third_party/FreeRDP/channels/rdpdr/CMakeLists.txt b/third_party/FreeRDP/channels/rdpdr/CMakeLists.txt new file mode 100644 index 0000000..76326be --- /dev/null +++ b/third_party/FreeRDP/channels/rdpdr/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel("rdpdr") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/third_party/FreeRDP/channels/rdpdr/ChannelOptions.cmake b/third_party/FreeRDP/channels/rdpdr/ChannelOptions.cmake new file mode 100644 index 0000000..6bc5fc2 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpdr/ChannelOptions.cmake @@ -0,0 +1,20 @@ +set(OPTION_DEFAULT ON) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options( + NAME + "rdpdr" + TYPE + "static" + DESCRIPTION + "Device Redirection Virtual Channel Extension" + SPECIFICATIONS + "[MS-RDPEFS] [MS-RDPEPC] [MS-RDPESC] [MS-RDPESP]" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/rdpdr/client/CMakeLists.txt b/third_party/FreeRDP/channels/rdpdr/client/CMakeLists.txt new file mode 100644 index 0000000..b996aed --- /dev/null +++ b/third_party/FreeRDP/channels/rdpdr/client/CMakeLists.txt @@ -0,0 +1,44 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# Copyright 2016 Inuvika Inc. +# Copyright 2016 David PHAM-VAN +# +# 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. + +define_channel_client("rdpdr") + +include(CheckFunctionExists) +check_function_exists(getmntent_r FREERDP_HAVE_GETMNTENT_R) +if(FREERDP_HAVE_GETMNTENT_R) + add_compile_definitions(FREERDP_HAVE_GETMNTENT_R) +endif() + +set(${MODULE_PREFIX}_SRCS + irp.c + irp.h + devman.c + devman.h + rdpdr_main.c + rdpdr_main.h + rdpdr_capabilities.c + rdpdr_capabilities.h +) + +set(${MODULE_PREFIX}_LIBS winpr freerdp) +if(APPLE AND (NOT IOS)) + find_library(CORESERVICES_LIBRARY CoreServices) + list(APPEND ${MODULE_PREFIX}_LIBS ${CORESERVICES_LIBRARY}) +endif() +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntryEx") diff --git a/third_party/FreeRDP/channels/rdpdr/client/devman.c b/third_party/FreeRDP/channels/rdpdr/client/devman.c new file mode 100644 index 0000000..25a51bd --- /dev/null +++ b/third_party/FreeRDP/channels/rdpdr/client/devman.c @@ -0,0 +1,237 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2012 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "rdpdr_main.h" + +#include "devman.h" + +#define TAG CHANNELS_TAG("rdpdr.client") + +static void devman_device_free(void* obj) +{ + DEVICE* device = (DEVICE*)obj; + + if (!device) + return; + + if (device->Free) + device->Free(device); +} + +DEVMAN* devman_new(rdpdrPlugin* rdpdr) +{ + DEVMAN* devman = nullptr; + + if (!rdpdr) + return nullptr; + + devman = (DEVMAN*)calloc(1, sizeof(DEVMAN)); + + if (!devman) + { + WLog_Print(rdpdr->log, WLOG_INFO, "calloc failed!"); + return nullptr; + } + + devman->plugin = (void*)rdpdr; + devman->id_sequence = 1; + devman->devices = ListDictionary_New(TRUE); + + if (!devman->devices) + { + WLog_Print(rdpdr->log, WLOG_INFO, "ListDictionary_New failed!"); + free(devman); + return nullptr; + } + + ListDictionary_ValueObject(devman->devices)->fnObjectFree = devman_device_free; + return devman; +} + +void devman_free(DEVMAN* devman) +{ + ListDictionary_Free(devman->devices); + free(devman); +} + +void devman_unregister_device(DEVMAN* devman, void* key) +{ + DEVICE* device = nullptr; + + if (!devman || !key) + return; + + device = (DEVICE*)ListDictionary_Take(devman->devices, key); + + if (device) + devman_device_free(device); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT devman_register_device(DEVMAN* devman, DEVICE* device) +{ + void* key = nullptr; + + if (!devman || !device) + return ERROR_INVALID_PARAMETER; + + device->id = devman->id_sequence++; + key = (void*)(size_t)device->id; + + if (!ListDictionary_Add(devman->devices, key, device)) + { + WLog_INFO(TAG, "ListDictionary_Add failed!"); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +DEVICE* devman_get_device_by_id(DEVMAN* devman, UINT32 id) +{ + DEVICE* device = nullptr; + void* key = (void*)(size_t)id; + + if (!devman) + { + WLog_ERR(TAG, "device manager=nullptr"); + return nullptr; + } + + device = (DEVICE*)ListDictionary_GetItemValue(devman->devices, key); + if (!device) + WLog_WARN(TAG, "could not find device ID 0x%08" PRIx32, id); + return device; +} + +DEVICE* devman_get_device_by_type(DEVMAN* devman, UINT32 type) +{ + DEVICE* device = nullptr; + ULONG_PTR* keys = nullptr; + + if (!devman) + return nullptr; + + ListDictionary_Lock(devman->devices); + const size_t count = ListDictionary_GetKeys(devman->devices, &keys); + + for (size_t x = 0; x < count; x++) + { + DEVICE* cur = (DEVICE*)ListDictionary_GetItemValue(devman->devices, (void*)keys[x]); + + if (!cur) + continue; + + if (cur->type != type) + continue; + + device = cur; + break; + } + + free(keys); + ListDictionary_Unlock(devman->devices); + return device; +} + +static const char DRIVE_SERVICE_NAME[] = "drive"; +static const char PRINTER_SERVICE_NAME[] = "printer"; +static const char SMARTCARD_SERVICE_NAME[] = "smartcard"; +static const char SERIAL_SERVICE_NAME[] = "serial"; +static const char PARALLEL_SERVICE_NAME[] = "parallel"; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT devman_load_device_service(DEVMAN* devman, RDPDR_DEVICE* device, rdpContext* rdpcontext) +{ + const char* ServiceName = nullptr; + DEVICE_SERVICE_ENTRY_POINTS ep = WINPR_C_ARRAY_INIT; + union + { + const RDPDR_DEVICE* cdp; + RDPDR_DEVICE* dp; + } devconv; + + devconv.cdp = device; + if (!devman || !device || !rdpcontext) + return ERROR_INVALID_PARAMETER; + + if (device->Type == RDPDR_DTYP_FILESYSTEM) + ServiceName = DRIVE_SERVICE_NAME; + else if (device->Type == RDPDR_DTYP_PRINT) + ServiceName = PRINTER_SERVICE_NAME; + else if (device->Type == RDPDR_DTYP_SMARTCARD) + ServiceName = SMARTCARD_SERVICE_NAME; + else if (device->Type == RDPDR_DTYP_SERIAL) + ServiceName = SERIAL_SERVICE_NAME; + else if (device->Type == RDPDR_DTYP_PARALLEL) + ServiceName = PARALLEL_SERVICE_NAME; + + if (!ServiceName) + { + WLog_INFO(TAG, "ServiceName %s did not match!", ServiceName); + return ERROR_INVALID_NAME; + } + + if (device->Name) + WLog_INFO(TAG, "Loading device service %s [%s] (static)", ServiceName, device->Name); + else + WLog_INFO(TAG, "Loading device service %s (static)", ServiceName); + + PVIRTUALCHANNELENTRY pvce = + freerdp_load_channel_addin_entry(ServiceName, nullptr, "DeviceServiceEntry", 0); + PDEVICE_SERVICE_ENTRY entry = WINPR_FUNC_PTR_CAST(pvce, PDEVICE_SERVICE_ENTRY); + + if (!entry) + { + WLog_INFO(TAG, "freerdp_load_channel_addin_entry failed!"); + return ERROR_INTERNAL_ERROR; + } + + ep.devman = devman; + ep.RegisterDevice = devman_register_device; + ep.device = devconv.dp; + ep.rdpcontext = rdpcontext; + return entry(&ep); +} diff --git a/third_party/FreeRDP/channels/rdpdr/client/devman.h b/third_party/FreeRDP/channels/rdpdr/client/devman.h new file mode 100644 index 0000000..7cd66bc --- /dev/null +++ b/third_party/FreeRDP/channels/rdpdr/client/devman.h @@ -0,0 +1,47 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2012 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPDR_CLIENT_DEVMAN_H +#define FREERDP_CHANNEL_RDPDR_CLIENT_DEVMAN_H + +#include + +#include "rdpdr_main.h" + +FREERDP_LOCAL void devman_unregister_device(DEVMAN* devman, void* key); +FREERDP_LOCAL void devman_free(DEVMAN* devman); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT devman_load_device_service(DEVMAN* devman, RDPDR_DEVICE* device, + rdpContext* rdpcontext); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL DEVICE* devman_get_device_by_id(DEVMAN* devman, UINT32 id); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL DEVICE* devman_get_device_by_type(DEVMAN* devman, UINT32 type); + +WINPR_ATTR_MALLOC(devman_free, 1) +WINPR_ATTR_NODISCARD +FREERDP_LOCAL DEVMAN* devman_new(rdpdrPlugin* rdpdr); + +#endif /* FREERDP_CHANNEL_RDPDR_CLIENT_DEVMAN_H */ diff --git a/third_party/FreeRDP/channels/rdpdr/client/irp.c b/third_party/FreeRDP/channels/rdpdr/client/irp.c new file mode 100644 index 0000000..b235b32 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpdr/client/irp.c @@ -0,0 +1,159 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2012 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include + +#include + +#include "rdpdr_main.h" +#include "devman.h" +#include "irp.h" + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT irp_free(IRP* irp) +{ + if (!irp) + return CHANNEL_RC_OK; + + if (irp->input) + Stream_Release(irp->input); + if (irp->output) + Stream_Release(irp->output); + + winpr_aligned_free(irp); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT irp_complete(IRP* irp) +{ + WINPR_ASSERT(irp); + WINPR_ASSERT(irp->output); + WINPR_ASSERT(irp->devman); + + rdpdrPlugin* rdpdr = (rdpdrPlugin*)irp->devman->plugin; + WINPR_ASSERT(rdpdr); + + const size_t pos = Stream_GetPosition(irp->output); + Stream_SetPosition(irp->output, RDPDR_DEVICE_IO_RESPONSE_LENGTH - 4); + Stream_Write_INT32(irp->output, irp->IoStatus); /* IoStatus (4 bytes) */ + Stream_SetPosition(irp->output, pos); + + const UINT error = rdpdr_send(rdpdr, irp->output); + irp->output = nullptr; + + irp_free(irp); + return error; +} + +IRP* irp_new(DEVMAN* devman, wStreamPool* pool, wStream* s, wLog* log, UINT* error) +{ + IRP* irp = nullptr; + DEVICE* device = nullptr; + UINT32 DeviceId = 0; + + WINPR_ASSERT(devman); + WINPR_ASSERT(pool); + WINPR_ASSERT(s); + + if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 20)) + { + if (error) + *error = ERROR_INVALID_DATA; + return nullptr; + } + + Stream_Read_UINT32(s, DeviceId); /* DeviceId (4 bytes) */ + device = devman_get_device_by_id(devman, DeviceId); + + if (!device) + { + if (error) + *error = ERROR_DEV_NOT_EXIST; + + return nullptr; + } + + irp = (IRP*)winpr_aligned_calloc(1, sizeof(IRP), MEMORY_ALLOCATION_ALIGNMENT); + + if (!irp) + { + WLog_Print(log, WLOG_ERROR, "_aligned_malloc failed!"); + if (error) + *error = CHANNEL_RC_NO_MEMORY; + return nullptr; + } + + Stream_Read_UINT32(s, irp->FileId); /* FileId (4 bytes) */ + Stream_Read_UINT32(s, irp->CompletionId); /* CompletionId (4 bytes) */ + Stream_Read_UINT32(s, irp->MajorFunction); /* MajorFunction (4 bytes) */ + Stream_Read_UINT32(s, irp->MinorFunction); /* MinorFunction (4 bytes) */ + + Stream_AddRef(s); + irp->input = s; + irp->device = device; + irp->devman = devman; + + irp->output = StreamPool_Take(pool, 256); + if (!irp->output) + { + WLog_Print(log, WLOG_ERROR, "Stream_New failed!"); + irp_free(irp); + if (error) + *error = CHANNEL_RC_NO_MEMORY; + return nullptr; + } + + if (!rdpdr_write_iocompletion_header(irp->output, DeviceId, irp->CompletionId, 0)) + { + irp_free(irp); + if (error) + *error = CHANNEL_RC_NO_MEMORY; + return nullptr; + } + + irp->Complete = irp_complete; + irp->Discard = irp_free; + + irp->thread = nullptr; + irp->cancelled = FALSE; + + if (error) + *error = CHANNEL_RC_OK; + + return irp; +} diff --git a/third_party/FreeRDP/channels/rdpdr/client/irp.h b/third_party/FreeRDP/channels/rdpdr/client/irp.h new file mode 100644 index 0000000..4fee614 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpdr/client/irp.h @@ -0,0 +1,31 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2012 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPDR_CLIENT_IRP_H +#define FREERDP_CHANNEL_RDPDR_CLIENT_IRP_H + +#include +#include +#include "rdpdr_main.h" + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL IRP* irp_new(DEVMAN* devman, wStreamPool* pool, wStream* s, wLog* log, UINT* error); + +#endif /* FREERDP_CHANNEL_RDPDR_CLIENT_IRP_H */ diff --git a/third_party/FreeRDP/channels/rdpdr/client/rdpdr_capabilities.c b/third_party/FreeRDP/channels/rdpdr/client/rdpdr_capabilities.c new file mode 100644 index 0000000..a94c993 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpdr/client/rdpdr_capabilities.c @@ -0,0 +1,326 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2012 Marc-Andre Moreau + * Copyright 2015-2016 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#include "rdpdr_main.h" +#include "rdpdr_capabilities.h" + +#define RDPDR_CAPABILITY_HEADER_LENGTH 8 + +/* Output device direction general capability set */ +static BOOL rdpdr_write_general_capset(rdpdrPlugin* rdpdr, wStream* s) +{ + WINPR_ASSERT(rdpdr); + const RDPDR_CAPABILITY_HEADER header = { CAP_GENERAL_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH + 36, + GENERAL_CAPABILITY_VERSION_02 }; + + const UINT32 ioCode1 = rdpdr->clientIOCode1 & rdpdr->serverIOCode1; + const UINT32 ioCode2 = rdpdr->clientIOCode2 & rdpdr->serverIOCode2; + + if (rdpdr_write_capset_header(rdpdr->log, s, &header) != CHANNEL_RC_OK) + return FALSE; + + if (!Stream_EnsureRemainingCapacity(s, 36)) + return FALSE; + + Stream_Write_UINT32(s, rdpdr->clientOsType); /* osType, ignored on receipt */ + Stream_Write_UINT32(s, rdpdr->clientOsVersion); /* osVersion, unused and must be set to zero */ + Stream_Write_UINT16(s, rdpdr->clientVersionMajor); /* protocolMajorVersion, must be set to 1 */ + Stream_Write_UINT16(s, rdpdr->clientVersionMinor); /* protocolMinorVersion */ + Stream_Write_UINT32(s, ioCode1); /* ioCode1 */ + Stream_Write_UINT32(s, ioCode2); /* ioCode2, must be set to zero, reserved for future use */ + Stream_Write_UINT32(s, rdpdr->clientExtendedPDU); /* extendedPDU */ + Stream_Write_UINT32(s, rdpdr->clientExtraFlags1); /* extraFlags1 */ + Stream_Write_UINT32( + s, + rdpdr->clientExtraFlags2); /* extraFlags2, must be set to zero, reserved for future use */ + Stream_Write_UINT32( + s, rdpdr->clientSpecialTypeDeviceCap); /* SpecialTypeDeviceCap, number of special devices to + be redirected before logon */ + return TRUE; +} + +/* Process device direction general capability set */ +static UINT rdpdr_process_general_capset(rdpdrPlugin* rdpdr, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_ASSERT(header); + + const BOOL gotV1 = header->Version == GENERAL_CAPABILITY_VERSION_01; + const size_t expect = gotV1 ? 32 : 36; + if (header->CapabilityLength != expect) + { + WLog_Print(rdpdr->log, WLOG_ERROR, + "CAP_GENERAL_TYPE::CapabilityLength expected %" PRIuz ", got %" PRIu32, expect, + header->CapabilityLength); + return ERROR_INVALID_DATA; + } + if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, expect)) + return ERROR_INVALID_DATA; + + rdpdr->serverOsType = Stream_Get_UINT32(s); /* osType, ignored on receipt */ + rdpdr->serverOsVersion = Stream_Get_UINT32(s); /* osVersion, unused and must be set to zero */ + rdpdr->serverVersionMajor = Stream_Get_UINT16(s); /* protocolMajorVersion, must be set to 1 */ + rdpdr->serverVersionMinor = Stream_Get_UINT16(s); /* protocolMinorVersion */ + rdpdr->serverIOCode1 = Stream_Get_UINT32(s); /* ioCode1 */ + rdpdr->serverIOCode2 = + Stream_Get_UINT32(s); /* ioCode2, must be set to zero, reserved for future use */ + rdpdr->serverExtendedPDU = Stream_Get_UINT32(s); /* extendedPDU */ + rdpdr->serverExtraFlags1 = Stream_Get_UINT32(s); /* extraFlags1 */ + rdpdr->serverExtraFlags2 = + Stream_Get_UINT32(s); /* extraFlags2, must be set to zero, reserved for future use */ + if (gotV1) + rdpdr->serverSpecialTypeDeviceCap = 0; + else + rdpdr->serverSpecialTypeDeviceCap = Stream_Get_UINT32( + s); /* SpecialTypeDeviceCap, number of special devices to + be redirected before logon */ + return CHANNEL_RC_OK; +} + +/* Output printer direction capability set */ +static BOOL rdpdr_write_printer_capset(rdpdrPlugin* rdpdr, wStream* s) +{ + WINPR_UNUSED(rdpdr); + const RDPDR_CAPABILITY_HEADER header = { CAP_PRINTER_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH, + PRINT_CAPABILITY_VERSION_01 }; + return rdpdr_write_capset_header(rdpdr->log, s, &header) == CHANNEL_RC_OK; +} + +/* Process printer direction capability set */ +static UINT rdpdr_process_printer_capset(WINPR_ATTR_UNUSED rdpdrPlugin* rdpdr, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_ASSERT(header); + Stream_Seek(s, header->CapabilityLength); + return CHANNEL_RC_OK; +} + +/* Output port redirection capability set */ +static BOOL rdpdr_write_port_capset(rdpdrPlugin* rdpdr, wStream* s) +{ + WINPR_UNUSED(rdpdr); + const RDPDR_CAPABILITY_HEADER header = { CAP_PORT_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH, + PORT_CAPABILITY_VERSION_01 }; + return rdpdr_write_capset_header(rdpdr->log, s, &header) == CHANNEL_RC_OK; +} + +/* Process port redirection capability set */ +static UINT rdpdr_process_port_capset(WINPR_ATTR_UNUSED rdpdrPlugin* rdpdr, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_ASSERT(header); + Stream_Seek(s, header->CapabilityLength); + return CHANNEL_RC_OK; +} + +/* Output drive redirection capability set */ +static BOOL rdpdr_write_drive_capset(rdpdrPlugin* rdpdr, wStream* s) +{ + WINPR_UNUSED(rdpdr); + const RDPDR_CAPABILITY_HEADER header = { CAP_DRIVE_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH, + DRIVE_CAPABILITY_VERSION_02 }; + return rdpdr_write_capset_header(rdpdr->log, s, &header) == CHANNEL_RC_OK; +} + +/* Process drive redirection capability set */ +static UINT rdpdr_process_drive_capset(WINPR_ATTR_UNUSED rdpdrPlugin* rdpdr, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_ASSERT(header); + Stream_Seek(s, header->CapabilityLength); + return CHANNEL_RC_OK; +} + +/* Output smart card redirection capability set */ +static BOOL rdpdr_write_smartcard_capset(rdpdrPlugin* rdpdr, wStream* s) +{ + WINPR_UNUSED(rdpdr); + const RDPDR_CAPABILITY_HEADER header = { CAP_SMARTCARD_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH, + SMARTCARD_CAPABILITY_VERSION_01 }; + return rdpdr_write_capset_header(rdpdr->log, s, &header) == CHANNEL_RC_OK; +} + +/* Process smartcard redirection capability set */ +static UINT rdpdr_process_smartcard_capset(WINPR_ATTR_UNUSED rdpdrPlugin* rdpdr, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_ASSERT(header); + Stream_Seek(s, header->CapabilityLength); + return CHANNEL_RC_OK; +} + +UINT rdpdr_process_capability_request(rdpdrPlugin* rdpdr, wStream* s) +{ + UINT status = CHANNEL_RC_OK; + UINT16 numCapabilities = 0; + + if (!rdpdr || !s) + return CHANNEL_RC_NULL_DATA; + + if (!rdpdr_state_advance(rdpdr, RDPDR_CHANNEL_STATE_CLIENT_CAPS)) + return ERROR_INVALID_STATE; + + if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, numCapabilities); + Stream_Seek(s, 2); /* pad (2 bytes) */ + + memset(rdpdr->capabilities, 0, sizeof(rdpdr->capabilities)); + for (UINT16 i = 0; i < numCapabilities; i++) + { + RDPDR_CAPABILITY_HEADER header = WINPR_C_ARRAY_INIT; + UINT error = rdpdr_read_capset_header(rdpdr->log, s, &header); + if (error != CHANNEL_RC_OK) + return error; + + switch (header.CapabilityType) + { + case CAP_GENERAL_TYPE: + rdpdr->capabilities[header.CapabilityType] = TRUE; + status = rdpdr_process_general_capset(rdpdr, s, &header); + break; + + case CAP_PRINTER_TYPE: + rdpdr->capabilities[header.CapabilityType] = TRUE; + status = rdpdr_process_printer_capset(rdpdr, s, &header); + break; + + case CAP_PORT_TYPE: + rdpdr->capabilities[header.CapabilityType] = TRUE; + status = rdpdr_process_port_capset(rdpdr, s, &header); + break; + + case CAP_DRIVE_TYPE: + rdpdr->capabilities[header.CapabilityType] = TRUE; + status = rdpdr_process_drive_capset(rdpdr, s, &header); + break; + + case CAP_SMARTCARD_TYPE: + rdpdr->capabilities[header.CapabilityType] = TRUE; + status = rdpdr_process_smartcard_capset(rdpdr, s, &header); + break; + + default: + break; + } + + if (status != CHANNEL_RC_OK) + return status; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpdr_send_capability_response(rdpdrPlugin* rdpdr) +{ + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(rdpdr->rdpcontext); + + rdpSettings* settings = rdpdr->rdpcontext->settings; + WINPR_ASSERT(settings); + + wStream* s = StreamPool_Take(rdpdr->pool, 256); + + if (!s) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + const RDPDR_DEVICE* cdrives = + freerdp_device_collection_find_type(settings, RDPDR_DTYP_FILESYSTEM); + const RDPDR_DEVICE* cserial = freerdp_device_collection_find_type(settings, RDPDR_DTYP_SERIAL); + const RDPDR_DEVICE* cparallel = + freerdp_device_collection_find_type(settings, RDPDR_DTYP_PARALLEL); + const RDPDR_DEVICE* csmart = + freerdp_device_collection_find_type(settings, RDPDR_DTYP_SMARTCARD); + const RDPDR_DEVICE* cprinter = freerdp_device_collection_find_type(settings, RDPDR_DTYP_PRINT); + + /* Only send capabilities the server announced */ + const BOOL drives = cdrives && rdpdr->capabilities[CAP_DRIVE_TYPE]; + const BOOL serial = cserial && rdpdr->capabilities[CAP_PORT_TYPE]; + const BOOL parallel = cparallel && rdpdr->capabilities[CAP_PORT_TYPE]; + const BOOL smart = csmart && rdpdr->capabilities[CAP_SMARTCARD_TYPE]; + const BOOL printer = cprinter && rdpdr->capabilities[CAP_PRINTER_TYPE]; + UINT16 count = 1; + if (drives) + count++; + if (serial || parallel) + count++; + if (smart) + count++; + if (printer) + count++; + + Stream_Write_UINT16(s, RDPDR_CTYP_CORE); + Stream_Write_UINT16(s, PAKID_CORE_CLIENT_CAPABILITY); + Stream_Write_UINT16(s, count); /* numCapabilities */ + Stream_Write_UINT16(s, 0); /* pad */ + + if (!rdpdr_write_general_capset(rdpdr, s)) + goto fail; + + if (printer) + { + if (!rdpdr_write_printer_capset(rdpdr, s)) + goto fail; + } + if (serial || parallel) + { + if (!rdpdr_write_port_capset(rdpdr, s)) + goto fail; + } + if (drives) + { + if (!rdpdr_write_drive_capset(rdpdr, s)) + goto fail; + } + if (smart) + { + if (!rdpdr_write_smartcard_capset(rdpdr, s)) + goto fail; + } + return rdpdr_send(rdpdr, s); + +fail: + Stream_Release(s); + return ERROR_OUTOFMEMORY; +} diff --git a/third_party/FreeRDP/channels/rdpdr/client/rdpdr_capabilities.h b/third_party/FreeRDP/channels/rdpdr/client/rdpdr_capabilities.h new file mode 100644 index 0000000..ca3b361 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpdr/client/rdpdr_capabilities.h @@ -0,0 +1,36 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2012 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPDR_CLIENT_CAPABILITIES_H +#define FREERDP_CHANNEL_RDPDR_CLIENT_CAPABILITIES_H + +#include + +#include "rdpdr_main.h" + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT rdpdr_process_capability_request(rdpdrPlugin* rdpdr, wStream* s); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT rdpdr_send_capability_response(rdpdrPlugin* rdpdr); + +#endif /* FREERDP_CHANNEL_RDPDR_CLIENT_CAPABILITIES_H */ diff --git a/third_party/FreeRDP/channels/rdpdr/client/rdpdr_main.c b/third_party/FreeRDP/channels/rdpdr/client/rdpdr_main.c new file mode 100644 index 0000000..7b51f6c --- /dev/null +++ b/third_party/FreeRDP/channels/rdpdr/client/rdpdr_main.c @@ -0,0 +1,2471 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2012 Marc-Andre Moreau + * Copyright 2015-2016 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 Armin Novak + * Copyright 2016 David PHAM-VAN + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#endif + +#ifdef __MACOSX__ +#include +#include +#include +#include +#include +#include +#endif + +#include "rdpdr_capabilities.h" + +#include "devman.h" +#include "irp.h" + +#include "rdpdr_main.h" + +#define TAG CHANNELS_TAG("rdpdr.client") + +/* IMPORTANT: Keep in sync with DRIVE_DEVICE */ +typedef struct +{ + DEVICE device; + WCHAR* path; + BOOL automount; +} DEVICE_DRIVE_EXT; + +static const char* rdpdr_state_str(enum RDPDR_CHANNEL_STATE state) +{ + switch (state) + { + case RDPDR_CHANNEL_STATE_INITIAL: + return "RDPDR_CHANNEL_STATE_INITIAL"; + case RDPDR_CHANNEL_STATE_ANNOUNCE: + return "RDPDR_CHANNEL_STATE_ANNOUNCE"; + case RDPDR_CHANNEL_STATE_ANNOUNCE_REPLY: + return "RDPDR_CHANNEL_STATE_ANNOUNCE_REPLY"; + case RDPDR_CHANNEL_STATE_NAME_REQUEST: + return "RDPDR_CHANNEL_STATE_NAME_REQUEST"; + case RDPDR_CHANNEL_STATE_SERVER_CAPS: + return "RDPDR_CHANNEL_STATE_SERVER_CAPS"; + case RDPDR_CHANNEL_STATE_CLIENT_CAPS: + return "RDPDR_CHANNEL_STATE_CLIENT_CAPS"; + case RDPDR_CHANNEL_STATE_CLIENTID_CONFIRM: + return "RDPDR_CHANNEL_STATE_CLIENTID_CONFIRM"; + case RDPDR_CHANNEL_STATE_READY: + return "RDPDR_CHANNEL_STATE_READY"; + case RDPDR_CHANNEL_STATE_USER_LOGGEDON: + return "RDPDR_CHANNEL_STATE_USER_LOGGEDON"; + default: + return "RDPDR_CHANNEL_STATE_UNKNOWN"; + } +} + +static const char* support_str(BOOL val) +{ + if (val) + return "supported"; + return "not found"; +} + +static const char* rdpdr_caps_pdu_str(UINT32 flag) +{ + switch (flag) + { + case RDPDR_DEVICE_REMOVE_PDUS: + return "RDPDR_USER_LOGGEDON_PDU"; + case RDPDR_CLIENT_DISPLAY_NAME_PDU: + return "RDPDR_CLIENT_DISPLAY_NAME_PDU"; + case RDPDR_USER_LOGGEDON_PDU: + return "RDPDR_USER_LOGGEDON_PDU"; + default: + return "RDPDR_UNKNONW"; + } +} + +static BOOL rdpdr_check_extended_pdu_flag(rdpdrPlugin* rdpdr, UINT32 flag) +{ + WINPR_ASSERT(rdpdr); + + const BOOL client = (rdpdr->clientExtendedPDU & flag) != 0; + const BOOL server = (rdpdr->serverExtendedPDU & flag) != 0; + + if (!client || !server) + { + WLog_Print(rdpdr->log, WLOG_WARN, "Checking ExtendedPDU::%s, client %s, server %s", + rdpdr_caps_pdu_str(flag), support_str(client), support_str(server)); + return FALSE; + } + return TRUE; +} + +BOOL rdpdr_state_advance(rdpdrPlugin* rdpdr, enum RDPDR_CHANNEL_STATE next) +{ + WINPR_ASSERT(rdpdr); + + if (next != rdpdr->state) + WLog_Print(rdpdr->log, WLOG_DEBUG, "[RDPDR] transition from %s to %s", + rdpdr_state_str(rdpdr->state), rdpdr_state_str(next)); + rdpdr->state = next; + return TRUE; +} + +static BOOL device_foreach(rdpdrPlugin* rdpdr, BOOL abortOnFail, + BOOL (*fkt)(ULONG_PTR key, void* element, void* data), void* data) +{ + BOOL rc = TRUE; + ULONG_PTR* keys = nullptr; + + ListDictionary_Lock(rdpdr->devman->devices); + const size_t count = ListDictionary_GetKeys(rdpdr->devman->devices, &keys); + for (size_t x = 0; x < count; x++) + { + void* element = ListDictionary_GetItemValue(rdpdr->devman->devices, (void*)keys[x]); + if (!fkt(keys[x], element, data)) + { + rc = FALSE; + if (abortOnFail) + break; + } + } + free(keys); + ListDictionary_Unlock(rdpdr->devman->devices); + return rc; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_try_send_device_list_announce_request(rdpdrPlugin* rdpdr); + +static BOOL rdpdr_load_drive(rdpdrPlugin* rdpdr, const char* name, const char* path, BOOL automount) +{ + UINT rc = ERROR_INTERNAL_ERROR; + union + { + RDPDR_DRIVE* drive; + RDPDR_DEVICE* device; + } drive; + const char* args[] = { name, path, automount ? nullptr : name }; + + drive.device = freerdp_device_new(RDPDR_DTYP_FILESYSTEM, ARRAYSIZE(args), args); + if (!drive.device) + goto fail; + + WINPR_ASSERT(rdpdr->context.RdpdrRegisterDevice); + rc = rdpdr->context.RdpdrRegisterDevice(&rdpdr->context, drive.device, &drive.device->Id); + if (rc != CHANNEL_RC_OK) + goto fail; + +fail: + freerdp_device_free(drive.device); + return rc == CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_send_device_list_remove_request(rdpdrPlugin* rdpdr, UINT32 count, + const UINT32 ids[]) +{ + wStream* s = nullptr; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(ids || (count == 0)); + + if (count == 0) + return CHANNEL_RC_OK; + + if (!rdpdr_check_extended_pdu_flag(rdpdr, RDPDR_DEVICE_REMOVE_PDUS)) + return CHANNEL_RC_OK; + + s = StreamPool_Take(rdpdr->pool, count * sizeof(UINT32) + 8); + + if (!s) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, RDPDR_CTYP_CORE); + Stream_Write_UINT16(s, PAKID_CORE_DEVICELIST_REMOVE); + Stream_Write_UINT32(s, count); + + for (UINT32 i = 0; i < count; i++) + Stream_Write_UINT32(s, ids[i]); + + Stream_SealLength(s); + return rdpdr_send(rdpdr, s); +} + +#if defined(_UWP) || defined(__IOS__) + +static UINT handle_hotplug(WINPR_ATTR_UNUSED RdpdrClientContext* context, + WINPR_ATTR_UNUSED RdpdrHotplugEventType type) +{ + return ERROR_CALL_NOT_IMPLEMENTED; +} + +static void first_hotplug(WINPR_ATTR_UNUSED rdpdrPlugin* rdpdr) +{ +} + +static DWORD WINAPI drive_hotplug_thread_func(WINPR_ATTR_UNUSED LPVOID arg) +{ + return CHANNEL_RC_OK; +} + +static UINT drive_hotplug_thread_terminate(WINPR_ATTR_UNUSED rdpdrPlugin* rdpdr) +{ + return CHANNEL_RC_OK; +} + +#elif defined(_WIN32) + +static UINT handle_hotplug(WINPR_ATTR_UNUSED RdpdrClientContext* context, + WINPR_ATTR_UNUSED RdpdrHotplugEventType type) +{ + return CHANNEL_RC_OK; +} + +static BOOL check_path(const char* path) +{ + UINT type = GetDriveTypeA(path); + + if (!(type == DRIVE_FIXED || type == DRIVE_REMOVABLE || type == DRIVE_CDROM || + type == DRIVE_REMOTE)) + return FALSE; + + return GetVolumeInformationA(path, nullptr, 0, nullptr, nullptr, nullptr, nullptr, 0); +} + +static void first_hotplug(rdpdrPlugin* rdpdr) +{ + DWORD unitmask = GetLogicalDrives(); + + for (size_t i = 0; i < 26; i++) + { + if (unitmask & 0x01) + { + char drive_path[] = { 'c', ':', '\\', '\0' }; + char drive_name[] = { 'c', '\0' }; + drive_path[0] = 'A' + (char)i; + drive_name[0] = 'A' + (char)i; + + if (check_path(drive_path)) + { + rdpdr_load_drive(rdpdr, drive_name, drive_path, TRUE); + } + } + + unitmask = unitmask >> 1; + } +} + +static LRESULT CALLBACK hotplug_proc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) +{ + rdpdrPlugin* rdpdr; + PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam; + UINT error; + rdpdr = (rdpdrPlugin*)GetWindowLongPtr(hWnd, GWLP_USERDATA); + + switch (Msg) + { + case WM_DEVICECHANGE: + switch (wParam) + { + case DBT_DEVICEARRIVAL: + if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME) + { + PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)lpdb; + DWORD unitmask = lpdbv->dbcv_unitmask; + + for (int i = 0; i < 26; i++) + { + if (unitmask & 0x01) + { + char drive_path[] = { 'c', ':', '/', '\0' }; + char drive_name[] = { 'c', '\0' }; + drive_path[0] = 'A' + (char)i; + drive_name[0] = 'A' + (char)i; + + if (check_path(drive_path)) + { + rdpdr_load_drive(rdpdr, drive_name, drive_path, TRUE); + } + } + + unitmask = unitmask >> 1; + } + } + + break; + + case DBT_DEVICEREMOVECOMPLETE: + if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME) + { + PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)lpdb; + DWORD unitmask = lpdbv->dbcv_unitmask; + char drive_name_upper, drive_name_lower; + ULONG_PTR* keys = nullptr; + DEVICE_DRIVE_EXT* device_ext; + + for (int i = 0; i < 26; i++) + { + if (unitmask & 0x01) + { + drive_name_upper = 'A' + i; + drive_name_lower = 'a' + i; + const size_t count = + ListDictionary_GetKeys(rdpdr->devman->devices, &keys); + + for (size_t j = 0; j < count; j++) + { + device_ext = (DEVICE_DRIVE_EXT*)ListDictionary_GetItemValue( + rdpdr->devman->devices, (void*)keys[j]); + + if (device_ext->device.type != RDPDR_DTYP_FILESYSTEM) + continue; + + if (device_ext->path[0] == drive_name_upper || + device_ext->path[0] == drive_name_lower) + { + if (device_ext->automount) + { + const uint32_t ids[] = { keys[j] }; + WINPR_ASSERT(rdpdr->context.RdpdrUnregisterDevice); + error = rdpdr->context.RdpdrUnregisterDevice( + &rdpdr->context, ARRAYSIZE(ids), ids); + if (error) + { + // don't end on error, just report ? + WLog_Print( + rdpdr->log, WLOG_ERROR, + "rdpdr_send_device_list_remove_request failed " + "with error %" PRIu32 "!", + error); + } + + break; + } + } + } + + free(keys); + } + + unitmask = unitmask >> 1; + } + } + + break; + + default: + break; + } + + break; + + default: + return DefWindowProc(hWnd, Msg, wParam, lParam); + } + + return DefWindowProc(hWnd, Msg, wParam, lParam); +} + +static DWORD WINAPI drive_hotplug_thread_func(LPVOID arg) +{ + rdpdrPlugin* rdpdr; + WNDCLASSEX wnd_cls; + HWND hwnd; + MSG msg; + BOOL bRet; + DEV_BROADCAST_HANDLE NotificationFilter; + HDEVNOTIFY hDevNotify; + rdpdr = (rdpdrPlugin*)arg; + /* init windows class */ + wnd_cls.cbSize = sizeof(WNDCLASSEX); + wnd_cls.style = CS_HREDRAW | CS_VREDRAW; + wnd_cls.lpfnWndProc = hotplug_proc; + wnd_cls.cbClsExtra = 0; + wnd_cls.cbWndExtra = 0; + wnd_cls.hIcon = LoadIcon(nullptr, IDI_APPLICATION); + wnd_cls.hCursor = nullptr; + wnd_cls.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); + wnd_cls.lpszMenuName = nullptr; + wnd_cls.lpszClassName = L"DRIVE_HOTPLUG"; + wnd_cls.hInstance = nullptr; + wnd_cls.hIconSm = LoadIcon(nullptr, IDI_APPLICATION); + RegisterClassEx(&wnd_cls); + /* create window */ + hwnd = CreateWindowEx(0, L"DRIVE_HOTPLUG", nullptr, 0, 0, 0, 0, 0, nullptr, nullptr, nullptr, + nullptr); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)rdpdr); + rdpdr->hotplug_wnd = hwnd; + /* register device interface to hwnd */ + NotificationFilter.dbch_size = sizeof(DEV_BROADCAST_HANDLE); + NotificationFilter.dbch_devicetype = DBT_DEVTYP_HANDLE; + hDevNotify = RegisterDeviceNotification(hwnd, &NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE); + + /* message loop */ + while ((bRet = GetMessage(&msg, 0, 0, 0)) != 0) + { + if (bRet == -1) + { + break; + } + else + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + UnregisterDeviceNotification(hDevNotify); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_hotplug_thread_terminate(rdpdrPlugin* rdpdr) +{ + UINT error = CHANNEL_RC_OK; + + if (rdpdr->hotplug_wnd && !PostMessage(rdpdr->hotplug_wnd, WM_QUIT, 0, 0)) + { + error = GetLastError(); + WLog_Print(rdpdr->log, WLOG_ERROR, "PostMessage failed with error %" PRIu32 "", error); + } + + return error; +} + +#elif defined(__MACOSX__) + +#define MAX_USB_DEVICES 100 + +typedef struct +{ + char* path; + BOOL to_add; +} hotplug_dev; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT handle_hotplug(WINPR_ATTR_UNUSED RdpdrClientContext* context, + WINPR_ATTR_UNUSED RdpdrHotplugEventType type) +{ + WINPR_ASSERT(context); + rdpdrPlugin* rdpdr = context->handle; + + struct dirent* pDirent = nullptr; + char fullpath[PATH_MAX] = WINPR_C_ARRAY_INIT; + char* szdir = (char*)"/Volumes"; + struct stat buf = WINPR_C_ARRAY_INIT; + hotplug_dev dev_array[MAX_USB_DEVICES] = WINPR_C_ARRAY_INIT; + int count = 0; + DEVICE_DRIVE_EXT* device_ext = nullptr; + ULONG_PTR* keys = nullptr; + int size = 0; + UINT error = ERROR_INTERNAL_ERROR; + UINT32 ids[1]; + + DIR* pDir = opendir(szdir); + + if (pDir == nullptr) + { + printf("Cannot open directory\n"); + return ERROR_OPEN_FAILED; + } + + while ((pDirent = readdir(pDir)) != nullptr) + { + if (pDirent->d_name[0] != '.') + { + (void)sprintf_s(fullpath, ARRAYSIZE(fullpath), "%s/%s", szdir, pDirent->d_name); + if (stat(fullpath, &buf) != 0) + continue; + + if (S_ISDIR(buf.st_mode)) + { + dev_array[size].path = _strdup(fullpath); + + if (!dev_array[size].path) + { + closedir(pDir); + error = CHANNEL_RC_NO_MEMORY; + goto cleanup; + } + + dev_array[size++].to_add = TRUE; + } + } + } + + closedir(pDir); + /* delete removed devices */ + count = ListDictionary_GetKeys(rdpdr->devman->devices, &keys); + + for (size_t j = 0; j < count; j++) + { + char* path = nullptr; + BOOL dev_found = FALSE; + device_ext = + (DEVICE_DRIVE_EXT*)ListDictionary_GetItemValue(rdpdr->devman->devices, (void*)keys[j]); + + if (!device_ext || !device_ext->automount) + continue; + + if (device_ext->device.type != RDPDR_DTYP_FILESYSTEM) + continue; + + if (device_ext->path == nullptr) + continue; + + path = ConvertWCharToUtf8Alloc(device_ext->path, nullptr); + if (!path) + continue; + + /* not pluggable device */ + if (strstr(path, "/Volumes/") == nullptr) + { + free(path); + continue; + } + + for (size_t i = 0; i < size; i++) + { + if (strstr(path, dev_array[i].path) != nullptr) + { + dev_found = TRUE; + dev_array[i].to_add = FALSE; + break; + } + } + + free(path); + + if (!dev_found) + { + const uint32_t ids[] = { keys[j] }; + WINPR_ASSERT(rdpdr->context.RdpdrUnregisterDevice); + error = rdpdr->context.RdpdrUnregisterDevice(&rdpdr->context, ARRAYSIZE(ids), ids); + if (error) + { + WLog_Print(rdpdr->log, WLOG_ERROR, + "rdpdr_send_device_list_remove_request failed with error %" PRIu32 "!", + error); + goto cleanup; + } + } + } + + /* add new devices */ + for (size_t i = 0; i < size; i++) + { + const hotplug_dev* dev = &dev_array[i]; + if (dev->to_add) + { + const char* path = dev->path; + const char* name = strrchr(path, '/') + 1; + error = rdpdr_load_drive(rdpdr, name, path, TRUE); + if (error) + goto cleanup; + } + } + +cleanup: + free(keys); + + for (size_t i = 0; i < size; i++) + free(dev_array[i].path); + + return error; +} + +static void drive_hotplug_fsevent_callback(ConstFSEventStreamRef streamRef, + void* clientCallBackInfo, size_t numEvents, + void* eventPaths, + const FSEventStreamEventFlags eventFlags[], + const FSEventStreamEventId eventIds[]) +{ + rdpdrPlugin* rdpdr; + UINT error; + char** paths = (char**)eventPaths; + rdpdr = (rdpdrPlugin*)clientCallBackInfo; + + for (size_t i = 0; i < numEvents; i++) + { + if (strcmp(paths[i], "/Volumes/") == 0) + { + UINT error = ERROR_CALL_NOT_IMPLEMENTED; + if (rdpdr->context.RdpdrHotplugDevice) + error = rdpdr->context.RdpdrHotplugDevice(&rdpdr->context, + RDPDR_HOTPLUG_CHECK_FOR_CHANGES); + switch (error) + { + case ERROR_DISK_CHANGE: + case CHANNEL_RC_OK: + break; + case ERROR_CALL_NOT_IMPLEMENTED: + break; + default: + WLog_Print(rdpdr->log, WLOG_ERROR, + "handle_hotplug failed with error %" PRIu32 "!", error); + break; + } + } + } +} + +static void first_hotplug(rdpdrPlugin* rdpdr) +{ + WINPR_ASSERT(rdpdr); + UINT error = ERROR_CALL_NOT_IMPLEMENTED; + if (rdpdr->context.RdpdrHotplugDevice) + error = rdpdr->context.RdpdrHotplugDevice(&rdpdr->context, RDPDR_HOTPLUG_FIRST_CHECK); + + switch (error) + { + case ERROR_DISK_CHANGE: + case CHANNEL_RC_OK: + case ERROR_CALL_NOT_IMPLEMENTED: + break; + default: + WLog_Print(rdpdr->log, WLOG_ERROR, "handle_hotplug failed with error %" PRIu32 "!", + error); + break; + } +} + +static DWORD WINAPI drive_hotplug_thread_func(LPVOID arg) +{ + rdpdrPlugin* rdpdr = (rdpdrPlugin*)arg; + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(rdpdr->stopEvent); + + CFStringRef path = CFSTR("/Volumes/"); + CFArrayRef pathsToWatch = CFArrayCreate(kCFAllocatorMalloc, (const void**)&path, 1, nullptr); + FSEventStreamContext ctx = { + .copyDescription = nullptr, .info = arg, .release = nullptr, .retain = nullptr, .version = 0 + }; + FSEventStreamRef fsev = + FSEventStreamCreate(kCFAllocatorMalloc, drive_hotplug_fsevent_callback, &ctx, pathsToWatch, + kFSEventStreamEventIdSinceNow, 1, kFSEventStreamCreateFlagNone); + + dispatch_queue_t queue = dispatch_queue_create(TAG, nullptr); + FSEventStreamSetDispatchQueue(fsev, queue); + FSEventStreamStart(fsev); + WLog_Print(rdpdr->log, WLOG_DEBUG, "Started hotplug watcher"); + HANDLE handles[] = { rdpdr->stopEvent, freerdp_abort_event(rdpdr->rdpcontext) }; + WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, INFINITE); + WLog_Print(rdpdr->log, WLOG_DEBUG, "Stopped hotplug watcher"); + FSEventStreamStop(fsev); + FSEventStreamRelease(fsev); + dispatch_release(queue); +out: + ExitThread(CHANNEL_RC_OK); + return CHANNEL_RC_OK; +} + +#else + +static const char* automountLocations[] = { "/run/user/%lu/gvfs", "/run/media/%s", "/media/%s", + "/media", "/mnt" }; + +static BOOL isAutomountLocation(const char* path) +{ + const size_t nrLocations = sizeof(automountLocations) / sizeof(automountLocations[0]); + char buffer[MAX_PATH] = WINPR_C_ARRAY_INIT; + uid_t uid = getuid(); + char uname[MAX_PATH] = WINPR_C_ARRAY_INIT; + ULONG size = sizeof(uname) - 1; + + if (!GetUserNameExA(NameSamCompatible, uname, &size)) + return FALSE; + + if (!path) + return FALSE; + + for (size_t x = 0; x < nrLocations; x++) + { + const char* location = automountLocations[x]; + size_t length = 0; + + WINPR_PRAGMA_DIAG_PUSH + WINPR_PRAGMA_DIAG_IGNORED_FORMAT_NONLITERAL + if (strstr(location, "%lu")) + (void)snprintf(buffer, sizeof(buffer), location, (unsigned long)uid); + else if (strstr(location, "%s")) + (void)snprintf(buffer, sizeof(buffer), location, uname); + else + (void)snprintf(buffer, sizeof(buffer), "%s", location); + WINPR_PRAGMA_DIAG_POP + + length = strnlen(buffer, sizeof(buffer)); + + if (strncmp(buffer, path, length) == 0) + { + const char* rest = &path[length]; + + /* Only consider mount locations with max depth of 1 below the + * base path or the base path itself. */ + if (*rest == '\0') + return TRUE; + else if (*rest == '/') + { + const char* token = strstr(&rest[1], "/"); + + if (!token || (token[1] == '\0')) + return TRUE; + } + } + } + + return FALSE; +} + +#define MAX_USB_DEVICES 100 + +typedef struct +{ + char* path; + BOOL to_add; +} hotplug_dev; + +static void handle_mountpoint(hotplug_dev* dev_array, size_t* size, const char* mountpoint) +{ + if (!mountpoint) + return; + /* copy hotpluged device mount point to the dev_array */ + if (isAutomountLocation(mountpoint) && (*size < MAX_USB_DEVICES)) + { + dev_array[*size].path = _strdup(mountpoint); + dev_array[*size].to_add = TRUE; + (*size)++; + } +} + +#ifdef __sun +#include +static UINT handle_platform_mounts_sun(wLog* log, hotplug_dev* dev_array, size_t* size) +{ + FILE* f; + struct mnttab ent; + f = winpr_fopen("/etc/mnttab", "r"); + if (f == nullptr) + { + WLog_Print(log, WLOG_ERROR, "fopen failed!"); + return ERROR_OPEN_FAILED; + } + while (getmntent(f, &ent) == 0) + { + handle_mountpoint(dev_array, size, ent.mnt_mountp); + } + fclose(f); + return ERROR_SUCCESS; +} +#endif + +#if defined(__FreeBSD__) || defined(__OpenBSD__) +#include +static UINT handle_platform_mounts_bsd(wLog* log, hotplug_dev* dev_array, size_t* size) +{ + int mntsize; + struct statfs* mntbuf = nullptr; + + mntsize = getmntinfo(&mntbuf, MNT_NOWAIT); + if (!mntsize) + { + /* TODO: handle 'errno' */ + WLog_Print(log, WLOG_ERROR, "getmntinfo failed!"); + return ERROR_OPEN_FAILED; + } + for (size_t idx = 0; idx < (size_t)mntsize; idx++) + { + handle_mountpoint(dev_array, size, mntbuf[idx].f_mntonname); + } + free(mntbuf); + return ERROR_SUCCESS; +} +#endif + +#if defined(__LINUX__) || defined(__linux__) +#include +static struct mntent* getmntent_x(FILE* f, struct mntent* buffer, char* pathbuffer, + size_t pathbuffersize) +{ +#if defined(FREERDP_HAVE_GETMNTENT_R) + WINPR_ASSERT(pathbuffersize <= INT32_MAX); + return getmntent_r(f, buffer, pathbuffer, (int)pathbuffersize); +#else + (void)buffer; + (void)pathbuffer; + (void)pathbuffersize; + return getmntent(f); +#endif +} + +static UINT handle_platform_mounts_linux(wLog* log, hotplug_dev* dev_array, size_t* size) +{ + FILE* f = nullptr; + struct mntent mnt = WINPR_C_ARRAY_INIT; + char pathbuffer[PATH_MAX] = WINPR_C_ARRAY_INIT; + struct mntent* ent = nullptr; + f = winpr_fopen("/proc/mounts", "r"); + if (f == nullptr) + { + WLog_Print(log, WLOG_ERROR, "fopen failed!"); + return ERROR_OPEN_FAILED; + } + while ((ent = getmntent_x(f, &mnt, pathbuffer, sizeof(pathbuffer))) != nullptr) + { + handle_mountpoint(dev_array, size, ent->mnt_dir); + } + (void)fclose(f); + return ERROR_SUCCESS; +} +#endif + +static UINT handle_platform_mounts(wLog* log, hotplug_dev* dev_array, size_t* size) +{ +#ifdef __sun + return handle_platform_mounts_sun(log, dev_array, size); +#elif defined(__FreeBSD__) || defined(__OpenBSD__) + return handle_platform_mounts_bsd(log, dev_array, size); +#elif defined(__LINUX__) || defined(__linux__) + return handle_platform_mounts_linux(log, dev_array, size); +#endif + return ERROR_CALL_NOT_IMPLEMENTED; +} + +static BOOL device_not_plugged(ULONG_PTR key, void* element, void* data) +{ + const WCHAR* path = (const WCHAR*)data; + DEVICE_DRIVE_EXT* device_ext = (DEVICE_DRIVE_EXT*)element; + + WINPR_UNUSED(key); + WINPR_ASSERT(path); + + if (!device_ext || (device_ext->device.type != RDPDR_DTYP_FILESYSTEM) || !device_ext->path) + return TRUE; + if (_wcscmp(device_ext->path, path) != 0) + return TRUE; + return FALSE; +} + +static BOOL device_already_plugged(rdpdrPlugin* rdpdr, const hotplug_dev* device) +{ + BOOL rc = FALSE; + WCHAR* path = nullptr; + + if (!rdpdr || !device) + return TRUE; + if (!device->to_add) + return TRUE; + + WINPR_ASSERT(rdpdr->devman); + WINPR_ASSERT(device->path); + + path = ConvertUtf8ToWCharAlloc(device->path, nullptr); + if (!path) + return TRUE; + + rc = device_foreach(rdpdr, TRUE, device_not_plugged, path); + free(path); + return !rc; +} + +struct hotplug_delete_arg +{ + hotplug_dev* dev_array; + size_t dev_array_size; + rdpdrPlugin* rdpdr; +}; + +static BOOL hotplug_delete_foreach(ULONG_PTR key, void* element, void* data) +{ + char* path = nullptr; + BOOL dev_found = FALSE; + struct hotplug_delete_arg* arg = (struct hotplug_delete_arg*)data; + DEVICE_DRIVE_EXT* device_ext = (DEVICE_DRIVE_EXT*)element; + + WINPR_ASSERT(arg); + WINPR_ASSERT(arg->rdpdr); + WINPR_ASSERT(arg->dev_array || (arg->dev_array_size == 0)); + WINPR_ASSERT(key <= UINT32_MAX); + + if (!device_ext || (device_ext->device.type != RDPDR_DTYP_FILESYSTEM) || !device_ext->path || + !device_ext->automount) + return TRUE; + + WINPR_ASSERT(device_ext->path); + path = ConvertWCharToUtf8Alloc(device_ext->path, nullptr); + if (!path) + return FALSE; + + /* not pluggable device */ + if (isAutomountLocation(path)) + { + for (size_t i = 0; i < arg->dev_array_size; i++) + { + hotplug_dev* cur = &arg->dev_array[i]; + if (cur->path && strstr(path, cur->path) != nullptr) + { + dev_found = TRUE; + cur->to_add = FALSE; + break; + } + } + } + + free(path); + + if (!dev_found) + { + const UINT32 ids[1] = { (UINT32)key }; + WINPR_ASSERT(arg->rdpdr->context.RdpdrUnregisterDevice); + const UINT error = + arg->rdpdr->context.RdpdrUnregisterDevice(&arg->rdpdr->context, ARRAYSIZE(ids), ids); + + if (error) + { + WLog_Print(arg->rdpdr->log, WLOG_ERROR, + "rdpdr_send_device_list_remove_request failed with error %" PRIu32 "!", + error); + return FALSE; + } + } + + return TRUE; +} + +static UINT handle_hotplug(RdpdrClientContext* context, + WINPR_ATTR_UNUSED RdpdrHotplugEventType type) +{ + WINPR_ASSERT(context); + rdpdrPlugin* rdpdr = context->handle; + + hotplug_dev dev_array[MAX_USB_DEVICES] = WINPR_C_ARRAY_INIT; + size_t size = 0; + UINT error = ERROR_SUCCESS; + struct hotplug_delete_arg arg = { dev_array, ARRAYSIZE(dev_array), rdpdr }; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(rdpdr->devman); + + error = handle_platform_mounts(rdpdr->log, dev_array, &size); + + /* delete removed devices */ + /* Ignore result */ device_foreach(rdpdr, FALSE, hotplug_delete_foreach, &arg); + + /* add new devices */ + for (size_t i = 0; i < size; i++) + { + hotplug_dev* cur = &dev_array[i]; + if (!device_already_plugged(rdpdr, cur)) + { + const char* path = cur->path; + const char* name = strrchr(path, '/') + 1; + + rdpdr_load_drive(rdpdr, name, path, TRUE); + error = ERROR_DISK_CHANGE; + } + } + + for (size_t i = 0; i < size; i++) + free(dev_array[i].path); + + return error; +} + +static void first_hotplug(rdpdrPlugin* rdpdr) +{ + UINT error = ERROR_CALL_NOT_IMPLEMENTED; + + WINPR_ASSERT(rdpdr); + if (rdpdr->context.RdpdrHotplugDevice) + error = rdpdr->context.RdpdrHotplugDevice(&rdpdr->context, RDPDR_HOTPLUG_FIRST_CHECK); + + switch (error) + { + case ERROR_DISK_CHANGE: + case CHANNEL_RC_OK: + case ERROR_OPEN_FAILED: + case ERROR_CALL_NOT_IMPLEMENTED: + break; + default: + WLog_Print(rdpdr->log, WLOG_ERROR, "handle_hotplug failed with error %" PRIu32 "!", + error); + break; + } +} + +static DWORD WINAPI drive_hotplug_thread_func(LPVOID arg) +{ + rdpdrPlugin* rdpdr = (rdpdrPlugin*)arg; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(rdpdr->stopEvent); + + while (WaitForSingleObject(rdpdr->stopEvent, 1000) == WAIT_TIMEOUT) + { + UINT error = ERROR_CALL_NOT_IMPLEMENTED; + if (rdpdr->context.RdpdrHotplugDevice) + error = + rdpdr->context.RdpdrHotplugDevice(&rdpdr->context, RDPDR_HOTPLUG_CHECK_FOR_CHANGES); + switch (error) + { + case ERROR_DISK_CHANGE: + break; + case CHANNEL_RC_OK: + case ERROR_OPEN_FAILED: + case ERROR_CALL_NOT_IMPLEMENTED: + break; + default: + WLog_Print(rdpdr->log, WLOG_ERROR, "handle_hotplug failed with error %" PRIu32 "!", + error); + goto out; + } + } + +out: +{ + const UINT error = GetLastError(); + if (error && rdpdr->rdpcontext) + setChannelError(rdpdr->rdpcontext, error, "reported an error"); + + ExitThread(error); + return error; +} +} + +#endif + +#if !defined(_WIN32) && !defined(__IOS__) +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT drive_hotplug_thread_terminate(rdpdrPlugin* rdpdr) +{ + UINT error = 0; + + WINPR_ASSERT(rdpdr); + + if (rdpdr->hotplugThread) + { +#if !defined(_WIN32) + if (rdpdr->stopEvent) + (void)SetEvent(rdpdr->stopEvent); +#endif + + if (WaitForSingleObject(rdpdr->hotplugThread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_Print(rdpdr->log, WLOG_ERROR, "WaitForSingleObject failed with error %" PRIu32 "!", + error); + return error; + } + + (void)CloseHandle(rdpdr->hotplugThread); + rdpdr->hotplugThread = nullptr; + } + + return CHANNEL_RC_OK; +} + +#endif + +static UINT rdpdr_add_devices(rdpdrPlugin* rdpdr) +{ + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(rdpdr->rdpcontext); + + rdpSettings* settings = rdpdr->rdpcontext->settings; + WINPR_ASSERT(settings); + + for (UINT32 index = 0; index < freerdp_settings_get_uint32(settings, FreeRDP_DeviceCount); + index++) + { + RDPDR_DEVICE* device = + freerdp_settings_get_pointer_array_writable(settings, FreeRDP_DeviceArray, index); + WINPR_ASSERT(device); + + if (device->Type == RDPDR_DTYP_FILESYSTEM) + { + const char DynamicDrives[] = "DynamicDrives"; + const RDPDR_DRIVE* drive = (const RDPDR_DRIVE*)device; + if (!drive->Path) + continue; + + const char wildcard[] = "*"; + BOOL hotplugAll = strncmp(drive->Path, wildcard, sizeof(wildcard)) == 0; + BOOL hotplugLater = strncmp(drive->Path, DynamicDrives, sizeof(DynamicDrives)) == 0; + + if (hotplugAll || hotplugLater) + { + if (!rdpdr->async) + { + WLog_Print(rdpdr->log, WLOG_WARN, + "Drive hotplug is not supported in synchronous mode!"); + continue; + } + + if (hotplugAll) + first_hotplug(rdpdr); + + /* There might be multiple hotplug related device entries. + * Ensure the thread is only started once + */ + if (!rdpdr->hotplugThread) + { + rdpdr->hotplugThread = + CreateThread(nullptr, 0, drive_hotplug_thread_func, rdpdr, 0, nullptr); + if (!rdpdr->hotplugThread) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "CreateThread failed!"); + return ERROR_INTERNAL_ERROR; + } + } + + continue; + } + } + + const UINT error = devman_load_device_service(rdpdr->devman, device, rdpdr->rdpcontext); + if (error) + { + WLog_Print(rdpdr->log, WLOG_ERROR, + "devman_load_device_service failed with error %" PRIu32 "!", error); + return error; + } + } + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_process_connect(rdpdrPlugin* rdpdr) +{ + WINPR_ASSERT(rdpdr); + + rdpdr->devman = devman_new(rdpdr); + + if (!rdpdr->devman) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "devman_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + WINPR_ASSERT(rdpdr->rdpcontext); + + rdpSettings* settings = rdpdr->rdpcontext->settings; + WINPR_ASSERT(settings); + + rdpdr->ignoreInvalidDevices = freerdp_settings_get_bool(settings, FreeRDP_IgnoreInvalidDevices); + + const char* name = freerdp_settings_get_string(settings, FreeRDP_ClientHostname); + if (!name) + name = freerdp_settings_get_string(settings, FreeRDP_ComputerName); + if (!name) + { + DWORD size = ARRAYSIZE(rdpdr->computerName); + if (!GetComputerNameExA(ComputerNameNetBIOS, rdpdr->computerName, &size)) + return ERROR_INTERNAL_ERROR; + } + else + strncpy(rdpdr->computerName, name, strnlen(name, sizeof(rdpdr->computerName))); + + return rdpdr_add_devices(rdpdr); +} + +static UINT rdpdr_process_server_announce_request(rdpdrPlugin* rdpdr, wStream* s) +{ + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, rdpdr->serverVersionMajor); + Stream_Read_UINT16(s, rdpdr->serverVersionMinor); + Stream_Read_UINT32(s, rdpdr->clientID); + rdpdr->sequenceId++; + + rdpdr->clientVersionMajor = MIN(RDPDR_VERSION_MAJOR, rdpdr->serverVersionMajor); + rdpdr->clientVersionMinor = MIN(RDPDR_VERSION_MINOR_RDP10X, rdpdr->serverVersionMinor); + WLog_Print(rdpdr->log, WLOG_DEBUG, + "[rdpdr] server announces version %" PRIu32 ".%" PRIu32 ", client uses %" PRIu32 + ".%" PRIu32, + rdpdr->serverVersionMajor, rdpdr->serverVersionMinor, rdpdr->clientVersionMajor, + rdpdr->clientVersionMinor); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_send_client_announce_reply(rdpdrPlugin* rdpdr) +{ + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(rdpdr->state == RDPDR_CHANNEL_STATE_ANNOUNCE); + if (!rdpdr_state_advance(rdpdr, RDPDR_CHANNEL_STATE_ANNOUNCE_REPLY)) + return ERROR_INVALID_STATE; + + wStream* s = StreamPool_Take(rdpdr->pool, 12); + + if (!s) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, RDPDR_CTYP_CORE); /* Component (2 bytes) */ + Stream_Write_UINT16(s, PAKID_CORE_CLIENTID_CONFIRM); /* PacketId (2 bytes) */ + Stream_Write_UINT16(s, rdpdr->clientVersionMajor); + Stream_Write_UINT16(s, rdpdr->clientVersionMinor); + Stream_Write_UINT32(s, rdpdr->clientID); + return rdpdr_send(rdpdr, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_send_client_name_request(rdpdrPlugin* rdpdr) +{ + wStream* s = nullptr; + WCHAR* computerNameW = nullptr; + size_t computerNameLenW = 0; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(rdpdr->state == RDPDR_CHANNEL_STATE_ANNOUNCE_REPLY); + if (!rdpdr_state_advance(rdpdr, RDPDR_CHANNEL_STATE_NAME_REQUEST)) + return ERROR_INVALID_STATE; + + const size_t len = strnlen(rdpdr->computerName, sizeof(rdpdr->computerName)); + if (len == 0) + return ERROR_INTERNAL_ERROR; + + WINPR_ASSERT(rdpdr->computerName); + computerNameW = ConvertUtf8NToWCharAlloc(rdpdr->computerName, len, &computerNameLenW); + computerNameLenW *= sizeof(WCHAR); + + if (computerNameLenW > 0) + computerNameLenW += sizeof(WCHAR); // also write '\0' + + s = StreamPool_Take(rdpdr->pool, 16U + computerNameLenW); + + if (!s) + { + free(computerNameW); + WLog_Print(rdpdr->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, RDPDR_CTYP_CORE); /* Component (2 bytes) */ + Stream_Write_UINT16(s, PAKID_CORE_CLIENT_NAME); /* PacketId (2 bytes) */ + Stream_Write_UINT32(s, 1); /* unicodeFlag, 0 for ASCII and 1 for Unicode */ + Stream_Write_UINT32(s, 0); /* codePage, must be set to zero */ + Stream_Write_UINT32(s, + (UINT32)computerNameLenW); /* computerNameLen, including null terminator */ + Stream_Write(s, computerNameW, computerNameLenW); + free(computerNameW); + return rdpdr_send(rdpdr, s); +} + +static UINT rdpdr_process_server_clientid_confirm(rdpdrPlugin* rdpdr, wStream* s) +{ + UINT16 versionMajor = 0; + UINT16 versionMinor = 0; + UINT32 clientID = 0; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + if (!Stream_CheckAndLogRequiredLengthWLog(rdpdr->log, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, versionMajor); + Stream_Read_UINT16(s, versionMinor); + Stream_Read_UINT32(s, clientID); + + if (versionMajor != rdpdr->clientVersionMajor || versionMinor != rdpdr->clientVersionMinor) + { + WLog_Print(rdpdr->log, WLOG_WARN, + "[rdpdr] server announced version %" PRIu32 ".%" PRIu32 ", client uses %" PRIu32 + ".%" PRIu32 " but clientid confirm requests version %" PRIu32 ".%" PRIu32, + rdpdr->serverVersionMajor, rdpdr->serverVersionMinor, rdpdr->clientVersionMajor, + rdpdr->clientVersionMinor, versionMajor, versionMinor); + rdpdr->clientVersionMajor = versionMajor; + rdpdr->clientVersionMinor = versionMinor; + } + + if (clientID != rdpdr->clientID) + rdpdr->clientID = clientID; + + return CHANNEL_RC_OK; +} + +struct device_announce_arg +{ + rdpdrPlugin* rdpdr; + wStream* s; + BOOL userLoggedOn; + UINT32 count; +}; + +static BOOL device_announce(ULONG_PTR key, void* element, void* data) +{ + struct device_announce_arg* arg = data; + rdpdrPlugin* rdpdr = nullptr; + DEVICE* device = (DEVICE*)element; + + WINPR_UNUSED(key); + + WINPR_ASSERT(arg); + WINPR_ASSERT(device); + WINPR_ASSERT(arg->rdpdr); + WINPR_ASSERT(arg->s); + + rdpdr = arg->rdpdr; + + /** + * 1. versionMinor 0x0005 doesn't send PAKID_CORE_USER_LOGGEDON + * so all devices should be sent regardless of user_loggedon + * 2. smartcard devices should be always sent + * 3. other devices are sent only after user_loggedon + */ + + if ((rdpdr->clientVersionMinor == RDPDR_VERSION_MINOR_RDP51) || + (device->type == RDPDR_DTYP_SMARTCARD) || arg->userLoggedOn) + { + size_t data_len = (device->data == nullptr ? 0 : Stream_GetPosition(device->data)); + + if (!Stream_EnsureRemainingCapacity(arg->s, 20 + data_len)) + { + Stream_Release(arg->s); + WLog_Print(rdpdr->log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!"); + return FALSE; + } + + Stream_Write_UINT32(arg->s, device->type); /* deviceType */ + Stream_Write_UINT32(arg->s, device->id); /* deviceID */ + strncpy(Stream_Pointer(arg->s), device->name, 8); + + for (size_t i = 0; i < 8; i++) + { + BYTE c = 0; + Stream_Peek_UINT8(arg->s, c); + + if (c > 0x7F) + Stream_Write_UINT8(arg->s, '_'); + else + Stream_Seek_UINT8(arg->s); + } + + WINPR_ASSERT(data_len <= UINT32_MAX); + Stream_Write_UINT32(arg->s, (UINT32)data_len); + + if (data_len > 0) + Stream_Write(arg->s, Stream_Buffer(device->data), data_len); + + arg->count++; + WLog_Print(rdpdr->log, WLOG_INFO, + "registered [%9s] device #%" PRIu32 ": %5s (type=%2" PRIu32 " id=%2" PRIu32 ")", + rdpdr_device_type_string(device->type), arg->count, device->name, device->type, + device->id); + } + return TRUE; +} + +static UINT rdpdr_send_device_list_announce_request(rdpdrPlugin* rdpdr, BOOL userLoggedOn) +{ + size_t pos = 0; + wStream* s = nullptr; + size_t count_pos = 0; + struct device_announce_arg arg = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(rdpdr->devman); + + if (userLoggedOn) + { + rdpdr->userLoggedOn = TRUE; + } + + s = StreamPool_Take(rdpdr->pool, 256); + + if (!s) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, RDPDR_CTYP_CORE); /* Component (2 bytes) */ + Stream_Write_UINT16(s, PAKID_CORE_DEVICELIST_ANNOUNCE); /* PacketId (2 bytes) */ + count_pos = Stream_GetPosition(s); + Stream_Seek_UINT32(s); /* deviceCount */ + + arg.rdpdr = rdpdr; + arg.userLoggedOn = userLoggedOn; + arg.s = s; + if (!device_foreach(rdpdr, TRUE, device_announce, &arg)) + return ERROR_INVALID_DATA; + + if (arg.count == 0) + { + Stream_Release(s); + return CHANNEL_RC_OK; + } + pos = Stream_GetPosition(s); + Stream_SetPosition(s, count_pos); + Stream_Write_UINT32(s, arg.count); + Stream_SetPosition(s, pos); + Stream_SealLength(s); + return rdpdr_send(rdpdr, s); +} + +UINT rdpdr_try_send_device_list_announce_request(rdpdrPlugin* rdpdr) +{ + WINPR_ASSERT(rdpdr); + if (rdpdr->state != RDPDR_CHANNEL_STATE_READY) + { + WLog_Print(rdpdr->log, WLOG_DEBUG, + "hotplug event received, but channel [RDPDR] is not ready (state %s), ignoring.", + rdpdr_state_str(rdpdr->state)); + return CHANNEL_RC_OK; + } + return rdpdr_send_device_list_announce_request(rdpdr, rdpdr->userLoggedOn); +} + +static UINT dummy_irp_response(rdpdrPlugin* rdpdr, wStream* s) +{ + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + wStream* output = StreamPool_Take(rdpdr->pool, 256); // RDPDR_DEVICE_IO_RESPONSE_LENGTH + if (!output) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_SetPosition(s, 4); /* see "rdpdr_process_receive" */ + + const uint32_t DeviceId = Stream_Get_UINT32(s); /* DeviceId (4 bytes) */ + const uint32_t FileId = Stream_Get_UINT32(s); /* FileId (4 bytes) */ + const uint32_t CompletionId = Stream_Get_UINT32(s); /* CompletionId (4 bytes) */ + + WLog_Print(rdpdr->log, WLOG_WARN, + "Dummy response {DeviceId=%" PRIu32 ", FileId=%" PRIu32 ", CompletionId=%" PRIu32 + "}", + DeviceId, FileId, CompletionId); + if (!rdpdr_write_iocompletion_header(output, DeviceId, CompletionId, STATUS_UNSUCCESSFUL)) + return CHANNEL_RC_NO_MEMORY; + + return rdpdr_send(rdpdr, output); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_process_irp(rdpdrPlugin* rdpdr, wStream* s) +{ + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + IRP* irp = irp_new(rdpdr->devman, rdpdr->pool, s, rdpdr->log, &error); + + if (!irp) + { + if ((error == CHANNEL_RC_OK) || + (error == ERROR_DEV_NOT_EXIST && rdpdr->ignoreInvalidDevices)) + { + return dummy_irp_response(rdpdr, s); + } + + WLog_Print(rdpdr->log, WLOG_ERROR, "irp_new failed with %" PRIu32 "!", error); + return error; + } + + if (irp->device->IRPRequest) + error = irp->device->IRPRequest(irp->device, irp); + else + error = irp->Discard(irp); + + if (error != CHANNEL_RC_OK) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "device->IRPRequest failed with error %" PRIu32 "", + error); + } + + return error; +} + +static UINT rdpdr_process_component(rdpdrPlugin* rdpdr, UINT16 component, UINT16 packetId, + wStream* s) +{ + UINT32 type = 0; + DEVICE* device = nullptr; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(s); + + switch (component) + { + case RDPDR_CTYP_PRN: + type = RDPDR_DTYP_PRINT; + break; + + default: + return ERROR_INVALID_DATA; + } + + device = devman_get_device_by_type(rdpdr->devman, type); + + if (!device) + return ERROR_DEV_NOT_EXIST; + + return IFCALLRESULT(ERROR_INVALID_PARAMETER, device->CustomComponentRequest, device, component, + packetId, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static BOOL device_init(ULONG_PTR key, void* element, void* data) +{ + wLog* log = data; + UINT error = CHANNEL_RC_OK; + DEVICE* device = element; + + WINPR_UNUSED(key); + WINPR_UNUSED(data); + + IFCALLRET(device->Init, error, device); + + if (error != CHANNEL_RC_OK) + { + WLog_Print(log, WLOG_ERROR, "Device init failed with %s", WTSErrorToString(error)); + return FALSE; + } + return TRUE; +} + +static UINT rdpdr_process_init(rdpdrPlugin* rdpdr) +{ + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(rdpdr->devman); + + rdpdr->userLoggedOn = FALSE; /* reset possible received state */ + if (!device_foreach(rdpdr, TRUE, device_init, rdpdr->log)) + return ERROR_INTERNAL_ERROR; + return CHANNEL_RC_OK; +} + +static BOOL state_match(enum RDPDR_CHANNEL_STATE state, size_t count, va_list ap) +{ + for (size_t x = 0; x < count; x++) + { + enum RDPDR_CHANNEL_STATE cur = va_arg(ap, enum RDPDR_CHANNEL_STATE); + if (state == cur) + return TRUE; + } + return FALSE; +} + +static const char* state_str(size_t count, va_list ap, char* buffer, size_t size) +{ + for (size_t x = 0; x < count; x++) + { + enum RDPDR_CHANNEL_STATE cur = va_arg(ap, enum RDPDR_CHANNEL_STATE); + const char* curstr = rdpdr_state_str(cur); + winpr_str_append(curstr, buffer, size, "|"); + } + return buffer; +} + +static BOOL rdpdr_state_check(rdpdrPlugin* rdpdr, UINT16 packetid, enum RDPDR_CHANNEL_STATE next, + size_t count, ...) +{ + va_list ap = WINPR_C_ARRAY_INIT; + WINPR_ASSERT(rdpdr); + + va_start(ap, count); + BOOL rc = state_match(rdpdr->state, count, ap); + va_end(ap); + + if (!rc) + { + const char* strstate = rdpdr_state_str(rdpdr->state); + char buffer[256] = WINPR_C_ARRAY_INIT; + + va_start(ap, count); + state_str(count, ap, buffer, sizeof(buffer)); + va_end(ap); + + WLog_Print(rdpdr->log, WLOG_ERROR, + "channel [RDPDR] received %s, expected states [%s] but have state %s, aborting.", + rdpdr_packetid_string(packetid), buffer, strstate); + + if (!rdpdr_state_advance(rdpdr, RDPDR_CHANNEL_STATE_INITIAL)) + return FALSE; + return FALSE; + } + return rdpdr_state_advance(rdpdr, next); +} + +static BOOL rdpdr_check_channel_state(rdpdrPlugin* rdpdr, UINT16 packetid) +{ + WINPR_ASSERT(rdpdr); + + switch (packetid) + { + case PAKID_CORE_SERVER_ANNOUNCE: + /* windows servers sometimes send this message. + * it seems related to session login (e.g. first initialization for RDP/TLS style login, + * then reinitialize the channel after login successful + */ + if (!rdpdr_state_advance(rdpdr, RDPDR_CHANNEL_STATE_INITIAL)) + return FALSE; + return rdpdr_state_check(rdpdr, packetid, RDPDR_CHANNEL_STATE_ANNOUNCE, 1, + RDPDR_CHANNEL_STATE_INITIAL); + case PAKID_CORE_SERVER_CAPABILITY: + return rdpdr_state_check( + rdpdr, packetid, RDPDR_CHANNEL_STATE_SERVER_CAPS, 6, + RDPDR_CHANNEL_STATE_NAME_REQUEST, RDPDR_CHANNEL_STATE_SERVER_CAPS, + RDPDR_CHANNEL_STATE_READY, RDPDR_CHANNEL_STATE_CLIENT_CAPS, + RDPDR_CHANNEL_STATE_CLIENTID_CONFIRM, RDPDR_CHANNEL_STATE_USER_LOGGEDON); + case PAKID_CORE_CLIENTID_CONFIRM: + return rdpdr_state_check(rdpdr, packetid, RDPDR_CHANNEL_STATE_CLIENTID_CONFIRM, 5, + RDPDR_CHANNEL_STATE_NAME_REQUEST, + RDPDR_CHANNEL_STATE_SERVER_CAPS, + RDPDR_CHANNEL_STATE_CLIENT_CAPS, RDPDR_CHANNEL_STATE_READY, + RDPDR_CHANNEL_STATE_USER_LOGGEDON); + case PAKID_CORE_USER_LOGGEDON: + if (!rdpdr_check_extended_pdu_flag(rdpdr, RDPDR_USER_LOGGEDON_PDU)) + return FALSE; + + return rdpdr_state_check( + rdpdr, packetid, RDPDR_CHANNEL_STATE_USER_LOGGEDON, 4, + RDPDR_CHANNEL_STATE_NAME_REQUEST, RDPDR_CHANNEL_STATE_CLIENT_CAPS, + RDPDR_CHANNEL_STATE_CLIENTID_CONFIRM, RDPDR_CHANNEL_STATE_READY); + default: + { + enum RDPDR_CHANNEL_STATE state = RDPDR_CHANNEL_STATE_READY; + return rdpdr_state_check(rdpdr, packetid, state, 1, state); + } + } +} + +static BOOL tryAdvance(rdpdrPlugin* rdpdr) +{ + if (rdpdr->haveClientId && rdpdr->haveServerCaps) + { + const UINT error = rdpdr_send_device_list_announce_request(rdpdr, FALSE); + if (error) + { + WLog_Print(rdpdr->log, WLOG_ERROR, + "rdpdr_send_device_list_announce_request failed with error %" PRIu32 "", + error); + return FALSE; + } + if (!rdpdr_state_advance(rdpdr, RDPDR_CHANNEL_STATE_READY)) + return FALSE; + } + return TRUE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_process_receive(rdpdrPlugin* rdpdr, wStream* s) +{ + UINT16 component = 0; + UINT16 packetId = 0; + UINT32 deviceId = 0; + UINT32 status = 0; + UINT error = ERROR_INVALID_DATA; + + if (!rdpdr || !s) + return CHANNEL_RC_NULL_DATA; + + rdpdr_dump_received_packet(rdpdr->log, WLOG_TRACE, s, "[rdpdr-channel] receive"); + if (Stream_GetRemainingLength(s) >= 4) + { + Stream_Read_UINT16(s, component); /* Component (2 bytes) */ + Stream_Read_UINT16(s, packetId); /* PacketId (2 bytes) */ + + if (component == RDPDR_CTYP_CORE) + { + if (!rdpdr_check_channel_state(rdpdr, packetId)) + return CHANNEL_RC_OK; + + switch (packetId) + { + case PAKID_CORE_SERVER_ANNOUNCE: + rdpdr->haveClientId = FALSE; + rdpdr->haveServerCaps = FALSE; + if ((error = rdpdr_process_server_announce_request(rdpdr, s))) + { + } + else if ((error = rdpdr_send_client_announce_reply(rdpdr))) + { + WLog_Print(rdpdr->log, WLOG_ERROR, + "rdpdr_send_client_announce_reply failed with error %" PRIu32 "", + error); + } + else if ((error = rdpdr_send_client_name_request(rdpdr))) + { + WLog_Print(rdpdr->log, WLOG_ERROR, + "rdpdr_send_client_name_request failed with error %" PRIu32 "", + error); + } + else if ((error = rdpdr_process_init(rdpdr))) + { + WLog_Print(rdpdr->log, WLOG_ERROR, + "rdpdr_process_init failed with error %" PRIu32 "", error); + } + + break; + + case PAKID_CORE_SERVER_CAPABILITY: + if ((error = rdpdr_process_capability_request(rdpdr, s))) + { + } + else if ((error = rdpdr_send_capability_response(rdpdr))) + { + WLog_Print(rdpdr->log, WLOG_ERROR, + "rdpdr_send_capability_response failed with error %" PRIu32 "", + error); + } + else + { + rdpdr->haveServerCaps = TRUE; + if (!tryAdvance(rdpdr)) + error = ERROR_INTERNAL_ERROR; + } + + break; + + case PAKID_CORE_CLIENTID_CONFIRM: + if ((error = rdpdr_process_server_clientid_confirm(rdpdr, s))) + { + } + else + { + rdpdr->haveClientId = TRUE; + if (!tryAdvance(rdpdr)) + error = ERROR_INTERNAL_ERROR; + } + break; + + case PAKID_CORE_USER_LOGGEDON: + if (!rdpdr->haveServerCaps) + { + WLog_Print(rdpdr->log, WLOG_ERROR, + "Wrong state %s for %s. [serverCaps=%d, clientId=%d]", + rdpdr_state_str(rdpdr->state), rdpdr_packetid_string(packetId), + rdpdr->haveServerCaps, rdpdr->haveClientId); + error = ERROR_INTERNAL_ERROR; + } + else if ((error = rdpdr_send_device_list_announce_request(rdpdr, TRUE))) + { + WLog_Print( + rdpdr->log, WLOG_ERROR, + "rdpdr_send_device_list_announce_request failed with error %" PRIu32 "", + error); + } + else if (!tryAdvance(rdpdr)) + { + error = ERROR_INTERNAL_ERROR; + } + + break; + + case PAKID_CORE_DEVICE_REPLY: + + /* connect to a specific resource */ + if (Stream_GetRemainingLength(s) >= 8) + { + Stream_Read_UINT32(s, deviceId); + Stream_Read_UINT32(s, status); + + if (status != 0) + devman_unregister_device(rdpdr->devman, (void*)((size_t)deviceId)); + error = CHANNEL_RC_OK; + } + + break; + + case PAKID_CORE_DEVICE_IOREQUEST: + if ((error = rdpdr_process_irp(rdpdr, s))) + { + WLog_Print(rdpdr->log, WLOG_ERROR, + "rdpdr_process_irp failed with error %" PRIu32 "", error); + return error; + } + else + s = nullptr; + + break; + + default: + WLog_Print(rdpdr->log, WLOG_ERROR, + "RDPDR_CTYP_CORE unknown PacketId: 0x%04" PRIX16 "", packetId); + error = ERROR_INVALID_DATA; + break; + } + } + else + { + error = rdpdr_process_component(rdpdr, component, packetId, s); + + if (error != CHANNEL_RC_OK) + { + DWORD level = WLOG_ERROR; + if (rdpdr->ignoreInvalidDevices) + { + if (error == ERROR_DEV_NOT_EXIST) + { + level = WLOG_WARN; + error = CHANNEL_RC_OK; + } + } + WLog_Print(rdpdr->log, level, + "Unknown message: Component: %s [0x%04" PRIX16 + "] PacketId: %s [0x%04" PRIX16 "]", + rdpdr_component_string(component), component, + rdpdr_packetid_string(packetId), packetId); + } + } + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpdr_send(rdpdrPlugin* rdpdr, wStream* s) +{ + rdpdrPlugin* plugin = rdpdr; + + if (!s) + { + Stream_Release(s); + return CHANNEL_RC_NULL_DATA; + } + + if (!plugin) + { + Stream_Release(s); + return CHANNEL_RC_BAD_INIT_HANDLE; + } + + const size_t pos = Stream_GetPosition(s); + UINT status = ERROR_INTERNAL_ERROR; + if (pos <= UINT32_MAX) + { + rdpdr_dump_send_packet(rdpdr->log, WLOG_TRACE, s, "[rdpdr-channel] send"); + status = plugin->channelEntryPoints.pVirtualChannelWriteEx( + plugin->InitHandle, plugin->OpenHandle, Stream_Buffer(s), (UINT32)pos, s); + } + + if (status != CHANNEL_RC_OK) + { + Stream_Release(s); + WLog_Print(rdpdr->log, WLOG_ERROR, "pVirtualChannelWriteEx failed with %s [%08" PRIX32 "]", + WTSErrorToString(status), status); + } + + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_virtual_channel_event_data_received(rdpdrPlugin* rdpdr, void* pData, + UINT32 dataLength, UINT32 totalLength, + UINT32 dataFlags) +{ + wStream* data_in = nullptr; + + WINPR_ASSERT(rdpdr); + WINPR_ASSERT(pData || (dataLength == 0)); + + if ((dataFlags & CHANNEL_FLAG_SUSPEND) || (dataFlags & CHANNEL_FLAG_RESUME)) + { + /* + * According to MS-RDPBCGR 2.2.6.1, "All virtual channel traffic MUST be suspended. + * This flag is only valid in server-to-client virtual channel traffic. It MUST be + * ignored in client-to-server data." Thus it would be best practice to cease data + * transmission. However, simply returning here avoids a crash. + */ + return CHANNEL_RC_OK; + } + + if (dataFlags & CHANNEL_FLAG_FIRST) + { + if (rdpdr->data_in != nullptr) + Stream_Release(rdpdr->data_in); + + rdpdr->data_in = StreamPool_Take(rdpdr->pool, totalLength); + + if (!rdpdr->data_in) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + } + + data_in = rdpdr->data_in; + + if (!Stream_EnsureRemainingCapacity(data_in, dataLength)) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!"); + return ERROR_INVALID_DATA; + } + + Stream_Write(data_in, pData, dataLength); + + if (dataFlags & CHANNEL_FLAG_LAST) + { + const size_t pos = Stream_GetPosition(data_in); + const size_t cap = Stream_Capacity(data_in); + if (cap < pos) + { + WLog_Print(rdpdr->log, WLOG_ERROR, + "rdpdr_virtual_channel_event_data_received: read error"); + return ERROR_INTERNAL_ERROR; + } + + Stream_SealLength(data_in); + Stream_ResetPosition(data_in); + + if (rdpdr->async) + { + if (!MessageQueue_Post(rdpdr->queue, nullptr, 0, (void*)data_in, nullptr)) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "MessageQueue_Post failed!"); + return ERROR_INTERNAL_ERROR; + } + rdpdr->data_in = nullptr; + } + else + { + UINT error = rdpdr_process_receive(rdpdr, data_in); + Stream_Release(data_in); + rdpdr->data_in = nullptr; + if (error) + return error; + } + } + + return CHANNEL_RC_OK; +} + +static VOID VCAPITYPE rdpdr_virtual_channel_open_event_ex(LPVOID lpUserParam, DWORD openHandle, + UINT event, LPVOID pData, + UINT32 dataLength, UINT32 totalLength, + UINT32 dataFlags) +{ + UINT error = CHANNEL_RC_OK; + rdpdrPlugin* rdpdr = (rdpdrPlugin*)lpUserParam; + + WINPR_ASSERT(rdpdr); + switch (event) + { + case CHANNEL_EVENT_DATA_RECEIVED: + if (!rdpdr || !pData || (rdpdr->OpenHandle != openHandle)) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "error no match"); + return; + } + if ((error = rdpdr_virtual_channel_event_data_received(rdpdr, pData, dataLength, + totalLength, dataFlags))) + WLog_Print(rdpdr->log, WLOG_ERROR, + "rdpdr_virtual_channel_event_data_received failed with error %" PRIu32 + "!", + error); + + break; + + case CHANNEL_EVENT_WRITE_CANCELLED: + case CHANNEL_EVENT_WRITE_COMPLETE: + { + wStream* s = (wStream*)pData; + Stream_Release(s); + } + break; + + case CHANNEL_EVENT_USER: + break; + default: + break; + } + + if (error && rdpdr && rdpdr->rdpcontext) + setChannelError(rdpdr->rdpcontext, error, + "rdpdr_virtual_channel_open_event_ex reported an error"); +} + +static DWORD WINAPI rdpdr_virtual_channel_client_thread(LPVOID arg) +{ + rdpdrPlugin* rdpdr = (rdpdrPlugin*)arg; + UINT error = 0; + + if (!rdpdr) + { + ExitThread((DWORD)CHANNEL_RC_NULL_DATA); + return CHANNEL_RC_NULL_DATA; + } + + if ((error = rdpdr_process_connect(rdpdr))) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "rdpdr_process_connect failed with error %" PRIu32 "!", + error); + + if (rdpdr->rdpcontext) + setChannelError(rdpdr->rdpcontext, error, + "rdpdr_virtual_channel_client_thread reported an error"); + + ExitThread(error); + return error; + } + + while (1) + { + wMessage message = WINPR_C_ARRAY_INIT; + WINPR_ASSERT(rdpdr); + + if (!MessageQueue_Wait(rdpdr->queue)) + break; + + if (MessageQueue_Peek(rdpdr->queue, &message, TRUE)) + { + if (message.id == WMQ_QUIT) + break; + + if (message.id == 0) + { + wStream* data = (wStream*)message.wParam; + + error = rdpdr_process_receive(rdpdr, data); + + Stream_Release(data); + if (error) + { + WLog_Print(rdpdr->log, WLOG_ERROR, + "rdpdr_process_receive failed with error %" PRIu32 "!", error); + + if (rdpdr->rdpcontext) + setChannelError(rdpdr->rdpcontext, error, + "rdpdr_virtual_channel_client_thread reported an error"); + + goto fail; + } + } + } + } + +fail: + if ((error = drive_hotplug_thread_terminate(rdpdr))) + WLog_Print(rdpdr->log, WLOG_ERROR, + "drive_hotplug_thread_terminate failed with error %" PRIu32 "!", error); + + ExitThread(error); + return error; +} + +static void queue_free(void* obj) +{ + wStream* s = nullptr; + wMessage* msg = (wMessage*)obj; + + if (!msg || (msg->id != 0)) + return; + + s = (wStream*)msg->wParam; + WINPR_ASSERT(s); + Stream_Release(s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_virtual_channel_event_connected(rdpdrPlugin* rdpdr, LPVOID pData, + UINT32 dataLength) +{ + wObject* obj = nullptr; + + WINPR_ASSERT(rdpdr); + WINPR_UNUSED(pData); + WINPR_UNUSED(dataLength); + + if (rdpdr->async) + { + rdpdr->queue = MessageQueue_New(nullptr); + + if (!rdpdr->queue) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "MessageQueue_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + obj = MessageQueue_Object(rdpdr->queue); + obj->fnObjectFree = queue_free; + + if (!(rdpdr->thread = CreateThread(nullptr, 0, rdpdr_virtual_channel_client_thread, + (void*)rdpdr, 0, nullptr))) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "CreateThread failed!"); + return ERROR_INTERNAL_ERROR; + } + } + else + { + UINT error = rdpdr_process_connect(rdpdr); + if (error) + { + WLog_Print(rdpdr->log, WLOG_ERROR, + "rdpdr_process_connect failed with error %" PRIu32 "!", error); + return error; + } + } + + return rdpdr->channelEntryPoints.pVirtualChannelOpenEx(rdpdr->InitHandle, &rdpdr->OpenHandle, + rdpdr->channelDef.name, + rdpdr_virtual_channel_open_event_ex); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_virtual_channel_event_disconnected(rdpdrPlugin* rdpdr) +{ + UINT error = 0; + + WINPR_ASSERT(rdpdr); + + if (rdpdr->OpenHandle == 0) + return CHANNEL_RC_OK; + + if (rdpdr->queue && rdpdr->thread) + { + if (MessageQueue_PostQuit(rdpdr->queue, 0) && + (WaitForSingleObject(rdpdr->thread, INFINITE) == WAIT_FAILED)) + { + error = GetLastError(); + WLog_Print(rdpdr->log, WLOG_ERROR, "WaitForSingleObject failed with error %" PRIu32 "!", + error); + return error; + } + } + + if (rdpdr->thread) + (void)CloseHandle(rdpdr->thread); + MessageQueue_Free(rdpdr->queue); + rdpdr->queue = nullptr; + rdpdr->thread = nullptr; + + WINPR_ASSERT(rdpdr->channelEntryPoints.pVirtualChannelCloseEx); + error = rdpdr->channelEntryPoints.pVirtualChannelCloseEx(rdpdr->InitHandle, rdpdr->OpenHandle); + + if (CHANNEL_RC_OK != error) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "pVirtualChannelCloseEx failed with %s [%08" PRIX32 "]", + WTSErrorToString(error), error); + } + + rdpdr->OpenHandle = 0; + + if (rdpdr->data_in) + { + Stream_Release(rdpdr->data_in); + rdpdr->data_in = nullptr; + } + + if (rdpdr->devman) + { + devman_free(rdpdr->devman); + rdpdr->devman = nullptr; + } + + return error; +} + +static void rdpdr_virtual_channel_event_terminated(rdpdrPlugin* rdpdr) +{ + WINPR_ASSERT(rdpdr); +#if !defined(_WIN32) + if (rdpdr->stopEvent) + { + (void)CloseHandle(rdpdr->stopEvent); + rdpdr->stopEvent = nullptr; + } +#endif + rdpdr->InitHandle = nullptr; + StreamPool_Free(rdpdr->pool); + free(rdpdr); +} + +static UINT rdpdr_register_device(RdpdrClientContext* context, const RDPDR_DEVICE* device, + uint32_t* pid) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(device); + WINPR_ASSERT(pid); + + rdpdrPlugin* rdpdr = context->handle; + WINPR_ASSERT(rdpdr); + + RDPDR_DEVICE* copy = freerdp_device_clone(device); + if (!copy) + return ERROR_INVALID_DATA; + UINT rc = devman_load_device_service(rdpdr->devman, copy, rdpdr->rdpcontext); + *pid = copy->Id; + freerdp_device_free(copy); + if (rc == CHANNEL_RC_OK) + rc = rdpdr_try_send_device_list_announce_request(rdpdr); + return rc; +} + +static UINT rdpdr_unregister_device(RdpdrClientContext* context, size_t count, const uint32_t ids[]) +{ + WINPR_ASSERT(context); + + rdpdrPlugin* rdpdr = context->handle; + WINPR_ASSERT(rdpdr); + + for (size_t x = 0; x < count; x++) + { + const uintptr_t id = ids[x]; + devman_unregister_device(rdpdr->devman, (void*)id); + } + return rdpdr_send_device_list_remove_request(rdpdr, WINPR_ASSERTING_INT_CAST(uint32_t, count), + ids); +} + +static UINT rdpdr_virtual_channel_event_initialized(rdpdrPlugin* rdpdr, + WINPR_ATTR_UNUSED LPVOID pData, + WINPR_ATTR_UNUSED UINT32 dataLength) +{ + WINPR_ASSERT(rdpdr); +#if !defined(_WIN32) + WINPR_ASSERT(!rdpdr->stopEvent); + rdpdr->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + WINPR_ASSERT(rdpdr->stopEvent); +#endif + + rdpdr->context.handle = rdpdr; + rdpdr->context.RdpdrHotplugDevice = handle_hotplug; + rdpdr->context.RdpdrRegisterDevice = rdpdr_register_device; + rdpdr->context.RdpdrUnregisterDevice = rdpdr_unregister_device; + return CHANNEL_RC_OK; +} + +static VOID VCAPITYPE rdpdr_virtual_channel_init_event_ex(LPVOID lpUserParam, LPVOID pInitHandle, + UINT event, LPVOID pData, UINT dataLength) +{ + UINT error = CHANNEL_RC_OK; + rdpdrPlugin* rdpdr = (rdpdrPlugin*)lpUserParam; + + if (!rdpdr || (rdpdr->InitHandle != pInitHandle)) + { + WLog_ERR(TAG, "error no match"); + return; + } + + WINPR_ASSERT(pData || (dataLength == 0)); + + switch (event) + { + case CHANNEL_EVENT_INITIALIZED: + error = rdpdr_virtual_channel_event_initialized(rdpdr, pData, dataLength); + break; + + case CHANNEL_EVENT_CONNECTED: + if ((error = rdpdr_virtual_channel_event_connected(rdpdr, pData, dataLength))) + WLog_Print(rdpdr->log, WLOG_ERROR, + "rdpdr_virtual_channel_event_connected failed with error %" PRIu32 "!", + error); + + break; + + case CHANNEL_EVENT_DISCONNECTED: + if ((error = rdpdr_virtual_channel_event_disconnected(rdpdr))) + WLog_Print(rdpdr->log, WLOG_ERROR, + "rdpdr_virtual_channel_event_disconnected failed with error %" PRIu32 + "!", + error); + + break; + + case CHANNEL_EVENT_TERMINATED: + rdpdr_virtual_channel_event_terminated(rdpdr); + rdpdr = nullptr; + break; + + case CHANNEL_EVENT_ATTACHED: + case CHANNEL_EVENT_DETACHED: + default: + WLog_Print(rdpdr->log, WLOG_ERROR, "unknown event %" PRIu32 "!", event); + break; + } + + if (error && rdpdr && rdpdr->rdpcontext) + setChannelError(rdpdr->rdpcontext, error, + "rdpdr_virtual_channel_init_event_ex reported an error"); +} + +/* rdpdr is always built-in */ +#define VirtualChannelEntryEx rdpdr_VirtualChannelEntryEx + +FREERDP_ENTRY_POINT(BOOL VCAPITYPE VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS_EX pEntryPoints, + PVOID pInitHandle)) +{ + WINPR_ASSERT(pEntryPoints); + WINPR_ASSERT(pInitHandle); + + rdpdrPlugin* rdpdr = (rdpdrPlugin*)calloc(1, sizeof(rdpdrPlugin)); + + if (!rdpdr) + { + WLog_ERR(TAG, "calloc failed!"); + return FALSE; + } + rdpdr->log = WLog_Get(TAG); + + rdpdr->clientExtendedPDU = + RDPDR_DEVICE_REMOVE_PDUS | RDPDR_CLIENT_DISPLAY_NAME_PDU | RDPDR_USER_LOGGEDON_PDU; + rdpdr->clientIOCode1 = + RDPDR_IRP_MJ_CREATE | RDPDR_IRP_MJ_CLEANUP | RDPDR_IRP_MJ_CLOSE | RDPDR_IRP_MJ_READ | + RDPDR_IRP_MJ_WRITE | RDPDR_IRP_MJ_FLUSH_BUFFERS | RDPDR_IRP_MJ_SHUTDOWN | + RDPDR_IRP_MJ_DEVICE_CONTROL | RDPDR_IRP_MJ_QUERY_VOLUME_INFORMATION | + RDPDR_IRP_MJ_SET_VOLUME_INFORMATION | RDPDR_IRP_MJ_QUERY_INFORMATION | + RDPDR_IRP_MJ_SET_INFORMATION | RDPDR_IRP_MJ_DIRECTORY_CONTROL | RDPDR_IRP_MJ_LOCK_CONTROL | + RDPDR_IRP_MJ_QUERY_SECURITY | RDPDR_IRP_MJ_SET_SECURITY; + + rdpdr->clientExtraFlags1 = ENABLE_ASYNCIO; + + rdpdr->pool = StreamPool_New(TRUE, 1024); + if (!rdpdr->pool) + { + free(rdpdr); + return FALSE; + } + + rdpdr->channelDef.options = + CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP | CHANNEL_OPTION_COMPRESS_RDP; + (void)sprintf_s(rdpdr->channelDef.name, ARRAYSIZE(rdpdr->channelDef.name), + RDPDR_SVC_CHANNEL_NAME); + rdpdr->sequenceId = 0; + CHANNEL_ENTRY_POINTS_FREERDP_EX* pEntryPointsEx = + (CHANNEL_ENTRY_POINTS_FREERDP_EX*)pEntryPoints; + + if ((pEntryPointsEx->cbSize >= sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)) && + (pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER)) + { + rdpdr->rdpcontext = pEntryPointsEx->context; + if (!freerdp_settings_get_bool(rdpdr->rdpcontext->settings, + FreeRDP_SynchronousStaticChannels)) + rdpdr->async = TRUE; + } + + CopyMemory(&(rdpdr->channelEntryPoints), pEntryPoints, sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)); + rdpdr->InitHandle = pInitHandle; + const UINT rc = rdpdr->channelEntryPoints.pVirtualChannelInitEx( + rdpdr, &rdpdr->context, pInitHandle, &rdpdr->channelDef, 1, VIRTUAL_CHANNEL_VERSION_WIN2000, + rdpdr_virtual_channel_init_event_ex); + + if (CHANNEL_RC_OK != rc) + { + WLog_Print(rdpdr->log, WLOG_ERROR, "pVirtualChannelInitEx failed with %s [%08" PRIX32 "]", + WTSErrorToString(rc), rc); + free(rdpdr); + return FALSE; + } + + return TRUE; +} diff --git a/third_party/FreeRDP/channels/rdpdr/client/rdpdr_main.h b/third_party/FreeRDP/channels/rdpdr/client/rdpdr_main.h new file mode 100644 index 0000000..58d58d2 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpdr/client/rdpdr_main.h @@ -0,0 +1,127 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2012 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 Inuvika Inc. + * Copyright 2016 David PHAM-VAN + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPDR_CLIENT_MAIN_H +#define FREERDP_CHANNEL_RDPDR_CLIENT_MAIN_H + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#ifdef __MACOSX__ +#include +#endif + +enum RDPDR_CHANNEL_STATE +{ + RDPDR_CHANNEL_STATE_INITIAL = 0, + RDPDR_CHANNEL_STATE_ANNOUNCE, + RDPDR_CHANNEL_STATE_ANNOUNCE_REPLY, + RDPDR_CHANNEL_STATE_NAME_REQUEST, + RDPDR_CHANNEL_STATE_SERVER_CAPS, + RDPDR_CHANNEL_STATE_CLIENT_CAPS, + RDPDR_CHANNEL_STATE_CLIENTID_CONFIRM, + RDPDR_CHANNEL_STATE_READY, + RDPDR_CHANNEL_STATE_USER_LOGGEDON +}; + +typedef struct +{ + CHANNEL_DEF channelDef; + CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints; + + enum RDPDR_CHANNEL_STATE state; + HANDLE thread; + wStream* data_in; + void* InitHandle; + DWORD OpenHandle; + wMessageQueue* queue; + + DEVMAN* devman; + BOOL ignoreInvalidDevices; + + UINT32 serverOsType; + UINT32 serverOsVersion; + UINT16 serverVersionMajor; + UINT16 serverVersionMinor; + UINT32 serverExtendedPDU; + UINT32 serverIOCode1; + UINT32 serverIOCode2; + UINT32 serverExtraFlags1; + UINT32 serverExtraFlags2; + UINT32 serverSpecialTypeDeviceCap; + + UINT32 clientOsType; + UINT32 clientOsVersion; + UINT16 clientVersionMajor; + UINT16 clientVersionMinor; + UINT32 clientExtendedPDU; + UINT32 clientIOCode1; + UINT32 clientIOCode2; + UINT32 clientExtraFlags1; + UINT32 clientExtraFlags2; + UINT32 clientSpecialTypeDeviceCap; + + UINT32 clientID; + char computerName[256]; + + UINT32 sequenceId; + BOOL userLoggedOn; + + /* hotplug support */ + HANDLE hotplugThread; +#ifdef _WIN32 + HWND hotplug_wnd; +#endif +#ifndef _WIN32 + HANDLE stopEvent; +#endif + rdpContext* rdpcontext; + wStreamPool* pool; + wLog* log; + BOOL async; + BOOL capabilities[6]; + BOOL haveClientId; + BOOL haveServerCaps; + + RdpdrClientContext context; +} rdpdrPlugin; + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL BOOL rdpdr_state_advance(rdpdrPlugin* rdpdr, enum RDPDR_CHANNEL_STATE next); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT rdpdr_send(rdpdrPlugin* rdpdr, wStream* s); + +#endif /* FREERDP_CHANNEL_RDPDR_CLIENT_MAIN_H */ diff --git a/third_party/FreeRDP/channels/rdpdr/server/CMakeLists.txt b/third_party/FreeRDP/channels/rdpdr/server/CMakeLists.txt new file mode 100644 index 0000000..d82c29f --- /dev/null +++ b/third_party/FreeRDP/channels/rdpdr/server/CMakeLists.txt @@ -0,0 +1,25 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_server("rdpdr") + +set(${MODULE_PREFIX}_SRCS rdpdr_main.c rdpdr_main.h) + +set(${MODULE_PREFIX}_LIBS freerdp) + +option(WITH_WCHAR_FILE_DIRECTORY_INFORMATION "Build with WCHAR FILE_DIRECTORY_INFORMATION::FileName" OFF) +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntry") diff --git a/third_party/FreeRDP/channels/rdpdr/server/rdpdr_main.c b/third_party/FreeRDP/channels/rdpdr/server/rdpdr_main.c new file mode 100644 index 0000000..23b5d87 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpdr/server/rdpdr_main.c @@ -0,0 +1,3770 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel Extension + * + * Copyright 2014 Dell Software + * Copyright 2013 Marc-Andre Moreau + * Copyright 2015-2022 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2022 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include "rdpdr_main.h" + +#define RDPDR_ADD_PRINTER_EVENT 0x00000001 +#define RDPDR_UPDATE_PRINTER_EVENT 0x00000002 +#define RDPDR_DELETE_PRINTER_EVENT 0x00000003 +#define RDPDR_RENAME_PRINTER_EVENT 0x00000004 + +#define RDPDR_HEADER_LENGTH 4 +#define RDPDR_CAPABILITY_HEADER_LENGTH 8 + +struct s_rdpdr_server_private +{ + HANDLE Thread; + HANDLE StopEvent; + void* ChannelHandle; + + UINT32 ClientId; + UINT16 VersionMajor; + UINT16 VersionMinor; + char* ClientComputerName; + + BOOL UserLoggedOnPdu; + + wListDictionary* IrpList; + UINT32 NextCompletionId; + + wHashTable* devicelist; + wLog* log; + UINT32 SpecialDeviceTypeCap; + UINT32 IoCode1; + UINT32 ExtendedPDU; +}; + +static const char* fileInformation2str(uint8_t val) +{ + switch (val) + { + case FILE_SUPERSEDED: + return "FILE_DOES_NOT_EXIST"; + case FILE_OPENED: + return "FILE_EXISTS"; + case FILE_CREATED: + return "FILE_OVERWRITTEN"; + case FILE_OVERWRITTEN: + return "FILE_CREATED"; + case FILE_EXISTS: + return "FILE_OPENED"; + case FILE_DOES_NOT_EXIST: + return "FILE_SUPERSEDED"; + default: + return "FILE_UNKNOWN"; + } +} + +static const char* DR_DRIVE_LOCK_REQ2str(uint32_t op) +{ + switch (op) + { + case RDP_LOWIO_OP_SHAREDLOCK: + return "RDP_LOWIO_OP_UNLOCK_MULTIPLE"; + case RDP_LOWIO_OP_EXCLUSIVELOCK: + return "RDP_LOWIO_OP_UNLOCK"; + case RDP_LOWIO_OP_UNLOCK: + return "RDP_LOWIO_OP_EXCLUSIVELOCK"; + case RDP_LOWIO_OP_UNLOCK_MULTIPLE: + return "RDP_LOWIO_OP_SHAREDLOCK"; + default: + return "RDP_LOWIO_OP_UNKNOWN"; + } +} + +static void rdpdr_device_free(RdpdrDevice* device) +{ + if (!device) + return; + free(device->DeviceData); + free(device); +} + +static void rdpdr_device_free_h(void* obj) +{ + RdpdrDevice* other = obj; + rdpdr_device_free(other); +} + +static UINT32 rdpdr_deviceid_hash(const void* id) +{ + WINPR_ASSERT(id); + return *((const UINT32*)id); +} + +static BOOL rdpdr_device_equal(const void* v1, const void* v2) +{ + const UINT32* p1 = (const UINT32*)v1; + const UINT32* p2 = (const UINT32*)v2; + if (!p1 && !p2) + return TRUE; + if (!p1 || !p2) + return FALSE; + return *p1 == *p2; +} + +static RdpdrDevice* rdpdr_device_new(void) +{ + return calloc(1, sizeof(RdpdrDevice)); +} + +static void* rdpdr_device_clone(const void* val) +{ + const RdpdrDevice* other = val; + + if (!other) + return nullptr; + + RdpdrDevice* tmp = rdpdr_device_new(); + if (!tmp) + goto fail; + + *tmp = *other; + if (other->DeviceData) + { + tmp->DeviceData = malloc(other->DeviceDataLength); + if (!tmp->DeviceData) + goto fail; + memcpy(tmp->DeviceData, other->DeviceData, other->DeviceDataLength); + } + return tmp; + +fail: + rdpdr_device_free(tmp); + return nullptr; +} + +static void* rdpdr_device_key_clone(const void* pvval) +{ + const UINT32* val = pvval; + if (!val) + return nullptr; + + UINT32* ptr = calloc(1, sizeof(UINT32)); + if (!ptr) + return nullptr; + *ptr = *val; + return ptr; +} + +static void rdpdr_device_key_free(void* obj) +{ + free(obj); +} + +static RdpdrDevice* rdpdr_get_device_by_id(RdpdrServerPrivate* priv, UINT32 DeviceId) +{ + WINPR_ASSERT(priv); + return HashTable_GetItemValue(priv->devicelist, &DeviceId); +} + +static BOOL rdpdr_remove_device_by_id(RdpdrServerPrivate* priv, UINT32 DeviceId) +{ + const RdpdrDevice* device = rdpdr_get_device_by_id(priv, DeviceId); + WINPR_ASSERT(priv); + + if (!device) + { + WLog_Print(priv->log, WLOG_WARN, "[del] Device Id: 0x%08" PRIX32 ": no such device", + DeviceId); + return FALSE; + } + WLog_Print(priv->log, WLOG_DEBUG, + "[del] Device Name: %s Id: 0x%08" PRIX32 " DataLength: %" PRIu32 "", + device->PreferredDosName, device->DeviceId, device->DeviceDataLength); + return HashTable_Remove(priv->devicelist, &DeviceId); +} + +static BOOL rdpdr_add_device(RdpdrServerPrivate* priv, const RdpdrDevice* device) +{ + WINPR_ASSERT(priv); + WINPR_ASSERT(device); + + WLog_Print(priv->log, WLOG_DEBUG, + "[add] Device Name: %s Id: 0x%08" PRIX32 " DataLength: %" PRIu32 "", + device->PreferredDosName, device->DeviceId, device->DeviceDataLength); + + return HashTable_Insert(priv->devicelist, &device->DeviceId, device); +} + +static UINT32 g_ClientId = 0; + +static const WCHAR* rdpdr_read_ustring(wLog* log, wStream* s, size_t bytelen) +{ + const size_t charlen = (bytelen + 1) / sizeof(WCHAR); + const WCHAR* str = Stream_ConstPointer(s); + if (!Stream_CheckAndLogRequiredLengthWLog(log, s, bytelen)) + return nullptr; + if (_wcsnlen(str, charlen) == charlen) + { + WLog_Print(log, WLOG_WARN, "[rdpdr] unicode string not '\\0' terminated"); + return nullptr; + } + Stream_Seek(s, bytelen); + return str; +} + +static RDPDR_IRP* rdpdr_server_irp_new(void) +{ + RDPDR_IRP* irp = (RDPDR_IRP*)calloc(1, sizeof(RDPDR_IRP)); + return irp; +} + +static void rdpdr_server_irp_free(RDPDR_IRP* irp) +{ + free(irp); +} + +static BOOL rdpdr_server_enqueue_irp(RdpdrServerContext* context, RDPDR_IRP* irp) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + const uintptr_t key = irp->CompletionId + 1ull; + return ListDictionary_Add(context->priv->IrpList, (void*)key, irp); +} + +static RDPDR_IRP* rdpdr_server_dequeue_irp(RdpdrServerContext* context, UINT32 completionId) +{ + RDPDR_IRP* irp = nullptr; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + const uintptr_t key = completionId + 1ull; + irp = (RDPDR_IRP*)ListDictionary_Take(context->priv->IrpList, (void*)key); + return irp; +} + +static UINT rdpdr_seal_send_free_request(RdpdrServerContext* context, wStream* s) +{ + BOOL status = 0; + ULONG written = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(s); + + Stream_SealLength(s); + const size_t length = Stream_Length(s); + WINPR_ASSERT(length <= UINT32_MAX); + Stream_ResetPosition(s); + + if (length >= RDPDR_HEADER_LENGTH) + { + RDPDR_HEADER header = WINPR_C_ARRAY_INIT; + Stream_Read_UINT16(s, header.Component); + Stream_Read_UINT16(s, header.PacketId); + + WLog_Print(context->priv->log, WLOG_DEBUG, + "sending message {Component %s[%04" PRIx16 "], PacketId %s[%04" PRIx16 "]", + rdpdr_component_string(header.Component), header.Component, + rdpdr_packetid_string(header.PacketId), header.PacketId); + } + winpr_HexLogDump(context->priv->log, WLOG_DEBUG, Stream_Buffer(s), Stream_Length(s)); + status = WTSVirtualChannelWrite(context->priv->ChannelHandle, Stream_BufferAs(s, char), + (ULONG)length, &written); + Stream_Free(s, TRUE); + return status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_announce_request(RdpdrServerContext* context) +{ + UINT error = 0; + wStream* s = nullptr; + RDPDR_HEADER header = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + header.Component = RDPDR_CTYP_CORE; + header.PacketId = PAKID_CORE_SERVER_ANNOUNCE; + + error = IFCALLRESULT(CHANNEL_RC_OK, context->SendServerAnnounce, context); + if (error != CHANNEL_RC_OK) + return error; + + s = Stream_New(nullptr, RDPDR_HEADER_LENGTH + 8); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, header.Component); /* Component (2 bytes) */ + Stream_Write_UINT16(s, header.PacketId); /* PacketId (2 bytes) */ + Stream_Write_UINT16(s, context->priv->VersionMajor); /* VersionMajor (2 bytes) */ + Stream_Write_UINT16(s, context->priv->VersionMinor); /* VersionMinor (2 bytes) */ + Stream_Write_UINT32(s, context->priv->ClientId); /* ClientId (4 bytes) */ + return rdpdr_seal_send_free_request(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_receive_announce_response(RdpdrServerContext* context, wStream* s, + const RDPDR_HEADER* header) +{ + UINT32 ClientId = 0; + UINT16 VersionMajor = 0; + UINT16 VersionMinor = 0; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(header); + + WINPR_UNUSED(header); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, VersionMajor); /* VersionMajor (2 bytes) */ + Stream_Read_UINT16(s, VersionMinor); /* VersionMinor (2 bytes) */ + Stream_Read_UINT32(s, ClientId); /* ClientId (4 bytes) */ + WLog_Print(context->priv->log, WLOG_DEBUG, + "Client Announce Response: VersionMajor: 0x%08" PRIX16 " VersionMinor: 0x%04" PRIX16 + " ClientId: 0x%08" PRIX32 "", + VersionMajor, VersionMinor, ClientId); + context->priv->ClientId = ClientId; + + return IFCALLRESULT(CHANNEL_RC_OK, context->ReceiveAnnounceResponse, context, VersionMajor, + VersionMinor, ClientId); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_receive_client_name_request(RdpdrServerContext* context, wStream* s, + const RDPDR_HEADER* header) +{ + UINT32 UnicodeFlag = 0; + UINT32 CodePage = 0; + UINT32 ComputerNameLen = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(s); + WINPR_ASSERT(header); + WINPR_UNUSED(header); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 12)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, UnicodeFlag); /* UnicodeFlag (4 bytes) */ + Stream_Read_UINT32(s, CodePage); /* CodePage (4 bytes), MUST be set to zero */ + Stream_Read_UINT32(s, ComputerNameLen); /* ComputerNameLen (4 bytes) */ + /* UnicodeFlag is either 0 or 1, the other 31 bits must be ignored. + */ + UnicodeFlag = UnicodeFlag & 0x00000001; + + if (CodePage != 0) + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.2.4 Client Name Request (DR_CORE_CLIENT_NAME_REQ)::CodePage " + "must be 0, but is 0x%08" PRIx32, + CodePage); + + /** + * Caution: ComputerNameLen is given *bytes*, + * not in characters, including the nullptr terminator! + */ + + if (UnicodeFlag) + { + if ((ComputerNameLen % 2) || ComputerNameLen > 512 || ComputerNameLen < 2) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "invalid unicode computer name length: %" PRIu32 "", ComputerNameLen); + return ERROR_INVALID_DATA; + } + } + else + { + if (ComputerNameLen > 256 || ComputerNameLen < 1) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "invalid ascii computer name length: %" PRIu32 "", ComputerNameLen); + return ERROR_INVALID_DATA; + } + } + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, ComputerNameLen)) + return ERROR_INVALID_DATA; + + /* ComputerName must be null terminated, check if it really is */ + const char* computerName = Stream_ConstPointer(s); + if (computerName[ComputerNameLen - 1] || (UnicodeFlag && computerName[ComputerNameLen - 2])) + { + WLog_Print(context->priv->log, WLOG_ERROR, "computer name must be null terminated"); + return ERROR_INVALID_DATA; + } + + if (context->priv->ClientComputerName) + { + free(context->priv->ClientComputerName); + context->priv->ClientComputerName = nullptr; + } + + if (UnicodeFlag) + { + context->priv->ClientComputerName = + Stream_Read_UTF16_String_As_UTF8(s, ComputerNameLen / sizeof(WCHAR), nullptr); + if (!context->priv->ClientComputerName) + { + WLog_Print(context->priv->log, WLOG_ERROR, "failed to convert client computer name"); + return ERROR_INVALID_DATA; + } + } + else + { + const char* name = Stream_ConstPointer(s); + context->priv->ClientComputerName = _strdup(name); + Stream_Seek(s, ComputerNameLen); + + if (!context->priv->ClientComputerName) + { + WLog_Print(context->priv->log, WLOG_ERROR, "failed to duplicate client computer name"); + return CHANNEL_RC_NO_MEMORY; + } + } + + WLog_Print(context->priv->log, WLOG_DEBUG, "ClientComputerName: %s", + context->priv->ClientComputerName); + return IFCALLRESULT(CHANNEL_RC_OK, context->ReceiveClientNameRequest, context, ComputerNameLen, + context->priv->ClientComputerName); +} + +static UINT rdpdr_server_write_capability_set_header_cb(RdpdrServerContext* context, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + UINT error = rdpdr_write_capset_header(context->priv->log, s, header); + if (error != CHANNEL_RC_OK) + return error; + + return IFCALLRESULT(CHANNEL_RC_OK, context->SendCaps, context, header, 0, nullptr); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_read_general_capability_set(RdpdrServerContext* context, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(header); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32)) + return ERROR_INVALID_DATA; + + const UINT32 OsType = Stream_Get_UINT32(s); /* osType (4 bytes), ignored on receipt */ + const UINT32 OsVersion = + Stream_Get_UINT32(s); /* osVersion (4 bytes), unused and must be set to zero */ + const UINT32 VersionMajor = Stream_Get_UINT16(s); /* protocolMajorVersion (2 bytes) */ + const UINT32 VersionMinor = Stream_Get_UINT16(s); /* protocolMinorVersion (2 bytes) */ + const UINT32 IoCode1 = Stream_Get_UINT32(s); /* ioCode1 (4 bytes) */ + const UINT32 IoCode2 = + Stream_Get_UINT32(s); /* ioCode2 (4 bytes), must be set to zero, reserved for future use */ + const UINT32 ExtendedPdu = Stream_Get_UINT32(s); /* extendedPdu (4 bytes) */ + const UINT32 ExtraFlags1 = Stream_Get_UINT32(s); /* extraFlags1 (4 bytes) */ + const UINT32 ExtraFlags2 = Stream_Get_UINT32( + s); /* extraFlags2 (4 bytes), must be set to zero, reserved for future use */ + + { + char buffer[1024] = WINPR_C_ARRAY_INIT; + WLog_Print(context->priv->log, WLOG_TRACE, + "OsType=%" PRIu32 ", OsVersion=%" PRIu32 ", VersionMajor=%" PRIu32 + ", VersionMinor=%" PRIu32 ", IoCode1=%s, IoCode2=%" PRIu32 + ", ExtendedPdu=%" PRIu32 ", ExtraFlags1=%" PRIu32 ", ExtraFlags2=%" PRIu32, + OsType, OsVersion, VersionMajor, VersionMinor, + rdpdr_irp_mask2str(IoCode1, buffer, sizeof(buffer)), IoCode2, ExtendedPdu, + ExtraFlags1, ExtraFlags2); + } + + if (VersionMajor != RDPDR_MAJOR_RDP_VERSION) + { + WLog_Print(context->priv->log, WLOG_ERROR, "unsupported RDPDR version %" PRIu16 ".%" PRIu16, + VersionMajor, VersionMinor); + return ERROR_INVALID_DATA; + } + + switch (VersionMinor) + { + case RDPDR_MINOR_RDP_VERSION_13: + case RDPDR_MINOR_RDP_VERSION_6_X: + case RDPDR_MINOR_RDP_VERSION_5_2: + case RDPDR_MINOR_RDP_VERSION_5_1: + case RDPDR_MINOR_RDP_VERSION_5_0: + break; + default: + WLog_Print(context->priv->log, WLOG_WARN, + "unsupported RDPDR minor version %" PRIu16 ".%" PRIu16, VersionMajor, + VersionMinor); + break; + } + + UINT32 SpecialTypeDeviceCap = 0; + if (header->Version == GENERAL_CAPABILITY_VERSION_02) + { + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4)) + return ERROR_INVALID_DATA; + + SpecialTypeDeviceCap = Stream_Get_UINT32(s); /* SpecialTypeDeviceCap (4 bytes) */ + } + context->priv->SpecialDeviceTypeCap = SpecialTypeDeviceCap; + + const UINT32 mask = + RDPDR_IRP_MJ_CREATE | RDPDR_IRP_MJ_CLEANUP | RDPDR_IRP_MJ_CLOSE | RDPDR_IRP_MJ_READ | + RDPDR_IRP_MJ_WRITE | RDPDR_IRP_MJ_FLUSH_BUFFERS | RDPDR_IRP_MJ_SHUTDOWN | + RDPDR_IRP_MJ_DEVICE_CONTROL | RDPDR_IRP_MJ_QUERY_VOLUME_INFORMATION | + RDPDR_IRP_MJ_SET_VOLUME_INFORMATION | RDPDR_IRP_MJ_QUERY_INFORMATION | + RDPDR_IRP_MJ_SET_INFORMATION | RDPDR_IRP_MJ_DIRECTORY_CONTROL | RDPDR_IRP_MJ_LOCK_CONTROL; + + if ((IoCode1 & mask) == 0) + { + char buffer[1024] = WINPR_C_ARRAY_INIT; + WLog_Print(context->priv->log, WLOG_ERROR, "Missing IRP mask values %s", + rdpdr_irp_mask2str(IoCode1 & mask, buffer, sizeof(buffer))); + return ERROR_INVALID_DATA; + } + context->priv->IoCode1 = IoCode1; + + if (IoCode2 != 0) + { + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.2.7.1 General Capability Set (GENERAL_CAPS_SET) ioCode2 is " + "reserved for future use, expected value 0 got %" PRIu32, + IoCode2); + } + + if ((ExtendedPdu & RDPDR_CLIENT_DISPLAY_NAME_PDU) == 0) + { + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.2.7.1 General Capability Set (GENERAL_CAPS_SET) extendedPDU " + "should always set RDPDR_CLIENT_DISPLAY_NAME_PDU[0x00000002]"); + } + + context->priv->ExtendedPDU = ExtendedPdu; + context->priv->UserLoggedOnPdu = (ExtendedPdu & RDPDR_USER_LOGGEDON_PDU) != 0; + + if (ExtraFlags2 != 0) + { + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.2.7.1 General Capability Set (GENERAL_CAPS_SET) ExtraFlags2 is " + "reserved for future use, expected value 0 got %" PRIu32, + ExtraFlags2); + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_write_general_capability_set(RdpdrServerContext* context, wStream* s) +{ + UINT32 ioCode1 = 0; + UINT32 extendedPdu = 0; + UINT32 extraFlags1 = 0; + UINT32 SpecialTypeDeviceCap = 0; + const RDPDR_CAPABILITY_HEADER header = { CAP_GENERAL_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH + 36, + GENERAL_CAPABILITY_VERSION_02 }; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + ioCode1 = 0; + ioCode1 |= RDPDR_IRP_MJ_CREATE; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_CLEANUP; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_CLOSE; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_READ; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_WRITE; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_FLUSH_BUFFERS; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_SHUTDOWN; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_DEVICE_CONTROL; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_QUERY_VOLUME_INFORMATION; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_SET_VOLUME_INFORMATION; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_QUERY_INFORMATION; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_SET_INFORMATION; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_DIRECTORY_CONTROL; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_LOCK_CONTROL; /* always set */ + ioCode1 |= RDPDR_IRP_MJ_QUERY_SECURITY; /* optional */ + ioCode1 |= RDPDR_IRP_MJ_SET_SECURITY; /* optional */ + extendedPdu = 0; + extendedPdu |= RDPDR_CLIENT_DISPLAY_NAME_PDU; /* always set */ + extendedPdu |= RDPDR_DEVICE_REMOVE_PDUS; /* optional */ + + if (context->priv->UserLoggedOnPdu) + extendedPdu |= RDPDR_USER_LOGGEDON_PDU; /* optional */ + + extraFlags1 = 0; + extraFlags1 |= ENABLE_ASYNCIO; /* optional */ + SpecialTypeDeviceCap = 0; + + UINT error = rdpdr_write_capset_header(context->priv->log, s, &header); + if (error != CHANNEL_RC_OK) + return error; + + const BYTE* data = Stream_ConstPointer(s); + const size_t start = Stream_GetPosition(s); + Stream_Write_UINT32(s, 0); /* osType (4 bytes), ignored on receipt */ + Stream_Write_UINT32(s, 0); /* osVersion (4 bytes), unused and must be set to zero */ + Stream_Write_UINT16(s, context->priv->VersionMajor); /* protocolMajorVersion (2 bytes) */ + Stream_Write_UINT16(s, context->priv->VersionMinor); /* protocolMinorVersion (2 bytes) */ + Stream_Write_UINT32(s, ioCode1); /* ioCode1 (4 bytes) */ + Stream_Write_UINT32(s, 0); /* ioCode2 (4 bytes), must be set to zero, reserved for future use */ + Stream_Write_UINT32(s, extendedPdu); /* extendedPdu (4 bytes) */ + Stream_Write_UINT32(s, extraFlags1); /* extraFlags1 (4 bytes) */ + Stream_Write_UINT32( + s, 0); /* extraFlags2 (4 bytes), must be set to zero, reserved for future use */ + Stream_Write_UINT32(s, SpecialTypeDeviceCap); /* SpecialTypeDeviceCap (4 bytes) */ + const size_t end = Stream_GetPosition(s); + return IFCALLRESULT(CHANNEL_RC_OK, context->SendCaps, context, &header, end - start, data); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_read_printer_capability_set(RdpdrServerContext* context, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(s); + WINPR_ASSERT(header); + WINPR_UNUSED(context); + WINPR_UNUSED(header); + WINPR_UNUSED(s); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_write_printer_capability_set(RdpdrServerContext* context, wStream* s) +{ + const RDPDR_CAPABILITY_HEADER header = { CAP_PRINTER_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH, + PRINT_CAPABILITY_VERSION_01 }; + WINPR_UNUSED(context); + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + return rdpdr_server_write_capability_set_header_cb(context, s, &header); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_read_port_capability_set(RdpdrServerContext* context, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_UNUSED(context); + WINPR_UNUSED(s); + WINPR_UNUSED(header); + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_write_port_capability_set(RdpdrServerContext* context, wStream* s) +{ + const RDPDR_CAPABILITY_HEADER header = { CAP_PORT_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH, + PORT_CAPABILITY_VERSION_01 }; + WINPR_UNUSED(context); + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + return rdpdr_server_write_capability_set_header_cb(context, s, &header); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_read_drive_capability_set(RdpdrServerContext* context, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_UNUSED(context); + WINPR_UNUSED(header); + WINPR_UNUSED(s); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_write_drive_capability_set(RdpdrServerContext* context, wStream* s) +{ + const RDPDR_CAPABILITY_HEADER header = { CAP_DRIVE_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH, + DRIVE_CAPABILITY_VERSION_02 }; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_UNUSED(context); + + return rdpdr_server_write_capability_set_header_cb(context, s, &header); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_read_smartcard_capability_set(RdpdrServerContext* context, wStream* s, + const RDPDR_CAPABILITY_HEADER* header) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_UNUSED(context); + WINPR_UNUSED(header); + WINPR_UNUSED(s); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_write_smartcard_capability_set(RdpdrServerContext* context, wStream* s) +{ + const RDPDR_CAPABILITY_HEADER header = { CAP_SMARTCARD_TYPE, RDPDR_CAPABILITY_HEADER_LENGTH, + SMARTCARD_CAPABILITY_VERSION_01 }; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + WINPR_UNUSED(context); + + return rdpdr_server_write_capability_set_header_cb(context, s, &header); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_core_capability_request(RdpdrServerContext* context) +{ + wStream* s = nullptr; + RDPDR_HEADER header = WINPR_C_ARRAY_INIT; + UINT16 numCapabilities = 0; + UINT error = 0; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + header.Component = RDPDR_CTYP_CORE; + header.PacketId = PAKID_CORE_SERVER_CAPABILITY; + numCapabilities = 1; + + if ((context->supported & RDPDR_DTYP_FILESYSTEM) != 0) + numCapabilities++; + + if (((context->supported & RDPDR_DTYP_PARALLEL) != 0) || + ((context->supported & RDPDR_DTYP_SERIAL) != 0)) + numCapabilities++; + + if ((context->supported & RDPDR_DTYP_PRINT) != 0) + numCapabilities++; + + if ((context->supported & RDPDR_DTYP_SMARTCARD) != 0) + numCapabilities++; + + s = Stream_New(nullptr, RDPDR_HEADER_LENGTH + 512); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, header.Component); /* Component (2 bytes) */ + Stream_Write_UINT16(s, header.PacketId); /* PacketId (2 bytes) */ + Stream_Write_UINT16(s, numCapabilities); /* numCapabilities (2 bytes) */ + Stream_Write_UINT16(s, 0); /* Padding (2 bytes) */ + + if ((error = rdpdr_server_write_general_capability_set(context, s))) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpdr_server_write_general_capability_set failed with error %" PRIu32 "!", + error); + goto out; + } + + if ((context->supported & RDPDR_DTYP_FILESYSTEM) != 0) + { + if ((error = rdpdr_server_write_drive_capability_set(context, s))) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpdr_server_write_drive_capability_set failed with error %" PRIu32 "!", + error); + goto out; + } + } + + if (((context->supported & RDPDR_DTYP_PARALLEL) != 0) || + ((context->supported & RDPDR_DTYP_SERIAL) != 0)) + { + if ((error = rdpdr_server_write_port_capability_set(context, s))) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpdr_server_write_port_capability_set failed with error %" PRIu32 "!", + error); + goto out; + } + } + + if ((context->supported & RDPDR_DTYP_PRINT) != 0) + { + if ((error = rdpdr_server_write_printer_capability_set(context, s))) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpdr_server_write_printer_capability_set failed with error %" PRIu32 "!", + error); + goto out; + } + } + + if ((context->supported & RDPDR_DTYP_SMARTCARD) != 0) + { + if ((error = rdpdr_server_write_smartcard_capability_set(context, s))) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpdr_server_write_printer_capability_set failed with error %" PRIu32 "!", + error); + goto out; + } + } + + return rdpdr_seal_send_free_request(context, s); +out: + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_receive_core_capability_response(RdpdrServerContext* context, wStream* s, + const RDPDR_HEADER* header) +{ + UINT status = 0; + UINT16 numCapabilities = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + WINPR_UNUSED(header); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, numCapabilities); /* numCapabilities (2 bytes) */ + Stream_Seek_UINT16(s); /* Padding (2 bytes) */ + + UINT16 caps = 0; + for (UINT16 i = 0; i < numCapabilities; i++) + { + RDPDR_CAPABILITY_HEADER capabilityHeader = WINPR_C_ARRAY_INIT; + const size_t start = Stream_GetPosition(s); + + if ((status = rdpdr_read_capset_header(context->priv->log, s, &capabilityHeader))) + { + WLog_Print(context->priv->log, WLOG_ERROR, "failed with error %" PRIu32 "!", status); + return status; + } + + status = IFCALLRESULT(CHANNEL_RC_OK, context->ReceiveCaps, context, &capabilityHeader, + Stream_GetRemainingLength(s), Stream_ConstPointer(s)); + if (status != CHANNEL_RC_OK) + return status; + + caps |= capabilityHeader.CapabilityType; + switch (capabilityHeader.CapabilityType) + { + case CAP_GENERAL_TYPE: + if ((status = + rdpdr_server_read_general_capability_set(context, s, &capabilityHeader))) + { + WLog_Print(context->priv->log, WLOG_ERROR, "failed with error %" PRIu32 "!", + status); + return status; + } + + break; + + case CAP_PRINTER_TYPE: + if ((status = + rdpdr_server_read_printer_capability_set(context, s, &capabilityHeader))) + { + WLog_Print(context->priv->log, WLOG_ERROR, "failed with error %" PRIu32 "!", + status); + return status; + } + + break; + + case CAP_PORT_TYPE: + if ((status = rdpdr_server_read_port_capability_set(context, s, &capabilityHeader))) + { + WLog_Print(context->priv->log, WLOG_ERROR, "failed with error %" PRIu32 "!", + status); + return status; + } + + break; + + case CAP_DRIVE_TYPE: + if ((status = + rdpdr_server_read_drive_capability_set(context, s, &capabilityHeader))) + { + WLog_Print(context->priv->log, WLOG_ERROR, "failed with error %" PRIu32 "!", + status); + return status; + } + + break; + + case CAP_SMARTCARD_TYPE: + if ((status = + rdpdr_server_read_smartcard_capability_set(context, s, &capabilityHeader))) + { + WLog_Print(context->priv->log, WLOG_ERROR, "failed with error %" PRIu32 "!", + status); + return status; + } + + break; + + default: + WLog_Print(context->priv->log, WLOG_WARN, "Unknown capabilityType %" PRIu16 "", + capabilityHeader.CapabilityType); + Stream_Seek(s, capabilityHeader.CapabilityLength); + return ERROR_INVALID_DATA; + } + + for (UINT16 x = 0; x < 16; x++) + { + const UINT16 mask = (UINT16)(1 << x); + if (((caps & mask) != 0) && ((context->supported & mask) == 0)) + { + WLog_Print(context->priv->log, WLOG_WARN, + "client sent capability %s we did not announce!", + freerdp_rdpdr_dtyp_string(mask)); + } + + /* we assume the server supports the capability. so only deactivate what the client did + * not respond with */ + if ((caps & mask) == 0) + context->supported &= ~mask; + } + + const size_t end = Stream_GetPosition(s); + const size_t diff = end - start; + if (diff != capabilityHeader.CapabilityLength + RDPDR_CAPABILITY_HEADER_LENGTH) + { + WLog_Print(context->priv->log, WLOG_WARN, + "{capability %s[0x%04" PRIx16 "]} processed %" PRIuz + " bytes, but expected to be %" PRIu16, + rdpdr_cap_type_string(capabilityHeader.CapabilityType), + capabilityHeader.CapabilityType, diff, capabilityHeader.CapabilityLength); + } + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_client_id_confirm(RdpdrServerContext* context) +{ + wStream* s = nullptr; + RDPDR_HEADER header = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + header.Component = RDPDR_CTYP_CORE; + header.PacketId = PAKID_CORE_CLIENTID_CONFIRM; + s = Stream_New(nullptr, RDPDR_HEADER_LENGTH + 8); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, header.Component); /* Component (2 bytes) */ + Stream_Write_UINT16(s, header.PacketId); /* PacketId (2 bytes) */ + Stream_Write_UINT16(s, context->priv->VersionMajor); /* VersionMajor (2 bytes) */ + Stream_Write_UINT16(s, context->priv->VersionMinor); /* VersionMinor (2 bytes) */ + Stream_Write_UINT32(s, context->priv->ClientId); /* ClientId (4 bytes) */ + return rdpdr_seal_send_free_request(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_receive_device_list_announce_request(RdpdrServerContext* context, + wStream* s, + const RDPDR_HEADER* header) +{ + UINT32 DeviceCount = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + WINPR_UNUSED(header); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, DeviceCount); /* DeviceCount (4 bytes) */ + WLog_Print(context->priv->log, WLOG_DEBUG, "DeviceCount: %" PRIu32 "", DeviceCount); + + for (UINT32 i = 0; i < DeviceCount; i++) + { + UINT error = 0; + RdpdrDevice device = WINPR_C_ARRAY_INIT; + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 20)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, device.DeviceType); /* DeviceType (4 bytes) */ + Stream_Read_UINT32(s, device.DeviceId); /* DeviceId (4 bytes) */ + Stream_Read(s, device.PreferredDosName, 8); /* PreferredDosName (8 bytes) */ + Stream_Read_UINT32(s, device.DeviceDataLength); /* DeviceDataLength (4 bytes) */ + device.DeviceData = Stream_Pointer(s); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, device.DeviceDataLength)) + return ERROR_INVALID_DATA; + + if (!rdpdr_add_device(context->priv, &device)) + return ERROR_INTERNAL_ERROR; + + error = IFCALLRESULT(CHANNEL_RC_OK, context->ReceiveDeviceAnnounce, context, &device); + if (error != CHANNEL_RC_OK) + return error; + + switch (device.DeviceType) + { + case RDPDR_DTYP_FILESYSTEM: + if ((context->supported & RDPDR_DTYP_FILESYSTEM) != 0) + error = IFCALLRESULT(CHANNEL_RC_OK, context->OnDriveCreate, context, &device); + break; + + case RDPDR_DTYP_PRINT: + if ((context->supported & RDPDR_DTYP_PRINT) != 0) + error = IFCALLRESULT(CHANNEL_RC_OK, context->OnPrinterCreate, context, &device); + break; + + case RDPDR_DTYP_SERIAL: + if (device.DeviceDataLength != 0) + { + WLog_Print(context->priv->log, WLOG_WARN, + "[rdpdr] RDPDR_DTYP_SERIAL::DeviceDataLength != 0 [%" PRIu32 "]", + device.DeviceDataLength); + error = ERROR_INVALID_DATA; + } + else if ((context->supported & RDPDR_DTYP_SERIAL) != 0) + error = + IFCALLRESULT(CHANNEL_RC_OK, context->OnSerialPortCreate, context, &device); + break; + + case RDPDR_DTYP_PARALLEL: + if (device.DeviceDataLength != 0) + { + WLog_Print(context->priv->log, WLOG_WARN, + "[rdpdr] RDPDR_DTYP_PARALLEL::DeviceDataLength != 0 [%" PRIu32 "]", + device.DeviceDataLength); + error = ERROR_INVALID_DATA; + } + else if ((context->supported & RDPDR_DTYP_PARALLEL) != 0) + error = IFCALLRESULT(CHANNEL_RC_OK, context->OnParallelPortCreate, context, + &device); + break; + + case RDPDR_DTYP_SMARTCARD: + if (device.DeviceDataLength != 0) + { + WLog_Print(context->priv->log, WLOG_WARN, + "[rdpdr] RDPDR_DTYP_SMARTCARD::DeviceDataLength != 0 [%" PRIu32 "]", + device.DeviceDataLength); + error = ERROR_INVALID_DATA; + } + else if ((context->supported & RDPDR_DTYP_SMARTCARD) != 0) + error = + IFCALLRESULT(CHANNEL_RC_OK, context->OnSmartcardCreate, context, &device); + break; + + default: + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.2.9 Client Device List Announce Request " + "(DR_CORE_DEVICELIST_ANNOUNCE_REQ) unknown device type %04" PRIx16 + " at position %" PRIu32, + device.DeviceType, i); + error = ERROR_INVALID_DATA; + break; + } + + if (error != CHANNEL_RC_OK) + return error; + + Stream_Seek(s, device.DeviceDataLength); + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_receive_device_list_remove_request(RdpdrServerContext* context, wStream* s, + const RDPDR_HEADER* header) +{ + UINT32 DeviceCount = 0; + UINT32 DeviceType = 0; + UINT32 DeviceId = 0; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + WINPR_UNUSED(header); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, DeviceCount); /* DeviceCount (4 bytes) */ + WLog_Print(context->priv->log, WLOG_DEBUG, "DeviceCount: %" PRIu32 "", DeviceCount); + + for (UINT32 i = 0; i < DeviceCount; i++) + { + UINT error = 0; + const RdpdrDevice* device = nullptr; + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, DeviceId); /* DeviceId (4 bytes) */ + device = rdpdr_get_device_by_id(context->priv, DeviceId); + WLog_Print(context->priv->log, WLOG_DEBUG, "Device %" PRIu32 " Id: 0x%08" PRIX32 "", i, + DeviceId); + DeviceType = 0; + if (device) + DeviceType = device->DeviceType; + + error = + IFCALLRESULT(CHANNEL_RC_OK, context->ReceiveDeviceRemove, context, DeviceId, device); + if (error != CHANNEL_RC_OK) + return error; + + switch (DeviceType) + { + case RDPDR_DTYP_FILESYSTEM: + if ((context->supported & RDPDR_DTYP_FILESYSTEM) != 0) + error = IFCALLRESULT(CHANNEL_RC_OK, context->OnDriveDelete, context, DeviceId); + break; + + case RDPDR_DTYP_PRINT: + if ((context->supported & RDPDR_DTYP_PRINT) != 0) + error = + IFCALLRESULT(CHANNEL_RC_OK, context->OnPrinterDelete, context, DeviceId); + break; + + case RDPDR_DTYP_SERIAL: + if ((context->supported & RDPDR_DTYP_SERIAL) != 0) + error = + IFCALLRESULT(CHANNEL_RC_OK, context->OnSerialPortDelete, context, DeviceId); + break; + + case RDPDR_DTYP_PARALLEL: + if ((context->supported & RDPDR_DTYP_PARALLEL) != 0) + error = IFCALLRESULT(CHANNEL_RC_OK, context->OnParallelPortDelete, context, + DeviceId); + break; + + case RDPDR_DTYP_SMARTCARD: + if ((context->supported & RDPDR_DTYP_SMARTCARD) != 0) + error = + IFCALLRESULT(CHANNEL_RC_OK, context->OnSmartcardDelete, context, DeviceId); + break; + + default: + break; + } + + if (error != CHANNEL_RC_OK) + return error; + + if (!rdpdr_remove_device_by_id(context->priv, DeviceId)) + return ERROR_INVALID_DATA; + } + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_io_create_request(RdpdrServerContext* context, wStream* s, + WINPR_ATTR_UNUSED UINT32 DeviceId, + WINPR_ATTR_UNUSED UINT32 FileId, + WINPR_ATTR_UNUSED UINT32 CompletionId) +{ + const WCHAR* path = nullptr; + UINT32 DesiredAccess = 0; + UINT32 AllocationSize = 0; + UINT32 FileAttributes = 0; + UINT32 SharedAccess = 0; + UINT32 CreateDisposition = 0; + UINT32 CreateOptions = 0; + UINT32 PathLength = 0; + + WINPR_ASSERT(context); + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, DesiredAccess); + Stream_Read_UINT32(s, AllocationSize); + Stream_Read_UINT32(s, FileAttributes); + Stream_Read_UINT32(s, SharedAccess); + Stream_Read_UINT32(s, CreateDisposition); + Stream_Read_UINT32(s, CreateOptions); + Stream_Read_UINT32(s, PathLength); + + path = rdpdr_read_ustring(context->priv->log, s, PathLength); + if (!path && (PathLength > 0)) + return ERROR_INVALID_DATA; + + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.1.4.1 Device Create Request (DR_CREATE_REQ) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO"); + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_io_close_request(RdpdrServerContext* context, wStream* s, + WINPR_ATTR_UNUSED UINT32 DeviceId, + WINPR_ATTR_UNUSED UINT32 FileId, + WINPR_ATTR_UNUSED UINT32 CompletionId) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32)) + return ERROR_INVALID_DATA; + + Stream_Seek(s, 32); /* Padding */ + + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.1.4.2 Device Close Request (DR_CLOSE_REQ) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO"); + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_io_read_request(RdpdrServerContext* context, wStream* s, + WINPR_ATTR_UNUSED UINT32 DeviceId, + WINPR_ATTR_UNUSED UINT32 FileId, + WINPR_ATTR_UNUSED UINT32 CompletionId) +{ + UINT32 Length = 0; + UINT64 Offset = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, Length); + Stream_Read_UINT64(s, Offset); + Stream_Seek(s, 20); /* Padding */ + + WLog_Print(context->priv->log, WLOG_DEBUG, "Got Offset [0x%016" PRIx64 "], Length %" PRIu32, + Offset, Length); + + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.1.4.3 Device Read Request (DR_READ_REQ) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO"); + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_io_write_request(RdpdrServerContext* context, wStream* s, + WINPR_ATTR_UNUSED UINT32 DeviceId, + WINPR_ATTR_UNUSED UINT32 FileId, + WINPR_ATTR_UNUSED UINT32 CompletionId) +{ + UINT32 Length = 0; + UINT64 Offset = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, Length); + Stream_Read_UINT64(s, Offset); + Stream_Seek(s, 20); /* Padding */ + + WLog_Print(context->priv->log, WLOG_DEBUG, "Got Offset [0x%016" PRIx64 "], Length %" PRIu32, + Offset, Length); + + const BYTE* data = Stream_ConstPointer(s); + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, Length)) + return ERROR_INVALID_DATA; + Stream_Seek(s, Length); + + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.1.4.4 Device Write Request (DR_WRITE_REQ) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO: parse %p", (const void*)data); + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_io_device_control_request(RdpdrServerContext* context, wStream* s, + WINPR_ATTR_UNUSED UINT32 DeviceId, + WINPR_ATTR_UNUSED UINT32 FileId, + WINPR_ATTR_UNUSED UINT32 CompletionId) +{ + UINT32 OutputBufferLength = 0; + UINT32 InputBufferLength = 0; + UINT32 IoControlCode = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, OutputBufferLength); + Stream_Read_UINT32(s, InputBufferLength); + Stream_Read_UINT32(s, IoControlCode); + Stream_Seek(s, 20); /* Padding */ + + const BYTE* InputBuffer = Stream_ConstPointer(s); + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, InputBufferLength)) + return ERROR_INVALID_DATA; + Stream_Seek(s, InputBufferLength); + + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.1.4.5 Device Control Request (DR_CONTROL_REQ) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, + "TODO: parse %p [%" PRIu32 "], OutputBufferLength=%" PRIu32, + (const void*)InputBuffer, InputBufferLength, OutputBufferLength); + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_io_query_volume_information_request( + RdpdrServerContext* context, wStream* s, WINPR_ATTR_UNUSED UINT32 DeviceId, + WINPR_ATTR_UNUSED UINT32 FileId, WINPR_ATTR_UNUSED UINT32 CompletionId) +{ + UINT32 FsInformationClass = 0; + UINT32 Length = 0; + + WINPR_ASSERT(context); + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, FsInformationClass); + Stream_Read_UINT32(s, Length); + Stream_Seek(s, 24); /* Padding */ + + WLog_Print(context->priv->log, WLOG_DEBUG, + "Got FSInformationClass %s [0x%08" PRIx32 "], Length %" PRIu32, + FSInformationClass2Tag(FsInformationClass), FsInformationClass, Length); + + const BYTE* QueryVolumeBuffer = Stream_ConstPointer(s); + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, Length)) + return ERROR_INVALID_DATA; + Stream_Seek(s, Length); + + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.3.3.6 Server Drive Query Volume Information Request " + "(DR_DRIVE_QUERY_VOLUME_INFORMATION_REQ) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO: parse %p", (const void*)QueryVolumeBuffer); + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_io_set_volume_information_request( + RdpdrServerContext* context, wStream* s, WINPR_ATTR_UNUSED UINT32 DeviceId, + WINPR_ATTR_UNUSED UINT32 FileId, WINPR_ATTR_UNUSED UINT32 CompletionId) +{ + UINT32 FsInformationClass = 0; + UINT32 Length = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, FsInformationClass); + Stream_Read_UINT32(s, Length); + Stream_Seek(s, 24); /* Padding */ + + WLog_Print(context->priv->log, WLOG_DEBUG, + "Got FSInformationClass %s [0x%08" PRIx32 "], Length %" PRIu32, + FSInformationClass2Tag(FsInformationClass), FsInformationClass, Length); + + const BYTE* SetVolumeBuffer = Stream_ConstPointer(s); + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, Length)) + return ERROR_INVALID_DATA; + Stream_Seek(s, Length); + + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.3.3.7 Server Drive Set Volume Information Request " + "(DR_DRIVE_SET_VOLUME_INFORMATION_REQ) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO: parse %p", (const void*)SetVolumeBuffer); + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_io_query_information_request(RdpdrServerContext* context, + wStream* s, + WINPR_ATTR_UNUSED UINT32 DeviceId, + WINPR_ATTR_UNUSED UINT32 FileId, + WINPR_ATTR_UNUSED UINT32 CompletionId) +{ + UINT32 FsInformationClass = 0; + UINT32 Length = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, FsInformationClass); + Stream_Read_UINT32(s, Length); + Stream_Seek(s, 24); /* Padding */ + + WLog_Print(context->priv->log, WLOG_DEBUG, + "Got FSInformationClass %s [0x%08" PRIx32 "], Length %" PRIu32, + FSInformationClass2Tag(FsInformationClass), FsInformationClass, Length); + + const BYTE* QueryBuffer = Stream_ConstPointer(s); + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, Length)) + return ERROR_INVALID_DATA; + Stream_Seek(s, Length); + + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.3.3.8 Server Drive Query Information Request " + "(DR_DRIVE_QUERY_INFORMATION_REQ) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO: parse %p", (const void*)QueryBuffer); + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_io_set_information_request(RdpdrServerContext* context, wStream* s, + WINPR_ATTR_UNUSED UINT32 DeviceId, + WINPR_ATTR_UNUSED UINT32 FileId, + WINPR_ATTR_UNUSED UINT32 CompletionId) +{ + UINT32 FsInformationClass = 0; + UINT32 Length = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, FsInformationClass); + Stream_Read_UINT32(s, Length); + Stream_Seek(s, 24); /* Padding */ + + WLog_Print(context->priv->log, WLOG_DEBUG, + "Got FSInformationClass %s [0x%08" PRIx32 "], Length %" PRIu32, + FSInformationClass2Tag(FsInformationClass), FsInformationClass, Length); + + const BYTE* SetBuffer = Stream_ConstPointer(s); + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, Length)) + return ERROR_INVALID_DATA; + Stream_Seek(s, Length); + + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.3.3.9 Server Drive Set Information Request " + "(DR_DRIVE_SET_INFORMATION_REQ) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO: parse %p", (const void*)SetBuffer); + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_io_query_directory_request(RdpdrServerContext* context, wStream* s, + WINPR_ATTR_UNUSED UINT32 DeviceId, + WINPR_ATTR_UNUSED UINT32 FileId, + WINPR_ATTR_UNUSED UINT32 CompletionId) +{ + BYTE InitialQuery = 0; + UINT32 FsInformationClass = 0; + UINT32 PathLength = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, FsInformationClass); + Stream_Read_UINT8(s, InitialQuery); + Stream_Read_UINT32(s, PathLength); + Stream_Seek(s, 23); /* Padding */ + + const WCHAR* wPath = rdpdr_read_ustring(context->priv->log, s, PathLength); + if (!wPath && (PathLength > 0)) + return ERROR_INVALID_DATA; + + char* Path = ConvertWCharNToUtf8Alloc(wPath, PathLength / sizeof(WCHAR), nullptr); + WLog_Print(context->priv->log, WLOG_DEBUG, + "Got FSInformationClass %s [0x%08" PRIx32 "], InitialQuery [%" PRIu8 + "] Path[%" PRIu32 "] %s", + FSInformationClass2Tag(FsInformationClass), FsInformationClass, InitialQuery, + PathLength, Path); + free(Path); + + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.3.3.10 Server Drive Query Directory Request " + "(DR_DRIVE_QUERY_DIRECTORY_REQ) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO"); + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_io_change_directory_request(RdpdrServerContext* context, + wStream* s, + WINPR_ATTR_UNUSED UINT32 DeviceId, + WINPR_ATTR_UNUSED UINT32 FileId, + WINPR_ATTR_UNUSED UINT32 CompletionId) +{ + BYTE WatchTree = 0; + UINT32 CompletionFilter = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT8(s, WatchTree); + Stream_Read_UINT32(s, CompletionFilter); + Stream_Seek(s, 27); /* Padding */ + + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.3.3.11 Server Drive NotifyChange Directory Request " + "(DR_DRIVE_NOTIFY_CHANGE_DIRECTORY_REQ) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO"); + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_io_directory_control_request(RdpdrServerContext* context, + wStream* s, UINT32 DeviceId, + UINT32 FileId, UINT32 CompletionId, + UINT32 MinorFunction) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + switch (MinorFunction) + { + case IRP_MN_QUERY_DIRECTORY: + return rdpdr_server_receive_io_query_directory_request(context, s, DeviceId, FileId, + CompletionId); + case IRP_MN_NOTIFY_CHANGE_DIRECTORY: + return rdpdr_server_receive_io_change_directory_request(context, s, DeviceId, FileId, + CompletionId); + default: + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.1.4 Device I/O Request (DR_DEVICE_IOREQUEST) " + "MajorFunction=%s, MinorFunction=%08" PRIx32 " is not supported", + rdpdr_irp_string(IRP_MJ_DIRECTORY_CONTROL), MinorFunction); + return ERROR_INVALID_DATA; + } +} + +static UINT rdpdr_server_receive_io_lock_control_request(RdpdrServerContext* context, wStream* s, + WINPR_ATTR_UNUSED UINT32 DeviceId, + WINPR_ATTR_UNUSED UINT32 FileId, + WINPR_ATTR_UNUSED UINT32 CompletionId) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 32)) + return ERROR_INVALID_DATA; + + const uint32_t Operation = Stream_Get_UINT32(s); + uint32_t Lock = Stream_Get_UINT32(s); + const uint32_t NumLocks = Stream_Get_UINT32(s); + Stream_Seek(s, 20); /* Padding */ + + WLog_Print(context->priv->log, WLOG_DEBUG, + "IRP_MJ_LOCK_CONTROL, Operation=%s, Lock=0x%08" PRIx32 ", NumLocks=%" PRIu32, + DR_DRIVE_LOCK_REQ2str(Operation), Lock, NumLocks); + + Lock &= 0x01; /* Only bit 0 is of importance */ + + for (UINT32 x = 0; x < NumLocks; x++) + { + UINT64 Length = 0; + UINT64 Offset = 0; + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 16)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT64(s, Length); + Stream_Read_UINT64(s, Offset); + + WLog_Print(context->priv->log, WLOG_DEBUG, + "Locking at Offset=0x%08" PRIx64 " [Length %" PRIu64 "]", Offset, Length); + } + + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.3.3.12 Server Drive Lock Control Request (DR_DRIVE_LOCK_REQ) " + "[Lock=0x%08" PRIx32 "]" + "not implemented", + Lock); + WLog_Print(context->priv->log, WLOG_WARN, "TODO"); + + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_device_io_request(RdpdrServerContext* context, wStream* s, + WINPR_ATTR_UNUSED const RDPDR_HEADER* header) +{ + UINT32 DeviceId = 0; + UINT32 FileId = 0; + UINT32 CompletionId = 0; + UINT32 MajorFunction = 0; + UINT32 MinorFunction = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(header); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 20)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, DeviceId); + Stream_Read_UINT32(s, FileId); + Stream_Read_UINT32(s, CompletionId); + Stream_Read_UINT32(s, MajorFunction); + Stream_Read_UINT32(s, MinorFunction); + if ((MinorFunction != 0) && (MajorFunction != IRP_MJ_DIRECTORY_CONTROL)) + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.1.4 Device I/O Request (DR_DEVICE_IOREQUEST) MajorFunction=%s, " + "MinorFunction=0x%08" PRIx32 " != 0", + rdpdr_irp_string(MajorFunction), MinorFunction); + + switch (MajorFunction) + { + case IRP_MJ_CREATE: + return rdpdr_server_receive_io_create_request(context, s, DeviceId, FileId, + CompletionId); + case IRP_MJ_CLOSE: + return rdpdr_server_receive_io_close_request(context, s, DeviceId, FileId, + CompletionId); + case IRP_MJ_READ: + return rdpdr_server_receive_io_read_request(context, s, DeviceId, FileId, CompletionId); + case IRP_MJ_WRITE: + return rdpdr_server_receive_io_write_request(context, s, DeviceId, FileId, + CompletionId); + case IRP_MJ_DEVICE_CONTROL: + return rdpdr_server_receive_io_device_control_request(context, s, DeviceId, FileId, + CompletionId); + case IRP_MJ_QUERY_VOLUME_INFORMATION: + return rdpdr_server_receive_io_query_volume_information_request(context, s, DeviceId, + FileId, CompletionId); + case IRP_MJ_QUERY_INFORMATION: + return rdpdr_server_receive_io_query_information_request(context, s, DeviceId, FileId, + CompletionId); + case IRP_MJ_SET_INFORMATION: + return rdpdr_server_receive_io_set_information_request(context, s, DeviceId, FileId, + CompletionId); + case IRP_MJ_DIRECTORY_CONTROL: + return rdpdr_server_receive_io_directory_control_request(context, s, DeviceId, FileId, + CompletionId, MinorFunction); + case IRP_MJ_LOCK_CONTROL: + return rdpdr_server_receive_io_lock_control_request(context, s, DeviceId, FileId, + CompletionId); + case IRP_MJ_SET_VOLUME_INFORMATION: + return rdpdr_server_receive_io_set_volume_information_request(context, s, DeviceId, + FileId, CompletionId); + + default: + WLog_Print( + context->priv->log, WLOG_WARN, + "[MS-RDPEFS] 2.2.1.4 Device I/O Request (DR_DEVICE_IOREQUEST) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, + "got DeviceId=0x%08" PRIx32 ", FileId=0x%08" PRIx32 + ", CompletionId=0x%08" PRIx32 ", MajorFunction=0x%08" PRIx32 + ", MinorFunction=0x%08" PRIx32, + DeviceId, FileId, CompletionId, MajorFunction, MinorFunction); + return ERROR_INVALID_DATA; + } +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_receive_device_io_completion(RdpdrServerContext* context, wStream* s, + const RDPDR_HEADER* header) +{ + UINT32 deviceId = 0; + UINT32 completionId = 0; + UINT32 ioStatus = 0; + RDPDR_IRP* irp = nullptr; + UINT error = CHANNEL_RC_OK; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(s); + WINPR_ASSERT(header); + + WINPR_UNUSED(header); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 12)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, deviceId); + Stream_Read_UINT32(s, completionId); + Stream_Read_UINT32(s, ioStatus); + WLog_Print(context->priv->log, WLOG_DEBUG, + "deviceId=%" PRIu32 ", completionId=0x%" PRIx32 ", ioStatus=0x%" PRIx32 "", deviceId, + completionId, ioStatus); + irp = rdpdr_server_dequeue_irp(context, completionId); + + if (!irp) + { + WLog_Print(context->priv->log, WLOG_ERROR, "IRP not found for completionId=0x%" PRIx32 "", + completionId); + return CHANNEL_RC_OK; + } + + /* Invoke the callback. */ + if (irp->Callback) + { + error = (*irp->Callback)(context, s, irp, deviceId, completionId, ioStatus); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_user_logged_on(RdpdrServerContext* context) +{ + wStream* s = nullptr; + RDPDR_HEADER header = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + header.Component = RDPDR_CTYP_CORE; + header.PacketId = PAKID_CORE_USER_LOGGEDON; + s = Stream_New(nullptr, RDPDR_HEADER_LENGTH); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, header.Component); /* Component (2 bytes) */ + Stream_Write_UINT16(s, header.PacketId); /* PacketId (2 bytes) */ + return rdpdr_seal_send_free_request(context, s); +} + +static UINT rdpdr_server_receive_prn_cache_add_printer(RdpdrServerContext* context, wStream* s) +{ + char PortDosName[9] = WINPR_C_ARRAY_INIT; + UINT32 PnPNameLen = 0; + UINT32 DriverNameLen = 0; + UINT32 PrinterNameLen = 0; + UINT32 CachedFieldsLen = 0; + const WCHAR* PnPName = nullptr; + const WCHAR* DriverName = nullptr; + const WCHAR* PrinterName = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 24)) + return ERROR_INVALID_DATA; + + Stream_Read(s, PortDosName, 8); + Stream_Read_UINT32(s, PnPNameLen); + Stream_Read_UINT32(s, DriverNameLen); + Stream_Read_UINT32(s, PrinterNameLen); + Stream_Read_UINT32(s, CachedFieldsLen); + + PnPName = rdpdr_read_ustring(context->priv->log, s, PnPNameLen); + if (!PnPName && (PnPNameLen > 0)) + return ERROR_INVALID_DATA; + DriverName = rdpdr_read_ustring(context->priv->log, s, DriverNameLen); + if (!DriverName && (DriverNameLen > 0)) + return ERROR_INVALID_DATA; + PrinterName = rdpdr_read_ustring(context->priv->log, s, PrinterNameLen); + if (!PrinterName && (PrinterNameLen > 0)) + return ERROR_INVALID_DATA; + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, CachedFieldsLen)) + return ERROR_INVALID_DATA; + Stream_Seek(s, CachedFieldsLen); + + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEPC] 2.2.2.3 Add Printer Cachedata (DR_PRN_ADD_CACHEDATA) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO"); + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_prn_cache_update_printer(RdpdrServerContext* context, wStream* s) +{ + UINT32 PrinterNameLen = 0; + UINT32 CachedFieldsLen = 0; + const WCHAR* PrinterName = nullptr; + + WINPR_ASSERT(context); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, PrinterNameLen); + Stream_Read_UINT32(s, CachedFieldsLen); + + PrinterName = rdpdr_read_ustring(context->priv->log, s, PrinterNameLen); + if (!PrinterName && (PrinterNameLen > 0)) + return ERROR_INVALID_DATA; + + const BYTE* config = Stream_ConstPointer(s); + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, CachedFieldsLen)) + return ERROR_INVALID_DATA; + Stream_Seek(s, CachedFieldsLen); + + WLog_Print( + context->priv->log, WLOG_WARN, + "[MS-RDPEPC] 2.2.2.4 Update Printer Cachedata (DR_PRN_UPDATE_CACHEDATA) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO: parse %p", (const void*)config); + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_prn_cache_delete_printer(RdpdrServerContext* context, wStream* s) +{ + UINT32 PrinterNameLen = 0; + const WCHAR* PrinterName = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, PrinterNameLen); + + PrinterName = rdpdr_read_ustring(context->priv->log, s, PrinterNameLen); + if (!PrinterName && (PrinterNameLen > 0)) + return ERROR_INVALID_DATA; + + WLog_Print( + context->priv->log, WLOG_WARN, + "[MS-RDPEPC] 2.2.2.5 Delete Printer Cachedata (DR_PRN_DELETE_CACHEDATA) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO"); + return CHANNEL_RC_OK; +} + +static UINT rdpdr_server_receive_prn_cache_rename_cachedata(RdpdrServerContext* context, wStream* s) +{ + UINT32 OldPrinterNameLen = 0; + UINT32 NewPrinterNameLen = 0; + const WCHAR* OldPrinterName = nullptr; + const WCHAR* NewPrinterName = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, OldPrinterNameLen); + Stream_Read_UINT32(s, NewPrinterNameLen); + + OldPrinterName = rdpdr_read_ustring(context->priv->log, s, OldPrinterNameLen); + if (!OldPrinterName && (OldPrinterNameLen > 0)) + return ERROR_INVALID_DATA; + NewPrinterName = rdpdr_read_ustring(context->priv->log, s, NewPrinterNameLen); + if (!NewPrinterName && (NewPrinterNameLen > 0)) + return ERROR_INVALID_DATA; + + WLog_Print( + context->priv->log, WLOG_WARN, + "[MS-RDPEPC] 2.2.2.6 Rename Printer Cachedata (DR_PRN_RENAME_CACHEDATA) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "TODO"); + return CHANNEL_RC_OK; +} + +static UINT +rdpdr_server_receive_prn_cache_data_request(RdpdrServerContext* context, wStream* s, + WINPR_ATTR_UNUSED const RDPDR_HEADER* header) +{ + UINT32 EventId = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(header); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, EventId); + switch (EventId) + { + case RDPDR_ADD_PRINTER_EVENT: + return rdpdr_server_receive_prn_cache_add_printer(context, s); + case RDPDR_UPDATE_PRINTER_EVENT: + return rdpdr_server_receive_prn_cache_update_printer(context, s); + case RDPDR_DELETE_PRINTER_EVENT: + return rdpdr_server_receive_prn_cache_delete_printer(context, s); + case RDPDR_RENAME_PRINTER_EVENT: + return rdpdr_server_receive_prn_cache_rename_cachedata(context, s); + default: + WLog_Print(context->priv->log, WLOG_WARN, + "[MS-RDPEPC] PAKID_PRN_CACHE_DATA unknown EventId=0x%08" PRIx32, EventId); + return ERROR_INVALID_DATA; + } +} + +static UINT rdpdr_server_receive_prn_using_xps_request(RdpdrServerContext* context, wStream* s, + WINPR_ATTR_UNUSED const RDPDR_HEADER* header) +{ + UINT32 PrinterId = 0; + UINT32 Flags = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(header); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, PrinterId); + Stream_Read_UINT32(s, Flags); + + WLog_Print( + context->priv->log, WLOG_WARN, + "[MS-RDPEPC] 2.2.2.2 Server Printer Set XPS Mode (DR_PRN_USING_XPS) not implemented"); + WLog_Print(context->priv->log, WLOG_WARN, "PrinterId=0x%08" PRIx32 ", Flags=0x%08" PRIx32, + PrinterId, Flags); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_receive_pdu(RdpdrServerContext* context, wStream* s, + const RDPDR_HEADER* header) +{ + UINT error = ERROR_INVALID_DATA; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(s); + WINPR_ASSERT(header); + + WLog_Print(context->priv->log, WLOG_DEBUG, + "receiving message {Component %s[%04" PRIx16 "], PacketId %s[%04" PRIx16 "]", + rdpdr_component_string(header->Component), header->Component, + rdpdr_packetid_string(header->PacketId), header->PacketId); + + if (header->Component == RDPDR_CTYP_CORE) + { + switch (header->PacketId) + { + case PAKID_CORE_SERVER_ANNOUNCE: + WLog_Print(context->priv->log, WLOG_ERROR, + "[MS-RDPEFS] 2.2.2.2 Server Announce Request " + "(DR_CORE_SERVER_ANNOUNCE_REQ) must not be sent by a client!"); + break; + + case PAKID_CORE_CLIENTID_CONFIRM: + error = rdpdr_server_receive_announce_response(context, s, header); + break; + + case PAKID_CORE_CLIENT_NAME: + error = rdpdr_server_receive_client_name_request(context, s, header); + if (error == CHANNEL_RC_OK) + error = rdpdr_server_send_core_capability_request(context); + if (error == CHANNEL_RC_OK) + error = rdpdr_server_send_client_id_confirm(context); + break; + + case PAKID_CORE_USER_LOGGEDON: + WLog_Print(context->priv->log, WLOG_ERROR, + "[MS-RDPEFS] 2.2.2.5 Server User Logged On (DR_CORE_USER_LOGGEDON) " + "must not be sent by a client!"); + break; + + case PAKID_CORE_SERVER_CAPABILITY: + WLog_Print(context->priv->log, WLOG_ERROR, + "[MS-RDPEFS] 2.2.2.7 Server Core Capability Request " + "(DR_CORE_CAPABILITY_REQ) must not be sent by a client!"); + break; + + case PAKID_CORE_CLIENT_CAPABILITY: + error = rdpdr_server_receive_core_capability_response(context, s, header); + if (error == CHANNEL_RC_OK) + { + if (context->priv->UserLoggedOnPdu) + error = rdpdr_server_send_user_logged_on(context); + } + + break; + + case PAKID_CORE_DEVICELIST_ANNOUNCE: + error = rdpdr_server_receive_device_list_announce_request(context, s, header); + break; + + case PAKID_CORE_DEVICELIST_REMOVE: + error = rdpdr_server_receive_device_list_remove_request(context, s, header); + break; + + case PAKID_CORE_DEVICE_REPLY: + WLog_Print(context->priv->log, WLOG_ERROR, + "[MS-RDPEFS] 2.2.2.1 Server Device Announce Response " + "(DR_CORE_DEVICE_ANNOUNCE_RSP) must not be sent by a client!"); + break; + + case PAKID_CORE_DEVICE_IOREQUEST: + error = rdpdr_server_receive_device_io_request(context, s, header); + break; + + case PAKID_CORE_DEVICE_IOCOMPLETION: + error = rdpdr_server_receive_device_io_completion(context, s, header); + break; + + default: + WLog_Print(context->priv->log, WLOG_WARN, + "Unknown RDPDR_HEADER.Component: %s [0x%04" PRIx16 "], PacketId: %s", + rdpdr_component_string(header->Component), header->Component, + rdpdr_packetid_string(header->PacketId)); + break; + } + } + else if (header->Component == RDPDR_CTYP_PRN) + { + switch (header->PacketId) + { + case PAKID_PRN_CACHE_DATA: + error = rdpdr_server_receive_prn_cache_data_request(context, s, header); + break; + + case PAKID_PRN_USING_XPS: + error = rdpdr_server_receive_prn_using_xps_request(context, s, header); + break; + + default: + WLog_Print(context->priv->log, WLOG_WARN, + "Unknown RDPDR_HEADER.Component: %s [0x%04" PRIx16 "], PacketId: %s", + rdpdr_component_string(header->Component), header->Component, + rdpdr_packetid_string(header->PacketId)); + break; + } + } + else + { + WLog_Print(context->priv->log, WLOG_WARN, + "Unknown RDPDR_HEADER.Component: %s [0x%04" PRIx16 "], PacketId: %s", + rdpdr_component_string(header->Component), header->Component, + rdpdr_packetid_string(header->PacketId)); + } + + return IFCALLRESULT(error, context->ReceivePDU, context, header, error); +} + +static DWORD WINAPI rdpdr_server_thread(LPVOID arg) +{ + DWORD status = 0; + DWORD nCount = 0; + void* buffer = nullptr; + HANDLE events[8] = WINPR_C_ARRAY_INIT; + HANDLE ChannelEvent = nullptr; + DWORD BytesReturned = 0; + UINT error = 0; + RdpdrServerContext* context = (RdpdrServerContext*)arg; + wStream* s = Stream_New(nullptr, 4096); + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + if (WTSVirtualChannelQuery(context->priv->ChannelHandle, WTSVirtualEventHandle, &buffer, + &BytesReturned) == TRUE) + { + if (BytesReturned == sizeof(HANDLE)) + ChannelEvent = *(HANDLE*)buffer; + + WTSFreeMemory(buffer); + } + + nCount = 0; + events[nCount++] = ChannelEvent; + events[nCount++] = context->priv->StopEvent; + + if ((error = rdpdr_server_send_announce_request(context))) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpdr_server_send_announce_request failed with error %" PRIu32 "!", error); + goto out_stream; + } + + while (1) + { + size_t capacity = 0; + BytesReturned = 0; + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_Print(context->priv->log, WLOG_ERROR, + "WaitForMultipleObjects failed with error %" PRIu32 "!", error); + goto out_stream; + } + + status = WaitForSingleObject(context->priv->StopEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_Print(context->priv->log, WLOG_ERROR, + "WaitForSingleObject failed with error %" PRIu32 "!", error); + goto out_stream; + } + + if (status == WAIT_OBJECT_0) + break; + + if (!WTSVirtualChannelRead(context->priv->ChannelHandle, 0, nullptr, 0, &BytesReturned)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "WTSVirtualChannelRead failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + capacity = MIN(Stream_Capacity(s), UINT32_MAX); + if (!WTSVirtualChannelRead(context->priv->ChannelHandle, 0, Stream_BufferAs(s, char), + (ULONG)capacity, &BytesReturned)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "WTSVirtualChannelRead failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (BytesReturned >= RDPDR_HEADER_LENGTH) + { + Stream_ResetPosition(s); + Stream_SetLength(s, BytesReturned); + + while (Stream_GetRemainingLength(s) >= RDPDR_HEADER_LENGTH) + { + RDPDR_HEADER header = WINPR_C_ARRAY_INIT; + + Stream_Read_UINT16(s, header.Component); /* Component (2 bytes) */ + Stream_Read_UINT16(s, header.PacketId); /* PacketId (2 bytes) */ + + if ((error = rdpdr_server_receive_pdu(context, s, &header))) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpdr_server_receive_pdu failed with error %" PRIu32 "!", error); + goto out_stream; + } + } + } + } + +out_stream: + Stream_Free(s, TRUE); +out: + + if (error && context->rdpcontext) + setChannelError(context->rdpcontext, error, "rdpdr_server_thread reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_start(RdpdrServerContext* context) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + context->priv->ChannelHandle = + WTSVirtualChannelOpen(context->vcm, WTS_CURRENT_SESSION, RDPDR_SVC_CHANNEL_NAME); + + if (!context->priv->ChannelHandle) + { + WLog_Print(context->priv->log, WLOG_ERROR, "WTSVirtualChannelOpen failed!"); + return CHANNEL_RC_BAD_CHANNEL; + } + + if (!(context->priv->StopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr))) + { + WLog_Print(context->priv->log, WLOG_ERROR, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (!(context->priv->Thread = + CreateThread(nullptr, 0, rdpdr_server_thread, (void*)context, 0, nullptr))) + { + WLog_Print(context->priv->log, WLOG_ERROR, "CreateThread failed!"); + (void)CloseHandle(context->priv->StopEvent); + context->priv->StopEvent = nullptr; + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_stop(RdpdrServerContext* context) +{ + UINT error = 0; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (context->priv->StopEvent) + { + (void)SetEvent(context->priv->StopEvent); + + if (WaitForSingleObject(context->priv->Thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_Print(context->priv->log, WLOG_ERROR, + "WaitForSingleObject failed with error %" PRIu32 "!", error); + return error; + } + + (void)CloseHandle(context->priv->Thread); + context->priv->Thread = nullptr; + (void)CloseHandle(context->priv->StopEvent); + context->priv->StopEvent = nullptr; + } + + if (context->priv->ChannelHandle) + { + (void)WTSVirtualChannelClose(context->priv->ChannelHandle); + context->priv->ChannelHandle = nullptr; + } + return CHANNEL_RC_OK; +} + +static void rdpdr_server_write_device_iorequest(wStream* s, UINT32 deviceId, UINT32 fileId, + UINT32 completionId, UINT32 majorFunction, + UINT32 minorFunction) +{ + Stream_Write_UINT16(s, RDPDR_CTYP_CORE); /* Component (2 bytes) */ + Stream_Write_UINT16(s, PAKID_CORE_DEVICE_IOREQUEST); /* PacketId (2 bytes) */ + Stream_Write_UINT32(s, deviceId); /* DeviceId (4 bytes) */ + Stream_Write_UINT32(s, fileId); /* FileId (4 bytes) */ + Stream_Write_UINT32(s, completionId); /* CompletionId (4 bytes) */ + Stream_Write_UINT32(s, majorFunction); /* MajorFunction (4 bytes) */ + Stream_Write_UINT32(s, minorFunction); /* MinorFunction (4 bytes) */ +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_read_file_directory_information(wLog* log, wStream* s, + FILE_DIRECTORY_INFORMATION* fdi) +{ + UINT32 fileNameLength = 0; + WINPR_ASSERT(fdi); + ZeroMemory(fdi, sizeof(FILE_DIRECTORY_INFORMATION)); + + if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 64)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, fdi->NextEntryOffset); /* NextEntryOffset (4 bytes) */ + Stream_Read_UINT32(s, fdi->FileIndex); /* FileIndex (4 bytes) */ + Stream_Read_INT64(s, fdi->CreationTime.QuadPart); /* CreationTime (8 bytes) */ + Stream_Read_INT64(s, fdi->LastAccessTime.QuadPart); /* LastAccessTime (8 bytes) */ + Stream_Read_INT64(s, fdi->LastWriteTime.QuadPart); /* LastWriteTime (8 bytes) */ + Stream_Read_INT64(s, fdi->ChangeTime.QuadPart); /* ChangeTime (8 bytes) */ + Stream_Read_INT64(s, fdi->EndOfFile.QuadPart); /* EndOfFile (8 bytes) */ + Stream_Read_INT64(s, fdi->AllocationSize.QuadPart); /* AllocationSize (8 bytes) */ + Stream_Read_UINT32(s, fdi->FileAttributes); /* FileAttributes (4 bytes) */ + Stream_Read_UINT32(s, fileNameLength); /* FileNameLength (4 bytes) */ + + if (!Stream_CheckAndLogRequiredLengthWLog(log, s, fileNameLength)) + return ERROR_INVALID_DATA; + + if (fileNameLength / sizeof(WCHAR) > ARRAYSIZE(fdi->FileName)) + return ERROR_INVALID_DATA; + +#if defined(__MINGW32__) || defined(WITH_WCHAR_FILE_DIRECTORY_INFORMATION) + if (Stream_Read_UTF16_String(s, fdi->FileName, fileNameLength / sizeof(WCHAR))) + return ERROR_INVALID_DATA; +#else + if (Stream_Read_UTF16_String_As_UTF8_Buffer(s, fileNameLength / sizeof(WCHAR), fdi->FileName, + ARRAYSIZE(fdi->FileName)) < 0) + return ERROR_INVALID_DATA; +#endif + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_device_create_request(RdpdrServerContext* context, UINT32 deviceId, + UINT32 completionId, const char* path, + UINT32 desiredAccess, UINT32 createOptions, + UINT32 createDisposition) +{ + size_t pathLength = 0; + wStream* s = nullptr; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerSendDeviceCreateRequest: deviceId=%" PRIu32 + ", path=%s, desiredAccess=0x%" PRIx32 " createOptions=0x%" PRIx32 + " createDisposition=0x%" PRIx32 "", + deviceId, path, desiredAccess, createOptions, createDisposition); + /* Compute the required Unicode size. */ + pathLength = (strlen(path) + 1U) * sizeof(WCHAR); + s = Stream_New(nullptr, 256U + pathLength); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rdpdr_server_write_device_iorequest(s, deviceId, 0, completionId, IRP_MJ_CREATE, 0); + Stream_Write_UINT32(s, desiredAccess); /* DesiredAccess (4 bytes) */ + Stream_Write_UINT32(s, 0); /* AllocationSize (8 bytes) */ + Stream_Write_UINT32(s, 0); + Stream_Write_UINT32(s, 0); /* FileAttributes (4 bytes) */ + Stream_Write_UINT32(s, 3); /* SharedAccess (4 bytes) */ + Stream_Write_UINT32(s, createDisposition); /* CreateDisposition (4 bytes) */ + Stream_Write_UINT32(s, createOptions); /* CreateOptions (4 bytes) */ + WINPR_ASSERT(pathLength <= UINT32_MAX); + Stream_Write_UINT32(s, (UINT32)pathLength); /* PathLength (4 bytes) */ + /* Convert the path to Unicode. */ + if (Stream_Write_UTF16_String_From_UTF8(s, pathLength / sizeof(WCHAR), path, + pathLength / sizeof(WCHAR), TRUE) < 0) + { + Stream_Free(s, TRUE); + return ERROR_INTERNAL_ERROR; + } + return rdpdr_seal_send_free_request(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_device_close_request(RdpdrServerContext* context, UINT32 deviceId, + UINT32 fileId, UINT32 completionId) +{ + wStream* s = nullptr; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerSendDeviceCloseRequest: deviceId=%" PRIu32 ", fileId=%" PRIu32 "", + deviceId, fileId); + s = Stream_New(nullptr, 128); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rdpdr_server_write_device_iorequest(s, deviceId, fileId, completionId, IRP_MJ_CLOSE, 0); + Stream_Zero(s, 32); /* Padding (32 bytes) */ + return rdpdr_seal_send_free_request(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_device_read_request(RdpdrServerContext* context, UINT32 deviceId, + UINT32 fileId, UINT32 completionId, UINT32 length, + UINT32 offset) +{ + wStream* s = nullptr; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerSendDeviceReadRequest: deviceId=%" PRIu32 ", fileId=%" PRIu32 + ", length=%" PRIu32 ", offset=%" PRIu32 "", + deviceId, fileId, length, offset); + s = Stream_New(nullptr, 128); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rdpdr_server_write_device_iorequest(s, deviceId, fileId, completionId, IRP_MJ_READ, 0); + Stream_Write_UINT32(s, length); /* Length (4 bytes) */ + Stream_Write_UINT32(s, offset); /* Offset (8 bytes) */ + Stream_Write_UINT32(s, 0); + Stream_Zero(s, 20); /* Padding (20 bytes) */ + return rdpdr_seal_send_free_request(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_device_write_request(RdpdrServerContext* context, UINT32 deviceId, + UINT32 fileId, UINT32 completionId, + const char* data, UINT32 length, UINT32 offset) +{ + wStream* s = nullptr; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerSendDeviceWriteRequest: deviceId=%" PRIu32 ", fileId=%" PRIu32 + ", length=%" PRIu32 ", offset=%" PRIu32 "", + deviceId, fileId, length, offset); + s = Stream_New(nullptr, 64 + length); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rdpdr_server_write_device_iorequest(s, deviceId, fileId, completionId, IRP_MJ_WRITE, 0); + Stream_Write_UINT32(s, length); /* Length (4 bytes) */ + Stream_Write_UINT32(s, offset); /* Offset (8 bytes) */ + Stream_Write_UINT32(s, 0); + Stream_Zero(s, 20); /* Padding (20 bytes) */ + Stream_Write(s, data, length); /* WriteData (variable) */ + return rdpdr_seal_send_free_request(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_device_query_directory_request(RdpdrServerContext* context, + UINT32 deviceId, UINT32 fileId, + UINT32 completionId, const char* path) +{ + size_t pathLength = 0; + wStream* s = nullptr; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerSendDeviceQueryDirectoryRequest: deviceId=%" PRIu32 ", fileId=%" PRIu32 + ", path=%s", + deviceId, fileId, path); + /* Compute the required Unicode size. */ + pathLength = path ? (strlen(path) + 1) * sizeof(WCHAR) : 0; + s = Stream_New(nullptr, 64 + pathLength); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rdpdr_server_write_device_iorequest(s, deviceId, fileId, completionId, IRP_MJ_DIRECTORY_CONTROL, + IRP_MN_QUERY_DIRECTORY); + Stream_Write_UINT32(s, FileDirectoryInformation); /* FsInformationClass (4 bytes) */ + Stream_Write_UINT8(s, path ? 1 : 0); /* InitialQuery (1 byte) */ + WINPR_ASSERT(pathLength <= UINT32_MAX); + Stream_Write_UINT32(s, (UINT32)pathLength); /* PathLength (4 bytes) */ + Stream_Zero(s, 23); /* Padding (23 bytes) */ + + /* Convert the path to Unicode. */ + if (pathLength > 0) + { + if (Stream_Write_UTF16_String_From_UTF8(s, pathLength / sizeof(WCHAR), path, + pathLength / sizeof(WCHAR), TRUE) < 0) + { + Stream_Free(s, TRUE); + return ERROR_INTERNAL_ERROR; + } + } + + return rdpdr_seal_send_free_request(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_send_device_file_rename_request(RdpdrServerContext* context, + UINT32 deviceId, UINT32 fileId, + UINT32 completionId, const char* path) +{ + size_t pathLength = 0; + wStream* s = nullptr; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerSendDeviceFileNameRequest: deviceId=%" PRIu32 ", fileId=%" PRIu32 + ", path=%s", + deviceId, fileId, path); + /* Compute the required Unicode size. */ + pathLength = path ? (strlen(path) + 1) * sizeof(WCHAR) : 0; + s = Stream_New(nullptr, 64 + pathLength); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rdpdr_server_write_device_iorequest(s, deviceId, fileId, completionId, IRP_MJ_SET_INFORMATION, + 0); + Stream_Write_UINT32(s, FileRenameInformation); /* FsInformationClass (4 bytes) */ + WINPR_ASSERT(pathLength <= UINT32_MAX - 6U); + Stream_Write_UINT32(s, (UINT32)pathLength + 6U); /* Length (4 bytes) */ + Stream_Zero(s, 24); /* Padding (24 bytes) */ + /* RDP_FILE_RENAME_INFORMATION */ + Stream_Write_UINT8(s, 0); /* ReplaceIfExists (1 byte) */ + Stream_Write_UINT8(s, 0); /* RootDirectory (1 byte) */ + Stream_Write_UINT32(s, (UINT32)pathLength); /* FileNameLength (4 bytes) */ + + /* Convert the path to Unicode. */ + if (pathLength > 0) + { + if (Stream_Write_UTF16_String_From_UTF8(s, pathLength / sizeof(WCHAR), path, + pathLength / sizeof(WCHAR), TRUE) < 0) + { + Stream_Free(s, TRUE); + return ERROR_INTERNAL_ERROR; + } + } + + return rdpdr_seal_send_free_request(context, s); +} + +static void rdpdr_server_convert_slashes(char* path, int size) +{ + WINPR_ASSERT(path || (size <= 0)); + + for (int i = 0; (i < size) && (path[i] != '\0'); i++) + { + if (path[i] == '/') + path[i] = '\\'; + } +} + +/************************************************* + * Drive Create Directory + ************************************************/ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_create_directory_callback2(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(s); + WINPR_ASSERT(irp); + WINPR_UNUSED(s); + + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveCreateDirectoryCallback2: deviceId=%" PRIu32 + ", completionId=%" PRIu32 ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + /* Invoke the create directory completion routine. */ + context->OnDriveCreateDirectoryComplete(context, irp->CallbackData, ioStatus); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_create_directory_callback1(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(s); + WINPR_ASSERT(irp); + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveCreateDirectoryCallback1: deviceId=%" PRIu32 + ", completionId=%" PRIu32 ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + + if (ioStatus != STATUS_SUCCESS) + { + /* Invoke the create directory completion routine. */ + context->OnDriveCreateDirectoryComplete(context, irp->CallbackData, ioStatus); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; + } + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 5)) + return ERROR_INVALID_DATA; + + const uint32_t fileId = Stream_Get_UINT32(s); /* FileId (4 bytes) */ + const uint8_t information = Stream_Get_UINT8(s); /* Information (1 byte) */ + WLog_Print(context->priv->log, WLOG_DEBUG, "fileId [0x%08" PRIx32 "], information %s", fileId, + fileInformation2str(information)); + + /* Setup the IRP. */ + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_create_directory_callback2; + irp->DeviceId = deviceId; + irp->FileId = fileId; + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to close the file */ + return rdpdr_server_send_device_close_request(context, deviceId, fileId, irp->CompletionId); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_create_directory(RdpdrServerContext* context, void* callbackData, + UINT32 deviceId, const char* path) +{ + RDPDR_IRP* irp = nullptr; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(callbackData); + WINPR_ASSERT(path); + irp = rdpdr_server_irp_new(); + + if (!irp) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_irp_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_create_directory_callback1; + irp->CallbackData = callbackData; + irp->DeviceId = deviceId; + strncpy(irp->PathName, path, sizeof(irp->PathName) - 1); + rdpdr_server_convert_slashes(irp->PathName, sizeof(irp->PathName)); + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to open the file. */ + return rdpdr_server_send_device_create_request( + context, deviceId, irp->CompletionId, irp->PathName, FILE_READ_DATA | SYNCHRONIZE, + FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATE); +} + +/************************************************* + * Drive Delete Directory + ************************************************/ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_delete_directory_callback2(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + WINPR_UNUSED(s); + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveDeleteDirectoryCallback2: deviceId=%" PRIu32 + ", completionId=%" PRIu32 ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + /* Invoke the delete directory completion routine. */ + context->OnDriveDeleteDirectoryComplete(context, irp->CallbackData, ioStatus); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_delete_directory_callback1(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveDeleteDirectoryCallback1: deviceId=%" PRIu32 + ", completionId=%" PRIu32 ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + + if (ioStatus != STATUS_SUCCESS) + { + /* Invoke the delete directory completion routine. */ + context->OnDriveDeleteFileComplete(context, irp->CallbackData, ioStatus); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; + } + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 5)) + return ERROR_INVALID_DATA; + + const uint32_t fileId = Stream_Get_UINT32(s); /* FileId (4 bytes) */ + const uint8_t information = Stream_Get_UINT8(s); /* Information (1 byte) */ + WLog_Print(context->priv->log, WLOG_DEBUG, "fileId [0x%08" PRIx32 "], information %s", fileId, + fileInformation2str(information)); + + /* Setup the IRP. */ + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_delete_directory_callback2; + irp->DeviceId = deviceId; + irp->FileId = fileId; + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to close the file */ + return rdpdr_server_send_device_close_request(context, deviceId, fileId, irp->CompletionId); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_delete_directory(RdpdrServerContext* context, void* callbackData, + UINT32 deviceId, const char* path) +{ + RDPDR_IRP* irp = rdpdr_server_irp_new(); + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + + if (!irp) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_irp_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_delete_directory_callback1; + irp->CallbackData = callbackData; + irp->DeviceId = deviceId; + strncpy(irp->PathName, path, sizeof(irp->PathName) - 1); + rdpdr_server_convert_slashes(irp->PathName, sizeof(irp->PathName)); + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to open the file. */ + return rdpdr_server_send_device_create_request( + context, deviceId, irp->CompletionId, irp->PathName, DELETE | SYNCHRONIZE, + FILE_DIRECTORY_FILE | FILE_DELETE_ON_CLOSE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPEN); +} + +/************************************************* + * Drive Query Directory + ************************************************/ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_query_directory_callback2(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + UINT error = 0; + UINT32 length = 0; + FILE_DIRECTORY_INFORMATION fdi = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveQueryDirectoryCallback2: deviceId=%" PRIu32 + ", completionId=%" PRIu32 ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, length); /* Length (4 bytes) */ + + if (length > 0) + { + if ((error = rdpdr_server_read_file_directory_information(context->priv->log, s, &fdi))) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpdr_server_read_file_directory_information failed with error %" PRIu32 + "!", + error); + return error; + } + } + else + { + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 1)) + return ERROR_INVALID_DATA; + + Stream_Seek(s, 1); /* Padding (1 byte) */ + } + + if (ioStatus == STATUS_SUCCESS) + { + /* Invoke the query directory completion routine. */ + context->OnDriveQueryDirectoryComplete(context, irp->CallbackData, ioStatus, + length > 0 ? &fdi : nullptr); + /* Setup the IRP. */ + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_query_directory_callback2; + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to query the directory. */ + return rdpdr_server_send_device_query_directory_request(context, irp->DeviceId, irp->FileId, + irp->CompletionId, nullptr); + } + else + { + /* Invoke the query directory completion routine. */ + context->OnDriveQueryDirectoryComplete(context, irp->CallbackData, ioStatus, nullptr); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_query_directory_callback1(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveQueryDirectoryCallback1: deviceId=%" PRIu32 + ", completionId=%" PRIu32 ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + + if (ioStatus != STATUS_SUCCESS) + { + /* Invoke the query directory completion routine. */ + context->OnDriveQueryDirectoryComplete(context, irp->CallbackData, ioStatus, nullptr); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; + } + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4)) + return ERROR_INVALID_DATA; + + const uint32_t fileId = Stream_Get_UINT32(s); + /* Setup the IRP. */ + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_query_directory_callback2; + irp->DeviceId = deviceId; + irp->FileId = fileId; + winpr_str_append("\\*.*", irp->PathName, ARRAYSIZE(irp->PathName), nullptr); + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to query the directory. */ + return rdpdr_server_send_device_query_directory_request(context, deviceId, fileId, + irp->CompletionId, irp->PathName); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_query_directory(RdpdrServerContext* context, void* callbackData, + UINT32 deviceId, const char* path) +{ + RDPDR_IRP* irp = rdpdr_server_irp_new(); + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + + if (!irp) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_irp_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_query_directory_callback1; + irp->CallbackData = callbackData; + irp->DeviceId = deviceId; + strncpy(irp->PathName, path, sizeof(irp->PathName) - 1); + rdpdr_server_convert_slashes(irp->PathName, sizeof(irp->PathName)); + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to open the directory. */ + return rdpdr_server_send_device_create_request( + context, deviceId, irp->CompletionId, irp->PathName, FILE_READ_DATA | SYNCHRONIZE, + FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPEN); +} + +/************************************************* + * Drive Open File + ************************************************/ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_open_file_callback(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveOpenFileCallback: deviceId=%" PRIu32 ", completionId=%" PRIu32 + ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 5)) + return ERROR_INVALID_DATA; + + const uint32_t fileId = Stream_Get_UINT32(s); /* FileId (4 bytes) */ + const uint8_t information = Stream_Get_UINT8(s); /* Information (1 byte) */ + WLog_Print(context->priv->log, WLOG_DEBUG, "fileId [0x%08" PRIx32 "], information %s", fileId, + fileInformation2str(information)); + + /* Invoke the open file completion routine. */ + context->OnDriveOpenFileComplete(context, irp->CallbackData, ioStatus, deviceId, fileId); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_open_file(RdpdrServerContext* context, void* callbackData, + UINT32 deviceId, const char* path, UINT32 desiredAccess, + UINT32 createDisposition) +{ + RDPDR_IRP* irp = rdpdr_server_irp_new(); + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + + if (!irp) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_irp_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_open_file_callback; + irp->CallbackData = callbackData; + irp->DeviceId = deviceId; + strncpy(irp->PathName, path, sizeof(irp->PathName) - 1); + rdpdr_server_convert_slashes(irp->PathName, sizeof(irp->PathName)); + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to open the file. */ + return rdpdr_server_send_device_create_request(context, deviceId, irp->CompletionId, + irp->PathName, desiredAccess | SYNCHRONIZE, + FILE_SYNCHRONOUS_IO_NONALERT, createDisposition); +} + +/************************************************* + * Drive Read File + ************************************************/ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_read_file_callback(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + UINT32 length = 0; + char* buffer = nullptr; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveReadFileCallback: deviceId=%" PRIu32 ", completionId=%" PRIu32 + ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, length); /* Length (4 bytes) */ + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, length)) + return ERROR_INVALID_DATA; + + if (length > 0) + { + buffer = Stream_Pointer(s); + Stream_Seek(s, length); + } + + /* Invoke the read file completion routine. */ + context->OnDriveReadFileComplete(context, irp->CallbackData, ioStatus, buffer, length); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_read_file(RdpdrServerContext* context, void* callbackData, + UINT32 deviceId, UINT32 fileId, UINT32 length, + UINT32 offset) +{ + RDPDR_IRP* irp = rdpdr_server_irp_new(); + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + + if (!irp) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_irp_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_read_file_callback; + irp->CallbackData = callbackData; + irp->DeviceId = deviceId; + irp->FileId = fileId; + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to open the directory. */ + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): rdpdr_server_enqueue_irp owns irp + return rdpdr_server_send_device_read_request(context, deviceId, fileId, irp->CompletionId, + length, offset); +} + +/************************************************* + * Drive Write File + ************************************************/ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_write_file_callback(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + UINT32 length = 0; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveWriteFileCallback: deviceId=%" PRIu32 ", completionId=%" PRIu32 + ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 5)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, length); /* Length (4 bytes) */ + Stream_Seek(s, 1); /* Padding (1 byte) */ + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, length)) + return ERROR_INVALID_DATA; + + /* Invoke the write file completion routine. */ + context->OnDriveWriteFileComplete(context, irp->CallbackData, ioStatus, length); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_write_file(RdpdrServerContext* context, void* callbackData, + UINT32 deviceId, UINT32 fileId, const char* buffer, + UINT32 length, UINT32 offset) +{ + RDPDR_IRP* irp = rdpdr_server_irp_new(); + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + + if (!irp) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_irp_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_write_file_callback; + irp->CallbackData = callbackData; + irp->DeviceId = deviceId; + irp->FileId = fileId; + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to open the directory. */ + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): rdpdr_server_enqueue_irp owns irp + return rdpdr_server_send_device_write_request(context, deviceId, fileId, irp->CompletionId, + buffer, length, offset); +} + +/************************************************* + * Drive Close File + ************************************************/ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_close_file_callback(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + WINPR_UNUSED(s); + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveCloseFileCallback: deviceId=%" PRIu32 ", completionId=%" PRIu32 + ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + + // padding 5 bytes + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 5)) + return ERROR_INVALID_DATA; + + Stream_Seek(s, 5); + + /* Invoke the close file completion routine. */ + context->OnDriveCloseFileComplete(context, irp->CallbackData, ioStatus); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_close_file(RdpdrServerContext* context, void* callbackData, + UINT32 deviceId, UINT32 fileId) +{ + RDPDR_IRP* irp = rdpdr_server_irp_new(); + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + + if (!irp) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_irp_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_close_file_callback; + irp->CallbackData = callbackData; + irp->DeviceId = deviceId; + irp->FileId = fileId; + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to open the directory. */ + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): rdpdr_server_enqueue_irp owns irp + return rdpdr_server_send_device_close_request(context, deviceId, fileId, irp->CompletionId); +} + +/************************************************* + * Drive Delete File + ************************************************/ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_delete_file_callback2(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + WINPR_UNUSED(s); + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveDeleteFileCallback2: deviceId=%" PRIu32 ", completionId=%" PRIu32 + ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + /* Invoke the delete file completion routine. */ + context->OnDriveDeleteFileComplete(context, irp->CallbackData, ioStatus); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_delete_file_callback1(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveDeleteFileCallback1: deviceId=%" PRIu32 ", completionId=%" PRIu32 + ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + + if (ioStatus != STATUS_SUCCESS) + { + /* Invoke the close file completion routine. */ + context->OnDriveDeleteFileComplete(context, irp->CallbackData, ioStatus); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; + } + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 5)) + return ERROR_INVALID_DATA; + + const uint32_t fileId = Stream_Get_UINT32(s); /* FileId (4 bytes) */ + const uint8_t information = Stream_Get_UINT8(s); /* Information (1 byte) */ + + WLog_Print(context->priv->log, WLOG_DEBUG, "fileId [0x%08" PRIx32 "], information %s", fileId, + fileInformation2str(information)); + /* Setup the IRP. */ + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_delete_file_callback2; + irp->DeviceId = deviceId; + irp->FileId = fileId; + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to close the file */ + return rdpdr_server_send_device_close_request(context, deviceId, fileId, irp->CompletionId); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_delete_file(RdpdrServerContext* context, void* callbackData, + UINT32 deviceId, const char* path) +{ + RDPDR_IRP* irp = rdpdr_server_irp_new(); + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + + if (!irp) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_irp_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_delete_file_callback1; + irp->CallbackData = callbackData; + irp->DeviceId = deviceId; + strncpy(irp->PathName, path, sizeof(irp->PathName) - 1); + rdpdr_server_convert_slashes(irp->PathName, sizeof(irp->PathName)); + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to open the file. */ + return rdpdr_server_send_device_create_request( + context, deviceId, irp->CompletionId, irp->PathName, FILE_READ_DATA | SYNCHRONIZE, + FILE_DELETE_ON_CLOSE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPEN); +} + +/************************************************* + * Drive Rename File + ************************************************/ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_rename_file_callback3(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + WINPR_UNUSED(context); + WINPR_UNUSED(s); + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveRenameFileCallback3: deviceId=%" PRIu32 ", completionId=%" PRIu32 + ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_rename_file_callback2(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + UINT32 length = 0; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveRenameFileCallback2: deviceId=%" PRIu32 ", completionId=%" PRIu32 + ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 5)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, length); /* Length (4 bytes) */ + Stream_Seek(s, 1); /* Padding (1 byte) */ + /* Invoke the rename file completion routine. */ + context->OnDriveRenameFileComplete(context, irp->CallbackData, ioStatus); + /* Setup the IRP. */ + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_rename_file_callback3; + irp->DeviceId = deviceId; + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to close the file */ + return rdpdr_server_send_device_close_request(context, deviceId, irp->FileId, + irp->CompletionId); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_rename_file_callback1(RdpdrServerContext* context, wStream* s, + RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(irp); + WLog_Print(context->priv->log, WLOG_DEBUG, + "RdpdrServerDriveRenameFileCallback1: deviceId=%" PRIu32 ", completionId=%" PRIu32 + ", ioStatus=0x%" PRIx32 "", + deviceId, completionId, ioStatus); + + if (ioStatus != STATUS_SUCCESS) + { + /* Invoke the rename file completion routine. */ + context->OnDriveRenameFileComplete(context, irp->CallbackData, ioStatus); + /* Destroy the IRP. */ + rdpdr_server_irp_free(irp); + return CHANNEL_RC_OK; + } + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 5)) + return ERROR_INVALID_DATA; + + const uint32_t fileId = Stream_Get_UINT32(s); /* FileId (4 bytes) */ + const uint8_t information = Stream_Get_UINT8(s); /* Information (1 byte) */ + WLog_Print(context->priv->log, WLOG_DEBUG, "fileId [0x%08" PRIx32 "], information %s", fileId, + fileInformation2str(information)); + + /* Setup the IRP. */ + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_rename_file_callback2; + irp->DeviceId = deviceId; + irp->FileId = fileId; + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to rename the file */ + return rdpdr_server_send_device_file_rename_request(context, deviceId, fileId, + irp->CompletionId, irp->ExtraBuffer); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpdr_server_drive_rename_file(RdpdrServerContext* context, void* callbackData, + UINT32 deviceId, const char* oldPath, + const char* newPath) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + RDPDR_IRP* irp = rdpdr_server_irp_new(); + + if (!irp) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_irp_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + irp->CompletionId = context->priv->NextCompletionId++; + irp->Callback = rdpdr_server_drive_rename_file_callback1; + irp->CallbackData = callbackData; + irp->DeviceId = deviceId; + strncpy(irp->PathName, oldPath, sizeof(irp->PathName) - 1); + strncpy(irp->ExtraBuffer, newPath, sizeof(irp->ExtraBuffer) - 1); + rdpdr_server_convert_slashes(irp->PathName, sizeof(irp->PathName)); + rdpdr_server_convert_slashes(irp->ExtraBuffer, sizeof(irp->ExtraBuffer)); + + if (!rdpdr_server_enqueue_irp(context, irp)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpdr_server_enqueue_irp failed!"); + rdpdr_server_irp_free(irp); + return ERROR_INTERNAL_ERROR; + } + + /* Send a request to open the file. */ + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): rdpdr_server_enqueue_irp owns irp + return rdpdr_server_send_device_create_request(context, deviceId, irp->CompletionId, + irp->PathName, FILE_READ_DATA | SYNCHRONIZE, + FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPEN); +} + +static void rdpdr_server_private_free(RdpdrServerPrivate* ctx) +{ + if (!ctx) + return; + ListDictionary_Free(ctx->IrpList); + HashTable_Free(ctx->devicelist); + free(ctx->ClientComputerName); + free(ctx); +} + +#define TAG CHANNELS_TAG("rdpdr.server") +static RdpdrServerPrivate* rdpdr_server_private_new(void) +{ + RdpdrServerPrivate* priv = (RdpdrServerPrivate*)calloc(1, sizeof(RdpdrServerPrivate)); + + if (!priv) + goto fail; + + priv->log = WLog_Get(TAG); + priv->VersionMajor = RDPDR_VERSION_MAJOR; + priv->VersionMinor = RDPDR_VERSION_MINOR_RDP6X; + priv->ClientId = g_ClientId++; + priv->UserLoggedOnPdu = TRUE; + priv->NextCompletionId = 1; + priv->IrpList = ListDictionary_New(TRUE); + + if (!priv->IrpList) + goto fail; + + priv->devicelist = HashTable_New(FALSE); + if (!priv->devicelist) + goto fail; + + HashTable_SetHashFunction(priv->devicelist, rdpdr_deviceid_hash); + + { + wObject* obj = HashTable_ValueObject(priv->devicelist); + WINPR_ASSERT(obj); + obj->fnObjectFree = rdpdr_device_free_h; + obj->fnObjectNew = rdpdr_device_clone; + } + + { + wObject* obj = HashTable_KeyObject(priv->devicelist); + obj->fnObjectEquals = rdpdr_device_equal; + obj->fnObjectFree = rdpdr_device_key_free; + obj->fnObjectNew = rdpdr_device_key_clone; + } + + return priv; +fail: + rdpdr_server_private_free(priv); + return nullptr; +} + +RdpdrServerContext* rdpdr_server_context_new(HANDLE vcm) +{ + RdpdrServerContext* context = (RdpdrServerContext*)calloc(1, sizeof(RdpdrServerContext)); + + if (!context) + goto fail; + + context->vcm = vcm; + context->Start = rdpdr_server_start; + context->Stop = rdpdr_server_stop; + context->DriveCreateDirectory = rdpdr_server_drive_create_directory; + context->DriveDeleteDirectory = rdpdr_server_drive_delete_directory; + context->DriveQueryDirectory = rdpdr_server_drive_query_directory; + context->DriveOpenFile = rdpdr_server_drive_open_file; + context->DriveReadFile = rdpdr_server_drive_read_file; + context->DriveWriteFile = rdpdr_server_drive_write_file; + context->DriveCloseFile = rdpdr_server_drive_close_file; + context->DriveDeleteFile = rdpdr_server_drive_delete_file; + context->DriveRenameFile = rdpdr_server_drive_rename_file; + context->priv = rdpdr_server_private_new(); + if (!context->priv) + goto fail; + + /* By default announce everything, the server application can deactivate that later on */ + context->supported = UINT16_MAX; + + return context; +fail: + WINPR_PRAGMA_DIAG_PUSH + WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC + rdpdr_server_context_free(context); + WINPR_PRAGMA_DIAG_POP + return nullptr; +} + +void rdpdr_server_context_free(RdpdrServerContext* context) +{ + if (!context) + return; + + rdpdr_server_private_free(context->priv); + free(context); +} diff --git a/third_party/FreeRDP/channels/rdpdr/server/rdpdr_main.h b/third_party/FreeRDP/channels/rdpdr/server/rdpdr_main.h new file mode 100644 index 0000000..dabbae2 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpdr/server/rdpdr_main.h @@ -0,0 +1,47 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Device Redirection Virtual Channel Extension + * + * Copyright 2014 Dell Software + * Copyright 2013 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPDR_SERVER_MAIN_H +#define FREERDP_CHANNEL_RDPDR_SERVER_MAIN_H + +#include +#include +#include +#include + +#include +#include + +typedef struct S_RDPDR_IRP +{ + UINT32 CompletionId; + UINT32 DeviceId; + UINT32 FileId; + char PathName[256]; + char ExtraBuffer[256]; + void* CallbackData; + UINT(*Callback) + (RdpdrServerContext* context, wStream* s, struct S_RDPDR_IRP* irp, UINT32 deviceId, + UINT32 completionId, UINT32 ioStatus); +} RDPDR_IRP; + +#endif /* FREERDP_CHANNEL_RDPDR_SERVER_MAIN_H */ diff --git a/third_party/FreeRDP/channels/rdpear/CMakeLists.txt b/third_party/FreeRDP/channels/rdpear/CMakeLists.txt new file mode 100644 index 0000000..b277c85 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpear/CMakeLists.txt @@ -0,0 +1,43 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2023 David Fort +# +# 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. + +if(NOT IOS AND NOT WIN32 AND NOT ANDROID) + find_package(KRB5) + + if(KRB5_FOUND) + define_channel("rdpear") + freerdp_client_pc_add_requires_private("mit-krb5") + + if(KRB5_FLAVOUR STREQUAL "Heimdal") + message(FATAL_ERROR "krb5 implementation Heimdal not supported with -DCHANNEL_RDPEAR=ON") + endif() + + include_directories(common) + + if(WITH_CLIENT_CHANNELS OR WITH_SERVER_CHANNELS) + add_subdirectory(common) + endif() + + if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) + endif() + + #if(WITH_SERVER_CHANNELS) + # add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) + #endif() + endif() +endif() diff --git a/third_party/FreeRDP/channels/rdpear/ChannelOptions.cmake b/third_party/FreeRDP/channels/rdpear/ChannelOptions.cmake new file mode 100644 index 0000000..0670649 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpear/ChannelOptions.cmake @@ -0,0 +1,20 @@ +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT OFF) + +define_channel_options( + NAME + "rdpear" + TYPE + "dynamic" + DESCRIPTION + "Authentication redirection Virtual Channel Extension" + SPECIFICATIONS + "[MS-RDPEAR]" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/rdpear/client/CMakeLists.txt b/third_party/FreeRDP/channels/rdpear/client/CMakeLists.txt new file mode 100644 index 0000000..6f5d1bb --- /dev/null +++ b/third_party/FreeRDP/channels/rdpear/client/CMakeLists.txt @@ -0,0 +1,24 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2023 David Fort +# +# 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. + +define_channel_client("rdpear") + +set(${MODULE_PREFIX}_SRCS rdpear_main.c) + +set(${MODULE_PREFIX}_LIBS winpr freerdp rdpear-common) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") diff --git a/third_party/FreeRDP/channels/rdpear/client/rdpear_main.c b/third_party/FreeRDP/channels/rdpear/client/rdpear_main.c new file mode 100644 index 0000000..2259512 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpear/client/rdpear_main.c @@ -0,0 +1,1061 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Authentication redirection virtual channel + * + * Copyright 2023 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define TAG CHANNELS_TAG("rdpear.client") + +#ifndef MAX_KEYTAB_NAME_LEN +#define MAX_KEYTAB_NAME_LEN 1100 /* Defined in MIT krb5.h */ +#endif + +/* defined in libkrb5 */ +krb5_error_code encode_krb5_authenticator(const krb5_authenticator* rep, krb5_data** code_out); +krb5_error_code encode_krb5_ap_rep(const krb5_ap_rep* rep, krb5_data** code_out); + +typedef struct +{ + GENERIC_DYNVC_PLUGIN base; + rdpContext* rdp_context; + krb5_context krbContext; +} RDPEAR_PLUGIN; + +static const BYTE payloadHeader[16] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static krb5_error_code RPC_ENCRYPTION_KEY_to_keyblock(krb5_context ctx, + const KERB_RPC_ENCRYPTION_KEY* key, + krb5_keyblock** pkeyblock) +{ + WINPR_ASSERT(ctx); + WINPR_ASSERT(key); + WINPR_ASSERT(pkeyblock); + + if (!key->reserved3.length) + return KRB5KDC_ERR_NULL_KEY; + + krb5_error_code rv = + krb5_init_keyblock(ctx, (krb5_enctype)key->reserved2, key->reserved3.length, pkeyblock); + if (rv) + return rv; + + krb5_keyblock* keyblock = *pkeyblock; + memcpy(keyblock->contents, key->reserved3.value, key->reserved3.length); + return rv; +} + +static krb5_error_code kerb_do_checksum(krb5_context ctx, const KERB_RPC_ENCRYPTION_KEY* key, + krb5_keyusage kusage, krb5_cksumtype cksumtype, + const KERB_ASN1_DATA* plain, krb5_checksum* out) +{ + WINPR_ASSERT(ctx); + WINPR_ASSERT(key); + WINPR_ASSERT(plain); + WINPR_ASSERT(out); + + krb5_keyblock* keyblock = nullptr; + krb5_data data = WINPR_C_ARRAY_INIT; + + krb5_error_code rv = RPC_ENCRYPTION_KEY_to_keyblock(ctx, key, &keyblock); + if (rv) + return rv; + + data.data = (char*)plain->Asn1Buffer; + data.length = plain->Asn1BufferHints.count; + + rv = krb5_c_make_checksum(ctx, cksumtype, keyblock, kusage, &data, out); + + krb5_free_keyblock(ctx, keyblock); + return rv; +} + +static krb5_error_code kerb_do_encrypt(krb5_context ctx, const KERB_RPC_ENCRYPTION_KEY* key, + krb5_keyusage kusage, const KERB_ASN1_DATA* plain, + krb5_data* out) +{ + WINPR_ASSERT(ctx); + WINPR_ASSERT(key); + WINPR_ASSERT(plain); + WINPR_ASSERT(out); + + krb5_keyblock* keyblock = nullptr; + krb5_data data = WINPR_C_ARRAY_INIT; + krb5_enc_data enc = WINPR_C_ARRAY_INIT; + size_t elen = 0; + + krb5_error_code rv = RPC_ENCRYPTION_KEY_to_keyblock(ctx, key, &keyblock); + if (rv) + return rv; + + data.data = (char*)plain->Asn1Buffer; + data.length = plain->Asn1BufferHints.count; + + rv = krb5_c_encrypt_length(ctx, keyblock->enctype, data.length, &elen); + if (rv) + goto out; + if (!elen || (elen > UINT32_MAX)) + { + rv = KRB5_PARSE_MALFORMED; + goto out; + } + enc.ciphertext.length = (unsigned int)elen; + enc.ciphertext.data = malloc(elen); + if (!enc.ciphertext.data) + { + rv = ENOMEM; + goto out; + } + + rv = krb5_c_encrypt(ctx, keyblock, kusage, nullptr, &data, &enc); + + out->data = enc.ciphertext.data; + out->length = enc.ciphertext.length; +out: + krb5_free_keyblock(ctx, keyblock); + return rv; +} + +static krb5_error_code kerb_do_decrypt(krb5_context ctx, const KERB_RPC_ENCRYPTION_KEY* key, + krb5_keyusage kusage, const krb5_data* cipher, + KERB_ASN1_DATA* plain) +{ + WINPR_ASSERT(ctx); + WINPR_ASSERT(key); + WINPR_ASSERT(cipher); + WINPR_ASSERT(cipher->length); + WINPR_ASSERT(plain); + + krb5_keyblock* keyblock = nullptr; + krb5_data data = WINPR_C_ARRAY_INIT; + krb5_enc_data enc = WINPR_C_ARRAY_INIT; + + krb5_error_code rv = RPC_ENCRYPTION_KEY_to_keyblock(ctx, key, &keyblock); + if (rv) + return rv; + + enc.kvno = KRB5_PVNO; + enc.enctype = (krb5_enctype)key->reserved2; + enc.ciphertext.length = cipher->length; + enc.ciphertext.data = cipher->data; + + data.length = cipher->length; + data.data = (char*)malloc(cipher->length); + if (!data.data) + { + rv = ENOMEM; + goto out; + } + + rv = krb5_c_decrypt(ctx, keyblock, kusage, nullptr, &enc, &data); + + plain->Asn1Buffer = (BYTE*)data.data; + plain->Asn1BufferHints.count = data.length; +out: + krb5_free_keyblock(ctx, keyblock); + return rv; +} + +static BOOL rdpear_send_payload(RDPEAR_PLUGIN* rdpear, IWTSVirtualChannelCallback* pChannelCallback, + BOOL isKerb, wStream* payload) +{ + GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + BOOL ret = FALSE; + wStream* finalStream = nullptr; + SecBuffer cryptedBuffer = WINPR_C_ARRAY_INIT; + wStream* unencodedContent = rdpear_encodePayload(isKerb, payload); + if (!unencodedContent) + goto out; + + const size_t unencodedLen = Stream_GetPosition(unencodedContent); + +#if UINT32_MAX < SIZE_MAX + if (unencodedLen > UINT32_MAX) + goto out; +#endif + + SecBuffer inBuffer = { (ULONG)unencodedLen, SECBUFFER_DATA, Stream_Buffer(unencodedContent) }; + + if (!freerdp_nla_encrypt(rdpear->rdp_context, &inBuffer, &cryptedBuffer)) + goto out; + + finalStream = Stream_New(nullptr, 200); + if (!finalStream) + goto out; + Stream_Write_UINT32(finalStream, 0x4EACC3C8); /* ProtocolMagic (4 bytes) */ + Stream_Write_UINT32(finalStream, cryptedBuffer.cbBuffer); /* Length (4 bytes) */ + Stream_Write_UINT32(finalStream, 0x00000000); /* Version (4 bytes) */ + Stream_Write_UINT32(finalStream, 0x00000000); /* Reserved (4 bytes) */ + Stream_Write_UINT64(finalStream, 0); /* TsPkgContext (8 bytes) */ + + /* payload */ + if (!Stream_EnsureRemainingCapacity(finalStream, cryptedBuffer.cbBuffer)) + goto out; + + Stream_Write(finalStream, cryptedBuffer.pvBuffer, cryptedBuffer.cbBuffer); + + const size_t pos = Stream_GetPosition(finalStream); +#if UINT32_MAX < SIZE_MAX + if (pos > UINT32_MAX) + goto out; +#endif + + UINT status = callback->channel->Write(callback->channel, (ULONG)pos, + Stream_Buffer(finalStream), nullptr); + ret = (status == CHANNEL_RC_OK); + if (!ret) + WLog_DBG(TAG, "rdpear_send_payload=0x%x", status); +out: + sspi_SecBufferFree(&cryptedBuffer); + Stream_Free(unencodedContent, TRUE); + Stream_Free(finalStream, TRUE); + return ret; +} + +static BOOL rdpear_prepare_response(NdrContext* rcontext, BOOL isKerb, UINT16 callId, UINT32 status, + NdrContext** pwcontext, wStream* retStream) +{ + WINPR_ASSERT(rcontext); + WINPR_ASSERT(pwcontext); + + BOOL ret = FALSE; + *pwcontext = nullptr; + NdrContext* wcontext = ndr_context_copy(rcontext); + if (!wcontext) + return FALSE; + + if (!Stream_EnsureRemainingCapacity(retStream, sizeof(payloadHeader))) + goto out; + + Stream_Write(retStream, payloadHeader, sizeof(payloadHeader)); + + if (!ndr_write_header(wcontext, retStream) || !ndr_start_constructed(wcontext, retStream) || + !ndr_write_pickle(wcontext, retStream)) /* pickle header */ + goto out; + if (isKerb) + { + /* for some reason there's 4 zero undocumented bytes here after the pickle record + * in the kerberos package packets */ + UINT32 v = 0; + if (!ndr_write_uint32(wcontext, retStream, v)) + goto out; + } + if (!ndr_write_uint16(wcontext, retStream, callId) || /* callId */ + !ndr_write_uint16(wcontext, retStream, 0x0000) || /* align padding */ + !ndr_write_uint32(wcontext, retStream, status) || /* status */ + !ndr_write_uint16(wcontext, retStream, callId) || /* callId */ + !ndr_write_uint16(wcontext, retStream, 0x0000)) /* align padding */ + goto out; + + *pwcontext = wcontext; + ret = TRUE; +out: + if (!ret) + ndr_context_destroy(&wcontext); + return ret; +} + +static BOOL rdpear_kerb_version(NdrContext* rcontext, wStream* s, UINT32* pstatus, UINT32* pversion) +{ + *pstatus = ERROR_INVALID_DATA; + + if (!ndr_read_uint32(rcontext, s, pversion)) + return TRUE; + + WLog_DBG(TAG, "-> KerbNegotiateVersion(v=0x%x)", *pversion); + *pstatus = 0; + + return TRUE; +} + +static BOOL rdpear_kerb_ComputeTgsChecksum(RDPEAR_PLUGIN* rdpear, NdrContext* rcontext, wStream* s, + UINT32* pstatus, KERB_ASN1_DATA* resp) +{ + ComputeTgsChecksumReq req = WINPR_C_ARRAY_INIT; + krb5_checksum checksum = WINPR_C_ARRAY_INIT; + wStream* asn1Payload = nullptr; + + *pstatus = ERROR_INVALID_DATA; + WLog_DBG(TAG, "-> ComputeTgsChecksum"); + + if (!ndr_read_ComputeTgsChecksumReq(rcontext, s, nullptr, &req) || + !ndr_treat_deferred_read(rcontext, s)) + goto out; + // ComputeTgsChecksumReq_dump(WLog_Get(""), WLOG_DEBUG, &req); + + krb5_error_code rv = + kerb_do_checksum(rdpear->krbContext, req.Key, KRB5_KEYUSAGE_TGS_REQ_AUTH_CKSUM, + (krb5_cksumtype)req.ChecksumType, req.requestBody, &checksum); + if (rv) + goto out; + + asn1Payload = rdpear_enc_Checksum(req.ChecksumType, &checksum); + if (!asn1Payload) + goto out; + + resp->Pdu = 8; + resp->Asn1Buffer = Stream_Buffer(asn1Payload); + const size_t pos = Stream_GetPosition(asn1Payload); + if (pos > UINT32_MAX) + goto out; + resp->Asn1BufferHints.count = (UINT32)pos; + *pstatus = 0; + +out: + ndr_destroy_ComputeTgsChecksumReq(rcontext, nullptr, &req); + krb5_free_checksum_contents(rdpear->krbContext, &checksum); + Stream_Free(asn1Payload, FALSE); + return TRUE; +} + +static BOOL rdpear_kerb_BuildEncryptedAuthData(RDPEAR_PLUGIN* rdpear, NdrContext* rcontext, + wStream* s, UINT32* pstatus, KERB_ASN1_DATA* asn1) +{ + BuildEncryptedAuthDataReq req = WINPR_C_ARRAY_INIT; + krb5_data encrypted = WINPR_C_ARRAY_INIT; + wStream* asn1Payload = nullptr; + krb5_error_code rv = 0; + + *pstatus = ERROR_INVALID_DATA; + WLog_DBG(TAG, "-> BuildEncryptedAuthData"); + + if (!ndr_read_BuildEncryptedAuthDataReq(rcontext, s, nullptr, &req) || + !ndr_treat_deferred_read(rcontext, s)) + goto out; + + rv = kerb_do_encrypt(rdpear->krbContext, req.Key, (krb5_keyusage)req.KeyUsage, + req.PlainAuthData, &encrypted); + if (rv) + goto out; + + /* do the encoding */ + asn1Payload = rdpear_enc_EncryptedData(req.Key->reserved2, &encrypted); + if (!asn1Payload) + goto out; + + // WLog_DBG(TAG, "rdpear_kerb_BuildEncryptedAuthData resp="); + // winpr_HexDump(TAG, WLOG_DEBUG, Stream_Buffer(asn1Payload), Stream_GetPosition(asn1Payload)); + asn1->Pdu = 6; + asn1->Asn1Buffer = Stream_Buffer(asn1Payload); + const size_t pos = Stream_GetPosition(asn1Payload); + if (pos > UINT32_MAX) + goto out; + asn1->Asn1BufferHints.count = (UINT32)pos; + *pstatus = 0; + +out: + krb5_free_data_contents(rdpear->krbContext, &encrypted); + ndr_destroy_BuildEncryptedAuthDataReq(rcontext, nullptr, &req); + Stream_Free(asn1Payload, FALSE); + return TRUE; +} + +static char* KERB_RPC_UNICODESTR_to_charptr(const RPC_UNICODE_STRING* src) +{ + WINPR_ASSERT(src); + return ConvertWCharNToUtf8Alloc(src->Buffer, src->strLength, nullptr); +} + +static BOOL extractAuthData(const KERB_ASN1_DATA* src, krb5_authdata* authData, BOOL* haveData) +{ + WinPrAsn1Decoder dec = WinPrAsn1Decoder_init(); + WinPrAsn1Decoder dec2 = WinPrAsn1Decoder_init(); + WinPrAsn1Decoder dec3 = WinPrAsn1Decoder_init(); + WinPrAsn1Decoder_InitMem(&dec, WINPR_ASN1_DER, src->Asn1Buffer, src->Asn1BufferHints.count); + BOOL error = FALSE; + WinPrAsn1_INTEGER adType = 0; + WinPrAsn1_OctetString os = WINPR_C_ARRAY_INIT; + + *haveData = FALSE; + if (!WinPrAsn1DecReadSequence(&dec, &dec2)) + return FALSE; + + wStream subStream = WinPrAsn1DecGetStream(&dec2); + if (!Stream_GetRemainingLength(&subStream)) + return TRUE; + + if (!WinPrAsn1DecReadSequence(&dec2, &dec3)) + return FALSE; + + if (!WinPrAsn1DecReadContextualInteger(&dec3, 0, &error, &adType) || + !WinPrAsn1DecReadContextualOctetString(&dec3, 1, &error, &os, FALSE)) + return FALSE; + + if (os.len > UINT32_MAX) + return FALSE; + + authData->ad_type = adType; + authData->length = (unsigned int)os.len; + authData->contents = os.data; + *haveData = TRUE; + return TRUE; +} + +static BOOL extractChecksum(const KERB_ASN1_DATA* src, krb5_checksum* dst) +{ + WinPrAsn1Decoder dec = WinPrAsn1Decoder_init(); + WinPrAsn1Decoder dec2 = WinPrAsn1Decoder_init(); + WinPrAsn1Decoder_InitMem(&dec, WINPR_ASN1_DER, src->Asn1Buffer, src->Asn1BufferHints.count); + BOOL error = FALSE; + WinPrAsn1_OctetString os; + + if (!WinPrAsn1DecReadSequence(&dec, &dec2)) + return FALSE; + + WinPrAsn1_INTEGER cksumtype = 0; + if (!WinPrAsn1DecReadContextualInteger(&dec2, 0, &error, &cksumtype) || + !WinPrAsn1DecReadContextualOctetString(&dec2, 1, &error, &os, FALSE)) + return FALSE; + + if (os.len > UINT32_MAX) + return FALSE; + dst->checksum_type = cksumtype; + dst->length = (unsigned int)os.len; + dst->contents = os.data; + return TRUE; +} + +#define FILETIME_TO_UNIX_OFFSET_S 11644473600LL + +static LONGLONG krb5_time_to_FILETIME(const krb5_timestamp* ts, krb5_int32 usec) +{ + WINPR_ASSERT(ts); + return (((*ts + FILETIME_TO_UNIX_OFFSET_S) * (1000LL * 1000LL) + usec) * 10LL); +} + +static void krb5_free_principal_contents(krb5_context ctx, krb5_principal principal) +{ + WINPR_ASSERT(principal); + krb5_free_data_contents(ctx, &principal->realm); + krb5_free_data(ctx, principal->data); +} + +static BOOL rdpear_kerb_CreateApReqAuthenticator(RDPEAR_PLUGIN* rdpear, NdrContext* rcontext, + wStream* s, UINT32* pstatus, + CreateApReqAuthenticatorResp* resp) +{ + krb5_error_code rv = 0; + wStream* asn1EncodedAuth = nullptr; + CreateApReqAuthenticatorReq req = WINPR_C_ARRAY_INIT; + krb5_data authenticator = WINPR_C_ARRAY_INIT; + krb5_data* der = nullptr; + krb5_keyblock* subkey = nullptr; + krb5_principal_data client = WINPR_C_ARRAY_INIT; + + *pstatus = ERROR_INVALID_DATA; + WLog_DBG(TAG, "-> CreateApReqAuthenticator"); + + if (!ndr_read_CreateApReqAuthenticatorReq(rcontext, s, nullptr, &req) || + !ndr_treat_deferred_read(rcontext, s)) + goto out; + + krb5_authdata authdata = WINPR_C_ARRAY_INIT; + krb5_authdata* authDataPtr[2] = { &authdata, nullptr }; + BOOL haveData = 0; + + if (!extractAuthData(req.AuthData, &authdata, &haveData)) + { + WLog_ERR(TAG, "error retrieving auth data"); + winpr_HexDump(TAG, WLOG_DEBUG, req.AuthData->Asn1Buffer, + req.AuthData->Asn1BufferHints.count); + goto out; + } + + if (req.SkewTime->QuadPart) + { + WLog_ERR(TAG, "!!!!! should handle SkewTime !!!!!"); + } + + if (req.SubKey) + { + rv = RPC_ENCRYPTION_KEY_to_keyblock(rdpear->krbContext, req.SubKey, &subkey); + if (rv) + { + WLog_ERR(TAG, "error importing subkey"); + goto out; + } + } + + krb5_authenticator authent = { .checksum = nullptr, + .subkey = nullptr, + .seq_number = req.SequenceNumber, + .authorization_data = haveData ? authDataPtr : nullptr }; + + client.type = req.ClientName->NameType; + if (req.ClientName->nameHints.count > INT32_MAX) + goto out; + + client.length = (krb5_int32)req.ClientName->nameHints.count; + client.data = calloc(req.ClientName->nameHints.count, sizeof(krb5_data)); + if (!client.data) + goto out; + + for (int i = 0; i < client.length; i++) + { + krb5_data* cur = &client.data[i]; + cur->data = KERB_RPC_UNICODESTR_to_charptr(&req.ClientName->Names[i]); + if (!cur->data) + goto out; + const size_t len = strnlen(cur->data, MAX_KEYTAB_NAME_LEN + 1); + if (len > MAX_KEYTAB_NAME_LEN) + { + WLog_ERR(TAG, + "Invalid ClientName length %d, limited to %" PRIuz + " characters. ClientName: (%s)", + MAX_KEYTAB_NAME_LEN, len, cur->data); + goto out; + } + cur->length = (unsigned int)len; + } + client.realm.data = KERB_RPC_UNICODESTR_to_charptr(req.ClientRealm); + if (!client.realm.data) + goto out; + + const size_t len = strnlen(client.realm.data, MAX_KEYTAB_NAME_LEN + 1); + if (len > MAX_KEYTAB_NAME_LEN) + { + WLog_ERR(TAG, "Invalid realm length %d, limited to %" PRIuz " characters. Realm: (%s)", + MAX_KEYTAB_NAME_LEN, len, client.realm.data); + goto out; + } + client.realm.length = (unsigned int)len; + authent.client = &client; + + krb5_checksum checksum; + krb5_checksum* pchecksum = nullptr; + if (req.GssChecksum) + { + if (!extractChecksum(req.GssChecksum, &checksum)) + { + WLog_ERR(TAG, "Error getting the checksum"); + goto out; + } + pchecksum = &checksum; + } + authent.checksum = pchecksum; + + krb5_us_timeofday(rdpear->krbContext, &authent.ctime, &authent.cusec); + + rv = encode_krb5_authenticator(&authent, &der); + if (rv) + { + WLog_ERR(TAG, "error encoding authenticator"); + goto out; + } + + KERB_ASN1_DATA plain_authent = { .Pdu = 0, + .Asn1Buffer = (BYTE*)der->data, + .Asn1BufferHints = { .count = der->length } }; + + rv = kerb_do_encrypt(rdpear->krbContext, req.EncryptionKey, (krb5_keyusage)req.KeyUsage, + &plain_authent, &authenticator); + if (rv) + { + WLog_ERR(TAG, "error encrypting authenticator"); + goto out; + } + + asn1EncodedAuth = rdpear_enc_EncryptedData(req.EncryptionKey->reserved2, &authenticator); + if (!asn1EncodedAuth) + { + WLog_ERR(TAG, "error encoding to ASN1"); + rv = ENOMEM; + goto out; + } + + // WLog_DBG(TAG, "authenticator="); + // winpr_HexDump(TAG, WLOG_DEBUG, Stream_Buffer(asn1EncodedAuth), + // Stream_GetPosition(asn1EncodedAuth)); + + const size_t size = Stream_GetPosition(asn1EncodedAuth); + if (size > UINT32_MAX) + goto out; + resp->Authenticator.Asn1BufferHints.count = (UINT32)size; + resp->Authenticator.Asn1Buffer = Stream_Buffer(asn1EncodedAuth); + resp->AuthenticatorTime.QuadPart = krb5_time_to_FILETIME(&authent.ctime, authent.cusec); + *pstatus = 0; + +out: + resp->Authenticator.Pdu = 6; + resp->KerbProtocolError = rv; + krb5_free_principal_contents(rdpear->krbContext, &client); + krb5_free_data(rdpear->krbContext, der); + krb5_free_data_contents(rdpear->krbContext, &authenticator); + if (subkey) + krb5_free_keyblock(rdpear->krbContext, subkey); + ndr_destroy_CreateApReqAuthenticatorReq(rcontext, nullptr, &req); + Stream_Free(asn1EncodedAuth, FALSE); + return TRUE; +} + +static BOOL rdpear_findEncryptedData(const KERB_ASN1_DATA* src, int* penctype, krb5_data* data) +{ + WinPrAsn1Decoder dec = WinPrAsn1Decoder_init(); + WinPrAsn1Decoder dec2 = WinPrAsn1Decoder_init(); + WinPrAsn1Decoder_InitMem(&dec, WINPR_ASN1_DER, src->Asn1Buffer, src->Asn1BufferHints.count); + BOOL error = FALSE; + WinPrAsn1_INTEGER encType = 0; + WinPrAsn1_OctetString os = WINPR_C_ARRAY_INIT; + + if (!WinPrAsn1DecReadSequence(&dec, &dec2) || + !WinPrAsn1DecReadContextualInteger(&dec2, 0, &error, &encType) || + !WinPrAsn1DecReadContextualOctetString(&dec2, 2, &error, &os, FALSE)) + return FALSE; + + if (os.len > UINT32_MAX) + return FALSE; + data->data = (char*)os.data; + data->length = (unsigned int)os.len; + *penctype = encType; + return TRUE; +} + +static BOOL rdpear_kerb_UnpackKdcReplyBody(RDPEAR_PLUGIN* rdpear, NdrContext* rcontext, wStream* s, + UINT32* pstatus, UnpackKdcReplyBodyResp* resp) +{ + UnpackKdcReplyBodyReq req = WINPR_C_ARRAY_INIT; + + *pstatus = ERROR_INVALID_DATA; + + if (!ndr_read_UnpackKdcReplyBodyReq(rcontext, s, nullptr, &req) || + !ndr_treat_deferred_read(rcontext, s)) + goto out; + + if (req.StrengthenKey) + { + WLog_ERR(TAG, "StrengthenKey not supported yet"); + goto out; + } + + WLog_DBG(TAG, "-> UnpackKdcReplyBody: KeyUsage=0x%x PDU=0x%x", req.KeyUsage, req.Pdu); + // WLog_DBG(TAG, "encryptedPayload="); + // winpr_HexDump(TAG, WLOG_DEBUG, req.EncryptedData->Asn1Buffer, + // req.EncryptedData->Asn1BufferHints.count); + + krb5_data asn1Data = WINPR_C_ARRAY_INIT; + int encType = 0; + if (!rdpear_findEncryptedData(req.EncryptedData, &encType, &asn1Data) || !asn1Data.length) + goto out; + + resp->KerbProtocolError = kerb_do_decrypt( + rdpear->krbContext, req.Key, (krb5_keyusage)req.KeyUsage, &asn1Data, &resp->ReplyBody); + resp->ReplyBody.Pdu = req.Pdu; + + *pstatus = 0; + +out: + ndr_destroy_UnpackKdcReplyBodyReq(rcontext, nullptr, &req); + return TRUE; +} + +static BOOL rdpear_kerb_DecryptApReply(RDPEAR_PLUGIN* rdpear, NdrContext* rcontext, wStream* s, + UINT32* pstatus, KERB_ASN1_DATA* resp) +{ + DecryptApReplyReq req = WINPR_C_ARRAY_INIT; + + *pstatus = ERROR_INVALID_DATA; + if (!ndr_read_DecryptApReplyReq(rcontext, s, nullptr, &req) || + !ndr_treat_deferred_read(rcontext, s)) + goto out; + + WLog_DBG(TAG, "-> DecryptApReply"); + // winpr_HexDump(TAG, WLOG_DEBUG, req.EncryptedReply->Asn1Buffer, + // req.EncryptedReply->Asn1BufferHints.count); + + krb5_data asn1Data = WINPR_C_ARRAY_INIT; + int encType = 0; + if (!rdpear_findEncryptedData(req.EncryptedReply, &encType, &asn1Data) || !asn1Data.length) + goto out; + + resp->Pdu = 0x31; + krb5_error_code rv = + kerb_do_decrypt(rdpear->krbContext, req.Key, KRB5_KEYUSAGE_AP_REP_ENCPART, &asn1Data, resp); + if (rv != 0) + { + WLog_ERR(TAG, "error decrypting"); + goto out; + } + + // WLog_DBG(TAG, "response="); + // winpr_HexDump(TAG, WLOG_DEBUG, resp->Asn1Buffer, resp->Asn1BufferHints.count); + *pstatus = 0; +out: + ndr_destroy_DecryptApReplyReq(rcontext, nullptr, &req); + return TRUE; +} + +static BOOL rdpear_kerb_PackApReply(RDPEAR_PLUGIN* rdpear, NdrContext* rcontext, wStream* s, + UINT32* pstatus, PackApReplyResp* resp) +{ + PackApReplyReq req = WINPR_C_ARRAY_INIT; + krb5_data asn1Data = WINPR_C_ARRAY_INIT; + krb5_data* out = nullptr; + + WLog_DBG(TAG, "-> PackApReply"); + *pstatus = ERROR_INVALID_DATA; + if (!ndr_read_PackApReplyReq(rcontext, s, nullptr, &req) || + !ndr_treat_deferred_read(rcontext, s)) + goto out; + + krb5_error_code rv = kerb_do_encrypt(rdpear->krbContext, req.SessionKey, + KRB5_KEYUSAGE_AP_REP_ENCPART, req.ReplyBody, &asn1Data); + if (rv) + goto out; + + krb5_ap_rep reply; + reply.enc_part.kvno = KRB5_PVNO; + reply.enc_part.enctype = (krb5_enctype)req.SessionKey->reserved2; + reply.enc_part.ciphertext.length = asn1Data.length; + reply.enc_part.ciphertext.data = asn1Data.data; + + rv = encode_krb5_ap_rep(&reply, &out); + if (rv) + goto out; + + resp->PackedReply = (BYTE*)out->data; + resp->PackedReplyHints.count = out->length; + *pstatus = 0; +out: + free(out); + krb5_free_data_contents(rdpear->krbContext, &asn1Data); + ndr_destroy_PackApReplyReq(rcontext, nullptr, &req); + return TRUE; +} + +static BOOL rdpear_ntlm_version(NdrContext* rcontext, wStream* s, UINT32* pstatus, UINT32* pversion) +{ + *pstatus = ERROR_INVALID_DATA; + + if (!ndr_read_uint32(rcontext, s, pversion)) + return TRUE; + + WLog_DBG(TAG, "-> NtlmNegotiateVersion(v=0x%x)", *pversion); + *pstatus = 0; + + return TRUE; +} + +static UINT rdpear_decode_payload(RDPEAR_PLUGIN* rdpear, + IWTSVirtualChannelCallback* pChannelCallback, + const WinPrAsn1_OctetString* packageName, wStream* s) +{ + UINT ret = ERROR_INVALID_DATA; + NdrContext* context = nullptr; + NdrContext* wcontext = nullptr; + UINT32 status = 0; + + UINT32 uint32Resp = 0; + KERB_ASN1_DATA asn1Data = WINPR_C_ARRAY_INIT; + CreateApReqAuthenticatorResp createApReqAuthenticatorResp = WINPR_C_ARRAY_INIT; + UnpackKdcReplyBodyResp unpackKdcReplyBodyResp = WINPR_C_ARRAY_INIT; + PackApReplyResp packApReplyResp = WINPR_C_ARRAY_INIT; + void* resp = nullptr; + NdrMessageType respDescr = nullptr; + + wStream* respStream = Stream_New(nullptr, 500); + if (!respStream) + goto out; + + BOOL isKerb = FALSE; + switch (rdpear_packageType_from_name(packageName)) + { + case RDPEAR_PACKAGE_KERBEROS: + isKerb = TRUE; + break; + case RDPEAR_PACKAGE_NTLM: + isKerb = FALSE; + break; + default: + WLog_ERR(TAG, "unknown package type"); + goto out; + } + + Stream_Seek(s, 16); /* skip first 16 bytes */ + wStream commandStream = WINPR_C_ARRAY_INIT; + UINT16 callId = 0; + UINT16 callId2 = 0; + + context = ndr_read_header(s); + if (!context || !ndr_read_constructed(context, s, &commandStream) || + !ndr_read_pickle(context, &commandStream)) + goto out; + + if (isKerb) + { + /* for some reason there's 4 zero undocumented bytes here after the pickle record + * in the kerberos package packets */ + UINT32 v = 0; + if (!ndr_read_uint32(context, &commandStream, &v)) + goto out; + } + + if (!ndr_read_uint16(context, &commandStream, &callId) || + !ndr_read_uint16(context, &commandStream, &callId2) || (callId != callId2)) + goto out; + + ret = CHANNEL_RC_NOT_OPEN; + switch (callId) + { + case RemoteCallKerbNegotiateVersion: + resp = &uint32Resp; + respDescr = ndr_uint32_descr(); + + if (rdpear_kerb_version(context, &commandStream, &status, &uint32Resp)) + ret = CHANNEL_RC_OK; + break; + case RemoteCallKerbCreateApReqAuthenticator: + resp = &createApReqAuthenticatorResp; + respDescr = ndr_CreateApReqAuthenticatorResp_descr(); + + if (rdpear_kerb_CreateApReqAuthenticator(rdpear, context, &commandStream, &status, + &createApReqAuthenticatorResp)) + ret = CHANNEL_RC_OK; + break; + case RemoteCallKerbDecryptApReply: + resp = &asn1Data; + respDescr = ndr_KERB_ASN1_DATA_descr(); + + if (rdpear_kerb_DecryptApReply(rdpear, context, &commandStream, &status, &asn1Data)) + ret = CHANNEL_RC_OK; + break; + case RemoteCallKerbComputeTgsChecksum: + resp = &asn1Data; + respDescr = ndr_KERB_ASN1_DATA_descr(); + + if (rdpear_kerb_ComputeTgsChecksum(rdpear, context, &commandStream, &status, &asn1Data)) + ret = CHANNEL_RC_OK; + break; + case RemoteCallKerbBuildEncryptedAuthData: + resp = &asn1Data; + respDescr = ndr_KERB_ASN1_DATA_descr(); + + if (rdpear_kerb_BuildEncryptedAuthData(rdpear, context, &commandStream, &status, + &asn1Data)) + ret = CHANNEL_RC_OK; + break; + case RemoteCallKerbUnpackKdcReplyBody: + resp = &unpackKdcReplyBodyResp; + respDescr = ndr_UnpackKdcReplyBodyResp_descr(); + + if (rdpear_kerb_UnpackKdcReplyBody(rdpear, context, &commandStream, &status, + &unpackKdcReplyBodyResp)) + ret = CHANNEL_RC_OK; + break; + case RemoteCallKerbPackApReply: + resp = &packApReplyResp; + respDescr = ndr_PackApReplyResp_descr(); + + if (rdpear_kerb_PackApReply(rdpear, context, &commandStream, &status, &packApReplyResp)) + ret = CHANNEL_RC_OK; + break; + case RemoteCallNtlmNegotiateVersion: + resp = &uint32Resp; + respDescr = ndr_uint32_descr(); + + if (rdpear_ntlm_version(context, &commandStream, &status, &uint32Resp)) + ret = CHANNEL_RC_OK; + break; + + default: + WLog_DBG(TAG, "Unhandled callId=0x%x", callId); + winpr_HexDump(TAG, WLOG_DEBUG, Stream_PointerAs(&commandStream, BYTE), + Stream_GetRemainingLength(&commandStream)); + break; + } + + if (!rdpear_prepare_response(context, isKerb, callId, status, &wcontext, respStream)) + goto out; + + if (resp && respDescr) + { + WINPR_ASSERT(respDescr->writeFn); + + BOOL r = respDescr->writeFn(wcontext, respStream, nullptr, resp) && + ndr_treat_deferred_write(wcontext, respStream); + + if (respDescr->destroyFn) + respDescr->destroyFn(wcontext, nullptr, resp); + + if (!r) + { + WLog_DBG(TAG, "!writeFn || !ndr_treat_deferred_write"); + goto out; + } + } + + if (!ndr_end_constructed(wcontext, respStream) || + !rdpear_send_payload(rdpear, pChannelCallback, isKerb, respStream)) + { + WLog_DBG(TAG, "rdpear_send_payload !!!!!!!!"); + goto out; + } +out: + if (context) + ndr_context_destroy(&context); + + if (wcontext) + ndr_context_destroy(&wcontext); + + if (respStream) + Stream_Free(respStream, TRUE); + return ret; +} + +static UINT rdpear_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* s) +{ + GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + WINPR_ASSERT(callback); + UINT ret = ERROR_INVALID_DATA; + + // winpr_HexDump(TAG, WLOG_DEBUG, Stream_PointerAs(s, BYTE), Stream_GetRemainingLength(s)); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 24)) + return ERROR_INVALID_DATA; + + UINT32 protocolMagic = 0; + UINT32 Length = 0; + UINT32 Version = 0; + Stream_Read_UINT32(s, protocolMagic); + if (protocolMagic != 0x4EACC3C8) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, Length); + + Stream_Read_UINT32(s, Version); + if (Version != 0x00000000) + return ERROR_INVALID_DATA; + + Stream_Seek(s, 4); /* Reserved (4 bytes) */ + Stream_Seek(s, 8); /* TsPkgContext (8 bytes) */ + + if (!Stream_CheckAndLogRequiredLength(TAG, s, Length)) + return ERROR_INVALID_DATA; + + SecBuffer inBuffer = { Length, SECBUFFER_TOKEN, Stream_PointerAs(s, void) }; + SecBuffer decrypted = WINPR_C_ARRAY_INIT; + + RDPEAR_PLUGIN* rdpear = (RDPEAR_PLUGIN*)callback->plugin; + WINPR_ASSERT(rdpear); + if (!freerdp_nla_decrypt(rdpear->rdp_context, &inBuffer, &decrypted)) + goto out; + + WinPrAsn1Decoder dec = WinPrAsn1Decoder_init(); + WinPrAsn1Decoder dec2 = WinPrAsn1Decoder_init(); + wStream decodedStream = WINPR_C_ARRAY_INIT; + Stream_StaticInit(&decodedStream, decrypted.pvBuffer, decrypted.cbBuffer); + WinPrAsn1Decoder_Init(&dec, WINPR_ASN1_DER, &decodedStream); + + if (!WinPrAsn1DecReadSequence(&dec, &dec2)) + goto out; + + WinPrAsn1_OctetString packageName = WINPR_C_ARRAY_INIT; + WinPrAsn1_OctetString payload = WINPR_C_ARRAY_INIT; + BOOL error = 0; + if (!WinPrAsn1DecReadContextualOctetString(&dec2, 1, &error, &packageName, FALSE)) + goto out; + + if (!WinPrAsn1DecReadContextualOctetString(&dec2, 2, &error, &payload, FALSE)) + goto out; + + wStream payloadStream = WINPR_C_ARRAY_INIT; + Stream_StaticInit(&payloadStream, payload.data, payload.len); + + ret = rdpear_decode_payload(rdpear, pChannelCallback, &packageName, &payloadStream); +out: + sspi_SecBufferFree(&decrypted); + return ret; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpear_on_open(IWTSVirtualChannelCallback* pChannelCallback) +{ + WINPR_UNUSED(pChannelCallback); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpear_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + WINPR_UNUSED(pChannelCallback); + return CHANNEL_RC_OK; +} + +static void terminate_plugin_cb(GENERIC_DYNVC_PLUGIN* base) +{ + WINPR_ASSERT(base); + + RDPEAR_PLUGIN* rdpear = (RDPEAR_PLUGIN*)base; + krb5_free_context(rdpear->krbContext); +} + +static UINT init_plugin_cb(GENERIC_DYNVC_PLUGIN* base, rdpContext* rcontext, rdpSettings* settings) +{ + WINPR_ASSERT(base); + WINPR_UNUSED(settings); + + RDPEAR_PLUGIN* rdpear = (RDPEAR_PLUGIN*)base; + rdpear->rdp_context = rcontext; + if (krb5_init_context(&rdpear->krbContext)) + return CHANNEL_RC_INITIALIZATION_ERROR; + return CHANNEL_RC_OK; +} + +static const IWTSVirtualChannelCallback rdpear_callbacks = { rdpear_on_data_received, + rdpear_on_open, rdpear_on_close, + nullptr }; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT rdpear_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)) +{ + return freerdp_generic_DVCPluginEntry(pEntryPoints, TAG, RDPEAR_DVC_CHANNEL_NAME, + sizeof(RDPEAR_PLUGIN), sizeof(GENERIC_CHANNEL_CALLBACK), + &rdpear_callbacks, init_plugin_cb, terminate_plugin_cb); +} diff --git a/third_party/FreeRDP/channels/rdpear/common/CMakeLists.txt b/third_party/FreeRDP/channels/rdpear/common/CMakeLists.txt new file mode 100644 index 0000000..f5a5930 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpear/common/CMakeLists.txt @@ -0,0 +1,40 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2024 David Fort +# +# 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. + +add_library(rdpear-common INTERFACE) +add_library( + rdpear-common-obj STATIC ndr.c rdpear_asn1.c rdpear_common.c rdpear-common/ndr.h rdpear-common/rdpear_asn1.h + rdpear-common/rdpear_common.h +) + +target_include_directories(rdpear-common SYSTEM INTERFACE ${KRB5_INCLUDE_DIRS}) +target_compile_options(rdpear-common INTERFACE ${KRB5_CFLAGS}) +target_link_options(rdpear-common INTERFACE ${KRB5_LDFLAGS}) + +target_include_directories(rdpear-common-obj PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +target_include_directories(rdpear-common-obj SYSTEM PRIVATE ${KRB5_INCLUDE_DIRS}) +target_link_directories(rdpear-common INTERFACE ${KRB5_LIBRARY_DIRS}) +target_link_libraries(rdpear-common INTERFACE ${KRB5_LIBRARIES} rdpear-common-obj) + +freerdp_client_pc_add_library_private(rdpear-common-obj) + +channel_install(rdpear-common ${FREERDP_ADDIN_PATH} "FreeRDPTargets") +channel_install(rdpear-common-obj ${FREERDP_ADDIN_PATH} "FreeRDPTargets") + +if(BUILD_TESTING_INTERNAL OR BUILD_TESTING) + add_subdirectory(test) +endif() diff --git a/third_party/FreeRDP/channels/rdpear/common/ndr.c b/third_party/FreeRDP/channels/rdpear/common/ndr.c new file mode 100644 index 0000000..6e4b10a --- /dev/null +++ b/third_party/FreeRDP/channels/rdpear/common/ndr.c @@ -0,0 +1,1011 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Authentication redirection virtual channel + * + * Copyright 2024 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include + +#include + +#include + +#define TAG FREERDP_TAG("ndr") + +#define NDR_MAX_CONSTRUCTS 16 +#define NDR_MAX_DEFERRED 50 + +struct NdrContext_s +{ + BYTE version; + BOOL bigEndianDrep; + size_t alignBytes; + + int currentLevel; + size_t indentLevels[16]; + + int constructLevel; + size_t constructs[NDR_MAX_CONSTRUCTS]; + + wHashTable* refPointers; + size_t ndeferred; + NdrDeferredEntry deferred[NDR_MAX_DEFERRED]; + + UINT32 refIdCounter; +}; + +NdrContext* ndr_context_new(BOOL bigEndianDrep, BYTE version) +{ + NdrContext* ret = calloc(1, sizeof(*ret)); + if (!ret) + return nullptr; + + ret->version = version; + ret->bigEndianDrep = bigEndianDrep; + ret->alignBytes = 4; + ret->refPointers = HashTable_New(FALSE); + if (!ret->refPointers) + { + free(ret); + return nullptr; + } + + ndr_context_reset(ret); + return ret; +} + +void ndr_context_reset(NdrContext* context) +{ + WINPR_ASSERT(context); + + context->currentLevel = 0; + context->constructLevel = -1; + memset(context->indentLevels, 0, sizeof(context->indentLevels)); + + if (context->refPointers) + HashTable_Clear(context->refPointers); + context->ndeferred = 0; + context->refIdCounter = 0x20000; +} + +NdrContext* ndr_context_copy(const NdrContext* src) +{ + WINPR_ASSERT(src); + + NdrContext* ret = calloc(1, sizeof(*ret)); + if (!ret) + return nullptr; + + *ret = *src; + + ret->refPointers = HashTable_New(FALSE); + if (!ret->refPointers) + { + free(ret); + return nullptr; + } + + ndr_context_reset(ret); + return ret; +} + +void ndr_context_free(NdrContext* context) +{ + if (context) + { + HashTable_Free(context->refPointers); + free(context); + } +} + +static void ndr_context_bytes_read(NdrContext* context, size_t len) +{ + WINPR_ASSERT(context); + context->indentLevels[context->currentLevel] += len; +} + +static void ndr_context_bytes_written(NdrContext* context, size_t len) +{ + ndr_context_bytes_read(context, len); +} + +NdrContext* ndr_read_header(wStream* s) +{ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return nullptr; + + BYTE version = Stream_Get_UINT8(s); + BYTE drep = Stream_Get_UINT8(s); + UINT16 headerLen = Stream_Get_UINT16(s); + + if (headerLen < 4 || !Stream_CheckAndLogRequiredLength(TAG, s, headerLen - 4)) + return nullptr; + + /* skip filler */ + Stream_Seek(s, headerLen - 4); + + return ndr_context_new((drep != 0x10), version); +} + +BOOL ndr_write_header(NdrContext* context, wStream* s) +{ + WINPR_ASSERT(context); + + if (!Stream_EnsureRemainingCapacity(s, 8)) + return FALSE; + + Stream_Write_UINT8(s, context->version); + Stream_Write_UINT8(s, context->bigEndianDrep ? 0x00 : 0x10); + Stream_Write_UINT16(s, 0x8); /* header len */ + + BYTE filler[] = { 0xcc, 0xcc, 0xcc, 0xcc }; + Stream_Write(s, filler, sizeof(filler)); + return TRUE; +} + +BOOL ndr_skip_bytes(NdrContext* context, wStream* s, size_t nbytes) +{ + WINPR_ASSERT(context); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, nbytes)) + return FALSE; + + context->indentLevels[context->currentLevel] += nbytes; + Stream_Seek(s, nbytes); + return TRUE; +} + +BOOL ndr_read_align(NdrContext* context, wStream* s, size_t sz) +{ + WINPR_ASSERT(context); + + size_t rest = context->indentLevels[context->currentLevel] % sz; + if (rest) + { + size_t padding = (sz - rest); + if (!Stream_CheckAndLogRequiredLength(TAG, s, padding)) + return FALSE; + + Stream_Seek(s, padding); + context->indentLevels[context->currentLevel] += padding; + } + + return TRUE; +} + +BOOL ndr_write_align(NdrContext* context, wStream* s, size_t sz) +{ + WINPR_ASSERT(context); + + size_t rest = context->indentLevels[context->currentLevel] % sz; + if (rest) + { + size_t padding = (sz - rest); + + if (!Stream_EnsureRemainingCapacity(s, padding)) + return FALSE; + + Stream_Zero(s, padding); + context->indentLevels[context->currentLevel] += padding; + } + + return TRUE; +} + +BOOL ndr_read_pickle(NdrContext* context, wStream* s) +{ + WINPR_ASSERT(context); + + UINT32 v = 0; + + /* NDR format label */ + return !(!ndr_read_uint32(context, s, &v) || v != 0x20000); +} + +BOOL ndr_write_pickle(NdrContext* context, wStream* s) +{ + WINPR_ASSERT(context); + + /* NDR format label */ + return ndr_write_uint32(context, s, 0x20000); +} + +BOOL ndr_read_constructed(NdrContext* context, wStream* s, wStream* target) +{ + WINPR_ASSERT(context); + + UINT32 len = 0; + + /* len */ + if (!ndr_read_uint32(context, s, &len)) + return FALSE; + + /* padding */ + if (!ndr_skip_bytes(context, s, 4)) + return FALSE; + + /* payload */ + if (!Stream_CheckAndLogRequiredLength(TAG, s, len)) + return FALSE; + + Stream_StaticInit(target, Stream_PointerAs(s, BYTE), len); + Stream_Seek(s, len); + return TRUE; +} + +BOOL ndr_start_constructed(NdrContext* context, wStream* s) +{ + WINPR_ASSERT(context); + + if (!Stream_EnsureRemainingCapacity(s, 8)) + return FALSE; + + if (context->constructLevel == NDR_MAX_CONSTRUCTS) + return FALSE; + + context->constructLevel++; + context->constructs[context->constructLevel] = Stream_GetPosition(s); + + Stream_Zero(s, 8); + return TRUE; +} + +BOOL ndr_end_constructed(NdrContext* context, wStream* s) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->constructs); + WINPR_ASSERT(context->constructLevel >= 0); + + size_t offset = context->constructs[context->constructLevel]; + + wStream staticS = WINPR_C_ARRAY_INIT; + Stream_StaticInit(&staticS, Stream_Buffer(s) + offset, 4); + + /* len */ + const size_t len = Stream_GetPosition(s) - (offset + 8); + if (len > UINT32_MAX) + return FALSE; + if (!ndr_write_uint32(context, &staticS, (UINT32)len)) + return FALSE; + + return TRUE; +} + +static size_t ndr_hintsCount(NdrMessageType msgType, const void* hints) +{ + WINPR_ASSERT(msgType); + + switch (msgType->arity) + { + case NDR_ARITY_SIMPLE: + return 1; + case NDR_ARITY_ARRAYOF: + WINPR_ASSERT(hints); + return ((const NdrArrayHints*)hints)->count; + case NDR_ARITY_VARYING_ARRAYOF: + WINPR_ASSERT(hints); + return ((const NdrVaryingArrayHints*)hints)->maxLength; + default: + WINPR_ASSERT(0 && "unknown arity"); + return 0; + } +} + +BOOL ndr_read_uint8(NdrContext* context, wStream* s, BYTE* v) +{ + WINPR_ASSERT(context); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return FALSE; + + Stream_Read_UINT8(s, *v); + + ndr_context_bytes_read(context, 1); + return TRUE; +} + +BOOL ndr_read_uint8_(NdrContext* context, wStream* s, const void* hints, void* v) +{ + WINPR_UNUSED(hints); + return ndr_read_uint8(context, s, (BYTE*)v); +} + +BOOL ndr_write_uint8(NdrContext* context, wStream* s, BYTE v) +{ + if (!Stream_EnsureRemainingCapacity(s, 1)) + return FALSE; + + Stream_Write_UINT8(s, v); + ndr_context_bytes_written(context, 1); + return TRUE; +} + +BOOL ndr_write_uint8_(NdrContext* context, wStream* s, const void* hints, const void* v) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(s); + WINPR_ASSERT(v); + WINPR_UNUSED(hints); + + return ndr_write_uint8(context, s, *(const BYTE*)v); +} + +const static NdrMessageDescr uint8_descr = { NDR_ARITY_SIMPLE, 1, ndr_read_uint8_, + ndr_write_uint8_, nullptr, nullptr }; + +NdrMessageType ndr_uint8_descr(void) +{ + return &uint8_descr; +} + +#define SIMPLE_TYPE_IMPL(UPPERTYPE, LOWERTYPE) \ + BOOL ndr_read_##LOWERTYPE(NdrContext* context, wStream* s, UPPERTYPE* v) \ + { \ + WINPR_ASSERT(context); \ + \ + if (!Stream_CheckAndLogRequiredLength(TAG, s, sizeof(UPPERTYPE))) \ + return FALSE; \ + \ + if (!ndr_read_align(context, s, sizeof(UPPERTYPE))) \ + return FALSE; \ + \ + if (context->bigEndianDrep) \ + Stream_Read_##UPPERTYPE##_BE(s, *v); \ + else \ + Stream_Read_##UPPERTYPE(s, *v); \ + \ + ndr_context_bytes_read(context, sizeof(UPPERTYPE)); \ + return TRUE; \ + } \ + \ + BOOL ndr_read_##LOWERTYPE##_(NdrContext* context, wStream* s, const void* hints, void* v) \ + { \ + WINPR_UNUSED(hints); \ + return ndr_read_##LOWERTYPE(context, s, (UPPERTYPE*)v); \ + } \ + \ + BOOL ndr_write_##LOWERTYPE(NdrContext* context, wStream* s, UPPERTYPE v) \ + { \ + if (!ndr_write_align(context, s, sizeof(UPPERTYPE)) || \ + !Stream_EnsureRemainingCapacity(s, sizeof(UPPERTYPE))) \ + return FALSE; \ + \ + if (context->bigEndianDrep) \ + Stream_Write_##UPPERTYPE##_BE(s, v); \ + else \ + Stream_Write_##UPPERTYPE(s, v); \ + \ + ndr_context_bytes_written(context, sizeof(UPPERTYPE)); \ + return TRUE; \ + } \ + \ + BOOL ndr_write_##LOWERTYPE##_(NdrContext* context, wStream* s, const void* hints, \ + const void* v) \ + { \ + WINPR_ASSERT(context); \ + WINPR_ASSERT(s); \ + WINPR_ASSERT(v); \ + WINPR_UNUSED(hints); \ + \ + return ndr_write_##LOWERTYPE(context, s, *(const UPPERTYPE*)v); \ + } \ + \ + const NdrMessageDescr ndr_##LOWERTYPE##_descr_s = { \ + NDR_ARITY_SIMPLE, sizeof(UPPERTYPE), ndr_read_##LOWERTYPE##_, \ + ndr_write_##LOWERTYPE##_, nullptr, nullptr \ + }; \ + \ + NdrMessageType ndr_##LOWERTYPE##_descr(void) \ + { \ + return &ndr_##LOWERTYPE##_descr_s; \ + } + +SIMPLE_TYPE_IMPL(UINT32, uint32) +SIMPLE_TYPE_IMPL(UINT16, uint16) +SIMPLE_TYPE_IMPL(UINT64, uint64) + +#define ARRAY_OF_TYPE_IMPL(TYPE, UPPERTYPE) \ + BOOL ndr_read_##TYPE##Array(NdrContext* context, wStream* s, const void* hints, void* v) \ + { \ + WINPR_ASSERT(context); \ + WINPR_ASSERT(s); \ + WINPR_ASSERT(hints); \ + return ndr_read_uconformant_array(context, s, hints, ndr_##TYPE##_descr(), v); \ + } \ + \ + BOOL ndr_write_##TYPE##Array(NdrContext* context, wStream* s, const void* hints, \ + const void* v) \ + { \ + WINPR_ASSERT(context); \ + WINPR_ASSERT(s); \ + WINPR_ASSERT(hints); \ + const NdrArrayHints* ahints = (const NdrArrayHints*)hints; \ + return ndr_write_uconformant_array(context, s, ahints->count, ndr_##TYPE##_descr(), v); \ + } \ + void ndr_destroy_##TYPE##Array(NdrContext* context, const void* hints, void* obj) \ + { \ + WINPR_ASSERT(context); \ + WINPR_ASSERT(obj); \ + WINPR_ASSERT(hints); \ + const NdrArrayHints* ahints = (const NdrArrayHints*)hints; \ + NdrMessageType descr = ndr_##TYPE##_descr(); \ + if (descr->destroyFn) \ + { \ + UPPERTYPE* ptr = (UPPERTYPE*)obj; \ + for (UINT32 i = 0; i < ahints->count; i++, ptr++) \ + descr->destroyFn(context, nullptr, ptr); \ + } \ + } \ + \ + const NdrMessageDescr ndr_##TYPE##Array_descr_s = { \ + NDR_ARITY_ARRAYOF, sizeof(UPPERTYPE), ndr_read_##TYPE##Array, \ + ndr_write_##TYPE##Array, ndr_destroy_##TYPE##Array, nullptr \ + }; \ + \ + NdrMessageType ndr_##TYPE##Array_descr(void) \ + { \ + return &ndr_##TYPE##Array_descr_s; \ + } \ + \ + BOOL ndr_read_##TYPE##VaryingArray(NdrContext* context, wStream* s, const void* hints, \ + void* v) \ + { \ + WINPR_ASSERT(context); \ + WINPR_ASSERT(s); \ + WINPR_ASSERT(hints); \ + return ndr_read_uconformant_varying_array(context, s, (const NdrVaryingArrayHints*)hints, \ + ndr_##TYPE##_descr(), v); \ + } \ + BOOL ndr_write_##TYPE##VaryingArray(NdrContext* context, wStream* s, const void* hints, \ + const void* v) \ + { \ + WINPR_ASSERT(context); \ + WINPR_ASSERT(s); \ + WINPR_ASSERT(hints); \ + return ndr_write_uconformant_varying_array(context, s, (const NdrVaryingArrayHints*)hints, \ + ndr_##TYPE##_descr(), v); \ + } \ + \ + const NdrMessageDescr ndr_##TYPE##VaryingArray_descr_s = { \ + NDR_ARITY_VARYING_ARRAYOF, sizeof(UPPERTYPE), ndr_read_##TYPE##VaryingArray, \ + ndr_write_##TYPE##VaryingArray, nullptr, nullptr \ + }; \ + \ + NdrMessageType ndr_##TYPE##VaryingArray_descr(void) \ + { \ + return &ndr_##TYPE##VaryingArray_descr_s; \ + } + +ARRAY_OF_TYPE_IMPL(uint8, BYTE) +ARRAY_OF_TYPE_IMPL(uint16, UINT16) + +BOOL ndr_read_wchar(NdrContext* context, wStream* s, WCHAR* ptr) +{ + return ndr_read_uint16(context, s, (UINT16*)ptr); +} + +BOOL ndr_read_uconformant_varying_array(NdrContext* context, wStream* s, + const NdrVaryingArrayHints* hints, NdrMessageType itemType, + void* ptarget) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(s); + WINPR_ASSERT(hints); + WINPR_ASSERT(itemType); + WINPR_ASSERT(ptarget); + + if (itemType->itemSize == 0) + return FALSE; + + UINT32 maxCount = 0; + UINT32 offset = 0; + UINT32 length = 0; + + if (!ndr_read_uint32(context, s, &maxCount) || !ndr_read_uint32(context, s, &offset) || + !ndr_read_uint32(context, s, &length)) + return FALSE; + + if ((1ull * length * itemType->itemSize) > hints->length) + return FALSE; + + if ((1ull * maxCount * itemType->itemSize) > hints->maxLength) + return FALSE; + + BYTE* target = (BYTE*)ptarget; + for (UINT32 i = 0; i < length; i++, target += itemType->itemSize) + { + if (!itemType->readFn(context, s, nullptr, target)) + return FALSE; + } + + return ndr_read_align(context, s, 4); +} + +BOOL ndr_write_uconformant_varying_array(NdrContext* context, wStream* s, + const NdrVaryingArrayHints* hints, NdrMessageType itemType, + const void* psrc) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(s); + WINPR_ASSERT(hints); + WINPR_ASSERT(itemType); + WINPR_ASSERT(psrc); + + if (itemType->itemSize == 0) + return FALSE; + + if (!ndr_write_uint32(context, s, hints->maxLength) || !ndr_write_uint32(context, s, 0) || + !ndr_write_uint32(context, s, hints->length)) + return FALSE; + + const BYTE* src = (const BYTE*)psrc; + for (UINT32 i = 0; i < hints->length; i++, src += itemType->itemSize) + { + if (!itemType->writeFn(context, s, nullptr, src)) + return FALSE; + } + + return TRUE; +} + +BOOL ndr_read_uconformant_array(NdrContext* context, wStream* s, const NdrArrayHints* hints, + NdrMessageType itemType, void* vtarget) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(s); + WINPR_ASSERT(itemType); + WINPR_ASSERT(vtarget); + + if (itemType->itemSize == 0) + return FALSE; + + UINT32 count = 0; + if (!ndr_read_uint32(context, s, &count)) + return FALSE; + + if (itemType->arity == NDR_ARITY_SIMPLE) + { + if (count > hints->count) + return FALSE; + } + else + { + if ((1ull * count * itemType->itemSize) > hints->count) + return FALSE; + } + + BYTE* target = (BYTE*)vtarget; + for (UINT32 i = 0; i < count; i++, target += itemType->itemSize) + { + if (!itemType->readFn(context, s, nullptr, target)) + return FALSE; + } + + return ndr_read_align(context, s, /*context->alignBytes*/ 4); +} + +BOOL ndr_write_uconformant_array(NdrContext* context, wStream* s, UINT32 len, + NdrMessageType itemType, const BYTE* ptr) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(s); + WINPR_ASSERT(itemType); + WINPR_ASSERT(ptr); + + size_t toWrite = len * itemType->itemSize; + size_t padding = (4 - (toWrite % 4)) % 4; + if (!ndr_write_uint32(context, s, len) || !Stream_EnsureRemainingCapacity(s, toWrite + padding)) + return FALSE; + + for (UINT32 i = 0; i < len; i++, ptr += itemType->itemSize) + { + if (!itemType->writeFn(context, s, nullptr, ptr)) + return FALSE; + } + + if (padding) + { + Stream_Zero(s, padding); + ndr_context_bytes_written(context, padding); + } + return TRUE; +} + +BOOL ndr_struct_read_fromDescr(NdrContext* context, wStream* s, const NdrStructDescr* descr, + void* target) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(s); + WINPR_ASSERT(descr); + WINPR_ASSERT(target); + +#define NDR_MAX_STRUCT_DEFERRED 16 + NdrDeferredEntry deferreds[NDR_MAX_STRUCT_DEFERRED] = WINPR_C_ARRAY_INIT; + size_t ndeferred = 0; + + for (size_t i = 0; i < descr->nfields; i++) + { + const NdrFieldStruct* field = &descr->fields[i]; + BYTE* ptr = target; + ptr += field->structOffset; + void* hints = nullptr; + + if (field->hintsField >= 0) + { + /* computes the address of the hints field if any */ + WINPR_ASSERT((size_t)field->hintsField < descr->nfields); + const NdrFieldStruct* hintsField = &descr->fields[field->hintsField]; + + hints = (BYTE*)target + hintsField->structOffset; + } + + switch (field->pointerType) + { + case NDR_NOT_POINTER: + if (!field->typeDescr->readFn(context, s, hints, ptr)) + { + WLog_ERR(TAG, "error when reading %s.%s", descr->name, field->name); + return FALSE; + } + break; + case NDR_POINTER: + case NDR_POINTER_NON_NULL: + { + NdrDeferredEntry* deferred = &deferreds[ndeferred]; + if (ndeferred >= NDR_MAX_STRUCT_DEFERRED) + { + WLog_ERR(TAG, "too many deferred when calling ndr_read_struct_fromDescr for %s", + descr->name); + return FALSE; + } + + deferred->name = field->name; + deferred->hints = hints; + deferred->target = ptr; + deferred->msg = field->typeDescr; + if (!ndr_read_refpointer(context, s, &deferred->ptrId)) + { + WLog_ERR(TAG, "error when reading %s.%s", descr->name, field->name); + return FALSE; + } + + if (!deferred->ptrId && field->pointerType == NDR_POINTER_NON_NULL) + { + WLog_ERR(TAG, "%s.%s can't be null", descr->name, field->name); + return FALSE; + } + ndeferred++; + break; + } + default: + WLog_ERR(TAG, "%s.%s unknown pointer type 0x%x", descr->name, field->name, + field->pointerType); + return FALSE; + } + } + + return ndr_push_deferreds(context, deferreds, ndeferred); +} + +BOOL ndr_struct_write_fromDescr(NdrContext* context, wStream* s, const NdrStructDescr* descr, + const void* src) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(s); + WINPR_ASSERT(descr); + WINPR_ASSERT(src); + + NdrDeferredEntry deferreds[NDR_MAX_STRUCT_DEFERRED] = WINPR_C_ARRAY_INIT; + size_t ndeferred = 0; + + for (size_t i = 0; i < descr->nfields; i++) + { + const NdrFieldStruct* field = &descr->fields[i]; + const BYTE* ptr = (const BYTE*)src + field->structOffset; + + const void* hints = nullptr; + + if (field->hintsField >= 0) + { + /* computes the address of the hints field if any */ + WINPR_ASSERT((size_t)field->hintsField < descr->nfields); + const NdrFieldStruct* hintsField = &descr->fields[field->hintsField]; + + hints = (const BYTE*)src + hintsField->structOffset; + } + + switch (field->pointerType) + { + case NDR_POINTER: + case NDR_POINTER_NON_NULL: + { + ndr_refid ptrId = NDR_PTR_NULL; + BOOL isNew = 0; + ptr = *(WINPR_CAST_CONST_PTR_AWAY(ptr, const void**)); + + if (!ptr && field->pointerType == NDR_POINTER_NON_NULL) + { + WLog_ERR(TAG, "%s.%s can't be null", descr->name, field->name); + return FALSE; + } + + if (!ndr_context_allocatePtr(context, ptr, &ptrId, &isNew)) + return FALSE; + + if (isNew) + { + NdrDeferredEntry* deferred = &deferreds[ndeferred]; + if (ndeferred >= NDR_MAX_STRUCT_DEFERRED) + { + WLog_ERR(TAG, + "too many deferred when calling ndr_read_struct_fromDescr for %s", + descr->name); + return FALSE; + } + + deferred->name = field->name; + deferred->hints = WINPR_CAST_CONST_PTR_AWAY(hints, void*); + deferred->target = WINPR_CAST_CONST_PTR_AWAY(ptr, void*); + deferred->msg = field->typeDescr; + ndeferred++; + } + + if (!ndr_write_uint32(context, s, ptrId)) + return FALSE; + break; + } + case NDR_NOT_POINTER: + if (!field->typeDescr->writeFn(context, s, hints, ptr)) + { + WLog_ERR(TAG, "error when writing %s.%s", descr->name, field->name); + return FALSE; + } + break; + default: + break; + } + } + + return ndr_push_deferreds(context, deferreds, ndeferred); +} + +void ndr_struct_dump_fromDescr(wLog* logger, UINT32 lvl, size_t identLevel, + const NdrStructDescr* descr, const void* obj) +{ + char tabArray[30 + 1]; + size_t ntabs = (identLevel <= 30) ? identLevel : 30; + + memset(tabArray, '\t', ntabs); + tabArray[ntabs] = 0; + + WLog_Print(logger, lvl, "%s%s", tabArray, descr->name); + for (size_t i = 0; i < descr->nfields; i++) + { + const NdrFieldStruct* field = &descr->fields[i]; + const BYTE* ptr = (const BYTE*)obj + field->structOffset; + + switch (field->pointerType) + { + case NDR_POINTER: + case NDR_POINTER_NON_NULL: + ptr = *(WINPR_CAST_CONST_PTR_AWAY(ptr, const void**)); + break; + case NDR_NOT_POINTER: + break; + default: + WLog_ERR(TAG, "invalid field->pointerType"); + break; + } + + WLog_Print(logger, lvl, "%s*%s:", tabArray, field->name); + if (field->typeDescr->dumpFn) + field->typeDescr->dumpFn(logger, lvl, identLevel + 1, ptr); + else + WLog_Print(logger, lvl, "%s\t", tabArray); + } +} + +void ndr_struct_destroy(NdrContext* context, const NdrStructDescr* descr, void* pptr) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(descr); + WINPR_ASSERT(pptr); + + for (size_t i = 0; i < descr->nfields; i++) + { + const NdrFieldStruct* field = &descr->fields[i]; + void* ptr = (BYTE*)pptr + field->structOffset; + void* hints = nullptr; + + if (field->hintsField >= 0) + { + /* computes the address of the hints field if any */ + WINPR_ASSERT((size_t)field->hintsField < descr->nfields); + const NdrFieldStruct* hintsField = &descr->fields[field->hintsField]; + + hints = (BYTE*)pptr + hintsField->structOffset; + } + + if (field->pointerType != NDR_NOT_POINTER) + ptr = *(void**)ptr; + + if (ptr && field->typeDescr->destroyFn) + field->typeDescr->destroyFn(context, hints, ptr); + + if (field->pointerType != NDR_NOT_POINTER) + free(ptr); + } +} + +ndr_refid ndr_pointer_refid(const void* ptr) +{ + return (ndr_refid)((ULONG_PTR)ptr); +} + +BOOL ndr_read_refpointer(NdrContext* context, wStream* s, ndr_refid* refId) +{ + return ndr_read_uint32(context, s, refId); +} + +typedef struct +{ + const void* needle; + ndr_refid* presult; +} FindValueArgs; + +static BOOL findValueRefFn(const void* key, void* value, void* parg) +{ + WINPR_ASSERT(parg); + + FindValueArgs* args = (FindValueArgs*)parg; + if (args->needle == value) + { + *args->presult = (ndr_refid)(UINT_PTR)key; + return FALSE; + } + return TRUE; +} + +BOOL ndr_context_allocatePtr(NdrContext* context, const void* ptr, ndr_refid* prefId, BOOL* pnewPtr) +{ + WINPR_ASSERT(context); + + FindValueArgs findArgs = { ptr, prefId }; + if (!HashTable_Foreach(context->refPointers, findValueRefFn, &findArgs)) + { + *pnewPtr = FALSE; + return TRUE; + } + + *pnewPtr = TRUE; + *prefId = context->refIdCounter + 4; + if (!HashTable_Insert(context->refPointers, (void*)(UINT_PTR)(*prefId), ptr)) + return FALSE; + + context->refIdCounter += 4; + return TRUE; +} + +BOOL ndr_read_pointedMessageEx(NdrContext* context, wStream* s, ndr_refid ptrId, + NdrMessageType descr, void* hints, void** target) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(s); + WINPR_ASSERT(descr); + WINPR_ASSERT(target); + + *target = nullptr; + if (!ptrId) + return TRUE; + + void* ret = HashTable_GetItemValue(context->refPointers, (void*)(UINT_PTR)ptrId); + if (!ret) + { + size_t itemCount = ndr_hintsCount(descr, hints); + if (itemCount == 0) + return FALSE; + ret = calloc(itemCount, descr->itemSize); + if (!ret) + return FALSE; + + if (!descr->readFn(context, s, hints, ret) || + !HashTable_Insert(context->refPointers, (void*)(UINT_PTR)ptrId, ret)) + { + if (descr->destroyFn) + descr->destroyFn(context, hints, ret); + free(ret); + return FALSE; + } + } + + *target = ret; + return TRUE; +} + +BOOL ndr_push_deferreds(NdrContext* context, NdrDeferredEntry* deferreds, size_t ndeferred) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(deferreds); + + if (!ndeferred) + return TRUE; + + if (context->ndeferred + ndeferred > NDR_MAX_DEFERRED) + { + WLog_ERR(TAG, "too many deferred"); + return FALSE; + } + + for (size_t i = ndeferred; i > 0; i--, context->ndeferred++) + { + context->deferred[context->ndeferred] = deferreds[i - 1]; + } + return TRUE; +} + +BOOL ndr_treat_deferred_read(NdrContext* context, wStream* s) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(s); + + while (context->ndeferred) + { + NdrDeferredEntry current = context->deferred[context->ndeferred - 1]; + context->ndeferred--; + + WLog_VRB(TAG, "treating read deferred 0x%x for %s", current.ptrId, current.name); + if (!ndr_read_pointedMessageEx(context, s, current.ptrId, current.msg, current.hints, + (void**)current.target)) + { + WLog_ERR(TAG, "error parsing deferred %s", current.name); + return FALSE; + } + } + + return TRUE; +} + +BOOL ndr_treat_deferred_write(NdrContext* context, wStream* s) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(s); + + while (context->ndeferred) + { + NdrDeferredEntry current = context->deferred[context->ndeferred - 1]; + context->ndeferred--; + + WLog_VRB(TAG, "treating write deferred for %s", current.name); + if (!current.msg->writeFn(context, s, current.hints, current.target)) + { + WLog_ERR(TAG, "error writing deferred %s", current.name); + return FALSE; + } + } + + return TRUE; +} + +BOOL ndr_write_data(NdrContext* context, wStream* s, const void* data, size_t sz) +{ + if (!Stream_EnsureRemainingCapacity(s, sz)) + return FALSE; + + Stream_Write(s, data, sz); + ndr_context_bytes_written(context, sz); + return TRUE; +} diff --git a/third_party/FreeRDP/channels/rdpear/common/rdpear-common/ndr.h b/third_party/FreeRDP/channels/rdpear/common/rdpear-common/ndr.h new file mode 100644 index 0000000..6545a77 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpear/common/rdpear-common/ndr.h @@ -0,0 +1,268 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Authentication redirection virtual channel + * + * Copyright 2024 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CHANNELS_RDPEAR_NDR_H_ +#define CHANNELS_RDPEAR_NDR_H_ + +#include +#include + +#define NDR_PTR_NULL (0UL) + +#define NDR_SIMPLE_TYPE_DECL(LOWER, UPPER) \ + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_read_##LOWER(NdrContext* context, wStream* s, \ + UPPER* v); \ + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_read_##LOWER##_(NdrContext* context, wStream* s, \ + const void* hints, void* v); \ + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_write_##LOWER(NdrContext* context, wStream* s, \ + UPPER v); \ + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_write_##LOWER##_( \ + NdrContext* context, wStream* s, const void* hints, const void* v); \ + FREERDP_LOCAL \ + extern const NdrMessageDescr ndr_##LOWER##_descr_s; \ + WINPR_ATTR_NODISCARD FREERDP_LOCAL NdrMessageType ndr_##LOWER##_descr(void) + +#define NDR_ARRAY_OF_TYPE_DECL(TYPE, UPPERTYPE) \ + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_read_##TYPE##Array( \ + NdrContext* context, wStream* s, const void* hints, void* v); \ + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_write_##TYPE##Array( \ + NdrContext* context, wStream* s, const void* hints, const void* v); \ + FREERDP_LOCAL \ + void ndr_destroy_##TYPE##Array(NdrContext* context, const void* hints, void* obj); \ + FREERDP_LOCAL \ + extern const NdrMessageDescr ndr_##TYPE##Array_descr_s; \ + WINPR_ATTR_NODISCARD FREERDP_LOCAL NdrMessageType ndr_##TYPE##Array_descr(void); \ + \ + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_read_##TYPE##VaryingArray( \ + NdrContext* context, wStream* s, const void* hints, void* v); \ + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_write_##TYPE##VaryingArray( \ + NdrContext* context, wStream* s, const void* hints, const void* v); \ + extern const NdrMessageDescr ndr_##TYPE##VaryingArray_descr_s; \ + NdrMessageType ndr_##TYPE##VaryingArray_descr(void) + +#ifdef __cplusplus +extern "C" +{ +#endif + + typedef struct NdrContext_s NdrContext; + + typedef UINT32 ndr_refid; + + typedef BOOL (*NDR_READER_FN)(NdrContext* context, wStream* s, const void* hints, void* target); + typedef BOOL (*NDR_WRITER_FN)(NdrContext* context, wStream* s, const void* hints, + const void* obj); + typedef void (*NDR_DESTROY_FN)(NdrContext* context, const void* hints, void* obj); + typedef void (*NDR_DUMP_FN)(wLog* logger, UINT32 lvl, size_t indentLevel, const void* obj); + + /** @brief arity of a message */ + typedef enum + { + NDR_ARITY_SIMPLE, + NDR_ARITY_ARRAYOF, + NDR_ARITY_VARYING_ARRAYOF, + } NdrTypeArity; + + /** @brief message descriptor */ + typedef struct + { + NdrTypeArity arity; + size_t itemSize; + WINPR_ATTR_NODISCARD NDR_READER_FN readFn; + WINPR_ATTR_NODISCARD NDR_WRITER_FN writeFn; + NDR_DESTROY_FN destroyFn; + NDR_DUMP_FN dumpFn; + } NdrMessageDescr; + + typedef const NdrMessageDescr* NdrMessageType; + + /** @brief pointer or not and if null is accepted */ + typedef enum + { + NDR_NOT_POINTER, + NDR_POINTER_NON_NULL, + NDR_POINTER + } NdrPointerType; + + /** @brief descriptor of a field in a structure */ + typedef struct + { + const char* name; + size_t structOffset; + NdrPointerType pointerType; + ssize_t hintsField; + NdrMessageType typeDescr; + } NdrFieldStruct; + + /** @brief structure descriptor */ + typedef struct + { + const char* name; + size_t nfields; + const NdrFieldStruct* fields; + } NdrStructDescr; + + /** @brief a deferred pointer */ + typedef struct + { + ndr_refid ptrId; + const char* name; + void* hints; + void* target; + NdrMessageType msg; + } NdrDeferredEntry; + + FREERDP_LOCAL void ndr_context_free(NdrContext* context); + + static inline void ndr_context_destroy(NdrContext** pcontext) + { + WINPR_ASSERT(pcontext); + ndr_context_free(*pcontext); + *pcontext = nullptr; + } + + WINPR_ATTR_MALLOC(ndr_context_free, 1) + WINPR_ATTR_NODISCARD + FREERDP_LOCAL NdrContext* ndr_context_new(BOOL bigEndianDrep, BYTE version); + + FREERDP_LOCAL void ndr_context_reset(NdrContext* context); + + WINPR_ATTR_MALLOC(ndr_context_free, 1) + WINPR_ATTR_NODISCARD + FREERDP_LOCAL NdrContext* ndr_context_copy(const NdrContext* src); + + WINPR_ATTR_MALLOC(ndr_context_free, 1) + WINPR_ATTR_NODISCARD + FREERDP_LOCAL NdrContext* ndr_read_header(wStream* s); + + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_write_header(NdrContext* context, wStream* s); + + NDR_SIMPLE_TYPE_DECL(uint8, UINT8); + NDR_SIMPLE_TYPE_DECL(uint16, UINT16); + NDR_SIMPLE_TYPE_DECL(uint32, UINT32); + NDR_SIMPLE_TYPE_DECL(uint64, UINT64); + + NDR_ARRAY_OF_TYPE_DECL(uint8, BYTE); + NDR_ARRAY_OF_TYPE_DECL(uint16, UINT16); + + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_skip_bytes(NdrContext* context, wStream* s, + size_t nbytes); + + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_read_align(NdrContext* context, wStream* s, + size_t sz); + + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_write_align(NdrContext* context, wStream* s, + size_t sz); + + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_write_data(NdrContext* context, wStream* s, + const void* data, size_t sz); + + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_read_pickle(NdrContext* context, wStream* s); + + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_write_pickle(NdrContext* context, wStream* s); + + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_read_constructed(NdrContext* context, wStream* s, + wStream* target); + + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_write_constructed(NdrContext* context, wStream* s, + wStream* payload); + + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_start_constructed(NdrContext* context, wStream* s); + + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_end_constructed(NdrContext* context, wStream* s); + + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_read_wchar(NdrContext* context, wStream* s, + WCHAR* ptr); + + /** @brief hints for a varying conformant array */ + typedef struct + { + UINT32 length; + UINT32 maxLength; + } NdrVaryingArrayHints; + + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_read_uconformant_varying_array( + NdrContext* context, wStream* s, const NdrVaryingArrayHints* hints, NdrMessageType itemType, + void* ptarget); + + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_write_uconformant_varying_array( + NdrContext* context, wStream* s, const NdrVaryingArrayHints* hints, NdrMessageType itemType, + const void* src); + + /** @brief hints for a conformant array */ + typedef struct + { + UINT32 count; + } NdrArrayHints; + + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_read_uconformant_array(NdrContext* context, + wStream* s, + const NdrArrayHints* hints, + NdrMessageType itemType, + void* vtarget); + + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_write_uconformant_array(NdrContext* context, + wStream* s, UINT32 len, + NdrMessageType itemType, + const BYTE* ptr); + + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_struct_read_fromDescr(NdrContext* context, + wStream* s, + const NdrStructDescr* descr, + void* target); + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_struct_write_fromDescr(NdrContext* context, + wStream* s, + const NdrStructDescr* descr, + const void* src); + FREERDP_LOCAL + void ndr_struct_dump_fromDescr(wLog* logger, UINT32 lvl, size_t identLevel, + const NdrStructDescr* descr, const void* obj); + FREERDP_LOCAL + void ndr_struct_destroy(NdrContext* context, const NdrStructDescr* descr, void* pptr); + + WINPR_ATTR_NODISCARD FREERDP_LOCAL ndr_refid ndr_pointer_refid(const void* ptr); + + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_read_refpointer(NdrContext* context, wStream* s, + UINT32* refId); + + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_context_allocatePtr(NdrContext* context, + const void* ptr, + ndr_refid* prefId, + BOOL* pnewPtr); + + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_read_pointedMessageEx(NdrContext* context, + wStream* s, ndr_refid ptrId, + NdrMessageType descr, + void* hints, void** target); + + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_push_deferreds(NdrContext* context, + NdrDeferredEntry* deferreds, + size_t ndeferred); + + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_treat_deferred_read(NdrContext* context, + wStream* s); + + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ndr_treat_deferred_write(NdrContext* context, + wStream* s); + +#ifdef __cplusplus +} +#endif + +#endif /* CHANNELS_RDPEAR_NDR_H_ */ diff --git a/third_party/FreeRDP/channels/rdpear/common/rdpear-common/rdpear_asn1.h b/third_party/FreeRDP/channels/rdpear/common/rdpear-common/rdpear_asn1.h new file mode 100644 index 0000000..9cdb7bb --- /dev/null +++ b/third_party/FreeRDP/channels/rdpear/common/rdpear-common/rdpear_asn1.h @@ -0,0 +1,36 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * ASN1 routines for RDPEAR + * + * Copyright 2024 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RPDEAR_RDPEAR_ASN1_H__ +#define RPDEAR_RDPEAR_ASN1_H__ + +#include + +#include +#include + +WINPR_ATTR_MALLOC(Stream_Free, 1) +WINPR_ATTR_NODISCARD FREERDP_LOCAL wStream* rdpear_enc_Checksum(UINT32 cksumtype, + krb5_checksum* csum); + +WINPR_ATTR_MALLOC(Stream_Free, 1) +WINPR_ATTR_NODISCARD FREERDP_LOCAL wStream* rdpear_enc_EncryptedData(UINT32 encType, + krb5_data* payload); + +#endif /* RPDEAR_RDPEAR_ASN1_H__ */ diff --git a/third_party/FreeRDP/channels/rdpear/common/rdpear-common/rdpear_common.h b/third_party/FreeRDP/channels/rdpear/common/rdpear-common/rdpear_common.h new file mode 100644 index 0000000..af83e3f --- /dev/null +++ b/third_party/FreeRDP/channels/rdpear/common/rdpear-common/rdpear_common.h @@ -0,0 +1,243 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2023 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPEAR_COMMON_H +#define FREERDP_CHANNEL_RDPEAR_COMMON_H + +#include +#include +#include +#include + +#include + +#include + +typedef enum +{ + RDPEAR_PACKAGE_KERBEROS, + RDPEAR_PACKAGE_NTLM, + RDPEAR_PACKAGE_UNKNOWN +} RdpEarPackageType; + +/* RDPEAR 2.2.1.1 */ +typedef enum +{ + // Start Kerberos remote calls + RemoteCallKerbMinimum = 0x100, + RemoteCallKerbNegotiateVersion = 0x100, + RemoteCallKerbBuildAsReqAuthenticator, + RemoteCallKerbVerifyServiceTicket, + RemoteCallKerbCreateApReqAuthenticator, + RemoteCallKerbDecryptApReply, + RemoteCallKerbUnpackKdcReplyBody, + RemoteCallKerbComputeTgsChecksum, + RemoteCallKerbBuildEncryptedAuthData, + RemoteCallKerbPackApReply, + RemoteCallKerbHashS4UPreauth, + RemoteCallKerbSignS4UPreauthData, + RemoteCallKerbVerifyChecksum, + RemoteCallKerbReserved1, + RemoteCallKerbReserved2, + RemoteCallKerbReserved3, + RemoteCallKerbReserved4, + RemoteCallKerbReserved5, + RemoteCallKerbReserved6, + RemoteCallKerbReserved7, + RemoteCallKerbDecryptPacCredentials, + RemoteCallKerbCreateECDHKeyAgreement, + RemoteCallKerbCreateDHKeyAgreement, + RemoteCallKerbDestroyKeyAgreement, + RemoteCallKerbKeyAgreementGenerateNonce, + RemoteCallKerbFinalizeKeyAgreement, + RemoteCallKerbMaximum = 0x1ff, + // End Kerberos remote calls + + // Start NTLM remote calls + RemoteCallNtlmMinimum = 0x200, + RemoteCallNtlmNegotiateVersion = 0x200, + RemoteCallNtlmLm20GetNtlm3ChallengeResponse, + RemoteCallNtlmCalculateNtResponse, + RemoteCallNtlmCalculateUserSessionKeyNt, + RemoteCallNtlmCompareCredentials, + RemoteCallNtlmMaximum = 0x2ff, + // End NTLM remote calls +} RemoteGuardCallId; + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL RdpEarPackageType rdpear_packageType_from_name(const WinPrAsn1_OctetString* package); + +WINPR_ATTR_MALLOC(Stream_Free, 1) +WINPR_ATTR_NODISCARD +FREERDP_LOCAL wStream* rdpear_encodePayload(BOOL isKerb, wStream* payload); + +#define RDPEAR_COMMON_MESSAGE_DECL(V) \ + WINPR_ATTR_NODISCARD \ + FREERDP_LOCAL BOOL ndr_read_##V(NdrContext* context, wStream* s, const void* hints, V* obj); \ + WINPR_ATTR_NODISCARD \ + FREERDP_LOCAL BOOL ndr_write_##V(NdrContext* context, wStream* s, const void* hints, \ + const V* obj); \ + FREERDP_LOCAL void ndr_destroy_##V(NdrContext* context, const void* hints, V* obj); \ + FREERDP_LOCAL void ndr_dump_##V(wLog* logger, UINT32 lvl, size_t indentLevel, const V* obj); \ + WINPR_ATTR_NODISCARD \ + FREERDP_LOCAL NdrMessageType ndr_##V##_descr(void) + +/** @brief 2.2.1.2.2 KERB_RPC_OCTET_STRING */ +typedef struct +{ + UINT32 length; + BYTE* value; +} KERB_RPC_OCTET_STRING; + +RDPEAR_COMMON_MESSAGE_DECL(KERB_RPC_OCTET_STRING); + +/** @brief 2.2.1.2.1 KERB_ASN1_DATA */ +typedef struct +{ + UINT32 Pdu; + NdrArrayHints Asn1BufferHints; + BYTE* Asn1Buffer; +} KERB_ASN1_DATA; + +RDPEAR_COMMON_MESSAGE_DECL(KERB_ASN1_DATA); + +/** @brief 2.3.10 RPC_UNICODE_STRING (MS-DTYP) */ +typedef struct +{ + NdrVaryingArrayHints lenHints; + UINT32 strLength; + WCHAR* Buffer; +} RPC_UNICODE_STRING; + +RDPEAR_COMMON_MESSAGE_DECL(RPC_UNICODE_STRING); + +/** @brief 2.2.1.2.3 KERB_RPC_INTERNAL_NAME */ +typedef struct +{ + UINT16 NameType; + NdrArrayHints nameHints; + RPC_UNICODE_STRING* Names; +} KERB_RPC_INTERNAL_NAME; + +RDPEAR_COMMON_MESSAGE_DECL(KERB_RPC_INTERNAL_NAME); + +/** @brief 2.2.1.2.8 KERB_RPC_ENCRYPTION_KEY */ +typedef struct +{ + UINT32 reserved1; + UINT32 reserved2; + KERB_RPC_OCTET_STRING reserved3; +} KERB_RPC_ENCRYPTION_KEY; + +RDPEAR_COMMON_MESSAGE_DECL(KERB_RPC_ENCRYPTION_KEY); + +/** @brief 2.2.2.1.8 BuildEncryptedAuthData */ +typedef struct +{ + UINT32 KeyUsage; + KERB_RPC_ENCRYPTION_KEY* Key; + KERB_ASN1_DATA* PlainAuthData; +} BuildEncryptedAuthDataReq; + +RDPEAR_COMMON_MESSAGE_DECL(BuildEncryptedAuthDataReq); + +/** @brief 2.2.2.1.7 ComputeTgsChecksum */ +typedef struct +{ + KERB_ASN1_DATA* requestBody; + KERB_RPC_ENCRYPTION_KEY* Key; + UINT32 ChecksumType; +} ComputeTgsChecksumReq; + +RDPEAR_COMMON_MESSAGE_DECL(ComputeTgsChecksumReq); + +/** @brief 2.2.2.1.4 CreateApReqAuthenticator */ +typedef struct +{ + KERB_RPC_ENCRYPTION_KEY* EncryptionKey; + ULONG SequenceNumber; + KERB_RPC_INTERNAL_NAME* ClientName; + RPC_UNICODE_STRING* ClientRealm; + PLARGE_INTEGER SkewTime; + KERB_RPC_ENCRYPTION_KEY* SubKey; // optional + KERB_ASN1_DATA* AuthData; // optional + KERB_ASN1_DATA* GssChecksum; // optional + ULONG KeyUsage; +} CreateApReqAuthenticatorReq; + +RDPEAR_COMMON_MESSAGE_DECL(CreateApReqAuthenticatorReq); + +/** @brief 2.2.2.1.4 CreateApReqAuthenticator */ +typedef struct +{ + LARGE_INTEGER AuthenticatorTime; + KERB_ASN1_DATA Authenticator; + LONG KerbProtocolError; +} CreateApReqAuthenticatorResp; + +RDPEAR_COMMON_MESSAGE_DECL(CreateApReqAuthenticatorResp); + +/** @brief 2.2.2.1.6 UnpackKdcReplyBody */ +typedef struct +{ + KERB_ASN1_DATA* EncryptedData; + KERB_RPC_ENCRYPTION_KEY* Key; + KERB_RPC_ENCRYPTION_KEY* StrengthenKey; + ULONG Pdu; + ULONG KeyUsage; +} UnpackKdcReplyBodyReq; + +RDPEAR_COMMON_MESSAGE_DECL(UnpackKdcReplyBodyReq); + +/** @brief 2.2.2.1.6 UnpackKdcReplyBody */ +typedef struct +{ + LONG KerbProtocolError; + KERB_ASN1_DATA ReplyBody; +} UnpackKdcReplyBodyResp; + +RDPEAR_COMMON_MESSAGE_DECL(UnpackKdcReplyBodyResp); + +typedef struct +{ + KERB_ASN1_DATA* EncryptedReply; + KERB_RPC_ENCRYPTION_KEY* Key; +} DecryptApReplyReq; + +RDPEAR_COMMON_MESSAGE_DECL(DecryptApReplyReq); + +typedef struct +{ + KERB_ASN1_DATA* Reply; + KERB_ASN1_DATA* ReplyBody; + KERB_RPC_ENCRYPTION_KEY* SessionKey; +} PackApReplyReq; + +RDPEAR_COMMON_MESSAGE_DECL(PackApReplyReq); + +typedef struct +{ + NdrArrayHints PackedReplyHints; + BYTE* PackedReply; +} PackApReplyResp; + +RDPEAR_COMMON_MESSAGE_DECL(PackApReplyResp); + +#undef RDPEAR_COMMON_MESSAGE_DECL + +#endif /* FREERDP_CHANNEL_RDPEAR_COMMON_H */ diff --git a/third_party/FreeRDP/channels/rdpear/common/rdpear_asn1.c b/third_party/FreeRDP/channels/rdpear/common/rdpear_asn1.c new file mode 100644 index 0000000..d24ab4f --- /dev/null +++ b/third_party/FreeRDP/channels/rdpear/common/rdpear_asn1.c @@ -0,0 +1,104 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * ASN1 routines for RDPEAR + * + * Copyright 2024 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +wStream* rdpear_enc_Checksum(UINT32 cksumtype, krb5_checksum* csum) +{ + /** + * Checksum ::= SEQUENCE { + * cksumtype [0] Int32, + * checksum [1] OCTET STRING + * } + */ + wStream* ret = nullptr; + WinPrAsn1Encoder* enc = WinPrAsn1Encoder_New(WINPR_ASN1_DER); + if (!enc) + return nullptr; + + if (!WinPrAsn1EncSeqContainer(enc)) + goto out; + + if (!WinPrAsn1EncContextualInteger(enc, 0, (WinPrAsn1_INTEGER)cksumtype)) + goto out; + + WinPrAsn1_OctetString octets; + octets.data = (BYTE*)csum->contents; + octets.len = csum->length; + if (!WinPrAsn1EncContextualOctetString(enc, 1, &octets) || !WinPrAsn1EncEndContainer(enc)) + goto out; + + ret = Stream_New(nullptr, 1024); + if (!ret) + goto out; + + if (!WinPrAsn1EncToStream(enc, ret)) + { + Stream_Free(ret, TRUE); + ret = nullptr; + goto out; + } + +out: + WinPrAsn1Encoder_Free(&enc); + return ret; +} + +wStream* rdpear_enc_EncryptedData(UINT32 encType, krb5_data* payload) +{ + /** + * EncryptedData ::= SEQUENCE { + * etype [0] Int32 -- EncryptionType --, + * kvno [1] UInt32 OPTIONAL, + * cipher [2] OCTET STRING -- ciphertext + * } + */ + wStream* ret = nullptr; + WinPrAsn1Encoder* enc = WinPrAsn1Encoder_New(WINPR_ASN1_DER); + if (!enc) + return nullptr; + + if (!WinPrAsn1EncSeqContainer(enc)) + goto out; + + if (!WinPrAsn1EncContextualInteger(enc, 0, (WinPrAsn1_INTEGER)encType)) + goto out; + + WinPrAsn1_OctetString octets; + octets.data = (BYTE*)payload->data; + octets.len = payload->length; + if (!WinPrAsn1EncContextualOctetString(enc, 2, &octets) || !WinPrAsn1EncEndContainer(enc)) + goto out; + + ret = Stream_New(nullptr, 1024); + if (!ret) + goto out; + + if (!WinPrAsn1EncToStream(enc, ret)) + { + Stream_Free(ret, TRUE); + ret = nullptr; + goto out; + } + +out: + WinPrAsn1Encoder_Free(&enc); + return ret; +} diff --git a/third_party/FreeRDP/channels/rdpear/common/rdpear_common.c b/third_party/FreeRDP/channels/rdpear/common/rdpear_common.c new file mode 100644 index 0000000..baad6cc --- /dev/null +++ b/third_party/FreeRDP/channels/rdpear/common/rdpear_common.c @@ -0,0 +1,572 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2023 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include +#include +#include + +#define TAG CHANNELS_TAG("rdpear") + +static char kerberosPackageName[] = { + 'K', 0, 'e', 0, 'r', 0, 'b', 0, 'e', 0, 'r', 0, 'o', 0, 's', 0 +}; +static char ntlmPackageName[] = { 'N', 0, 'T', 0, 'L', 0, 'M', 0 }; + +RdpEarPackageType rdpear_packageType_from_name(const WinPrAsn1_OctetString* package) +{ + if (package->len == sizeof(kerberosPackageName) && + memcmp(package->data, kerberosPackageName, package->len) == 0) + return RDPEAR_PACKAGE_KERBEROS; + + if (package->len == sizeof(ntlmPackageName) && + memcmp(package->data, ntlmPackageName, package->len) == 0) + return RDPEAR_PACKAGE_NTLM; + + return RDPEAR_PACKAGE_UNKNOWN; +} + +wStream* rdpear_encodePayload(BOOL isKerb, wStream* payload) +{ + wStream* ret = nullptr; + WinPrAsn1Encoder* enc = WinPrAsn1Encoder_New(WINPR_ASN1_DER); + if (!enc) + return nullptr; + + /* TSRemoteGuardInnerPacket ::= SEQUENCE { */ + if (!WinPrAsn1EncSeqContainer(enc)) + goto out; + + /* packageName [1] OCTET STRING */ + WinPrAsn1_OctetString packageOctetString; + if (isKerb) + { + packageOctetString.data = (BYTE*)kerberosPackageName; + packageOctetString.len = sizeof(kerberosPackageName); + } + else + { + packageOctetString.data = (BYTE*)ntlmPackageName; + packageOctetString.len = sizeof(ntlmPackageName); + } + + if (!WinPrAsn1EncContextualOctetString(enc, 1, &packageOctetString)) + goto out; + + /* buffer [2] OCTET STRING*/ + WinPrAsn1_OctetString payloadOctetString = { Stream_GetPosition(payload), + Stream_Buffer(payload) }; + if (!WinPrAsn1EncContextualOctetString(enc, 2, &payloadOctetString)) + goto out; + + /* } */ + if (!WinPrAsn1EncEndContainer(enc)) + goto out; + + ret = Stream_New(nullptr, 100); + if (!ret) + goto out; + + if (!WinPrAsn1EncToStream(enc, ret)) + { + Stream_Free(ret, TRUE); + ret = nullptr; + goto out; + } +out: + WinPrAsn1Encoder_Free(&enc); + return ret; +} + +#define RDPEAR_SIMPLE_MESSAGE_TYPE(V) \ + BOOL ndr_read_##V(NdrContext* context, wStream* s, const void* hints, V* obj) \ + { \ + WINPR_UNUSED(hints); \ + return ndr_struct_read_fromDescr(context, s, &V##_struct, obj); \ + } \ + BOOL ndr_write_##V(NdrContext* context, wStream* s, const void* hints, const V* obj) \ + { \ + WINPR_UNUSED(hints); \ + return ndr_struct_write_fromDescr(context, s, &V##_struct, obj); \ + } \ + void ndr_destroy_##V(NdrContext* context, const void* hints, V* obj) \ + { \ + WINPR_UNUSED(hints); \ + ndr_struct_destroy(context, &V##_struct, obj); \ + } \ + void ndr_dump_##V(wLog* logger, UINT32 lvl, size_t indentLevel, const V* obj) \ + { \ + ndr_struct_dump_fromDescr(logger, lvl, indentLevel, &V##_struct, obj); \ + } \ + \ + static BOOL ndr_descr_read_##V(NdrContext* context, wStream* s, const void* hints, void* obj) \ + { \ + WINPR_UNUSED(hints); \ + return ndr_struct_read_fromDescr(context, s, &V##_struct, obj); \ + } \ + static BOOL ndr_descr_write_##V(NdrContext* context, wStream* s, const void* hints, \ + const void* obj) \ + { \ + WINPR_UNUSED(hints); \ + return ndr_struct_write_fromDescr(context, s, &V##_struct, obj); \ + } \ + static void ndr_descr_destroy_##V(NdrContext* context, const void* hints, void* obj) \ + { \ + WINPR_UNUSED(hints); \ + ndr_struct_destroy(context, &V##_struct, obj); \ + } \ + static void ndr_descr_dump_##V(wLog* logger, UINT32 lvl, size_t indentLevel, const void* obj) \ + { \ + ndr_struct_dump_fromDescr(logger, lvl, indentLevel, &V##_struct, obj); \ + } \ + \ + static NdrMessageDescr ndr_##V##_descr_s = { \ + NDR_ARITY_SIMPLE, sizeof(V), ndr_descr_read_##V, ndr_descr_write_##V, \ + ndr_descr_destroy_##V, ndr_descr_dump_##V, \ + }; \ + \ + NdrMessageType ndr_##V##_descr(void) \ + { \ + return &ndr_##V##_descr_s; \ + } + +static const NdrFieldStruct KERB_RPC_OCTET_STRING_fields[] = { + { "Length", offsetof(KERB_RPC_OCTET_STRING, length), NDR_NOT_POINTER, -1, &ndr_uint32_descr_s }, + { "value", offsetof(KERB_RPC_OCTET_STRING, value), NDR_POINTER_NON_NULL, 0, + &ndr_uint8Array_descr_s } +}; +static const NdrStructDescr KERB_RPC_OCTET_STRING_struct = { "KERB_RPC_OCTET_STRING", 2, + KERB_RPC_OCTET_STRING_fields }; + +RDPEAR_SIMPLE_MESSAGE_TYPE(KERB_RPC_OCTET_STRING) + +/* ============================= KERB_ASN1_DATA ============================== */ + +static const NdrFieldStruct KERB_ASN1_DATA_fields[] = { + { "Pdu", offsetof(KERB_ASN1_DATA, Pdu), NDR_NOT_POINTER, -1, &ndr_uint32_descr_s }, + { "Count", offsetof(KERB_ASN1_DATA, Asn1BufferHints.count), NDR_NOT_POINTER, -1, + &ndr_uint32_descr_s }, + { "Asn1Buffer", offsetof(KERB_ASN1_DATA, Asn1Buffer), NDR_POINTER_NON_NULL, 1, + &ndr_uint8Array_descr_s } +}; +static const NdrStructDescr KERB_ASN1_DATA_struct = { "KERB_ASN1_DATA", + ARRAYSIZE(KERB_ASN1_DATA_fields), + KERB_ASN1_DATA_fields }; + +RDPEAR_SIMPLE_MESSAGE_TYPE(KERB_ASN1_DATA) + +/* ============================ RPC_UNICODE_STRING ========================== */ + +BOOL ndr_read_RPC_UNICODE_STRING(NdrContext* context, wStream* s, const void* hints, + RPC_UNICODE_STRING* res) +{ + NdrDeferredEntry bufferDesc = { NDR_PTR_NULL, "RPC_UNICODE_STRING.Buffer", &res->lenHints, + (void*)&res->Buffer, ndr_uint16VaryingArray_descr() }; + UINT16 Length = 0; + UINT16 MaximumLength = 0; + + WINPR_UNUSED(hints); + if (!ndr_read_uint16(context, s, &Length) || !ndr_read_uint16(context, s, &MaximumLength) || + !ndr_read_refpointer(context, s, &bufferDesc.ptrId) || Length > MaximumLength) + return FALSE; + + res->lenHints.length = Length; + res->lenHints.maxLength = MaximumLength; + res->strLength = Length / 2; + + return ndr_push_deferreds(context, &bufferDesc, 1); +} + +static BOOL ndr_descr_read_RPC_UNICODE_STRING(NdrContext* context, wStream* s, const void* hints, + void* res) +{ + return ndr_read_RPC_UNICODE_STRING(context, s, hints, res); +} + +BOOL ndr_write_RPC_UNICODE_STRING(NdrContext* context, wStream* s, + WINPR_ATTR_UNUSED const void* hints, + const RPC_UNICODE_STRING* res) +{ + WINPR_ASSERT(res); + if (!ndr_write_uint32(context, s, res->lenHints.length)) + return FALSE; + + if (!ndr_write_uint32(context, s, res->lenHints.maxLength)) + return FALSE; + + return ndr_write_data(context, s, res->Buffer, res->strLength); +} + +static BOOL ndr_write_RPC_UNICODE_STRING_(NdrContext* context, wStream* s, const void* hints, + const void* pvres) +{ + const RPC_UNICODE_STRING* res = pvres; + return ndr_write_RPC_UNICODE_STRING(context, s, hints, res); +} + +void ndr_dump_RPC_UNICODE_STRING(wLog* logger, UINT32 lvl, size_t indentLevel, + const RPC_UNICODE_STRING* obj) +{ + WINPR_UNUSED(indentLevel); + WLog_Print(logger, lvl, "\tLength=%u MaximumLength=%u", obj->lenHints.length, + obj->lenHints.maxLength); + winpr_HexLogDump(logger, lvl, obj->Buffer, obj->lenHints.length); +} + +static void ndr_descr_dump_RPC_UNICODE_STRING(wLog* logger, UINT32 lvl, size_t indentLevel, + const void* obj) +{ + ndr_dump_RPC_UNICODE_STRING(logger, lvl, indentLevel, obj); +} + +void ndr_destroy_RPC_UNICODE_STRING(NdrContext* context, const void* hints, RPC_UNICODE_STRING* obj) +{ + WINPR_UNUSED(context); + WINPR_UNUSED(hints); + if (!obj) + return; + free(obj->Buffer); + obj->Buffer = nullptr; +} + +static void ndr_descr_destroy_RPC_UNICODE_STRING(NdrContext* context, const void* hints, void* obj) +{ + ndr_destroy_RPC_UNICODE_STRING(context, hints, obj); +} + +static const NdrMessageDescr RPC_UNICODE_STRING_descr_s = { NDR_ARITY_SIMPLE, + sizeof(RPC_UNICODE_STRING), + ndr_descr_read_RPC_UNICODE_STRING, + ndr_write_RPC_UNICODE_STRING_, + ndr_descr_destroy_RPC_UNICODE_STRING, + ndr_descr_dump_RPC_UNICODE_STRING }; + +NdrMessageType ndr_RPC_UNICODE_STRING_descr(void) +{ + return &RPC_UNICODE_STRING_descr_s; +} + +/* ========================= RPC_UNICODE_STRING_Array ======================== */ + +static BOOL ndr_read_RPC_UNICODE_STRING_Array(NdrContext* context, wStream* s, const void* hints, + void* v) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(s); + WINPR_ASSERT(hints); + return ndr_read_uconformant_array(context, s, hints, ndr_RPC_UNICODE_STRING_descr(), v); +} + +static BOOL ndr_write_RPC_UNICODE_STRING_Array(NdrContext* context, wStream* s, const void* ghints, + const void* v) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(s); + WINPR_ASSERT(ghints); + + const NdrArrayHints* hints = (const NdrArrayHints*)ghints; + + return ndr_write_uconformant_array(context, s, hints->count, ndr_RPC_UNICODE_STRING_descr(), v); +} + +static const NdrMessageDescr RPC_UNICODE_STRING_Array_descr_s = { + NDR_ARITY_ARRAYOF, + sizeof(RPC_UNICODE_STRING), + ndr_read_RPC_UNICODE_STRING_Array, + ndr_write_RPC_UNICODE_STRING_Array, + nullptr, + nullptr +}; + +static NdrMessageType ndr_RPC_UNICODE_STRING_Array_descr(void) +{ + return &RPC_UNICODE_STRING_Array_descr_s; +} + +/* ========================== KERB_RPC_INTERNAL_NAME ======================== */ + +BOOL ndr_read_KERB_RPC_INTERNAL_NAME(NdrContext* context, wStream* s, const void* hints, + KERB_RPC_INTERNAL_NAME* res) +{ + WINPR_ASSERT(res); + + union + { + RPC_UNICODE_STRING** ppstr; + void* pv; + } cnv; + cnv.ppstr = &res->Names; + NdrDeferredEntry names = { NDR_PTR_NULL, "KERB_RPC_INTERNAL_NAME.Names", &res->nameHints, + cnv.pv, ndr_RPC_UNICODE_STRING_Array_descr() }; + + UINT16 nameCount = 0; + WINPR_UNUSED(hints); + + if (!ndr_read_uint16(context, s, &res->NameType) || !ndr_read_uint16(context, s, &nameCount)) + return FALSE; + + res->nameHints.count = nameCount; + + return ndr_read_refpointer(context, s, &names.ptrId) && ndr_push_deferreds(context, &names, 1); +} + +static BOOL ndr_descr_read_KERB_RPC_INTERNAL_NAME(NdrContext* context, wStream* s, + const void* hints, void* res) +{ + return ndr_read_KERB_RPC_INTERNAL_NAME(context, s, hints, res); +} + +BOOL ndr_write_KERB_RPC_INTERNAL_NAME(NdrContext* context, wStream* s, const void* hints, + const KERB_RPC_INTERNAL_NAME* res) +{ + WINPR_UNUSED(context); + WINPR_UNUSED(s); + WINPR_UNUSED(hints); + WINPR_UNUSED(res); + WLog_ERR(TAG, "TODO: implement this"); + return FALSE; +} + +void ndr_dump_KERB_RPC_INTERNAL_NAME(wLog* logger, UINT32 lvl, size_t indentLevel, + const KERB_RPC_INTERNAL_NAME* obj) +{ + WINPR_UNUSED(indentLevel); + WINPR_UNUSED(obj); + WLog_Print(logger, lvl, "TODO: implement this"); +} + +static void ndr_descr_dump_KERB_RPC_INTERNAL_NAME(wLog* logger, UINT32 lvl, size_t indentLevel, + const void* obj) +{ + ndr_dump_KERB_RPC_INTERNAL_NAME(logger, lvl, indentLevel, obj); +} + +void ndr_destroy_KERB_RPC_INTERNAL_NAME(NdrContext* context, const void* hints, + KERB_RPC_INTERNAL_NAME* obj) +{ + WINPR_UNUSED(hints); + if (!obj) + return; + + for (UINT32 i = 0; i < obj->nameHints.count; i++) + ndr_destroy_RPC_UNICODE_STRING(context, nullptr, &obj->Names[i]); + + free(obj->Names); + obj->Names = nullptr; +} + +static void ndr_descr_destroy_KERB_RPC_INTERNAL_NAME(NdrContext* context, const void* hints, + void* obj) +{ + ndr_destroy_KERB_RPC_INTERNAL_NAME(context, hints, obj); +} + +static NdrMessageDescr KERB_RPC_INTERNAL_NAME_descr_s = { NDR_ARITY_SIMPLE, + sizeof(KERB_RPC_INTERNAL_NAME), + ndr_descr_read_KERB_RPC_INTERNAL_NAME, + nullptr, + ndr_descr_destroy_KERB_RPC_INTERNAL_NAME, + ndr_descr_dump_KERB_RPC_INTERNAL_NAME }; + +NdrMessageType ndr_KERB_RPC_INTERNAL_NAME_descr(void) +{ + return &KERB_RPC_INTERNAL_NAME_descr_s; +} + +/* ========================== KERB_RPC_ENCRYPTION_KEY ======================== */ + +static const NdrFieldStruct KERB_RPC_ENCRYPTION_KEY_fields[] = { + { "reserved1", offsetof(KERB_RPC_ENCRYPTION_KEY, reserved1), NDR_NOT_POINTER, -1, + &ndr_uint32_descr_s }, + { "reserved2", offsetof(KERB_RPC_ENCRYPTION_KEY, reserved2), NDR_NOT_POINTER, -1, + &ndr_uint32_descr_s }, + { "reserved3", offsetof(KERB_RPC_ENCRYPTION_KEY, reserved3), NDR_NOT_POINTER, -1, + &ndr_KERB_RPC_OCTET_STRING_descr_s } +}; +static const NdrStructDescr KERB_RPC_ENCRYPTION_KEY_struct = { + "KERB_RPC_ENCRYPTION_KEY", ARRAYSIZE(KERB_RPC_ENCRYPTION_KEY_fields), + KERB_RPC_ENCRYPTION_KEY_fields +}; + +RDPEAR_SIMPLE_MESSAGE_TYPE(KERB_RPC_ENCRYPTION_KEY) + +/* ========================== BuildEncryptedAuthDataReq ======================== */ + +static const NdrFieldStruct BuildEncryptedAuthDataReq_fields[] = { + { "KeyUsage", offsetof(BuildEncryptedAuthDataReq, KeyUsage), NDR_NOT_POINTER, -1, + &ndr_uint32_descr_s }, + { "key", offsetof(BuildEncryptedAuthDataReq, Key), NDR_POINTER_NON_NULL, -1, + &ndr_KERB_RPC_ENCRYPTION_KEY_descr_s }, + { "plainAuthData", offsetof(BuildEncryptedAuthDataReq, PlainAuthData), NDR_POINTER_NON_NULL, -1, + &ndr_KERB_ASN1_DATA_descr_s } +}; +static const NdrStructDescr BuildEncryptedAuthDataReq_struct = { + "BuildEncryptedAuthDataReq", ARRAYSIZE(BuildEncryptedAuthDataReq_fields), + BuildEncryptedAuthDataReq_fields +}; + +RDPEAR_SIMPLE_MESSAGE_TYPE(BuildEncryptedAuthDataReq) + +/* ========================== ComputeTgsChecksumReq ======================== */ + +static const NdrFieldStruct ComputeTgsChecksumReq_fields[] = { + { "requestBody", offsetof(ComputeTgsChecksumReq, requestBody), NDR_POINTER_NON_NULL, -1, + &ndr_KERB_ASN1_DATA_descr_s }, + { "key", offsetof(ComputeTgsChecksumReq, Key), NDR_POINTER_NON_NULL, -1, + &ndr_KERB_RPC_ENCRYPTION_KEY_descr_s }, + { "ChecksumType", offsetof(ComputeTgsChecksumReq, ChecksumType), NDR_NOT_POINTER, -1, + &ndr_uint32_descr_s } +}; +static const NdrStructDescr ComputeTgsChecksumReq_struct = { + "ComputeTgsChecksumReq", ARRAYSIZE(ComputeTgsChecksumReq_fields), ComputeTgsChecksumReq_fields +}; + +RDPEAR_SIMPLE_MESSAGE_TYPE(ComputeTgsChecksumReq) + +/* ========================== CreateApReqAuthenticatorReq ======================== */ + +static const NdrFieldStruct CreateApReqAuthenticatorReq_fields[] = { + { "EncryptionKey", offsetof(CreateApReqAuthenticatorReq, EncryptionKey), NDR_POINTER_NON_NULL, + -1, &ndr_KERB_RPC_ENCRYPTION_KEY_descr_s }, + { "SequenceNumber", offsetof(CreateApReqAuthenticatorReq, SequenceNumber), NDR_NOT_POINTER, -1, + &ndr_uint32_descr_s }, + { "ClientName", offsetof(CreateApReqAuthenticatorReq, ClientName), NDR_POINTER_NON_NULL, -1, + &KERB_RPC_INTERNAL_NAME_descr_s }, + { "ClientRealm", offsetof(CreateApReqAuthenticatorReq, ClientRealm), NDR_POINTER_NON_NULL, -1, + &RPC_UNICODE_STRING_descr_s }, + { "SkewTime", offsetof(CreateApReqAuthenticatorReq, SkewTime), NDR_POINTER_NON_NULL, -1, + &ndr_uint64_descr_s }, + { "SubKey", offsetof(CreateApReqAuthenticatorReq, SubKey), NDR_POINTER, -1, + &ndr_KERB_RPC_ENCRYPTION_KEY_descr_s }, + { "AuthData", offsetof(CreateApReqAuthenticatorReq, AuthData), NDR_POINTER_NON_NULL, -1, + &ndr_KERB_ASN1_DATA_descr_s }, + { "GssChecksum", offsetof(CreateApReqAuthenticatorReq, GssChecksum), NDR_POINTER, -1, + &ndr_KERB_ASN1_DATA_descr_s }, + { "KeyUsage", offsetof(CreateApReqAuthenticatorReq, KeyUsage), NDR_NOT_POINTER, -1, + &ndr_uint32_descr_s }, +}; +static const NdrStructDescr CreateApReqAuthenticatorReq_struct = { + "CreateApReqAuthenticatorReq", ARRAYSIZE(CreateApReqAuthenticatorReq_fields), + CreateApReqAuthenticatorReq_fields +}; + +RDPEAR_SIMPLE_MESSAGE_TYPE(CreateApReqAuthenticatorReq) + +/* ========================== CreateApReqAuthenticatorResp ======================== */ + +static const NdrFieldStruct CreateApReqAuthenticatorResp_fields[] = { + { "AuthenticatorTime", offsetof(CreateApReqAuthenticatorResp, AuthenticatorTime), + NDR_NOT_POINTER, -1, &ndr_uint64_descr_s }, + { "Authenticator", offsetof(CreateApReqAuthenticatorResp, Authenticator), NDR_NOT_POINTER, -1, + &ndr_KERB_ASN1_DATA_descr_s }, + { "KerbProtocolError", offsetof(CreateApReqAuthenticatorResp, KerbProtocolError), + NDR_NOT_POINTER, -1, &ndr_uint32_descr_s }, +}; + +static const NdrStructDescr CreateApReqAuthenticatorResp_struct = { + "CreateApReqAuthenticatorResp", ARRAYSIZE(CreateApReqAuthenticatorResp_fields), + CreateApReqAuthenticatorResp_fields +}; + +RDPEAR_SIMPLE_MESSAGE_TYPE(CreateApReqAuthenticatorResp) + +/* ========================== UnpackKdcReplyBodyReq ======================== */ + +static const NdrFieldStruct UnpackKdcReplyBodyReq_fields[] = { + { "EncryptedData", offsetof(UnpackKdcReplyBodyReq, EncryptedData), NDR_POINTER_NON_NULL, -1, + &ndr_KERB_ASN1_DATA_descr_s }, + { "Key", offsetof(UnpackKdcReplyBodyReq, Key), NDR_POINTER_NON_NULL, -1, + &ndr_KERB_RPC_ENCRYPTION_KEY_descr_s }, + { "StrenghtenKey", offsetof(UnpackKdcReplyBodyReq, StrengthenKey), NDR_POINTER, -1, + &ndr_KERB_RPC_ENCRYPTION_KEY_descr_s }, + { "Pdu", offsetof(UnpackKdcReplyBodyReq, Pdu), NDR_NOT_POINTER, -1, &ndr_uint32_descr_s }, + { "KeyUsage", offsetof(UnpackKdcReplyBodyReq, KeyUsage), NDR_NOT_POINTER, -1, + &ndr_uint32_descr_s }, +}; + +static const NdrStructDescr UnpackKdcReplyBodyReq_struct = { + "UnpackKdcReplyBodyReq", ARRAYSIZE(UnpackKdcReplyBodyReq_fields), UnpackKdcReplyBodyReq_fields +}; + +RDPEAR_SIMPLE_MESSAGE_TYPE(UnpackKdcReplyBodyReq) + +/* ========================== UnpackKdcReplyBodyResp ======================== */ + +static const NdrFieldStruct UnpackKdcReplyBodyResp_fields[] = { + { "KerbProtocolError", offsetof(UnpackKdcReplyBodyResp, KerbProtocolError), NDR_NOT_POINTER, -1, + &ndr_uint32_descr_s }, + { "ReplyBody", offsetof(UnpackKdcReplyBodyResp, ReplyBody), NDR_NOT_POINTER, -1, + &ndr_KERB_ASN1_DATA_descr_s } +}; + +static const NdrStructDescr UnpackKdcReplyBodyResp_struct = { + "UnpackKdcReplyBodyResp", ARRAYSIZE(UnpackKdcReplyBodyResp_fields), + UnpackKdcReplyBodyResp_fields +}; + +RDPEAR_SIMPLE_MESSAGE_TYPE(UnpackKdcReplyBodyResp) + +/* ========================== DecryptApReplyReq ======================== */ + +static const NdrFieldStruct DecryptApReplyReq_fields[] = { + { "EncryptedReply", offsetof(DecryptApReplyReq, EncryptedReply), NDR_POINTER_NON_NULL, -1, + &ndr_KERB_ASN1_DATA_descr_s }, + { "Key", offsetof(DecryptApReplyReq, Key), NDR_POINTER_NON_NULL, -1, + &ndr_KERB_RPC_ENCRYPTION_KEY_descr_s } +}; + +static const NdrStructDescr DecryptApReplyReq_struct = { "DecryptApReplyReq", + ARRAYSIZE(DecryptApReplyReq_fields), + DecryptApReplyReq_fields }; + +RDPEAR_SIMPLE_MESSAGE_TYPE(DecryptApReplyReq) + +/* ========================== PackApReplyReq ======================== */ + +static const NdrFieldStruct PackApReplyReq_fields[] = { + { "Reply", offsetof(PackApReplyReq, Reply), NDR_POINTER_NON_NULL, -1, + &ndr_KERB_ASN1_DATA_descr_s }, + { "ReplyBody", offsetof(PackApReplyReq, ReplyBody), NDR_POINTER_NON_NULL, -1, + &ndr_KERB_ASN1_DATA_descr_s }, + { "SessionKey", offsetof(PackApReplyReq, SessionKey), NDR_POINTER_NON_NULL, -1, + &ndr_KERB_RPC_ENCRYPTION_KEY_descr_s } +}; + +static const NdrStructDescr PackApReplyReq_struct = { "PackApReplyReq", + ARRAYSIZE(PackApReplyReq_fields), + PackApReplyReq_fields }; + +RDPEAR_SIMPLE_MESSAGE_TYPE(PackApReplyReq) + +/* ========================== PackApReplyResp ======================== */ + +static const NdrFieldStruct PackApReplyResp_fields[] = { + { "PackedReplySize", offsetof(PackApReplyResp, PackedReplyHints), NDR_NOT_POINTER, -1, + &ndr_uint32_descr_s }, + { "PackedReply", offsetof(PackApReplyResp, PackedReply), NDR_POINTER_NON_NULL, 0, + &ndr_uint8Array_descr_s }, +}; + +static const NdrStructDescr PackApReplyResp_struct = { "PackApReplyResp", + ARRAYSIZE(PackApReplyResp_fields), + PackApReplyResp_fields }; + +RDPEAR_SIMPLE_MESSAGE_TYPE(PackApReplyResp) diff --git a/third_party/FreeRDP/channels/rdpear/common/test/CMakeLists.txt b/third_party/FreeRDP/channels/rdpear/common/test/CMakeLists.txt new file mode 100644 index 0000000..68ebd4b --- /dev/null +++ b/third_party/FreeRDP/channels/rdpear/common/test/CMakeLists.txt @@ -0,0 +1,30 @@ +set(MODULE_NAME "TestRdpear") +set(MODULE_PREFIX "TEST_RDPEAR") + +set(TEST_RDPEAR_DRIVER TestRdpear.c) + +disable_warnings_for_directory(${CMAKE_CURRENT_BINARY_DIR}) + +set(TEST_RDPEAR_TESTS TestNdr.c) + +if(BUILD_TESTING_INTERNAL) + list(APPEND TEST_RDPEAR_TESTS TestNdrEar.c) +endif() + +create_test_sourcelist(TEST_RDPEAR_SRCS TestRdpear.c ${TEST_RDPEAR_TESTS}) + +add_executable(${MODULE_NAME} ${TEST_RDPEAR_SRCS}) + +add_compile_definitions(TESTING_OUTPUT_DIRECTORY="${PROJECT_BINARY_DIR}") +add_compile_definitions(TESTING_SRC_DIRECTORY="${PROJECT_SOURCE_DIR}") + +target_link_libraries(${MODULE_NAME} freerdp winpr freerdp-client rdpear-common) + +set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}") + +foreach(test ${${MODULE_PREFIX}_TESTS}) + get_filename_component(TestName ${test} NAME_WE) + add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName}) +endforeach() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "FreeRDP/Rdpear/Test") diff --git a/third_party/FreeRDP/channels/rdpear/common/test/TestNdr.c b/third_party/FreeRDP/channels/rdpear/common/test/TestNdr.c new file mode 100644 index 0000000..bc471be --- /dev/null +++ b/third_party/FreeRDP/channels/rdpear/common/test/TestNdr.c @@ -0,0 +1,40 @@ +#include + +int TestNdr(int argc, char* argv[]) +{ + WINPR_UNUSED(argc); + WINPR_UNUSED(argv); + + int retCode = -2; + NdrContext* context = ndr_context_new(FALSE, 1); + if (!context) + return -1; + + BYTE payload[] = { + // == conformant array == + 0x02, 0x00, 0x00, 0x00, // (nitems) + 0x30, 0x00, // content + 0x00, 0x00 // (padding) + }; + wStream staticS; + wStream* s = Stream_StaticInit(&staticS, payload, sizeof(payload)); + + BYTE* target = nullptr; + NdrArrayHints hints = { 2 }; + NdrDeferredEntry e = { 0x020028, "arrayContent", &hints, (void*)&target, + ndr_uint8Array_descr() }; + + if (!ndr_push_deferreds(context, &e, 1)) + goto out; + + if (!ndr_treat_deferred_read(context, s)) + goto out; + + NdrMessageType descr = ndr_uint8Array_descr(); + descr->destroyFn(context, &hints, target); + free(target); + retCode = 0; +out: + ndr_context_destroy(&context); + return retCode; +} diff --git a/third_party/FreeRDP/channels/rdpear/common/test/TestNdrEar.c b/third_party/FreeRDP/channels/rdpear/common/test/TestNdrEar.c new file mode 100644 index 0000000..4411a6b --- /dev/null +++ b/third_party/FreeRDP/channels/rdpear/common/test/TestNdrEar.c @@ -0,0 +1,417 @@ +#include + +#include +#include + +#ifndef MAX +#define MAX(a, b) ((a) > (b)) ? (a) : (b) +#endif +#ifndef MIN +#define MIN(a, b) ((a) < (b)) ? (a) : (b) +#endif + +static BYTE nextValue(BYTE old, INT32 offset, char symbol, char startSymbol) +{ + const INT32 uold = 16 * old; + const INT32 diff = symbol - startSymbol; + const INT32 res = uold + diff + offset; + return (BYTE)MIN(MAX(0, res), UINT8_MAX); +} + +static BYTE* parseHexBlock(const char* str, size_t* plen) +{ + WINPR_ASSERT(str); + WINPR_ASSERT(plen); + + BYTE* ret = malloc(strlen(str) / 2); + BYTE* dest = ret; + const char* ptr = str; + BYTE tmp = 0; + size_t nchars = 0; + size_t len = 0; + + for (; *ptr; ptr++) + { + switch (*ptr) + { + case ' ': + case '\n': + case '\t': + if (nchars) + { + WLog_ERR("", "error parsing hex block, unpaired char"); + free(ret); + return nullptr; + } + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + tmp = nextValue(tmp, 0, *ptr, '0'); + nchars++; + if (nchars == 2) + { + *dest = tmp; + dest++; + len++; + tmp = 0; + nchars = 0; + } + break; + + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + tmp = nextValue(tmp, 10, *ptr, 'a'); + nchars++; + if (nchars == 2) + { + *dest = tmp; + dest++; + len++; + tmp = 0; + nchars = 0; + } + break; + + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + tmp = nextValue(tmp, 10, *ptr, 'A'); + nchars++; + if (nchars == 2) + { + *dest = tmp; + dest++; + len++; + tmp = 0; + nchars = 0; + } + break; + default: + WLog_ERR("", "invalid char in hex block"); + free(ret); + return nullptr; + } + } + + *plen = len; + return ret; +} + +static int TestNdrEarWrite(int argc, char* argv[]) +{ + WINPR_UNUSED(argc); + WINPR_UNUSED(argv); + + int rc = -1; + BYTE buffer[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + KERB_ASN1_DATA asn1 = { 7, { 16 }, buffer }; + + wStream* s = Stream_New(nullptr, 100); + if (!s) + return -1; + + NdrContext* context = ndr_context_new(FALSE, 1); + if (!context) + goto fail; + + if (!ndr_write_KERB_ASN1_DATA(context, s, nullptr, &asn1)) + goto fail; + if (!ndr_treat_deferred_write(context, s)) + goto fail; + + // winpr_HexDump("", WLOG_DEBUG, Stream_Buffer(s), Stream_GetPosition(s)); + + rc = 0; +fail: + ndr_context_destroy(&context); + Stream_Free(s, TRUE); + return rc; +} + +static BOOL run_payload(NdrContext* context, const BYTE* payload4, size_t sizeofPayload4) +{ + WINPR_ASSERT(context); + + if (!payload4) + return FALSE; + + CreateApReqAuthenticatorReq createApReqAuthenticatorReq = WINPR_C_ARRAY_INIT; + + wStream staticS = WINPR_C_ARRAY_INIT; + wStream* s = Stream_StaticInit(&staticS, payload4, sizeofPayload4); + if (!ndr_skip_bytes(context, s, 4)) /* skip union id */ + return FALSE; + if (!ndr_read_CreateApReqAuthenticatorReq(context, s, nullptr, &createApReqAuthenticatorReq)) + return FALSE; + if (!ndr_treat_deferred_read(context, s)) + return FALSE; + if (createApReqAuthenticatorReq.KeyUsage != 7) + return FALSE; + if (!createApReqAuthenticatorReq.EncryptionKey) + return FALSE; + if (createApReqAuthenticatorReq.SubKey) + return FALSE; + if (!createApReqAuthenticatorReq.ClientName) + return FALSE; + if (createApReqAuthenticatorReq.ClientName->nameHints.count != 1) + return FALSE; + if (!createApReqAuthenticatorReq.ClientRealm) + return FALSE; + if (!createApReqAuthenticatorReq.AuthData) + return FALSE; + if (createApReqAuthenticatorReq.AuthData->Asn1BufferHints.count != 2) + return FALSE; + if (!createApReqAuthenticatorReq.SkewTime) + return FALSE; + if (createApReqAuthenticatorReq.SkewTime->QuadPart != 0) + return FALSE; + + ndr_destroy_CreateApReqAuthenticatorReq(context, nullptr, &createApReqAuthenticatorReq); + ndr_context_reset(context); + return TRUE; +} + +static int TestNdrEarRead(int argc, char* argv[]) +{ + WINPR_UNUSED(argc); + WINPR_UNUSED(argv); + + int retCode = -1; + + /* ====================================================================== */ + NdrContext* context = ndr_context_new(FALSE, 1); + if (!context) + return -1; + + { + retCode = -2; + const BYTE payload[] = { + 0x00, 0x00, 0x00, 0x00, // (PduType) + 0x02, 0x00, 0x00, 0x00, // (Length) + 0x28, 0x00, 0x02, 0x00, // (Asn1Buffer) + + // == conformant array == + 0x02, 0x00, 0x00, 0x00, // (nitems) + 0x30, 0x00, // content + 0x00, 0x00 // (padding) + }; + + wStream staticS = WINPR_C_ARRAY_INIT; + wStream* s = Stream_StaticInit(&staticS, payload, sizeof(payload)); + + KERB_ASN1_DATA asn1 = WINPR_C_ARRAY_INIT; + if (!ndr_read_KERB_ASN1_DATA(context, s, nullptr, &asn1) || + !ndr_treat_deferred_read(context, s) || asn1.Asn1BufferHints.count != 2 || + *asn1.Asn1Buffer != 0x30) + goto out; + ndr_destroy_KERB_ASN1_DATA(context, nullptr, &asn1); + ndr_context_reset(context); + + /* ====================================================================== */ + } + { + retCode = -3; + const BYTE payload2[] = { + // ------------ a RPC_UNICODE_STRING: Administrateur ------------------------- + 0x1c, 0x00, // (Length) + 0x1e, 0x00, // (MaximumLength) + 0x1c, 0x00, 0x02, 0x00, // (Buffer ptr) + // == conformant array == + 0x0f, 0x00, 0x00, 0x00, // (maximum count) + 0x00, 0x00, 0x00, 0x00, // (offset) + 0x0e, 0x00, 0x00, 0x00, // (length) + + 0x48, 0x00, 0x41, 0x00, 0x52, 0x00, 0x44, 0x00, 0x45, 0x00, 0x4e, 0x00, 0x49, 0x00, + 0x4e, 0x00, 0x47, 0x00, 0x33, 0x00, 0x2e, 0x00, 0x43, 0x00, 0x4f, 0x00, 0x4d, 0x00, + 0x00, 0x00, + + 0x00, 0x00 + }; + + wStream staticS = WINPR_C_ARRAY_INIT; + wStream* s = Stream_StaticInit(&staticS, payload2, sizeof(payload2)); + RPC_UNICODE_STRING unicode = WINPR_C_ARRAY_INIT; + if (!ndr_read_RPC_UNICODE_STRING(context, s, nullptr, &unicode) || + !ndr_treat_deferred_read(context, s)) + goto out; + ndr_destroy_RPC_UNICODE_STRING(context, nullptr, &unicode); + ndr_context_reset(context); + } + { + retCode = -4; + /* ====================================================================== */ + const BYTE payload3[] = { + // ------------ an KERB_RPC_INTERNAL_NAME: HARDENING3.COM ------------------------- + 0x01, 0x00, // (NameType) + 0x01, 0x00, // (NameCount) + 0x10, 0x00, 0x02, 0x00, // (Names) + // == conformant array == + 0x01, 0x00, 0x00, 0x00, // (nitems) + + // = RPC_UNICODE_STRING = + 0x1c, 0x00, // (Length) + 0x1e, 0x00, // (MaximumLength) + 0x14, 0x00, 0x02, 0x00, /// (Buffer ptr) + // == Uni-dimensional Conformant-varying Array == + 0x0f, 0x00, 0x00, 0x00, // (maximum count) + 0x00, 0x00, 0x00, 0x00, // (offset) + 0x0e, 0x00, 0x00, 0x00, // (length) + 0x41, 0x00, 0x64, 0x00, 0x6d, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x69, 0x00, 0x73, 0x00, + 0x74, 0x00, 0x72, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x75, 0x00, 0x72, 0x00, + 0x00, 0x00, + + 0x00, 0x00 + }; + KERB_RPC_INTERNAL_NAME intName = WINPR_C_ARRAY_INIT; + + wStream staticS = WINPR_C_ARRAY_INIT; + wStream* s = Stream_StaticInit(&staticS, payload3, sizeof(payload3)); + if (!ndr_read_KERB_RPC_INTERNAL_NAME(context, s, nullptr, &intName) || + !ndr_treat_deferred_read(context, s)) + goto out; + ndr_destroy_KERB_RPC_INTERNAL_NAME(context, nullptr, &intName); + ndr_context_reset(context); + } + + /* ====================================================================== */ + { + retCode = -5; + const BYTE payload4[] = { + 0x03, 0x01, 0x03, 0x01, // unionId / unionId + 0x04, 0x00, 0x02, 0x00, // (EncryptionKey ptr) + 0xf8, 0xca, 0x95, 0x11, // (SequenceNumber) + 0x0c, 0x00, 0x02, 0x00, // (ClientName ptr) + 0x18, 0x00, 0x02, 0x00, // (ClientRealm ptr) + 0x20, 0x00, 0x02, 0x00, // (SkewTime ptr) + 0x00, 0x00, 0x00, 0x00, // (SubKey ptr) + 0x24, 0x00, 0x02, 0x00, // (AuthData ptr) + 0x2c, 0x00, 0x02, 0x00, // (GssChecksum ptr) + 0x07, 0x00, 0x00, 0x00, // (KeyUsage) + + // === EncryptionKey === + 0x40, 0xe9, 0x12, 0xdf, // reserved1 + 0x12, 0x00, 0x00, 0x00, // reserved2 + // KERB_RPC_OCTET_STRING + 0x4c, 0x00, 0x00, 0x00, // (length) + 0x08, 0x00, 0x02, 0x00, // (value ptr) + // == conformant array == + 0x4c, 0x00, 0x00, 0x00, // (length) + 0xc4, 0x41, 0xee, 0x34, 0x82, 0x2b, 0x29, 0x61, 0xe2, 0x96, 0xb5, 0x75, 0x61, 0x2d, + 0xbf, 0x86, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x89, 0x08, 0x60, 0x2e, 0x30, 0x3e, 0xfe, 0x56, 0x11, 0xf0, + 0x31, 0xf2, 0xd6, 0x2e, 0x3d, 0x33, 0xfe, 0xce, 0x56, 0x12, 0xbf, 0xb2, 0xe5, 0x86, + 0x29, 0x8d, 0x29, 0x74, 0x1f, 0x8a, 0xf9, 0xb9, 0x8c, 0xd4, 0x86, 0x3a, 0x21, 0x92, + 0xb2, 0x07, 0x95, 0x4b, 0xea, 0xee, + + //=== ClientName - KERB_RPC_INTERNAL_NAME === + 0x01, 0x00, // (NameType) + 0x01, 0x00, // (NameCount) + 0x10, 0x00, 0x02, 0x00, // (Names) + + 0x01, 0x00, 0x00, 0x00, // (nitems) + + // = RPC_UNICODE_STRING = + 0x1c, 0x00, // (Length) + 0x1e, 0x00, // (MaximumLength) + 0x14, 0x00, 0x02, 0x00, //(Buffer ptr) + // == Uni-dimensional Conformant-varying Array == + 0x0f, 0x00, 0x00, 0x00, // (maximum count) + 0x00, 0x00, 0x00, 0x00, // (offset) + 0x0e, 0x00, 0x00, 0x00, // (length) + 0x41, 0x00, 0x64, 0x00, 0x6d, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x69, 0x00, 0x73, 0x00, + 0x74, 0x00, 0x72, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x75, 0x00, 0x72, 0x00, + + // === ClientRealm - RPC_UNICODE_STRING === + 0x1c, 0x00, // (Length) + 0x1e, 0x00, // (MaximumLength) + 0x1c, 0x00, 0x02, 0x00, // (Buffer ptr) + // == Uni-dimensional conformant varying array == + 0x0f, 0x00, 0x00, 0x00, // (maximum count) + 0x00, 0x00, 0x00, 0x00, // (offset) + 0x0e, 0x00, 0x00, 0x00, // (length) + 0x48, 0x00, 0x41, 0x00, 0x52, 0x00, 0x44, 0x00, 0x45, 0x00, 0x4e, 0x00, 0x49, 0x00, + 0x4e, 0x00, 0x47, 0x00, 0x33, 0x00, 0x2e, 0x00, 0x43, 0x00, 0x4f, 0x00, 0x4d, 0x00, + + 0x00, 0x00, 0x00, 0x00, // padding + + // == SkewTime == + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + // === AuthData - KERB_ASN1_DATA == + 0x00, 0x00, 0x00, 0x00, // (PduType) + 0x02, 0x00, 0x00, 0x00, // (Length) + 0x28, 0x00, 0x02, 0x00, // (Asn1Buffer) + // == conformant array == + 0x02, 0x00, 0x00, 0x00, // (nitems) + 0x30, 0x00, 0x00, 0x00, // (padding) + + // === GssChecksum - KERB_ASN1_DATA === + 0x08, 0x00, 0x00, 0x00, // (PduType) + 0x1b, 0x00, 0x00, 0x00, // (Length) + 0x30, 0x00, 0x02, 0x00, // (Asn1Buffer) + // == conformant array == + 0x1b, 0x00, 0x00, 0x00, // (length) + 0x30, 0x19, 0xa0, 0x03, 0x02, 0x01, 0x07, 0xa1, 0x12, 0x04, 0x10, 0xb9, 0x4f, 0xcd, + 0xae, 0xd9, 0xa8, 0xff, 0x49, 0x69, 0x5a, 0xd1, 0x1d, 0x38, 0x49, 0xb6, 0x92, 0x00 + }; + if (!run_payload(context, payload4, sizeof(payload4))) + goto out; + } + + { + retCode = -6; + size_t sizeofPayload4 = 0; + BYTE* payload4 = parseHexBlock("03 01 03 01 \ + 04 00 02 00 38 9e ef 6b 0c 00 02 00 18 00 02 00 \ + 20 00 02 00 00 00 00 00 24 00 02 00 2c 00 02 00 \ + 07 00 00 00 13 8a a5 a8 12 00 00 00 20 00 00 00 \ + 08 00 02 00 20 00 00 00 c9 03 42 a8 17 8f d9 c4 \ + 9b d2 c4 6e 73 64 98 7b 90 f5 9a 28 77 8e ca de \ + 29 2e a3 8d 8a 56 36 d5 01 00 01 00 10 00 02 00 \ + 01 00 00 00 1c 00 1e 00 14 00 02 00 0f 00 00 00 \ + 00 00 00 00 0e 00 00 00 41 00 64 00 6d 00 69 00 \ + 6e 00 69 00 73 00 74 00 72 00 61 00 74 00 65 00 \ + 75 00 72 00 1c 00 1e 00 1c 00 02 00 0f 00 00 00 \ + 00 00 00 00 0e 00 00 00 48 00 41 00 52 00 44 00 \ + 45 00 4e 00 49 00 4e 00 47 00 33 00 2e 00 43 00 \ + 4f 00 4d 00 00 00 00 00 00 00 00 00 00 00 00 00 \ + 02 00 00 00 28 00 02 00 02 00 00 00 30 00 00 00 \ + 08 00 00 00 1b 00 00 00 30 00 02 00 1b 00 00 00 \ + 30 19 a0 03 02 01 07 a1 12 04 10 e4 aa ff 2b 93 \ + 97 4c f2 5c 0b 49 85 72 92 94 54 00", + &sizeofPayload4); + const BOOL rc = run_payload(context, payload4, sizeofPayload4); + free(payload4); + if (!rc) + goto out; + } + + /* ============ successful end of test =============== */ + retCode = 0; +out: + ndr_context_destroy(&context); + return retCode; +} + +int TestNdrEar(int argc, char* argv[]) +{ + const int rc = TestNdrEarWrite(argc, argv); + if (rc) + return rc; + return TestNdrEarRead(argc, argv); +} diff --git a/third_party/FreeRDP/channels/rdpecam/CMakeLists.txt b/third_party/FreeRDP/channels/rdpecam/CMakeLists.txt new file mode 100644 index 0000000..19fd20f --- /dev/null +++ b/third_party/FreeRDP/channels/rdpecam/CMakeLists.txt @@ -0,0 +1,31 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2022 Pascal Nowack +# +# 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. + +define_channel("rdpecam") + +if(WITH_SERVER_CHANNELS) + include_directories(common) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_CLIENT_CHANNELS) + option(RDPECAM_CLIENT_CHANNEL_STUB "Only build [MS-RDPECAM] channel stub" OFF) + if(NOT RDPECAM_CLIENT_CHANNEL_STUB) + include_directories(common) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) + endif() +endif() diff --git a/third_party/FreeRDP/channels/rdpecam/ChannelOptions.cmake b/third_party/FreeRDP/channels/rdpecam/ChannelOptions.cmake new file mode 100644 index 0000000..ba12c2d --- /dev/null +++ b/third_party/FreeRDP/channels/rdpecam/ChannelOptions.cmake @@ -0,0 +1,20 @@ +set(OPTION_DEFAULT ON) +set(OPTION_CLIENT_DEFAULT OFF) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options( + NAME + "rdpecam" + TYPE + "dynamic" + DESCRIPTION + "Video Capture Virtual Channel Extension" + SPECIFICATIONS + "[MS-RDPECAM]" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/rdpecam/client/CMakeLists.txt b/third_party/FreeRDP/channels/rdpecam/client/CMakeLists.txt new file mode 100644 index 0000000..9019c50 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpecam/client/CMakeLists.txt @@ -0,0 +1,55 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2024 Oleg Turovski +# +# 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. + +define_channel_client("rdpecam") + +if(NOT WITH_SWSCALE OR NOT WITH_FFMPEG) + message(FATAL_ERROR "WITH_FFMPEG and WITH_SWSCALE required for CHANNEL_RDPECAM_CLIENT") +endif() +freerdp_client_pc_add_requires_private("libswscale") + +# currently camera redirect client supported for platforms with Video4Linux only +find_package(FFmpeg REQUIRED COMPONENTS SWSCALE) +find_package(V4L) +if(V4L_FOUND) + set(WITH_V4L ON) + add_compile_definitions("WITH_V4L") +else() + message(FATAL_ERROR "libv4l-dev required for CHANNEL_RDPECAM_CLIENT") +endif() + +option(RDPECAM_INPUT_FORMAT_H264 "[MS-RDPECAM] Enable H264 camera format (passthrough)" ON) +if(RDPECAM_INPUT_FORMAT_H264) + add_compile_definitions("WITH_INPUT_FORMAT_H264") +endif() + +option(RDPECAM_INPUT_FORMAT_MJPG "[MS-RDPECAM] Enable MJPG camera format" ON) +if(RDPECAM_INPUT_FORMAT_MJPG) + add_compile_definitions("WITH_INPUT_FORMAT_MJPG") +endif() + +include_directories(SYSTEM ${SWSCALE_INCLUDE_DIRS}) + +set(${MODULE_PREFIX}_SRCS camera_device_enum_main.c camera_device_main.c encoding.c) + +set(${MODULE_PREFIX}_LIBS freerdp winpr ${SWSCALE_LIBRARIES} ${FFMPEG_LIBRARIES}) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") + +if(V4L_FOUND) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "v4l" "") +endif() diff --git a/third_party/FreeRDP/channels/rdpecam/client/camera.h b/third_party/FreeRDP/channels/rdpecam/client/camera.h new file mode 100644 index 0000000..42ebfe2 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpecam/client/camera.h @@ -0,0 +1,295 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * MS-RDPECAM Implementation, main header file + * + * Copyright 2024 Oleg Turovski + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_CAMERA_H +#define FREERDP_CLIENT_CAMERA_H + +#include +#include +#include +#include + +#if defined(WITH_INPUT_FORMAT_MJPG) +#include +#endif + +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#define ECAM_PROTO_VERSION 0x02 +/* currently supporting 1 stream per device */ +#define ECAM_DEVICE_MAX_STREAMS 1 +#define ECAM_MAX_MEDIA_TYPE_DESCRIPTORS 256 + +/* Allow to send up to that many unsolicited samples. + * For example, to support 30 fps with 250 ms round trip + * ECAM_MAX_SAMPLE_CREDITS has to be at least 8. + */ +#define ECAM_MAX_SAMPLE_CREDITS 8 + +/* Having this hardcoded allows to preallocate and reuse buffer + * for sample responses. Excessive size is to make sure any sample + * will fit in, even with highest resolution. + */ +#define ECAM_SAMPLE_RESPONSE_BUFFER_SIZE (1024ULL * 4050ULL) + +/* Special format addition for CAM_MEDIA_FORMAT enum formats + * used to support H264 stream muxed in MJPG container stream. + * The value picked not to overlap with enum values + */ +#define CAM_MEDIA_FORMAT_MJPG_H264 0x0401 + +typedef struct s_ICamHal ICamHal; + +typedef struct +{ + IWTSPlugin iface; + IWTSListener* listener; + GENERIC_LISTENER_CALLBACK* hlistener; + + /* HAL interface */ + ICamHal* ihal; + char* subsystem; + + BOOL initialized; + BOOL attached; + + UINT32 version; + wHashTable* devices; + +} CameraPlugin; + +typedef struct +{ + CAM_MEDIA_FORMAT inputFormat; /* camera side */ + CAM_MEDIA_FORMAT outputFormat; /* network side */ + +} CAM_MEDIA_FORMAT_INFO; + +typedef struct +{ + BOOL streaming; + CAM_MEDIA_FORMAT_INFO formats; + CAM_MEDIA_TYPE_DESCRIPTION currMediaType; + + GENERIC_CHANNEL_CALLBACK* hSampleReqChannel; + CRITICAL_SECTION lock; + volatile LONG samplesRequested; + wStream* pendingSample; + volatile BOOL haveSample; + wStream* sampleRespBuffer; + + H264_CONTEXT* h264; + +#if defined(WITH_INPUT_FORMAT_MJPG) + AVCodecContext* avContext; + AVPacket* avInputPkt; + AVFrame* avOutFrame; +#endif + +#if defined(WITH_INPUT_FORMAT_H264) + size_t h264FrameMaxSize; + BYTE* h264Frame; +#endif + + /* sws_scale */ + uint32_t swsWidth; + uint32_t swsHeight; + struct SwsContext* sws; + +} CameraDeviceStream; + +WINPR_ATTR_NODISCARD +static inline CAM_MEDIA_FORMAT streamInputFormat(CameraDeviceStream* stream) +{ + return stream->formats.inputFormat; +} + +WINPR_ATTR_NODISCARD +static inline CAM_MEDIA_FORMAT streamOutputFormat(CameraDeviceStream* stream) +{ + return stream->formats.outputFormat; +} + +typedef struct +{ + IWTSListener* listener; + GENERIC_LISTENER_CALLBACK* hlistener; + CameraPlugin* ecam; + ICamHal* ihal; /* HAL interface, same as used by CameraPlugin */ + char deviceId[32]; + CameraDeviceStream streams[ECAM_DEVICE_MAX_STREAMS]; + +} CameraDevice; + +/** + * Subsystem (Hardware Abstraction Layer, HAL) Interface + */ + +typedef UINT (*ICamHalEnumCallback)(CameraPlugin* ecam, GENERIC_CHANNEL_CALLBACK* hchannel, + const char* deviceId, const char* deviceName); + +/* may run in context of different thread */ +typedef UINT (*ICamHalSampleCapturedCallback)(CameraDevice* dev, size_t streamIndex, + const BYTE* sample, size_t size); + +/** @brief interface to implement for the camera HAL*/ +struct s_ICamHal +{ + /** callback to enumerate available camera calling callback for each found item + * + * @param ihal the hal interface + * @param callback the enum callback + * @param ecam the camera plugin + * @param hchannel the generic freerdp channel + * @return the number of found cameras + */ + WINPR_ATTR_NODISCARD UINT (*Enumerate)(ICamHal* ihal, ICamHalEnumCallback callback, + CameraPlugin* ecam, GENERIC_CHANNEL_CALLBACK* hchannel); + + /** + * callback to activate a given camera device + * @param ihal the hal interface + * @param deviceId the name of the device + * @param errorCode a pointer to an error code set if the call failed + * @return if the operation was successful + * @since 3.18.0 + */ + WINPR_ATTR_NODISCARD BOOL (*Activate)(ICamHal* ihal, const char* deviceId, + CAM_ERROR_CODE* errorCode); + + /** + * callback to deactivate a given camera device + * @param ihal the hal interface + * @param deviceId the name of the device + * @param errorCode a pointer to an error code set if the call failed + * @return if the operation was successful + * @since 3.18.0 + */ + WINPR_ATTR_NODISCARD BOOL (*Deactivate)(ICamHal* ihal, const char* deviceId, + CAM_ERROR_CODE* errorCode); + + /** + * callback that returns the list of compatible media types given a set of supported formats + * @param ihal the hal interface + * @param deviceId the name of the device + * @param streamIndex stream index number + * @param supportedFormats a pointer to supported formats + * @param nSupportedFormats number of supported formats + * @param mediaTypes resulting media type descriptors + * @param nMediaTypes output number of media descriptors + * @return number of matched supported formats + */ + WINPR_ATTR_NODISCARD INT16 (*GetMediaTypeDescriptions)( + ICamHal* ihal, const char* deviceId, size_t streamIndex, + const CAM_MEDIA_FORMAT_INFO* supportedFormats, size_t nSupportedFormats, + CAM_MEDIA_TYPE_DESCRIPTION* mediaTypes, size_t* nMediaTypes); + + /** + * callback to start a stream + * @param ihal the hal interface + * @param dev + * @param streamIndex stream index number + * @param mediaType + * @param callback + * @return \b CAM_ERROR_CODE_None on success, a CAM_Error otherwise + */ + WINPR_ATTR_NODISCARD CAM_ERROR_CODE (*StartStream)(ICamHal* ihal, CameraDevice* dev, + size_t streamIndex, + const CAM_MEDIA_TYPE_DESCRIPTION* mediaType, + ICamHalSampleCapturedCallback callback); + + /** + * callback to stop a stream + * @param ihal the hal interface + * @param deviceId the name of the device + * @param streamIndex stream index number + * @return \b CAM_ERROR_CODE_None on success, a CAM_Error otherwise + */ + CAM_ERROR_CODE (*StopStream)(ICamHal* ihal, const char* deviceId, size_t streamIndex); + + /** + * callback to free the ICamHal + * @param hal the hal interface + * @return \b CAM_ERROR_CODE_None on success, a CAM_Error otherwise + */ + CAM_ERROR_CODE (*Free)(ICamHal* ihal); +}; + +typedef UINT (*PREGISTERCAMERAHAL)(IWTSPlugin* plugin, ICamHal* hal); + +typedef struct +{ + IWTSPlugin* plugin; + WINPR_ATTR_NODISCARD PREGISTERCAMERAHAL pRegisterCameraHal; + CameraPlugin* ecam; + const ADDIN_ARGV* args; + +} FREERDP_CAMERA_HAL_ENTRY_POINTS; + +typedef FREERDP_CAMERA_HAL_ENTRY_POINTS* PFREERDP_CAMERA_HAL_ENTRY_POINTS; + +/* entry point called by addin manager */ +typedef UINT(VCAPITYPE* PFREERDP_CAMERA_HAL_ENTRY)(PFREERDP_CAMERA_HAL_ENTRY_POINTS pEntryPoints); + +/* common functions */ +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT ecam_channel_send_generic_msg( + CameraPlugin* ecam, GENERIC_CHANNEL_CALLBACK* hchannel, CAM_MSG_ID msg); + +FREERDP_LOCAL UINT ecam_channel_send_error_response(CameraPlugin* ecam, + GENERIC_CHANNEL_CALLBACK* hchannel, + CAM_ERROR_CODE code); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT ecam_channel_write(CameraPlugin* ecam, + GENERIC_CHANNEL_CALLBACK* hchannel, + CAM_MSG_ID msg, wStream* out, + BOOL freeStream); + +/* ecam device interface */ +FREERDP_LOCAL void ecam_dev_destroy(CameraDevice* dev); + +WINPR_ATTR_MALLOC(ecam_dev_destroy, 1) +WINPR_ATTR_NODISCARD +FREERDP_LOCAL CameraDevice* ecam_dev_create(CameraPlugin* ecam, const char* deviceId, + const char* deviceName); + +/* video encoding interface */ +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ecam_encoder_context_init(CameraDeviceStream* stream); + +FREERDP_LOCAL BOOL ecam_encoder_context_free(CameraDeviceStream* stream); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL ecam_encoder_compress(CameraDeviceStream* stream, + const BYTE* srcData, size_t srcSize, + BYTE** ppDstData, size_t* pDstSize); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT32 h264_get_max_bitrate(UINT32 height); + +#endif /* FREERDP_CLIENT_CAMERA_H */ diff --git a/third_party/FreeRDP/channels/rdpecam/client/camera_device_enum_main.c b/third_party/FreeRDP/channels/rdpecam/client/camera_device_enum_main.c new file mode 100644 index 0000000..111863f --- /dev/null +++ b/third_party/FreeRDP/channels/rdpecam/client/camera_device_enum_main.c @@ -0,0 +1,567 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * MS-RDPECAM Implementation, Device Enumeration Channel + * + * Copyright 2024 Oleg Turovski + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "camera.h" + +#define TAG CHANNELS_TAG("rdpecam-enum.client") + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT ecam_channel_send_error_response(CameraPlugin* ecam, GENERIC_CHANNEL_CALLBACK* hchannel, + CAM_ERROR_CODE code) +{ + CAM_MSG_ID msg = CAM_MSG_ID_ErrorResponse; + + WINPR_ASSERT(ecam); + + wStream* s = Stream_New(nullptr, CAM_HEADER_SIZE + 4); + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return ERROR_NOT_ENOUGH_MEMORY; + } + + Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, ecam->version)); + Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, msg)); + Stream_Write_UINT32(s, code); + + return ecam_channel_write(ecam, hchannel, msg, s, TRUE); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT ecam_channel_send_generic_msg(CameraPlugin* ecam, GENERIC_CHANNEL_CALLBACK* hchannel, + CAM_MSG_ID msg) +{ + WINPR_ASSERT(ecam); + + wStream* s = Stream_New(nullptr, CAM_HEADER_SIZE); + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return ERROR_NOT_ENOUGH_MEMORY; + } + + Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, ecam->version)); + Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, msg)); + + return ecam_channel_write(ecam, hchannel, msg, s, TRUE); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT ecam_channel_write(WINPR_ATTR_UNUSED CameraPlugin* ecam, GENERIC_CHANNEL_CALLBACK* hchannel, + CAM_MSG_ID msg, wStream* out, BOOL freeStream) +{ + if (!hchannel || !out) + return ERROR_INVALID_PARAMETER; + + Stream_SealLength(out); + WINPR_ASSERT(Stream_Length(out) <= UINT32_MAX); + + WLog_DBG(TAG, "ChannelId=%" PRIu32 ", MessageId=0x%02" PRIx8 ", Length=%" PRIuz, + hchannel->channel_mgr->GetChannelId(hchannel->channel), msg, Stream_Length(out)); + + const UINT error = hchannel->channel->Write(hchannel->channel, (ULONG)Stream_Length(out), + Stream_Buffer(out), nullptr); + + if (freeStream) + Stream_Free(out, TRUE); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_send_device_added_notification(CameraPlugin* ecam, + GENERIC_CHANNEL_CALLBACK* hchannel, + const char* deviceName, const char* channelName) +{ + CAM_MSG_ID msg = CAM_MSG_ID_DeviceAddedNotification; + + WINPR_ASSERT(ecam); + + wStream* s = Stream_New(nullptr, 256); + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return ERROR_NOT_ENOUGH_MEMORY; + } + + Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, ecam->version)); + Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, msg)); + + size_t devNameLen = strlen(deviceName); + if (Stream_Write_UTF16_String_From_UTF8(s, devNameLen + 1, deviceName, devNameLen, TRUE) < 0) + { + Stream_Free(s, TRUE); + return ERROR_INTERNAL_ERROR; + } + Stream_Write(s, channelName, strlen(channelName) + 1); + + return ecam_channel_write(ecam, hchannel, msg, s, TRUE); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_ihal_device_added_callback(CameraPlugin* ecam, GENERIC_CHANNEL_CALLBACK* hchannel, + const char* deviceId, const char* deviceName) +{ + WLog_DBG(TAG, "deviceId=%s, deviceName=%s", deviceId, deviceName); + + if (!HashTable_ContainsKey(ecam->devices, deviceId)) + { + CameraDevice* dev = ecam_dev_create(ecam, deviceId, deviceName); + if (!HashTable_Insert(ecam->devices, deviceId, dev)) + { + ecam_dev_destroy(dev); + return ERROR_INTERNAL_ERROR; + } + } + else + { + WLog_DBG(TAG, "Device %s already exists", deviceId); + } + + ecam_send_device_added_notification(ecam, hchannel, deviceName, deviceId /*channelName*/); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_enumerate_devices(CameraPlugin* ecam, GENERIC_CHANNEL_CALLBACK* hchannel) +{ + return ecam->ihal->Enumerate(ecam->ihal, ecam_ihal_device_added_callback, ecam, hchannel); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_process_select_version_response(CameraPlugin* ecam, + GENERIC_CHANNEL_CALLBACK* hchannel, + WINPR_ATTR_UNUSED wStream* s, BYTE serverVersion) +{ + const BYTE clientVersion = ECAM_PROTO_VERSION; + + /* check remaining s capacity */ + + WLog_DBG(TAG, "ServerVersion=%" PRIu8 ", ClientVersion=%" PRIu8, serverVersion, clientVersion); + + if (serverVersion > clientVersion) + { + WLog_ERR(TAG, + "Incompatible protocol version server=%" PRIu8 ", client supports version=%" PRIu8, + serverVersion, clientVersion); + return CHANNEL_RC_OK; + } + ecam->version = serverVersion; + + if (ecam->ihal) + ecam_enumerate_devices(ecam, hchannel); + else + WLog_ERR(TAG, "No HAL registered"); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data) +{ + UINT error = CHANNEL_RC_OK; + BYTE version = 0; + BYTE messageId = 0; + GENERIC_CHANNEL_CALLBACK* hchannel = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + + if (!hchannel || !data) + return ERROR_INVALID_PARAMETER; + + CameraPlugin* ecam = (CameraPlugin*)hchannel->plugin; + + if (!ecam) + return ERROR_INTERNAL_ERROR; + + if (!Stream_CheckAndLogRequiredCapacity(TAG, data, CAM_HEADER_SIZE)) + return ERROR_NO_DATA; + + Stream_Read_UINT8(data, version); + Stream_Read_UINT8(data, messageId); + WLog_DBG(TAG, "ChannelId=%" PRIu32 ", MessageId=0x%02" PRIx8 ", Version=%d", + hchannel->channel_mgr->GetChannelId(hchannel->channel), messageId, version); + + switch (messageId) + { + case CAM_MSG_ID_SelectVersionResponse: + error = ecam_process_select_version_response(ecam, hchannel, data, version); + break; + + default: + WLog_WARN(TAG, "unknown MessageId=0x%02" PRIx8 "", messageId); + error = ERROR_INVALID_DATA; + ecam_channel_send_error_response(ecam, hchannel, CAM_ERROR_CODE_OperationNotSupported); + break; + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_on_open(IWTSVirtualChannelCallback* pChannelCallback) +{ + GENERIC_CHANNEL_CALLBACK* hchannel = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + WINPR_ASSERT(hchannel); + + CameraPlugin* ecam = (CameraPlugin*)hchannel->plugin; + WINPR_ASSERT(ecam); + + WLog_DBG(TAG, "entered"); + return ecam_channel_send_generic_msg(ecam, hchannel, CAM_MSG_ID_SelectVersionRequest); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + GENERIC_CHANNEL_CALLBACK* hchannel = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + WINPR_ASSERT(hchannel); + + WLog_DBG(TAG, "entered"); + + free(hchannel); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_on_new_channel_connection(IWTSListenerCallback* pListenerCallback, + IWTSVirtualChannel* pChannel, + WINPR_ATTR_UNUSED BYTE* Data, + WINPR_ATTR_UNUSED BOOL* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +{ + GENERIC_LISTENER_CALLBACK* hlistener = (GENERIC_LISTENER_CALLBACK*)pListenerCallback; + + if (!hlistener || !hlistener->plugin) + return ERROR_INTERNAL_ERROR; + + WLog_DBG(TAG, "entered"); + GENERIC_CHANNEL_CALLBACK* hchannel = + (GENERIC_CHANNEL_CALLBACK*)calloc(1, sizeof(GENERIC_CHANNEL_CALLBACK)); + + if (!hchannel) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + hchannel->iface.OnDataReceived = ecam_on_data_received; + hchannel->iface.OnOpen = ecam_on_open; + hchannel->iface.OnClose = ecam_on_close; + hchannel->plugin = hlistener->plugin; + hchannel->channel_mgr = hlistener->channel_mgr; + hchannel->channel = pChannel; + *ppCallback = (IWTSVirtualChannelCallback*)hchannel; + return CHANNEL_RC_OK; +} + +static void ecam_dev_destroy_pv(void* obj) +{ + CameraDevice* dev = obj; + ecam_dev_destroy(dev); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr) +{ + CameraPlugin* ecam = (CameraPlugin*)pPlugin; + + WLog_DBG(TAG, "entered"); + + if (!ecam || !pChannelMgr) + return ERROR_INVALID_PARAMETER; + + if (ecam->initialized) + { + WLog_ERR(TAG, "[%s] plugin initialized twice, aborting", RDPECAM_CONTROL_DVC_CHANNEL_NAME); + return ERROR_INVALID_DATA; + } + + ecam->version = ECAM_PROTO_VERSION; + + ecam->devices = HashTable_New(FALSE); + if (!ecam->devices) + { + WLog_ERR(TAG, "HashTable_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + HashTable_SetupForStringData(ecam->devices, FALSE); + + wObject* obj = HashTable_ValueObject(ecam->devices); + WINPR_ASSERT(obj); + obj->fnObjectFree = ecam_dev_destroy_pv; + + ecam->hlistener = (GENERIC_LISTENER_CALLBACK*)calloc(1, sizeof(GENERIC_LISTENER_CALLBACK)); + + if (!ecam->hlistener) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + ecam->hlistener->iface.OnNewChannelConnection = ecam_on_new_channel_connection; + ecam->hlistener->plugin = pPlugin; + ecam->hlistener->channel_mgr = pChannelMgr; + const UINT rc = pChannelMgr->CreateListener(pChannelMgr, RDPECAM_CONTROL_DVC_CHANNEL_NAME, 0, + &ecam->hlistener->iface, &ecam->listener); + + ecam->initialized = (rc == CHANNEL_RC_OK); + return rc; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_plugin_terminated(IWTSPlugin* pPlugin) +{ + CameraPlugin* ecam = (CameraPlugin*)pPlugin; + + if (!ecam) + return ERROR_INVALID_DATA; + + WLog_DBG(TAG, "entered"); + + if (ecam->hlistener) + { + IWTSVirtualChannelManager* mgr = ecam->hlistener->channel_mgr; + if (mgr) + IFCALL(mgr->DestroyListener, mgr, ecam->listener); + } + + free(ecam->hlistener); + + HashTable_Free(ecam->devices); + + UINT rc = CHANNEL_RC_OK; + if (ecam->ihal) + rc = ecam->ihal->Free(ecam->ihal); + + free(ecam); + return rc; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_plugin_attached(IWTSPlugin* pPlugin) +{ + CameraPlugin* ecam = (CameraPlugin*)pPlugin; + UINT error = CHANNEL_RC_OK; + + if (!ecam) + return ERROR_INVALID_PARAMETER; + + ecam->attached = TRUE; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_plugin_detached(IWTSPlugin* pPlugin) +{ + CameraPlugin* ecam = (CameraPlugin*)pPlugin; + UINT error = CHANNEL_RC_OK; + + if (!ecam) + return ERROR_INVALID_PARAMETER; + + ecam->attached = FALSE; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_register_hal_plugin(IWTSPlugin* pPlugin, ICamHal* ihal) +{ + CameraPlugin* ecam = (CameraPlugin*)pPlugin; + + WINPR_ASSERT(ecam); + + if (ecam->ihal) + { + WLog_DBG(TAG, "already registered"); + return ERROR_ALREADY_EXISTS; + } + + WLog_DBG(TAG, "HAL registered"); + ecam->ihal = ihal; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_load_hal_plugin(CameraPlugin* ecam, const char* name, const ADDIN_ARGV* args) +{ + WINPR_ASSERT(ecam); + + FREERDP_CAMERA_HAL_ENTRY_POINTS entryPoints = WINPR_C_ARRAY_INIT; + UINT error = ERROR_INTERNAL_ERROR; + union + { + PVIRTUALCHANNELENTRY pvce; + const PFREERDP_CAMERA_HAL_ENTRY entry; + } cnv; + cnv.pvce = freerdp_load_channel_addin_entry(RDPECAM_CHANNEL_NAME, name, nullptr, 0); + + if (cnv.entry == nullptr) + { + WLog_ERR(TAG, + "freerdp_load_channel_addin_entry did not return any function pointers for %s ", + name); + return ERROR_INVALID_FUNCTION; + } + + entryPoints.plugin = &ecam->iface; + entryPoints.pRegisterCameraHal = ecam_register_hal_plugin; + entryPoints.args = args; + entryPoints.ecam = ecam; + + error = cnv.entry(&entryPoints); + if (error) + { + WLog_ERR(TAG, "%s entry returned error %" PRIu32 ".", name, error); + return error; + } + + WLog_INFO(TAG, "Loaded %s HAL for ecam", name); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE rdpecam_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)) +{ + UINT error = CHANNEL_RC_INITIALIZATION_ERROR; + + WINPR_ASSERT(pEntryPoints); + WINPR_ASSERT(pEntryPoints->GetPlugin); + CameraPlugin* ecam = (CameraPlugin*)pEntryPoints->GetPlugin(pEntryPoints, RDPECAM_CHANNEL_NAME); + + if (ecam != nullptr) + return CHANNEL_RC_ALREADY_INITIALIZED; + + ecam = (CameraPlugin*)calloc(1, sizeof(CameraPlugin)); + + if (!ecam) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + ecam->attached = TRUE; + ecam->iface.Initialize = ecam_plugin_initialize; + ecam->iface.Connected = nullptr; /* server connects to client */ + ecam->iface.Disconnected = nullptr; + ecam->iface.Terminated = ecam_plugin_terminated; + ecam->iface.Attached = ecam_plugin_attached; + ecam->iface.Detached = ecam_plugin_detached; + + /* TODO: camera redirect only supported for platforms with Video4Linux */ +#if defined(WITH_V4L) + ecam->subsystem = "v4l"; +#else + ecam->subsystem = nullptr; +#endif + + if (ecam->subsystem) + { + if ((error = ecam_load_hal_plugin(ecam, ecam->subsystem, nullptr /*args*/))) + { + WLog_ERR(TAG, + "Unable to load camera redirection subsystem %s because of error %" PRIu32 "", + ecam->subsystem, error); + goto out; + } + } + + error = pEntryPoints->RegisterPlugin(pEntryPoints, RDPECAM_CHANNEL_NAME, &ecam->iface); + if (error == CHANNEL_RC_OK) + return error; + +out: + ecam_plugin_terminated(&ecam->iface); + return error; +} diff --git a/third_party/FreeRDP/channels/rdpecam/client/camera_device_main.c b/third_party/FreeRDP/channels/rdpecam/client/camera_device_main.c new file mode 100644 index 0000000..9bc6740 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpecam/client/camera_device_main.c @@ -0,0 +1,926 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * MS-RDPECAM Implementation, Device Channels + * + * Copyright 2024 Oleg Turovski + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include "camera.h" +#include "rdpecam-utils.h" + +#define TAG CHANNELS_TAG("rdpecam-device.client") + +/* supported formats in preference order: + * H264, MJPG, I420 (used as input for H264 encoder), other YUV based, RGB based + */ +static const CAM_MEDIA_FORMAT_INFO supportedFormats[] = { +/* inputFormat, outputFormat */ +#if defined(WITH_INPUT_FORMAT_H264) + { CAM_MEDIA_FORMAT_H264, CAM_MEDIA_FORMAT_H264 }, /* passthrough */ + { CAM_MEDIA_FORMAT_MJPG_H264, CAM_MEDIA_FORMAT_H264 }, +#endif +#if defined(WITH_INPUT_FORMAT_MJPG) + { CAM_MEDIA_FORMAT_MJPG, CAM_MEDIA_FORMAT_H264 }, + { CAM_MEDIA_FORMAT_MJPG, CAM_MEDIA_FORMAT_MJPG }, +#endif + { CAM_MEDIA_FORMAT_I420, CAM_MEDIA_FORMAT_H264 }, + { CAM_MEDIA_FORMAT_YUY2, CAM_MEDIA_FORMAT_H264 }, + { CAM_MEDIA_FORMAT_NV12, CAM_MEDIA_FORMAT_H264 }, + { CAM_MEDIA_FORMAT_RGB24, CAM_MEDIA_FORMAT_H264 }, + { CAM_MEDIA_FORMAT_RGB32, CAM_MEDIA_FORMAT_H264 }, +}; +static const size_t nSupportedFormats = ARRAYSIZE(supportedFormats); + +static void ecam_dev_write_media_type(wStream* s, CAM_MEDIA_TYPE_DESCRIPTION* mediaType) +{ + WINPR_ASSERT(mediaType); + + Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, mediaType->Format)); + Stream_Write_UINT32(s, mediaType->Width); + Stream_Write_UINT32(s, mediaType->Height); + Stream_Write_UINT32(s, mediaType->FrameRateNumerator); + Stream_Write_UINT32(s, mediaType->FrameRateDenominator); + Stream_Write_UINT32(s, mediaType->PixelAspectRatioNumerator); + Stream_Write_UINT32(s, mediaType->PixelAspectRatioDenominator); + Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, mediaType->Flags)); +} + +static BOOL ecam_dev_read_media_type(wStream* s, CAM_MEDIA_TYPE_DESCRIPTION* mediaType) +{ + WINPR_ASSERT(mediaType); + + const uint8_t format = Stream_Get_UINT8(s); + if (!rdpecam_valid_CamMediaFormat(format)) + return FALSE; + + mediaType->Format = WINPR_ASSERTING_INT_CAST(CAM_MEDIA_FORMAT, format); + Stream_Read_UINT32(s, mediaType->Width); + Stream_Read_UINT32(s, mediaType->Height); + Stream_Read_UINT32(s, mediaType->FrameRateNumerator); + Stream_Read_UINT32(s, mediaType->FrameRateDenominator); + Stream_Read_UINT32(s, mediaType->PixelAspectRatioNumerator); + Stream_Read_UINT32(s, mediaType->PixelAspectRatioDenominator); + + const uint8_t flags = Stream_Get_UINT8(s); + if (!rdpecam_valid_MediaTypeDescriptionFlags(flags)) + return FALSE; + mediaType->Flags = WINPR_ASSERTING_INT_CAST(CAM_MEDIA_TYPE_DESCRIPTION_FLAGS, flags); + return TRUE; +} + +static void ecam_dev_print_media_type(CAM_MEDIA_TYPE_DESCRIPTION* mediaType) +{ + WINPR_ASSERT(mediaType); + + WLog_DBG(TAG, "Format: %u, width: %u, height: %u, fps: %u, flags: %u", mediaType->Format, + mediaType->Width, mediaType->Height, mediaType->FrameRateNumerator, mediaType->Flags); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_dev_send_sample_response(CameraDevice* dev, size_t streamIndex, const BYTE* sample, + size_t size) +{ + WINPR_ASSERT(dev); + + CameraDeviceStream* stream = &dev->streams[streamIndex]; + CAM_MSG_ID msg = CAM_MSG_ID_SampleResponse; + + Stream_ResetPosition(stream->sampleRespBuffer); + + Stream_Write_UINT8(stream->sampleRespBuffer, + WINPR_ASSERTING_INT_CAST(uint8_t, dev->ecam->version)); + Stream_Write_UINT8(stream->sampleRespBuffer, WINPR_ASSERTING_INT_CAST(uint8_t, msg)); + Stream_Write_UINT8(stream->sampleRespBuffer, WINPR_ASSERTING_INT_CAST(uint8_t, streamIndex)); + + Stream_Write(stream->sampleRespBuffer, sample, size); + + /* channel write is protected by critical section in dvcman_write_channel */ + return ecam_channel_write(dev->ecam, stream->hSampleReqChannel, msg, stream->sampleRespBuffer, + FALSE /* don't free stream */); +} + +static BOOL mediaSupportDrops(CAM_MEDIA_FORMAT format) +{ + switch (format) + { + case CAM_MEDIA_FORMAT_H264: + return FALSE; + default: + return TRUE; + } +} + +static UINT ecam_dev_send_pending(CameraDevice* dev, size_t streamIndex, CameraDeviceStream* stream) +{ + WINPR_ASSERT(dev); + WINPR_ASSERT(stream); + + if (stream->samplesRequested <= 0) + { + WLog_VRB(TAG, "Frame delayed: No sample requested"); + return CHANNEL_RC_OK; + } + + if (!stream->haveSample) + { + WLog_VRB(TAG, "Frame response delayed: No sample available"); + return CHANNEL_RC_OK; + } + + BYTE* encodedSample = Stream_Buffer(stream->pendingSample); + size_t encodedSize = Stream_Length(stream->pendingSample); + if (streamInputFormat(stream) != streamOutputFormat(stream)) + { + if (!ecam_encoder_compress(stream, encodedSample, encodedSize, &encodedSample, + &encodedSize)) + { + WLog_DBG(TAG, "Frame dropped: error in ecam_encoder_compress"); + stream->haveSample = FALSE; + return CHANNEL_RC_OK; + } + + if (!stream->streaming) + { + WLog_DBG(TAG, "Frame delayed/dropped: stream stopped"); + return CHANNEL_RC_OK; + } + } + + stream->samplesRequested--; + stream->haveSample = FALSE; + + return ecam_dev_send_sample_response(dev, streamIndex, encodedSample, encodedSize); +} + +static UINT ecam_dev_sample_captured_callback(CameraDevice* dev, size_t streamIndex, + const BYTE* sample, size_t size) +{ + WINPR_ASSERT(dev); + + if (streamIndex >= ECAM_DEVICE_MAX_STREAMS) + return ERROR_INVALID_INDEX; + + CameraDeviceStream* stream = &dev->streams[streamIndex]; + + if (!stream->streaming) + { + WLog_DBG(TAG, "Frame drop: stream not running"); + return CHANNEL_RC_OK; + } + + EnterCriticalSection(&stream->lock); + UINT ret = CHANNEL_RC_NO_MEMORY; + + /* If we already have a waiting sample, let's see if the input format support dropping + * frames so that we could just "refresh" the pending sample, otherwise we must wait until + * a frame request flushes it + */ + + if (stream->haveSample && !mediaSupportDrops(stream->formats.inputFormat)) + { + /* we can't drop samples, so we have to wait until the pending sample is + * sent, by a sample request. + * + * When we're here we already have a sample ready to be sent, the delay between 2 frames + * seems like a reasonable wait delay. For instance 60 FPS means a frame every 16ms. + * We also cap that wait delay to not spinloop and not get stuck for too long. + * */ + DWORD waitDelay = (1000 * stream->currMediaType.FrameRateDenominator) / + stream->currMediaType.FrameRateNumerator; + if (waitDelay < 16) + waitDelay = 16; + if (waitDelay > 100) + waitDelay = 100; + + while (stream->haveSample && stream->streaming) + { + LeaveCriticalSection(&stream->lock); + + SleepEx(waitDelay, TRUE); + + EnterCriticalSection(&stream->lock); + } + + if (!stream->streaming) + { + WLog_DBG(TAG, "Frame drop: stream not running"); + ret = CHANNEL_RC_OK; + goto out; + } + } + + Stream_ResetPosition(stream->pendingSample); + if (!Stream_EnsureRemainingCapacity(stream->pendingSample, size)) + goto out; + + Stream_Write(stream->pendingSample, sample, size); + Stream_SealLength(stream->pendingSample); + stream->haveSample = TRUE; + + ret = ecam_dev_send_pending(dev, streamIndex, stream); + +out: + LeaveCriticalSection(&stream->lock); + return ret; +} + +static void ecam_dev_stop_stream(CameraDevice* dev, size_t streamIndex) +{ + WINPR_ASSERT(dev); + + if (streamIndex >= ECAM_DEVICE_MAX_STREAMS) + return; + + CameraDeviceStream* stream = &dev->streams[streamIndex]; + + if (stream->streaming) + { + stream->streaming = FALSE; + dev->ihal->StopStream(dev->ihal, dev->deviceId, 0); + + DeleteCriticalSection(&stream->lock); + } + + Stream_Free(stream->sampleRespBuffer, TRUE); + stream->sampleRespBuffer = nullptr; + + Stream_Free(stream->pendingSample, TRUE); + stream->pendingSample = nullptr; + + ecam_encoder_context_free(stream); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_dev_process_stop_streams_request(CameraDevice* dev, + GENERIC_CHANNEL_CALLBACK* hchannel, wStream* s) +{ + WINPR_ASSERT(dev); + WINPR_UNUSED(s); + + for (size_t i = 0; i < ECAM_DEVICE_MAX_STREAMS; i++) + ecam_dev_stop_stream(dev, i); + + return ecam_channel_send_generic_msg(dev->ecam, hchannel, CAM_MSG_ID_SuccessResponse); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_dev_process_start_streams_request(CameraDevice* dev, + GENERIC_CHANNEL_CALLBACK* hchannel, wStream* s) +{ + BYTE streamIndex = 0; + CAM_MEDIA_TYPE_DESCRIPTION mediaType = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(dev); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1 + 26)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT8(s, streamIndex); + + if (streamIndex >= ECAM_DEVICE_MAX_STREAMS) + { + WLog_ERR(TAG, "Incorrect streamIndex %" PRIu8, streamIndex); + ecam_channel_send_error_response(dev->ecam, hchannel, CAM_ERROR_CODE_InvalidStreamNumber); + return ERROR_INVALID_INDEX; + } + + if (!ecam_dev_read_media_type(s, &mediaType)) + { + WLog_ERR(TAG, "Unable to read MEDIA_TYPE_DESCRIPTION"); + ecam_channel_send_error_response(dev->ecam, hchannel, CAM_ERROR_CODE_InvalidMessage); + return ERROR_INVALID_DATA; + } + + ecam_dev_print_media_type(&mediaType); + + CameraDeviceStream* stream = &dev->streams[streamIndex]; + + if (stream->streaming) + { + WLog_ERR(TAG, "Streaming already in progress, device %s, streamIndex %d", dev->deviceId, + streamIndex); + return CAM_ERROR_CODE_UnexpectedError; + } + + /* saving media type description for CurrentMediaTypeRequest, + * to be done before calling ecam_encoder_context_init + */ + stream->currMediaType = mediaType; + + /* initialize encoder, if input and output formats differ */ + if (streamInputFormat(stream) != streamOutputFormat(stream) && + !ecam_encoder_context_init(stream)) + { + WLog_ERR(TAG, "stream_ecam_encoder_init failed"); + ecam_channel_send_error_response(dev->ecam, hchannel, CAM_ERROR_CODE_UnexpectedError); + return ERROR_INVALID_DATA; + } + + stream->sampleRespBuffer = Stream_New(nullptr, ECAM_SAMPLE_RESPONSE_BUFFER_SIZE); + if (!stream->sampleRespBuffer) + { + WLog_ERR(TAG, "Stream_New failed"); + ecam_dev_stop_stream(dev, streamIndex); + ecam_channel_send_error_response(dev->ecam, hchannel, CAM_ERROR_CODE_OutOfMemory); + return ERROR_INVALID_DATA; + } + + /* replacing outputFormat with inputFormat in mediaType before starting stream */ + mediaType.Format = streamInputFormat(stream); + + stream->samplesRequested = 0; + stream->haveSample = FALSE; + + if (!InitializeCriticalSectionEx(&stream->lock, 0, 0)) + { + WLog_ERR(TAG, "InitializeCriticalSectionEx failed"); + ecam_dev_stop_stream(dev, streamIndex); + ecam_channel_send_error_response(dev->ecam, hchannel, CAM_ERROR_CODE_OutOfMemory); + return ERROR_INVALID_DATA; + } + + stream->pendingSample = Stream_New(nullptr, 4ull * mediaType.Width * mediaType.Height); + if (!stream->pendingSample) + { + WLog_ERR(TAG, "pending stream failed"); + ecam_dev_stop_stream(dev, streamIndex); + ecam_channel_send_error_response(dev->ecam, hchannel, CAM_ERROR_CODE_OutOfMemory); + return ERROR_INVALID_DATA; + } + + const CAM_ERROR_CODE error = dev->ihal->StartStream(dev->ihal, dev, streamIndex, &mediaType, + ecam_dev_sample_captured_callback); + if (error) + { + WLog_ERR(TAG, "StartStream failure"); + ecam_dev_stop_stream(dev, streamIndex); + ecam_channel_send_error_response(dev->ecam, hchannel, error); + return ERROR_INVALID_DATA; + } + + stream->streaming = TRUE; + return ecam_channel_send_generic_msg(dev->ecam, hchannel, CAM_MSG_ID_SuccessResponse); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_dev_process_property_list_request(CameraDevice* dev, + GENERIC_CHANNEL_CALLBACK* hchannel, + WINPR_ATTR_UNUSED wStream* s) +{ + WINPR_ASSERT(dev); + // TODO: supported properties implementation + + return ecam_channel_send_generic_msg(dev->ecam, hchannel, CAM_MSG_ID_PropertyListResponse); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_dev_send_current_media_type_response(CameraDevice* dev, + GENERIC_CHANNEL_CALLBACK* hchannel, + CAM_MEDIA_TYPE_DESCRIPTION* mediaType) +{ + CAM_MSG_ID msg = CAM_MSG_ID_CurrentMediaTypeResponse; + + WINPR_ASSERT(dev); + + wStream* s = Stream_New(nullptr, CAM_HEADER_SIZE + sizeof(CAM_MEDIA_TYPE_DESCRIPTION)); + if (!s) + { + WLog_ERR(TAG, "Stream_New failed"); + return ERROR_NOT_ENOUGH_MEMORY; + } + + Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, dev->ecam->version)); + Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, msg)); + + ecam_dev_write_media_type(s, mediaType); + + return ecam_channel_write(dev->ecam, hchannel, msg, s, TRUE); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_dev_process_sample_request(CameraDevice* dev, GENERIC_CHANNEL_CALLBACK* hchannel, + wStream* s) +{ + BYTE streamIndex = 0; + + WINPR_ASSERT(dev); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT8(s, streamIndex); + + if (streamIndex >= ECAM_DEVICE_MAX_STREAMS) + { + WLog_ERR(TAG, "Incorrect streamIndex %d", streamIndex); + ecam_channel_send_error_response(dev->ecam, hchannel, CAM_ERROR_CODE_InvalidStreamNumber); + return ERROR_INVALID_INDEX; + } + + CameraDeviceStream* stream = &dev->streams[streamIndex]; + + EnterCriticalSection(&stream->lock); + + /* need to save channel because responses are asynchronous and coming from capture thread */ + if (stream->hSampleReqChannel != hchannel) + stream->hSampleReqChannel = hchannel; + + stream->samplesRequested++; + const UINT ret = ecam_dev_send_pending(dev, streamIndex, stream); + + LeaveCriticalSection(&stream->lock); + return ret; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_dev_process_current_media_type_request(CameraDevice* dev, + GENERIC_CHANNEL_CALLBACK* hchannel, + wStream* s) +{ + BYTE streamIndex = 0; + + WINPR_ASSERT(dev); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT8(s, streamIndex); + + if (streamIndex >= ECAM_DEVICE_MAX_STREAMS) + { + WLog_ERR(TAG, "Incorrect streamIndex %d", streamIndex); + ecam_channel_send_error_response(dev->ecam, hchannel, CAM_ERROR_CODE_InvalidStreamNumber); + return ERROR_INVALID_INDEX; + } + + CameraDeviceStream* stream = &dev->streams[streamIndex]; + + if (stream->currMediaType.Format == 0) + { + WLog_ERR(TAG, "Current media type unknown for streamIndex %d", streamIndex); + ecam_channel_send_error_response(dev->ecam, hchannel, CAM_ERROR_CODE_NotInitialized); + return ERROR_DEVICE_REINITIALIZATION_NEEDED; + } + + return ecam_dev_send_current_media_type_response(dev, hchannel, &stream->currMediaType); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_dev_send_media_type_list_response(CameraDevice* dev, + GENERIC_CHANNEL_CALLBACK* hchannel, + CAM_MEDIA_TYPE_DESCRIPTION* mediaTypes, + size_t nMediaTypes) +{ + CAM_MSG_ID msg = CAM_MSG_ID_MediaTypeListResponse; + + WINPR_ASSERT(dev); + + wStream* s = Stream_New(nullptr, CAM_HEADER_SIZE + ECAM_MAX_MEDIA_TYPE_DESCRIPTORS * + sizeof(CAM_MEDIA_TYPE_DESCRIPTION)); + if (!s) + { + WLog_ERR(TAG, "Stream_New failed"); + return ERROR_NOT_ENOUGH_MEMORY; + } + + Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, dev->ecam->version)); + Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, msg)); + + for (size_t i = 0; i < nMediaTypes; i++, mediaTypes++) + { + ecam_dev_write_media_type(s, mediaTypes); + } + + return ecam_channel_write(dev->ecam, hchannel, msg, s, TRUE); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_dev_process_media_type_list_request(CameraDevice* dev, + GENERIC_CHANNEL_CALLBACK* hchannel, wStream* s) +{ + UINT error = CHANNEL_RC_OK; + BYTE streamIndex = 0; + CAM_MEDIA_TYPE_DESCRIPTION* mediaTypes = nullptr; + size_t nMediaTypes = ECAM_MAX_MEDIA_TYPE_DESCRIPTORS; + + WINPR_ASSERT(dev); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT8(s, streamIndex); + + if (streamIndex >= ECAM_DEVICE_MAX_STREAMS) + { + WLog_ERR(TAG, "Incorrect streamIndex %d", streamIndex); + ecam_channel_send_error_response(dev->ecam, hchannel, CAM_ERROR_CODE_InvalidStreamNumber); + return ERROR_INVALID_INDEX; + } + CameraDeviceStream* stream = &dev->streams[streamIndex]; + + mediaTypes = + (CAM_MEDIA_TYPE_DESCRIPTION*)calloc(nMediaTypes, sizeof(CAM_MEDIA_TYPE_DESCRIPTION)); + if (!mediaTypes) + { + WLog_ERR(TAG, "calloc failed"); + ecam_channel_send_error_response(dev->ecam, hchannel, CAM_ERROR_CODE_OutOfMemory); + return CHANNEL_RC_NO_MEMORY; + } + + INT16 formatIndex = + dev->ihal->GetMediaTypeDescriptions(dev->ihal, dev->deviceId, streamIndex, supportedFormats, + nSupportedFormats, mediaTypes, &nMediaTypes); + if (formatIndex == -1 || nMediaTypes == 0) + { + WLog_ERR(TAG, "Camera doesn't support any compatible video formats"); + ecam_channel_send_error_response(dev->ecam, hchannel, CAM_ERROR_CODE_ItemNotFound); + error = ERROR_DEVICE_FEATURE_NOT_SUPPORTED; + goto error; + } + + stream->formats = supportedFormats[formatIndex]; + + /* replacing inputFormat with outputFormat in mediaTypes before sending response */ + for (size_t i = 0; i < nMediaTypes; i++) + { + mediaTypes[i].Format = streamOutputFormat(stream); + mediaTypes[i].Flags = CAM_MEDIA_TYPE_DESCRIPTION_FLAG_DecodingRequired; + } + + if (stream->currMediaType.Format == 0) + { + /* saving 1st media type description for CurrentMediaTypeRequest */ + stream->currMediaType = mediaTypes[0]; + } + + error = ecam_dev_send_media_type_list_response(dev, hchannel, mediaTypes, nMediaTypes); + +error: + free(mediaTypes); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_dev_send_stream_list_response(CameraDevice* dev, + GENERIC_CHANNEL_CALLBACK* hchannel) +{ + CAM_MSG_ID msg = CAM_MSG_ID_StreamListResponse; + + WINPR_ASSERT(dev); + + wStream* s = Stream_New(nullptr, CAM_HEADER_SIZE + sizeof(CAM_STREAM_DESCRIPTION)); + if (!s) + { + WLog_ERR(TAG, "Stream_New failed"); + return ERROR_NOT_ENOUGH_MEMORY; + } + + Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, dev->ecam->version)); + Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, msg)); + + /* single stream description */ + Stream_Write_UINT16(s, CAM_STREAM_FRAME_SOURCE_TYPE_Color); + Stream_Write_UINT8(s, CAM_STREAM_CATEGORY_Capture); + Stream_Write_UINT8(s, TRUE /* Selected */); + Stream_Write_UINT8(s, FALSE /* CanBeShared */); + + return ecam_channel_write(dev->ecam, hchannel, msg, s, TRUE); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_dev_process_stream_list_request(CameraDevice* dev, + GENERIC_CHANNEL_CALLBACK* hchannel, + WINPR_ATTR_UNUSED wStream* s) +{ + return ecam_dev_send_stream_list_response(dev, hchannel); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_dev_process_activate_device_request(CameraDevice* dev, + GENERIC_CHANNEL_CALLBACK* hchannel, + WINPR_ATTR_UNUSED wStream* s) +{ + WINPR_ASSERT(dev); + CAM_ERROR_CODE errorCode = CAM_ERROR_CODE_None; + + if (dev->ihal->Activate(dev->ihal, dev->deviceId, &errorCode)) + return ecam_channel_send_generic_msg(dev->ecam, hchannel, CAM_MSG_ID_SuccessResponse); + + return ecam_channel_send_error_response(dev->ecam, hchannel, errorCode); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_dev_process_deactivate_device_request(CameraDevice* dev, + GENERIC_CHANNEL_CALLBACK* hchannel, + wStream* s) +{ + WINPR_ASSERT(dev); + WINPR_UNUSED(s); + + for (size_t i = 0; i < ECAM_DEVICE_MAX_STREAMS; i++) + ecam_dev_stop_stream(dev, i); + + CAM_ERROR_CODE errorCode = CAM_ERROR_CODE_None; + if (dev->ihal->Deactivate(dev->ihal, dev->deviceId, &errorCode)) + return ecam_channel_send_generic_msg(dev->ecam, hchannel, CAM_MSG_ID_SuccessResponse); + + return ecam_channel_send_error_response(dev->ecam, hchannel, errorCode); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_dev_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data) +{ + UINT error = CHANNEL_RC_OK; + BYTE version = 0; + BYTE messageId = 0; + GENERIC_CHANNEL_CALLBACK* hchannel = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + + if (!hchannel || !data) + return ERROR_INVALID_PARAMETER; + + CameraDevice* dev = (CameraDevice*)hchannel->plugin; + + if (!dev) + return ERROR_INTERNAL_ERROR; + + if (!Stream_CheckAndLogRequiredCapacity(TAG, data, CAM_HEADER_SIZE)) + return ERROR_NO_DATA; + + Stream_Read_UINT8(data, version); + Stream_Read_UINT8(data, messageId); + WLog_DBG(TAG, "ChannelId=%" PRIu32 ", MessageId=0x%02" PRIx8 ", Version=%d", + hchannel->channel_mgr->GetChannelId(hchannel->channel), messageId, version); + + switch (messageId) + { + case CAM_MSG_ID_ActivateDeviceRequest: + error = ecam_dev_process_activate_device_request(dev, hchannel, data); + break; + + case CAM_MSG_ID_DeactivateDeviceRequest: + error = ecam_dev_process_deactivate_device_request(dev, hchannel, data); + break; + + case CAM_MSG_ID_StreamListRequest: + error = ecam_dev_process_stream_list_request(dev, hchannel, data); + break; + + case CAM_MSG_ID_MediaTypeListRequest: + error = ecam_dev_process_media_type_list_request(dev, hchannel, data); + break; + + case CAM_MSG_ID_CurrentMediaTypeRequest: + error = ecam_dev_process_current_media_type_request(dev, hchannel, data); + break; + + case CAM_MSG_ID_PropertyListRequest: + error = ecam_dev_process_property_list_request(dev, hchannel, data); + break; + + case CAM_MSG_ID_StartStreamsRequest: + error = ecam_dev_process_start_streams_request(dev, hchannel, data); + break; + + case CAM_MSG_ID_StopStreamsRequest: + error = ecam_dev_process_stop_streams_request(dev, hchannel, data); + break; + + case CAM_MSG_ID_SampleRequest: + error = ecam_dev_process_sample_request(dev, hchannel, data); + break; + + default: + WLog_WARN(TAG, "unknown MessageId=0x%02" PRIx8 "", messageId); + error = ERROR_INVALID_DATA; + ecam_channel_send_error_response(dev->ecam, hchannel, + CAM_ERROR_CODE_OperationNotSupported); + break; + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_dev_on_open(WINPR_ATTR_UNUSED IWTSVirtualChannelCallback* pChannelCallback) +{ + WLog_DBG(TAG, "entered"); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_dev_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + GENERIC_CHANNEL_CALLBACK* hchannel = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + WINPR_ASSERT(hchannel); + + CameraDevice* dev = (CameraDevice*)hchannel->plugin; + WINPR_ASSERT(dev); + + WLog_DBG(TAG, "entered"); + + for (size_t i = 0; i < ECAM_DEVICE_MAX_STREAMS; i++) + ecam_dev_stop_stream(dev, i); + + /* make sure this channel is not used for sample responses */ + for (size_t i = 0; i < ECAM_DEVICE_MAX_STREAMS; i++) + if (dev->streams[i].hSampleReqChannel == hchannel) + dev->streams[i].hSampleReqChannel = nullptr; + + free(hchannel); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ecam_dev_on_new_channel_connection(IWTSListenerCallback* pListenerCallback, + IWTSVirtualChannel* pChannel, + WINPR_ATTR_UNUSED BYTE* Data, + WINPR_ATTR_UNUSED BOOL* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +{ + GENERIC_LISTENER_CALLBACK* hlistener = (GENERIC_LISTENER_CALLBACK*)pListenerCallback; + + if (!hlistener || !hlistener->plugin) + return ERROR_INTERNAL_ERROR; + + WLog_DBG(TAG, "entered"); + GENERIC_CHANNEL_CALLBACK* hchannel = + (GENERIC_CHANNEL_CALLBACK*)calloc(1, sizeof(GENERIC_CHANNEL_CALLBACK)); + + if (!hchannel) + { + WLog_ERR(TAG, "calloc failed"); + return CHANNEL_RC_NO_MEMORY; + } + + hchannel->iface.OnDataReceived = ecam_dev_on_data_received; + hchannel->iface.OnOpen = ecam_dev_on_open; + hchannel->iface.OnClose = ecam_dev_on_close; + hchannel->plugin = hlistener->plugin; + hchannel->channel_mgr = hlistener->channel_mgr; + hchannel->channel = pChannel; + *ppCallback = (IWTSVirtualChannelCallback*)hchannel; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return CameraDevice pointer or nullptr in case of error + */ +CameraDevice* ecam_dev_create(CameraPlugin* ecam, const char* deviceId, + WINPR_ATTR_UNUSED const char* deviceName) +{ + WINPR_ASSERT(ecam); + WINPR_ASSERT(ecam->hlistener); + + IWTSVirtualChannelManager* pChannelMgr = ecam->hlistener->channel_mgr; + WINPR_ASSERT(pChannelMgr); + + WLog_DBG(TAG, "entered for %s", deviceId); + + CameraDevice* dev = (CameraDevice*)calloc(1, sizeof(CameraDevice)); + + if (!dev) + { + WLog_ERR(TAG, "calloc failed"); + return nullptr; + } + + dev->ecam = ecam; + dev->ihal = ecam->ihal; + strncpy(dev->deviceId, deviceId, sizeof(dev->deviceId) - 1); + dev->hlistener = (GENERIC_LISTENER_CALLBACK*)calloc(1, sizeof(GENERIC_LISTENER_CALLBACK)); + + if (!dev->hlistener) + { + free(dev); + WLog_ERR(TAG, "calloc failed"); + return nullptr; + } + + dev->hlistener->iface.OnNewChannelConnection = ecam_dev_on_new_channel_connection; + dev->hlistener->plugin = (IWTSPlugin*)dev; + dev->hlistener->channel_mgr = pChannelMgr; + if (CHANNEL_RC_OK != pChannelMgr->CreateListener(pChannelMgr, deviceId, 0, + &dev->hlistener->iface, &dev->listener)) + { + free(dev->hlistener); + free(dev); + WLog_ERR(TAG, "CreateListener failed"); + return nullptr; + } + + return dev; +} + +/** + * Function description + * + * OBJECT_FREE_FN for devices hash table value + * + */ +void ecam_dev_destroy(CameraDevice* dev) +{ + if (!dev) + return; + + WLog_DBG(TAG, "entered for %s", dev->deviceId); + + if (dev->hlistener) + { + IWTSVirtualChannelManager* mgr = dev->hlistener->channel_mgr; + if (mgr) + IFCALL(mgr->DestroyListener, mgr, dev->listener); + } + + free(dev->hlistener); + + for (size_t i = 0; i < ECAM_DEVICE_MAX_STREAMS; i++) + ecam_dev_stop_stream(dev, i); + + free(dev); +} diff --git a/third_party/FreeRDP/channels/rdpecam/client/encoding.c b/third_party/FreeRDP/channels/rdpecam/client/encoding.c new file mode 100644 index 0000000..0925d06 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpecam/client/encoding.c @@ -0,0 +1,642 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * MS-RDPECAM Implementation, Video Encoding + * + * Copyright 2024 Oleg Turovski + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "camera.h" + +#define TAG CHANNELS_TAG("rdpecam-video.client") + +#if defined(WITH_INPUT_FORMAT_H264) +/* + * demux a H264 frame from a MJPG container + * args: + * srcData - pointer to buffer with h264 muxed in MJPG container + * srcSize - buff size + * h264_data - pointer to h264 data + * h264_max_size - maximum size allowed by h264_data buffer + * + * Credits: + * guvcview http://guvcview.sourceforge.net + * Paulo Assis + * + * see Figure 5 Payload Size in USB_Video_Payload_H 264_1 0.pdf + * for format details + * + * @return: data size and copies demuxed data to h264 buffer + */ +static size_t demux_uvcH264(const BYTE* srcData, size_t srcSize, BYTE* h264_data, + size_t h264_max_size) +{ + WINPR_ASSERT(h264_data); + WINPR_ASSERT(srcData); + + if (srcSize < 30) + { + WLog_ERR(TAG, "Expected srcSize >= 30, got %" PRIuz, srcSize); + return 0; + } + const uint8_t* spl = nullptr; + uint8_t* ph264 = h264_data; + + /* search for 1st APP4 marker + * (30 = 2 APP4 marker + 2 length + 22 header + 4 payload size) + */ + for (const uint8_t* sp = srcData; sp < srcData + srcSize - 30; sp++) + { + if (sp[0] == 0xFF && sp[1] == 0xE4) + { + spl = sp + 2; /* exclude APP4 marker */ + break; + } + } + + if (spl == nullptr) + { + WLog_ERR(TAG, "Expected 1st APP4 marker but none found"); + return 0; + } + + if (spl > srcData + srcSize - 4) + { + WLog_ERR(TAG, "Payload + Header size bigger than srcData buffer"); + return 0; + } + + /* 1st segment length in big endian + * includes payload size + header + 6 bytes (2 length + 4 payload size) + */ + uint16_t length = (uint16_t)(spl[0] << 8) & UINT16_MAX; + length |= (uint16_t)spl[1]; + + spl += 2; /* header */ + /* header length in little endian at offset 2 */ + uint16_t header_length = (uint16_t)spl[2]; + header_length |= (uint16_t)spl[3] << 8; + + spl += header_length; + if (spl > srcData + srcSize) + { + WLog_ERR(TAG, "Header size bigger than srcData buffer"); + return 0; + } + + /* payload size in little endian */ + uint32_t payload_size = (uint32_t)spl[0] << 0; + payload_size |= (uint32_t)spl[1] << 8; + payload_size |= (uint32_t)spl[2] << 16; + payload_size |= (uint32_t)spl[3] << 24; + + if (payload_size > h264_max_size) + { + WLog_ERR(TAG, "Payload size bigger than h264_data buffer"); + return 0; + } + + spl += 4; /* payload start */ + const uint8_t* epl = spl + payload_size; /* payload end */ + + if (epl > srcData + srcSize) + { + WLog_ERR(TAG, "Payload size bigger than srcData buffer"); + return 0; + } + + length -= header_length + 6; + + /* copy 1st segment to h264 buffer */ + memcpy(ph264, spl, length); + ph264 += length; + spl += length; + + /* copy other segments */ + while (epl > spl + 4) + { + if (spl[0] != 0xFF || spl[1] != 0xE4) + { + WLog_ERR(TAG, "Expected 2nd+ APP4 marker but none found"); + const intptr_t diff = ph264 - h264_data; + return WINPR_ASSERTING_INT_CAST(size_t, diff); + } + + /* 2nd+ segment length in big endian */ + length = (uint16_t)(spl[2] << 8) & UINT16_MAX; + length |= (uint16_t)spl[3]; + if (length < 2) + { + WLog_ERR(TAG, "Expected 2nd+ APP4 length >= 2 but have %" PRIu16, length); + return 0; + } + + length -= 2; + spl += 4; /* APP4 marker + length */ + + /* copy segment to h264 buffer */ + memcpy(ph264, spl, length); + ph264 += length; + spl += length; + } + + const intptr_t diff = ph264 - h264_data; + return WINPR_ASSERTING_INT_CAST(size_t, diff); +} +#endif + +/** + * Function description + * + * @return bitrate in bps + */ +UINT32 h264_get_max_bitrate(UINT32 height) +{ + static struct Bitrates + { + UINT32 height; + UINT32 bitrate; /* kbps */ + + } bitrates[] = { + /* source: https://livekit.io/webrtc/bitrate-guide (webcam streaming) + * + * sorted by height in descending order + */ + { 1080, 2700 }, { 720, 1250 }, { 480, 700 }, { 360, 400 }, + { 240, 170 }, { 180, 140 }, { 0, 100 }, + }; + const size_t nBitrates = ARRAYSIZE(bitrates); + + for (size_t i = 0; i < nBitrates; i++) + { + if (height >= bitrates[i].height) + { + UINT32 bitrate = bitrates[i].bitrate; + WLog_DBG(TAG, "Setting h264 max bitrate: %u kbps", bitrate); + return bitrate * 1000; + } + } + + WINPR_ASSERT(FALSE); + return 0; +} + +/** + * Function description + * + * @return enum AVPixelFormat value + */ +static enum AVPixelFormat ecamToAVPixFormat(CAM_MEDIA_FORMAT ecamFormat) +{ + switch (ecamFormat) + { + case CAM_MEDIA_FORMAT_YUY2: + return AV_PIX_FMT_YUYV422; + case CAM_MEDIA_FORMAT_NV12: + return AV_PIX_FMT_NV12; + case CAM_MEDIA_FORMAT_I420: + return AV_PIX_FMT_YUV420P; + case CAM_MEDIA_FORMAT_RGB24: + return AV_PIX_FMT_RGB24; + case CAM_MEDIA_FORMAT_RGB32: + return AV_PIX_FMT_RGB32; + default: + WLog_ERR(TAG, "Unsupported ecamFormat %u", ecamFormat); + return AV_PIX_FMT_NONE; + } +} + +static void ecam_sws_free(CameraDeviceStream* stream) +{ + if (stream->sws) + { + sws_freeContext(stream->sws); + stream->sws = nullptr; + } +} + +static BOOL ecam_sws_valid(const CameraDeviceStream* stream) +{ + if (!stream->sws) + return FALSE; + if (stream->swsWidth != stream->currMediaType.Width) + return FALSE; + if (stream->swsHeight != stream->currMediaType.Height) + return FALSE; + if (stream->currMediaType.Width > INT32_MAX) + return FALSE; + if (stream->currMediaType.Height > INT32_MAX) + return FALSE; + return TRUE; +} + +/** + * Function description + * initialize libswscale + * + * @return success/failure + */ +static BOOL ecam_init_sws_context(CameraDeviceStream* stream, enum AVPixelFormat pixFormat) +{ + WINPR_ASSERT(stream); + + if (stream->currMediaType.Width > INT32_MAX) + return FALSE; + if (stream->currMediaType.Height > INT32_MAX) + return FALSE; + + if (ecam_sws_valid(stream)) + return TRUE; + + ecam_sws_free(stream); + + /* replacing deprecated JPEG formats, still produced by decoder */ + switch (pixFormat) + { + case AV_PIX_FMT_YUVJ411P: + pixFormat = AV_PIX_FMT_YUV411P; + break; + + case AV_PIX_FMT_YUVJ420P: + pixFormat = AV_PIX_FMT_YUV420P; + break; + + case AV_PIX_FMT_YUVJ422P: + pixFormat = AV_PIX_FMT_YUV422P; + break; + + case AV_PIX_FMT_YUVJ440P: + pixFormat = AV_PIX_FMT_YUV440P; + break; + + case AV_PIX_FMT_YUVJ444P: + pixFormat = AV_PIX_FMT_YUV444P; + break; + + default: + break; + } + + stream->swsWidth = stream->currMediaType.Width; + stream->swsHeight = stream->currMediaType.Height; + const int width = WINPR_ASSERTING_INT_CAST(int, stream->currMediaType.Width); + const int height = WINPR_ASSERTING_INT_CAST(int, stream->currMediaType.Height); + + const enum AVPixelFormat outPixFormat = + h264_context_get_option(stream->h264, H264_CONTEXT_OPTION_HW_ACCEL) ? AV_PIX_FMT_NV12 + : AV_PIX_FMT_YUV420P; + + stream->sws = sws_getContext(width, height, pixFormat, width, height, outPixFormat, 0, nullptr, + nullptr, nullptr); + if (!stream->sws) + { + WLog_ERR(TAG, "sws_getContext failed"); + return FALSE; + } + + return TRUE; +} + +/** + * Function description + * + * @return success/failure + */ +static BOOL ecam_encoder_compress_h264(CameraDeviceStream* stream, const BYTE* srcData, + size_t srcSize, BYTE** ppDstData, size_t* pDstSize) +{ + UINT32 dstSize = 0; + BYTE* srcSlice[4] = WINPR_C_ARRAY_INIT; + int srcLineSizes[4] = WINPR_C_ARRAY_INIT; + BYTE* yuvData[3] = WINPR_C_ARRAY_INIT; + UINT32 yuvLineSizes[3] = WINPR_C_ARRAY_INIT; + prim_size_t size = { stream->currMediaType.Width, stream->currMediaType.Height }; + CAM_MEDIA_FORMAT inputFormat = streamInputFormat(stream); + enum AVPixelFormat pixFormat = AV_PIX_FMT_NONE; + +#if defined(WITH_INPUT_FORMAT_H264) + if (inputFormat == CAM_MEDIA_FORMAT_MJPG_H264) + { + const size_t rc = + demux_uvcH264(srcData, srcSize, stream->h264Frame, stream->h264FrameMaxSize); + dstSize = WINPR_ASSERTING_INT_CAST(uint32_t, rc); + *ppDstData = stream->h264Frame; + *pDstSize = dstSize; + return dstSize > 0; + } + else +#endif + +#if defined(WITH_INPUT_FORMAT_MJPG) + if (inputFormat == CAM_MEDIA_FORMAT_MJPG) + { + stream->avInputPkt->data = WINPR_CAST_CONST_PTR_AWAY(srcData, uint8_t*); + WINPR_ASSERT(srcSize <= INT32_MAX); + stream->avInputPkt->size = (int)srcSize; + + if (avcodec_send_packet(stream->avContext, stream->avInputPkt) < 0) + { + WLog_ERR(TAG, "avcodec_send_packet failed"); + return FALSE; + } + + if (avcodec_receive_frame(stream->avContext, stream->avOutFrame) < 0) + { + WLog_ERR(TAG, "avcodec_receive_frame failed"); + return FALSE; + } + + for (size_t i = 0; i < 4; i++) + { + srcSlice[i] = stream->avOutFrame->data[i]; + srcLineSizes[i] = stream->avOutFrame->linesize[i]; + } + + /* get pixFormat produced by MJPEG decoder */ + pixFormat = stream->avContext->pix_fmt; + } + else +#endif + { + pixFormat = ecamToAVPixFormat(inputFormat); + + if (av_image_fill_linesizes(srcLineSizes, pixFormat, (int)size.width) < 0) + { + WLog_ERR(TAG, "av_image_fill_linesizes failed"); + return FALSE; + } + + if (av_image_fill_pointers(srcSlice, pixFormat, (int)size.height, + WINPR_CAST_CONST_PTR_AWAY(srcData, BYTE*), srcLineSizes) < 0) + { + WLog_ERR(TAG, "av_image_fill_pointers failed"); + return FALSE; + } + } + + /* get buffers for YUV420P or NV12 */ + if (h264_get_yuv_buffer(stream->h264, 0, size.width, size.height, yuvData, yuvLineSizes) < 0) + return FALSE; + + /* convert from source format to YUV420P or NV12 */ + if (!ecam_init_sws_context(stream, pixFormat)) + return FALSE; + + const BYTE* cSrcSlice[4] = { srcSlice[0], srcSlice[1], srcSlice[2], srcSlice[3] }; + if (sws_scale(stream->sws, cSrcSlice, srcLineSizes, 0, (int)size.height, yuvData, + (int*)yuvLineSizes) <= 0) + return FALSE; + + /* encode from YUV420P or NV12 to H264 */ + if (h264_compress(stream->h264, ppDstData, &dstSize) < 0) + return FALSE; + + *pDstSize = dstSize; + + return TRUE; +} + +/** + * Function description + * + */ +static void ecam_encoder_context_free_h264(CameraDeviceStream* stream) +{ + WINPR_ASSERT(stream); + + ecam_sws_free(stream); + +#if defined(WITH_INPUT_FORMAT_MJPG) + if (stream->avOutFrame) + av_frame_free(&stream->avOutFrame); /* sets to nullptr */ + + if (stream->avInputPkt) + { + stream->avInputPkt->data = nullptr; + stream->avInputPkt->size = 0; + av_packet_free(&stream->avInputPkt); /* sets to nullptr */ + } + + if (stream->avContext) + avcodec_free_context(&stream->avContext); /* sets to nullptr */ +#endif + +#if defined(WITH_INPUT_FORMAT_H264) + if (stream->h264Frame) + { + free(stream->h264Frame); + stream->h264Frame = nullptr; + } +#endif + + if (stream->h264) + { + h264_context_free(stream->h264); + stream->h264 = nullptr; + } +} + +#if defined(WITH_INPUT_FORMAT_MJPG) +/** + * Function description + * + * @return success/failure + */ +static BOOL ecam_init_mjpeg_decoder(CameraDeviceStream* stream) +{ + WINPR_ASSERT(stream); + + const AVCodec* avcodec = avcodec_find_decoder(AV_CODEC_ID_MJPEG); + if (!avcodec) + { + WLog_ERR(TAG, "avcodec_find_decoder failed to find MJPEG codec"); + return FALSE; + } + + stream->avContext = avcodec_alloc_context3(avcodec); + if (!stream->avContext) + { + WLog_ERR(TAG, "avcodec_alloc_context3 failed"); + return FALSE; + } + + stream->avContext->width = WINPR_ASSERTING_INT_CAST(int, stream->currMediaType.Width); + stream->avContext->height = WINPR_ASSERTING_INT_CAST(int, stream->currMediaType.Height); + + /* AV_EF_EXPLODE flag is to abort decoding on minor error detection, + * return error, so we can skip corrupted frames, if any */ + stream->avContext->err_recognition |= AV_EF_EXPLODE; + + if (avcodec_open2(stream->avContext, avcodec, nullptr) < 0) + { + WLog_ERR(TAG, "avcodec_open2 failed"); + return FALSE; + } + + stream->avInputPkt = av_packet_alloc(); + if (!stream->avInputPkt) + { + WLog_ERR(TAG, "av_packet_alloc failed"); + return FALSE; + } + + stream->avOutFrame = av_frame_alloc(); + if (!stream->avOutFrame) + { + WLog_ERR(TAG, "av_frame_alloc failed"); + return FALSE; + } + + return TRUE; +} +#endif + +/** + * Function description + * + * @return success/failure + */ +static BOOL ecam_encoder_context_init_h264(CameraDeviceStream* stream) +{ + WINPR_ASSERT(stream); + +#if defined(WITH_INPUT_FORMAT_H264) + if (streamInputFormat(stream) == CAM_MEDIA_FORMAT_MJPG_H264) + { + stream->h264FrameMaxSize = 1ULL * stream->currMediaType.Width * + stream->currMediaType.Height; /* 1 byte per pixel */ + stream->h264Frame = (BYTE*)calloc(stream->h264FrameMaxSize, sizeof(BYTE)); + return TRUE; /* encoder not needed */ + } +#endif + + if (!stream->h264) + stream->h264 = h264_context_new(TRUE); + + if (!stream->h264) + { + WLog_ERR(TAG, "h264_context_new failed"); + return FALSE; + } + + if (!h264_context_set_option(stream->h264, H264_CONTEXT_OPTION_USAGETYPE, + H264_CAMERA_VIDEO_REAL_TIME)) + goto fail; + + if (!h264_context_set_option(stream->h264, H264_CONTEXT_OPTION_FRAMERATE, + stream->currMediaType.FrameRateNumerator / + stream->currMediaType.FrameRateDenominator)) + goto fail; + + if (!h264_context_set_option(stream->h264, H264_CONTEXT_OPTION_BITRATE, + h264_get_max_bitrate(stream->currMediaType.Height))) + goto fail; + + /* Using CQP mode for rate control. It produces more comparable quality + * between VAAPI and software encoding than VBR mode + */ + if (!h264_context_set_option(stream->h264, H264_CONTEXT_OPTION_RATECONTROL, + H264_RATECONTROL_CQP)) + goto fail; + + /* Using 26 as CQP value. Lower values will produce better quality but + * higher bitrate; higher values - lower bitrate but degraded quality + */ + if (!h264_context_set_option(stream->h264, H264_CONTEXT_OPTION_QP, 26)) + goto fail; + + /* Requesting hardware acceleration before calling h264_context_reset */ + if (!h264_context_set_option(stream->h264, H264_CONTEXT_OPTION_HW_ACCEL, TRUE)) + goto fail; + + if (!h264_context_reset(stream->h264, stream->currMediaType.Width, + stream->currMediaType.Height)) + { + WLog_ERR(TAG, "h264_context_reset failed"); + goto fail; + } + +#if defined(WITH_INPUT_FORMAT_MJPG) + if (streamInputFormat(stream) == CAM_MEDIA_FORMAT_MJPG && !ecam_init_mjpeg_decoder(stream)) + goto fail; +#endif + + return TRUE; + +fail: + ecam_encoder_context_free_h264(stream); + return FALSE; +} + +/** + * Function description + * + * @return success/failure + */ +BOOL ecam_encoder_context_init(CameraDeviceStream* stream) +{ + CAM_MEDIA_FORMAT format = streamOutputFormat(stream); + + switch (format) + { + case CAM_MEDIA_FORMAT_H264: + return ecam_encoder_context_init_h264(stream); + + default: + WLog_ERR(TAG, "Unsupported output format %u", format); + return FALSE; + } +} + +/** + * Function description + * + * @return success/failure + */ +BOOL ecam_encoder_context_free(CameraDeviceStream* stream) +{ + CAM_MEDIA_FORMAT format = streamOutputFormat(stream); + switch (format) + { + case CAM_MEDIA_FORMAT_H264: + ecam_encoder_context_free_h264(stream); + break; + + default: + return FALSE; + } + return TRUE; +} + +/** + * Function description + * + * @return success/failure + */ +BOOL ecam_encoder_compress(CameraDeviceStream* stream, const BYTE* srcData, size_t srcSize, + BYTE** ppDstData, size_t* pDstSize) +{ + CAM_MEDIA_FORMAT format = streamOutputFormat(stream); + switch (format) + { + case CAM_MEDIA_FORMAT_H264: + return ecam_encoder_compress_h264(stream, srcData, srcSize, ppDstData, pDstSize); + default: + WLog_ERR(TAG, "Unsupported output format %u", format); + return FALSE; + } +} diff --git a/third_party/FreeRDP/channels/rdpecam/client/v4l/CMakeLists.txt b/third_party/FreeRDP/channels/rdpecam/client/v4l/CMakeLists.txt new file mode 100644 index 0000000..0a463e8 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpecam/client/v4l/CMakeLists.txt @@ -0,0 +1,34 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2024 Oleg Turovski +# +# 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. + +if(WITH_V4L) + + define_channel_client_subsystem("rdpecam" "v4l" "") + + find_package(libusb-1.0 REQUIRED) + freerdp_client_pc_add_requires_private("libusb-1.0") + include_directories(SYSTEM ${LIBUSB_1_INCLUDE_DIRS}) + + set(${MODULE_PREFIX}_SRCS camera_v4l.c uvc_h264.c) + + set(${MODULE_PREFIX}_LIBS winpr freerdp ${LIBUSB_1_LIBRARIES}) + + include_directories(..) + + add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") + +endif() diff --git a/third_party/FreeRDP/channels/rdpecam/client/v4l/camera_v4l.c b/third_party/FreeRDP/channels/rdpecam/client/v4l/camera_v4l.c new file mode 100644 index 0000000..034516b --- /dev/null +++ b/third_party/FreeRDP/channels/rdpecam/client/v4l/camera_v4l.c @@ -0,0 +1,834 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * MS-RDPECAM Implementation, V4L Interface + * + * Copyright 2024 Oleg Turovski + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +/* v4l includes */ +#include + +#include "camera_v4l.h" +#include "uvc_h264.h" + +#define TAG CHANNELS_TAG("rdpecam-v4l.client") + +#define CAM_V4L2_BUFFERS_COUNT 4 +#define CAM_V4L2_CAPTURE_THREAD_SLEEP_MS 1000 + +#define CAM_V4L2_FRAMERATE_NUMERATOR_DEFAULT 30 +#define CAM_V4L2_FRAMERATE_DENOMINATOR_DEFAULT 1 + +typedef struct +{ + ICamHal iHal; + + wHashTable* streams; /* Index: deviceId, Value: CamV4lStream */ + +} CamV4lHal; + +static CamV4lStream* cam_v4l_stream_create(const char* deviceId, size_t streamIndex); +static void cam_v4l_stream_free(void* obj); +static void cam_v4l_stream_close_device(CamV4lStream* stream); +static CAM_ERROR_CODE cam_v4l_stream_stop(CamV4lStream* stream); + +/** + * Function description + * + * @return \0-terminated fourcc string + */ +static const char* cam_v4l_get_fourcc_str(unsigned int fourcc, char* buffer, size_t size) +{ + if (size < 5) + return nullptr; + + buffer[0] = (char)(fourcc & 0xFF); + buffer[1] = (char)((fourcc >> 8) & 0xFF); + buffer[2] = (char)((fourcc >> 16) & 0xFF); + buffer[3] = (char)((fourcc >> 24) & 0xFF); + buffer[4] = '\0'; + return buffer; +} + +/** + * Function description + * + * @return one of V4L2_PIX_FMT + */ +static UINT32 ecamToV4L2PixFormat(CAM_MEDIA_FORMAT ecamFormat) +{ + switch (ecamFormat) + { + case CAM_MEDIA_FORMAT_H264: + return V4L2_PIX_FMT_H264; + case CAM_MEDIA_FORMAT_MJPG: + return V4L2_PIX_FMT_MJPEG; + case CAM_MEDIA_FORMAT_YUY2: + return V4L2_PIX_FMT_YUYV; + case CAM_MEDIA_FORMAT_NV12: + return V4L2_PIX_FMT_NV12; + case CAM_MEDIA_FORMAT_I420: + return V4L2_PIX_FMT_YUV420; + case CAM_MEDIA_FORMAT_RGB24: + return V4L2_PIX_FMT_RGB24; + case CAM_MEDIA_FORMAT_RGB32: + return V4L2_PIX_FMT_RGB32; + default: + WLog_ERR(TAG, "Unsupported CAM_MEDIA_FORMAT %u", ecamFormat); + return 0; + } +} + +/** + * Function description + * + * @return TRUE or FALSE + */ +static BOOL cam_v4l_format_supported(int fd, UINT32 format) +{ + struct v4l2_fmtdesc fmtdesc = WINPR_C_ARRAY_INIT; + fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + for (fmtdesc.index = 0; ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == 0; fmtdesc.index++) + { + if (fmtdesc.pixelformat == format) + return TRUE; + } + return FALSE; +} + +/** + * Function description + * + * @return file descriptor + */ +static int cam_v4l_open_device(const char* deviceId, int flags) +{ + char device[20] = WINPR_C_ARRAY_INIT; + int fd = -1; + struct v4l2_capability cap = WINPR_C_ARRAY_INIT; + + if (!deviceId) + return -1; + + if (0 == strncmp(deviceId, "/dev/video", 10)) + return open(deviceId, flags); + + for (UINT n = 0; n < 64; n++) + { + (void)_snprintf(device, sizeof(device), "/dev/video%" PRIu32, n); + if ((fd = open(device, flags)) == -1) + continue; + + /* query device capabilities and make sure this is a video capture device */ + if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0 || !(cap.device_caps & V4L2_CAP_VIDEO_CAPTURE)) + { + close(fd); + continue; + } + + if (cap.bus_info[0] != 0 && 0 == strcmp((const char*)cap.bus_info, deviceId)) + return fd; + + close(fd); + } + + return fd; +} + +static BOOL cam_v4l_activate(ICamHal* ihal, const char* deviceId, CAM_ERROR_CODE* errorCode) +{ + WINPR_UNUSED(ihal); + WINPR_UNUSED(deviceId); + + *errorCode = CAM_ERROR_CODE_None; + return TRUE; +} + +static BOOL cam_v4l_deactivate(ICamHal* ihal, const char* deviceId, CAM_ERROR_CODE* errorCode) +{ + WINPR_UNUSED(ihal); + WINPR_UNUSED(deviceId); + + *errorCode = CAM_ERROR_CODE_None; + return TRUE; +} + +/** + * Function description + * + * @return -1 if error, otherwise index of supportedFormats array and mediaTypes/nMediaTypes filled + * in + */ +static INT16 cam_v4l_get_media_type_descriptions(ICamHal* ihal, const char* deviceId, + size_t streamIndex, + const CAM_MEDIA_FORMAT_INFO* supportedFormats, + size_t nSupportedFormats, + CAM_MEDIA_TYPE_DESCRIPTION* mediaTypes, + size_t* nMediaTypes) +{ + CamV4lHal* hal = (CamV4lHal*)ihal; + size_t maxMediaTypes = *nMediaTypes; + size_t nTypes = 0; + BOOL formatFound = FALSE; + + CamV4lStream* stream = (CamV4lStream*)HashTable_GetItemValue(hal->streams, deviceId); + + if (!stream) + { + stream = cam_v4l_stream_create(deviceId, streamIndex); + if (!stream) + return CAM_ERROR_CODE_OutOfMemory; + + if (!HashTable_Insert(hal->streams, deviceId, stream)) + { + cam_v4l_stream_free(stream); + return CAM_ERROR_CODE_UnexpectedError; + } + } + + int fd = cam_v4l_open_device(deviceId, O_RDONLY); + if (fd == -1) + { + WLog_ERR(TAG, "Unable to open device %s", deviceId); + return -1; + } + + size_t formatIndex = 0; + for (; formatIndex < nSupportedFormats; formatIndex++) + { + UINT32 pixelFormat = 0; + if (supportedFormats[formatIndex].inputFormat == CAM_MEDIA_FORMAT_MJPG_H264) + { + if (stream->h264UnitId > 0) + pixelFormat = V4L2_PIX_FMT_MJPEG; + else + continue; /* not supported */ + } + else + { + pixelFormat = ecamToV4L2PixFormat(supportedFormats[formatIndex].inputFormat); + } + + WINPR_ASSERT(pixelFormat != 0); + struct v4l2_frmsizeenum frmsize = WINPR_C_ARRAY_INIT; + + if (!cam_v4l_format_supported(fd, pixelFormat)) + continue; + + frmsize.pixel_format = pixelFormat; + for (frmsize.index = 0; ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) == 0; frmsize.index++) + { + struct v4l2_frmivalenum frmival = WINPR_C_ARRAY_INIT; + + if (frmsize.type != V4L2_FRMSIZE_TYPE_DISCRETE) + break; /* don't support size types other than discrete */ + + formatFound = TRUE; + mediaTypes->Width = frmsize.discrete.width; + mediaTypes->Height = frmsize.discrete.height; + mediaTypes->Format = supportedFormats[formatIndex].inputFormat; + + /* query frame rate (1st is highest fps supported) */ + frmival.index = 0; + frmival.pixel_format = pixelFormat; + frmival.width = frmsize.discrete.width; + frmival.height = frmsize.discrete.height; + if (ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmival) == 0 && + frmival.type == V4L2_FRMIVAL_TYPE_DISCRETE) + { + /* inverse of a fraction */ + mediaTypes->FrameRateNumerator = frmival.discrete.denominator; + mediaTypes->FrameRateDenominator = frmival.discrete.numerator; + } + else + { + WLog_DBG(TAG, "VIDIOC_ENUM_FRAMEINTERVALS failed, using default framerate"); + mediaTypes->FrameRateNumerator = CAM_V4L2_FRAMERATE_NUMERATOR_DEFAULT; + mediaTypes->FrameRateDenominator = CAM_V4L2_FRAMERATE_DENOMINATOR_DEFAULT; + } + + mediaTypes->PixelAspectRatioNumerator = mediaTypes->PixelAspectRatioDenominator = 1; + + char fourccstr[5] = WINPR_C_ARRAY_INIT; + WLog_DBG(TAG, "Camera format: %s, width: %u, height: %u, fps: %u/%u", + cam_v4l_get_fourcc_str(pixelFormat, fourccstr, ARRAYSIZE(fourccstr)), + mediaTypes->Width, mediaTypes->Height, mediaTypes->FrameRateNumerator, + mediaTypes->FrameRateDenominator); + + mediaTypes++; + nTypes++; + + if (nTypes == maxMediaTypes) + { + WLog_ERR(TAG, "Media types reached buffer maximum %" PRIuz "", maxMediaTypes); + goto error; + } + } + + if (formatFound) + { + /* we are interested in 1st supported format only, with all supported sizes */ + break; + } + } + +error: + + *nMediaTypes = nTypes; + close(fd); + if (formatIndex > INT16_MAX) + return -1; + return (INT16)formatIndex; +} + +/** + * Function description + * + * @return number of video capture devices + */ +static UINT cam_v4l_enumerate(WINPR_ATTR_UNUSED ICamHal* ihal, ICamHalEnumCallback callback, + CameraPlugin* ecam, GENERIC_CHANNEL_CALLBACK* hchannel) +{ + UINT count = 0; + + for (UINT n = 0; n < 64; n++) + { + char device[20] = WINPR_C_ARRAY_INIT; + struct v4l2_capability cap = WINPR_C_ARRAY_INIT; + (void)_snprintf(device, sizeof(device), "/dev/video%" PRIu32, n); + int fd = open(device, O_RDONLY); + if (fd == -1) + continue; + + /* query device capabilities and make sure this is a video capture device */ + if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0 || !(cap.device_caps & V4L2_CAP_VIDEO_CAPTURE)) + { + close(fd); + continue; + } + count++; + + const char* deviceName = (char*)cap.card; + const char* deviceId = device; + if (cap.bus_info[0] != 0) /* may not be available in all drivers */ + deviceId = (char*)cap.bus_info; + + IFCALL(callback, ecam, hchannel, deviceId, deviceName); + + close(fd); + } + + return count; +} + +static void cam_v4l_stream_free_buffers(CamV4lStream* stream) +{ + if (!stream || !stream->buffers) + return; + + /* unmap buffers */ + for (size_t i = 0; i < stream->nBuffers; i++) + { + if (stream->buffers[i].length && stream->buffers[i].start != MAP_FAILED) + { + munmap(stream->buffers[i].start, stream->buffers[i].length); + } + } + + free(stream->buffers); + stream->buffers = nullptr; + stream->nBuffers = 0; +} + +/** + * Function description + * + * @return 0 on failure, otherwise allocated buffer size + */ +static size_t cam_v4l_stream_alloc_buffers(CamV4lStream* stream) +{ + struct v4l2_requestbuffers rbuffer = WINPR_C_ARRAY_INIT; + + rbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + rbuffer.memory = V4L2_MEMORY_MMAP; + rbuffer.count = CAM_V4L2_BUFFERS_COUNT; + + if (ioctl(stream->fd, VIDIOC_REQBUFS, &rbuffer) < 0 || rbuffer.count == 0) + { + char buffer[64] = WINPR_C_ARRAY_INIT; + WLog_ERR(TAG, "Failure in VIDIOC_REQBUFS, errno %s [%d], count %u", + winpr_strerror(errno, buffer, sizeof(buffer)), errno, rbuffer.count); + return 0; + } + + stream->nBuffers = rbuffer.count; + + /* Map the buffers */ + stream->buffers = (CamV4lBuffer*)calloc(rbuffer.count, sizeof(CamV4lBuffer)); + if (!stream->buffers) + { + WLog_ERR(TAG, "Failure in calloc"); + return 0; + } + + for (unsigned int i = 0; i < rbuffer.count; i++) + { + struct v4l2_buffer vbuffer = WINPR_C_ARRAY_INIT; + vbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vbuffer.memory = V4L2_MEMORY_MMAP; + vbuffer.index = i; + + if (ioctl(stream->fd, VIDIOC_QUERYBUF, &vbuffer) < 0) + { + char buffer[64] = WINPR_C_ARRAY_INIT; + WLog_ERR(TAG, "Failure in VIDIOC_QUERYBUF, errno %s [%d]", + winpr_strerror(errno, buffer, sizeof(buffer)), errno); + cam_v4l_stream_free_buffers(stream); + return 0; + } + + stream->buffers[i].start = mmap(nullptr, vbuffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, + stream->fd, vbuffer.m.offset); + + if (MAP_FAILED == stream->buffers[i].start) + { + char buffer[64] = WINPR_C_ARRAY_INIT; + WLog_ERR(TAG, "Failure in mmap, errno %s [%d]", + winpr_strerror(errno, buffer, sizeof(buffer)), errno); + cam_v4l_stream_free_buffers(stream); + return 0; + } + + stream->buffers[i].length = vbuffer.length; + + WLog_DBG(TAG, "Buffer %u mapped, size: %u", i, vbuffer.length); + + if (ioctl(stream->fd, VIDIOC_QBUF, &vbuffer) < 0) + { + char buffer[64] = WINPR_C_ARRAY_INIT; + WLog_ERR(TAG, "Failure in VIDIOC_QBUF, errno %s [%d]", + winpr_strerror(errno, buffer, sizeof(buffer)), errno); + cam_v4l_stream_free_buffers(stream); + return 0; + } + } + + return stream->buffers[0].length; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static DWORD WINAPI cam_v4l_stream_capture_thread(LPVOID param) +{ + CamV4lStream* stream = (CamV4lStream*)param; + WINPR_ASSERT(stream); + + int fd = stream->fd; + + do + { + int retVal = 0; + struct pollfd pfd = WINPR_C_ARRAY_INIT; + + pfd.fd = fd; + pfd.events = POLLIN; + + retVal = poll(&pfd, 1, CAM_V4L2_CAPTURE_THREAD_SLEEP_MS); + + if (retVal == 0) + { + /* poll timed out */ + continue; + } + else if (retVal < 0) + { + char buffer[64] = WINPR_C_ARRAY_INIT; + WLog_DBG(TAG, "Failure in poll, errno %s [%d]", + winpr_strerror(errno, buffer, sizeof(buffer)), errno); + Sleep(CAM_V4L2_CAPTURE_THREAD_SLEEP_MS); /* trying to recover */ + continue; + } + else if (!(pfd.revents & POLLIN)) + { + WLog_DBG(TAG, "poll reported non-read event %d", pfd.revents); + Sleep(CAM_V4L2_CAPTURE_THREAD_SLEEP_MS); /* also trying to recover */ + continue; + } + + EnterCriticalSection(&stream->lock); + if (stream->streaming) + { + struct v4l2_buffer buf = WINPR_C_ARRAY_INIT; + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + + /* dequeue buffers until empty */ + while (ioctl(fd, VIDIOC_DQBUF, &buf) != -1) + { + const UINT error = + stream->sampleCallback(stream->dev, stream->streamIndex, + stream->buffers[buf.index].start, buf.bytesused); + if (error != CHANNEL_RC_OK) + WLog_ERR(TAG, "Failure in sampleCallback: %" PRIu32, error); + + /* enqueue buffer back */ + if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) + { + char buffer[64] = WINPR_C_ARRAY_INIT; + WLog_ERR(TAG, "Failure in VIDIOC_QBUF, errno %s [%d]", + winpr_strerror(errno, buffer, sizeof(buffer)), errno); + } + } + } + LeaveCriticalSection(&stream->lock); + + } while (stream->streaming); + + return CHANNEL_RC_OK; +} + +void cam_v4l_stream_close_device(CamV4lStream* stream) +{ + if (stream->fd != -1) + { + close(stream->fd); + stream->fd = -1; + } +} + +/** + * Function description + * + * @return Null on failure, otherwise pointer to new CamV4lStream + */ +WINPR_ATTR_MALLOC(cam_v4l_stream_free, 1) +CamV4lStream* cam_v4l_stream_create(const char* deviceId, size_t streamIndex) +{ + CamV4lStream* stream = calloc(1, sizeof(CamV4lStream)); + + if (!stream) + { + WLog_ERR(TAG, "Failure in calloc"); + return nullptr; + } + stream->streamIndex = streamIndex; + stream->fd = -1; + stream->h264UnitId = get_uvc_h624_unit_id(deviceId); + + if (!InitializeCriticalSectionEx(&stream->lock, 0, 0)) + { + WLog_ERR(TAG, "Failure in calloc"); + free(stream); + return nullptr; + } + + return stream; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +CAM_ERROR_CODE cam_v4l_stream_stop(CamV4lStream* stream) +{ + if (!stream || !stream->streaming) + return CAM_ERROR_CODE_None; + + stream->streaming = FALSE; /* this will terminate capture thread */ + + if (stream->captureThread) + { + (void)WaitForSingleObject(stream->captureThread, INFINITE); + (void)CloseHandle(stream->captureThread); + stream->captureThread = nullptr; + } + + EnterCriticalSection(&stream->lock); + + /* stop streaming */ + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (ioctl(stream->fd, VIDIOC_STREAMOFF, &type) < 0) + { + char buffer[64] = WINPR_C_ARRAY_INIT; + WLog_ERR(TAG, "Failure in VIDIOC_STREAMOFF, errno %s [%d]", + winpr_strerror(errno, buffer, sizeof(buffer)), errno); + } + + cam_v4l_stream_free_buffers(stream); + cam_v4l_stream_close_device(stream); + + LeaveCriticalSection(&stream->lock); + + return CAM_ERROR_CODE_None; +} + +static CAM_ERROR_CODE cam_v4l_stream_start(ICamHal* ihal, CameraDevice* dev, size_t streamIndex, + const CAM_MEDIA_TYPE_DESCRIPTION* mediaType, + ICamHalSampleCapturedCallback callback) +{ + CamV4lHal* hal = (CamV4lHal*)ihal; + WINPR_ASSERT(hal); + + CamV4lStream* stream = (CamV4lStream*)HashTable_GetItemValue(hal->streams, dev->deviceId); + + if (!stream) + { + WLog_ERR(TAG, "Unable to find stream, device %s, streamIndex %" PRIuz, dev->deviceId, + streamIndex); + return CAM_ERROR_CODE_UnexpectedError; + } + + if (stream->streaming) + { + WLog_ERR(TAG, "Streaming already in progress, device %s, streamIndex %" PRIuz, + dev->deviceId, streamIndex); + return CAM_ERROR_CODE_UnexpectedError; + } + + stream->dev = dev; + stream->sampleCallback = callback; + + if ((stream->fd = cam_v4l_open_device(dev->deviceId, O_RDWR | O_NONBLOCK)) == -1) + { + WLog_ERR(TAG, "Unable to open device %s", dev->deviceId); + return CAM_ERROR_CODE_UnexpectedError; + } + + struct v4l2_format video_fmt = WINPR_C_ARRAY_INIT; + video_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + UINT32 pixelFormat = 0; + if (mediaType->Format == CAM_MEDIA_FORMAT_MJPG_H264) + { + if (!set_h264_muxed_format(stream, mediaType)) + { + WLog_ERR(TAG, "Failure to set H264 muxed format"); + cam_v4l_stream_close_device(stream); + return CAM_ERROR_CODE_UnexpectedError; + } + /* setup container stream format */ + pixelFormat = V4L2_PIX_FMT_MJPEG; + /* limit container stream resolution to save USB bandwidth - required */ + video_fmt.fmt.pix.width = 640; + video_fmt.fmt.pix.height = 480; + } + else + { + pixelFormat = ecamToV4L2PixFormat(mediaType->Format); + video_fmt.fmt.pix.width = mediaType->Width; + video_fmt.fmt.pix.height = mediaType->Height; + } + + if (pixelFormat == 0) + { + cam_v4l_stream_close_device(stream); + return CAM_ERROR_CODE_InvalidMediaType; + } + + video_fmt.fmt.pix.pixelformat = pixelFormat; + + /* set format and frame size */ + if (ioctl(stream->fd, VIDIOC_S_FMT, &video_fmt) < 0) + { + char buffer[64] = WINPR_C_ARRAY_INIT; + WLog_ERR(TAG, "Failure in VIDIOC_S_FMT, errno %s [%d]", + winpr_strerror(errno, buffer, sizeof(buffer)), errno); + cam_v4l_stream_close_device(stream); + return CAM_ERROR_CODE_InvalidMediaType; + } + + /* trying to set frame rate, if driver supports it */ + struct v4l2_streamparm sp1 = WINPR_C_ARRAY_INIT; + struct v4l2_streamparm sp2 = WINPR_C_ARRAY_INIT; + sp1.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (ioctl(stream->fd, VIDIOC_G_PARM, &sp1) < 0 || + !(sp1.parm.capture.capability & V4L2_CAP_TIMEPERFRAME)) + { + WLog_INFO(TAG, "Driver doesn't support setting framerate"); + } + else + { + sp2.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + /* inverse of a fraction */ + sp2.parm.capture.timeperframe.numerator = mediaType->FrameRateDenominator; + sp2.parm.capture.timeperframe.denominator = mediaType->FrameRateNumerator; + + if (ioctl(stream->fd, VIDIOC_S_PARM, &sp2) < 0) + { + char buffer[64] = WINPR_C_ARRAY_INIT; + WLog_INFO(TAG, "Failed to set the framerate, errno %s [%d]", + winpr_strerror(errno, buffer, sizeof(buffer)), errno); + } + } + + size_t maxSample = cam_v4l_stream_alloc_buffers(stream); + if (maxSample == 0) + { + WLog_ERR(TAG, "Failure to allocate video buffers"); + cam_v4l_stream_close_device(stream); + return CAM_ERROR_CODE_OutOfMemory; + } + + stream->streaming = TRUE; + + /* start streaming */ + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (ioctl(stream->fd, VIDIOC_STREAMON, &type) < 0) + { + char buffer[64] = WINPR_C_ARRAY_INIT; + WLog_ERR(TAG, "Failure in VIDIOC_STREAMON, errno %s [%d]", + winpr_strerror(errno, buffer, sizeof(buffer)), errno); + cam_v4l_stream_stop(stream); + return CAM_ERROR_CODE_UnexpectedError; + } + + stream->captureThread = + CreateThread(nullptr, 0, cam_v4l_stream_capture_thread, stream, 0, nullptr); + if (!stream->captureThread) + { + WLog_ERR(TAG, "CreateThread failure"); + cam_v4l_stream_stop(stream); + return CAM_ERROR_CODE_OutOfMemory; + } + + char fourccstr[16] = WINPR_C_ARRAY_INIT; + if (mediaType->Format == CAM_MEDIA_FORMAT_MJPG_H264) + strncpy(fourccstr, "H264 muxed", ARRAYSIZE(fourccstr) - 1); + else + cam_v4l_get_fourcc_str(pixelFormat, fourccstr, ARRAYSIZE(fourccstr)); + + WLog_INFO(TAG, "Camera format: %s, width: %u, height: %u, fps: %u/%u", fourccstr, + mediaType->Width, mediaType->Height, mediaType->FrameRateNumerator, + mediaType->FrameRateDenominator); + + return CAM_ERROR_CODE_None; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static CAM_ERROR_CODE cam_v4l_stream_stop_by_device_id(ICamHal* ihal, const char* deviceId, + WINPR_ATTR_UNUSED size_t streamIndex) +{ + CamV4lHal* hal = (CamV4lHal*)ihal; + + CamV4lStream* stream = (CamV4lStream*)HashTable_GetItemValue(hal->streams, deviceId); + + if (!stream) + return CAM_ERROR_CODE_NotInitialized; + + return cam_v4l_stream_stop(stream); +} + +/** + * Function description + * + * OBJECT_FREE_FN for streams hash table value + * + */ +void cam_v4l_stream_free(void* obj) +{ + CamV4lStream* stream = (CamV4lStream*)obj; + if (!stream) + return; + + cam_v4l_stream_stop(stream); + + DeleteCriticalSection(&stream->lock); + free(stream); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static CAM_ERROR_CODE cam_v4l_free(ICamHal* ihal) +{ + CamV4lHal* hal = (CamV4lHal*)ihal; + + if (hal == nullptr) + return CAM_ERROR_CODE_NotInitialized; + + HashTable_Free(hal->streams); + + free(hal); + + return CAM_ERROR_CODE_None; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE v4l_freerdp_rdpecam_client_subsystem_entry( + PFREERDP_CAMERA_HAL_ENTRY_POINTS pEntryPoints)) +{ + UINT ret = CHANNEL_RC_OK; + WINPR_ASSERT(pEntryPoints); + + CamV4lHal* hal = (CamV4lHal*)calloc(1, sizeof(CamV4lHal)); + + if (hal == nullptr) + return CHANNEL_RC_NO_MEMORY; + + hal->iHal.Enumerate = cam_v4l_enumerate; + hal->iHal.GetMediaTypeDescriptions = cam_v4l_get_media_type_descriptions; + hal->iHal.Activate = cam_v4l_activate; + hal->iHal.Deactivate = cam_v4l_deactivate; + hal->iHal.StartStream = cam_v4l_stream_start; + hal->iHal.StopStream = cam_v4l_stream_stop_by_device_id; + hal->iHal.Free = cam_v4l_free; + + hal->streams = HashTable_New(FALSE); + if (!hal->streams) + { + ret = CHANNEL_RC_NO_MEMORY; + goto error; + } + + HashTable_SetupForStringData(hal->streams, FALSE); + + wObject* obj = HashTable_ValueObject(hal->streams); + WINPR_ASSERT(obj); + obj->fnObjectFree = cam_v4l_stream_free; + + if ((ret = pEntryPoints->pRegisterCameraHal(pEntryPoints->plugin, &hal->iHal))) + { + WLog_ERR(TAG, "RegisterCameraHal failed with error %" PRIu32 "", ret); + goto error; + } + + return ret; + +error: + cam_v4l_free(&hal->iHal); + return ret; +} diff --git a/third_party/FreeRDP/channels/rdpecam/client/v4l/camera_v4l.h b/third_party/FreeRDP/channels/rdpecam/client/v4l/camera_v4l.h new file mode 100644 index 0000000..5d5de27 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpecam/client/v4l/camera_v4l.h @@ -0,0 +1,54 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * MS-RDPECAM Implementation, V4L Interface + * + * Copyright 2025 Oleg Turovski + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CAMERA_V4L_H +#define CAMERA_V4L_H + +#include +#include + +#include "../camera.h" + +typedef struct +{ + void* start; + size_t length; + +} CamV4lBuffer; + +typedef struct +{ + CRITICAL_SECTION lock; + + /* members used to call the callback */ + CameraDevice* dev; + size_t streamIndex; + WINPR_ATTR_NODISCARD ICamHalSampleCapturedCallback sampleCallback; + + BOOL streaming; + int fd; + uint8_t h264UnitId; /* UVC H264 UnitId, if 0 then UVC H264 is not supported */ + + size_t nBuffers; + CamV4lBuffer* buffers; + HANDLE captureThread; + +} CamV4lStream; + +#endif /* CAMERA_V4L_H */ diff --git a/third_party/FreeRDP/channels/rdpecam/client/v4l/uvc_h264.c b/third_party/FreeRDP/channels/rdpecam/client/v4l/uvc_h264.c new file mode 100644 index 0000000..30f0cf4 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpecam/client/v4l/uvc_h264.c @@ -0,0 +1,489 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * MS-RDPECAM Implementation, UVC H264 support + * + * See USB_Video_Payload_H 264_1 0.pdf for more details + * + * Credits: + * guvcview http://guvcview.sourceforge.net + * Paulo Assis + * + * Copyright 2025 Oleg Turovski + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include "uvc_h264.h" + +/* UVC H.264 extension unit GUID: {A29E7641-DE04-47E3-8B2B-F4341AFF003B} */ +static uint8_t GUID_UVCX_H264_XU[16] = { 0x41, 0x76, 0x9E, 0xA2, 0x04, 0xDE, 0xE3, 0x47, + 0x8B, 0x2B, 0xF4, 0x34, 0x1A, 0xFF, 0x00, 0x3B }; + +#define TAG CHANNELS_TAG("rdpecam-uvch264.client") + +/* + * get length of xu control defined by unit id and selector + * args: + * stream - pointer to video device data + * unit - unit id of xu control + * selector - selector for control + * + * returns: length of xu control + */ +static uint16_t get_length_xu_control(CamV4lStream* stream, uint8_t unit, uint8_t selector) +{ + WINPR_ASSERT(stream); + WINPR_ASSERT(stream->fd > 0); + + uint16_t length = 0; + + struct uvc_xu_control_query xu_ctrl_query = { .unit = unit, + .selector = selector, + .query = UVC_GET_LEN, + .size = sizeof(length), + .data = (uint8_t*)&length }; + + if (ioctl(stream->fd, UVCIOC_CTRL_QUERY, &xu_ctrl_query) < 0) + { + char ebuffer[256] = WINPR_C_ARRAY_INIT; + WLog_ERR(TAG, "UVCIOC_CTRL_QUERY (GET_LEN) - Error: %s", + winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + return 0; + } + + return length; +} + +/* + * runs a query on xu control defined by unit id and selector + * args: + * stream - pointer to video device data + * unit - unit id of xu control + * selector - selector for control + * query - query type + * data - pointer to query data + * + * returns: 0 if query succeeded or error code on fail + */ +static int query_xu_control(CamV4lStream* stream, uint8_t unit, uint8_t selector, uint8_t query, + void* data) +{ + int err = 0; + uint16_t len = get_length_xu_control(stream, unit, selector); + + struct uvc_xu_control_query xu_ctrl_query = { + .unit = unit, .selector = selector, .query = query, .size = len, .data = (uint8_t*)data + }; + + /*get query data*/ + if ((err = ioctl(stream->fd, UVCIOC_CTRL_QUERY, &xu_ctrl_query)) < 0) + { + char ebuffer[256] = WINPR_C_ARRAY_INIT; + WLog_ERR(TAG, "UVCIOC_CTRL_QUERY (%" PRIu8 ") - Error: %s", query, + winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + } + + return err; +} + +/* + * resets the h264 encoder + * args: + * stream - pointer to video device data + * + * returns: 0 on success or error code on fail + */ +static int uvcx_video_encoder_reset(CamV4lStream* stream) +{ + WINPR_ASSERT(stream); + + uvcx_encoder_reset encoder_reset_req = WINPR_C_ARRAY_INIT; + + int err = 0; + + if ((err = query_xu_control(stream, stream->h264UnitId, UVCX_ENCODER_RESET, UVC_SET_CUR, + &encoder_reset_req)) < 0) + { + char ebuffer[256] = WINPR_C_ARRAY_INIT; + WLog_ERR(TAG, "UVCX_ENCODER_RESET error: %s", + winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + } + + return err; +} + +/* + * probes the h264 encoder config + * args: + * stream - pointer to video device data + * query - probe query + * uvcx_video_config - pointer to probe/commit config data + * + * returns: 0 on success or error code on fail + */ +static int uvcx_video_probe(CamV4lStream* stream, uint8_t query, + uvcx_video_config_probe_commit_t* uvcx_video_config) +{ + WINPR_ASSERT(stream); + + int err = 0; + + if ((err = query_xu_control(stream, stream->h264UnitId, UVCX_VIDEO_CONFIG_PROBE, query, + uvcx_video_config)) < 0) + { + char ebuffer[256] = WINPR_C_ARRAY_INIT; + WLog_ERR(TAG, "UVCX_VIDEO_CONFIG_PROBE error: %s", + winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + } + + return err; +} + +/* + * commits the h264 encoder config + * args: + * stream - pointer to video device data + * uvcx_video_config - pointer to probe/commit config data + * + * returns: 0 on success or error code on fail + */ +static int uvcx_video_commit(CamV4lStream* stream, + uvcx_video_config_probe_commit_t* uvcx_video_config) +{ + WINPR_ASSERT(stream); + + int err = 0; + + if ((err = query_xu_control(stream, stream->h264UnitId, UVCX_VIDEO_CONFIG_COMMIT, UVC_SET_CUR, + uvcx_video_config)) < 0) + { + char ebuffer[256] = WINPR_C_ARRAY_INIT; + WLog_ERR(TAG, "UVCX_VIDEO_CONFIG_COMMIT error: %s", + winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + } + + return err; +} + +/* + * sets h264 muxed format (must not be called while streaming) + * args: + * stream - pointer to video device data + * mediaType + * + * returns: TRUE on success or FALSE on fail + */ +BOOL set_h264_muxed_format(CamV4lStream* stream, const CAM_MEDIA_TYPE_DESCRIPTION* mediaType) +{ + WINPR_ASSERT(stream); + WINPR_ASSERT(mediaType); + + uvcx_video_config_probe_commit_t config_probe_req = WINPR_C_ARRAY_INIT; + int err = 0; + + /* reset the encoder */ + err = uvcx_video_encoder_reset(stream); + if (err != 0) + return FALSE; + + /* get default values */ + err = uvcx_video_probe(stream, UVC_GET_DEF, &config_probe_req); + if (err != 0) + return FALSE; + + /* set resolution */ + config_probe_req.wWidth = WINPR_ASSERTING_INT_CAST(uint16_t, mediaType->Width); + config_probe_req.wHeight = WINPR_ASSERTING_INT_CAST(uint16_t, mediaType->Height); + + /* set frame rate in 100ns units */ + uint32_t frame_interval = + (mediaType->FrameRateDenominator * 1000000000LL / mediaType->FrameRateNumerator) / 100; + config_probe_req.dwFrameInterval = frame_interval; + + /* quality settings */ + config_probe_req.wProfile = PROFILE_HIGH; + config_probe_req.bUsageType = USAGETYPE_REALTIME; + config_probe_req.bRateControlMode = RATECONTROL_VBR; + config_probe_req.dwBitRate = h264_get_max_bitrate(mediaType->Height); + config_probe_req.bEntropyCABAC = ENTROPY_CABAC; + config_probe_req.wIFramePeriod = 1000; /* ms, 1 sec */ + + /* hints which parameters are configured */ + config_probe_req.bmHints = BMHINTS_RESOLUTION | BMHINTS_FRAME_INTERVAL | BMHINTS_PROFILE | + BMHINTS_USAGE | BMHINTS_RATECONTROL | BMHINTS_BITRATE | + BMHINTS_ENTROPY | BMHINTS_IFRAMEPERIOD; + + /* set the aux stream */ + config_probe_req.bStreamMuxOption = STREAMMUX_H264; + + /* probe the format */ + err = uvcx_video_probe(stream, UVC_SET_CUR, &config_probe_req); + if (err != 0) + return FALSE; + + err = uvcx_video_probe(stream, UVC_GET_CUR, &config_probe_req); + if (err != 0) + return FALSE; + + if (config_probe_req.wWidth != mediaType->Width) + { + WLog_ERR(TAG, "Requested width %" PRIu16 " but got %" PRIu16, mediaType->Width, + config_probe_req.wWidth); + return FALSE; + } + if (config_probe_req.wHeight != mediaType->Height) + { + WLog_ERR(TAG, "Requested height %" PRIu16 " but got %" PRIu16, mediaType->Height, + config_probe_req.wHeight); + return FALSE; + } + if (config_probe_req.dwFrameInterval != frame_interval) + { + WLog_ERR(TAG, "Requested frame interval %" PRIu32 " but got %" PRIu32, frame_interval, + config_probe_req.dwFrameInterval); + return FALSE; + } + + /* commit the format */ + err = uvcx_video_commit(stream, &config_probe_req); + return (err == 0); +} + +/* + * parses deviceId such as usb-0000:00:1a.0-1.2.2 to return devpath (1.2.2) + * + * deviceID format is: usb-- + * see kernel's usb_make_path() + * + * args: + * deviceId + * path - buffer to return devpath + * size - buffer size + * + * returns: TRUE if success, FALSE otherwise + */ +static BOOL get_devpath_from_device_id(const char* deviceId, char* path, size_t size) +{ + if (0 != strncmp(deviceId, "usb-", 4)) + return FALSE; + + /* find second `-` */ + const char* p = strchr(deviceId + 4, '-'); + if (!p) + return FALSE; + + p++; // now points to nullptr terminated devpath + + strncpy(path, p, size - 1); + return TRUE; +} + +/* + * return devpath of a given libusb_device as text string such as: 1.2.2 or 2.3 + * + * args: + * device + * path - buffer to return devpath + * size - buffer size + * + * returns: TRUE if success, FALSE otherwise + */ +static BOOL get_devpath_from_device(libusb_device* device, char* path, size_t size) +{ + uint8_t ports[MAX_DEVPATH_DEPTH] = WINPR_C_ARRAY_INIT; + int nPorts = libusb_get_port_numbers(device, ports, sizeof(ports)); + + if (nPorts <= 0) + return FALSE; + + for (int i = 0; i < nPorts; i++) + { + int nChars = snprintf(path, size, "%" PRIu8, ports[i]); + if ((nChars <= 0) || ((size_t)nChars >= size)) + return FALSE; + + size -= (size_t)nChars; + path += nChars; + + if (i < nPorts - 1) + { + *path++ = '.'; + size--; + } + } + return TRUE; +} + +static uint8_t get_guid_unit_id_from_config_descriptor(struct libusb_config_descriptor* config, + const uint8_t* guid, + const struct libusb_device_descriptor* ddesc) +{ + WINPR_ASSERT(config); + WINPR_ASSERT(guid); + WINPR_ASSERT(ddesc); + + for (uint8_t j = 0; j < config->bNumInterfaces; j++) + { + const struct libusb_interface* cfg = &config->interface[j]; + for (int k = 0; k < cfg->num_altsetting; k++) + { + const struct libusb_interface_descriptor* interface = &cfg->altsetting[k]; + if (interface->bInterfaceClass != LIBUSB_CLASS_VIDEO || + interface->bInterfaceSubClass != USB_VIDEO_CONTROL) + continue; + + const uint8_t* ptr = interface->extra; + while (ptr < interface->extra + interface->extra_length) + { + const xu_descriptor* desc = (const xu_descriptor*)ptr; + if (desc->bDescriptorType == USB_VIDEO_CONTROL_INTERFACE && + desc->bDescriptorSubType == USB_VIDEO_CONTROL_XU_TYPE && + memcmp(desc->guidExtensionCode, guid, 16) == 0) + { + int8_t unit_id = desc->bUnitID; + + WLog_DBG(TAG, + "For camera %04" PRIx16 ":%04" PRIx16 + " found UVCX H264 UnitID %" PRId8, + ddesc->idVendor, ddesc->idProduct, unit_id); + if (unit_id < 0) + return 0; + return WINPR_CXX_COMPAT_CAST(uint8_t, unit_id); + } + ptr += desc->bLength; + } + } + } + return 0; +} + +/* + * get GUID unit id from libusb_device, if any + * + * args: + * device + * guid - 16 byte xu GUID + * + * returns: unit id for the matching GUID or 0 if none + */ +static uint8_t get_guid_unit_id_from_device(libusb_device* device, const uint8_t* guid) +{ + struct libusb_device_descriptor ddesc = WINPR_C_ARRAY_INIT; + + if (libusb_get_device_descriptor(device, &ddesc) != 0) + { + WLog_ERR(TAG, "Couldn't get device descriptor"); + return 0; + } + + for (uint8_t i = 0; i < ddesc.bNumConfigurations; ++i) + { + uint8_t rc = 0; + struct libusb_config_descriptor* config = nullptr; + + if (libusb_get_config_descriptor(device, i, &config) != 0) + { + WLog_ERR(TAG, + "Couldn't get config descriptor for " + "configuration %" PRIu8, + i); + } + else + rc = get_guid_unit_id_from_config_descriptor(config, guid, &ddesc); + + libusb_free_config_descriptor(config); + if (rc != 0) + return rc; + } + + /* no match found */ + return 0; +} + +/* + * get GUID unit id, if any + * + * args: + * deviceId - camera deviceId such as: usb-0000:00:1a.0-1.2.2 + * guid - 16 byte xu GUID + * + * returns: unit id for the matching GUID or 0 if none + */ +static uint8_t get_guid_unit_id(const char* deviceId, const uint8_t* guid) +{ + char cam_devpath[MAX_DEVPATH_STR_SIZE] = WINPR_C_ARRAY_INIT; + libusb_context* usb_ctx = nullptr; + libusb_device** device_list = nullptr; + uint8_t unit_id = 0; + + if (!get_devpath_from_device_id(deviceId, cam_devpath, sizeof(cam_devpath))) + { + WLog_ERR(TAG, "Unable to get devpath from deviceId %s", deviceId); + return 0; + } + + if (0 != libusb_init(&usb_ctx)) + { + WLog_ERR(TAG, "Unable to initialize libusb"); + return 0; + } + + ssize_t cnt = libusb_get_device_list(usb_ctx, &device_list); + + for (ssize_t i = 0; i < cnt; i++) + { + char path[MAX_DEVPATH_STR_SIZE] = WINPR_C_ARRAY_INIT; + libusb_device* device = device_list[i]; + + if (!device || !get_devpath_from_device(device, path, sizeof(path))) + continue; + + if (0 != strcmp(cam_devpath, path)) + continue; + + /* found device with matching devpath, try to get guid unit id */ + unit_id = get_guid_unit_id_from_device(device, guid); + + if (unit_id > 0) + break; /* got it */ + + /* there's chance for another devpath match - continue */ + } + + libusb_free_device_list(device_list, TRUE); + libusb_exit(usb_ctx); + return unit_id; +} + +/* + * gets the uvc h264 xu control unit id, if any + * + * args: + * deviceId - camera deviceId such as: usb-0000:00:1a.0-1.2.2 + * + * returns: unit id or 0 if none + */ +uint8_t get_uvc_h624_unit_id(const char* deviceId) +{ + WINPR_ASSERT(deviceId); + + WLog_DBG(TAG, "Checking for UVCX H264 UnitID for %s", deviceId); + + return get_guid_unit_id(deviceId, GUID_UVCX_H264_XU); +} diff --git a/third_party/FreeRDP/channels/rdpecam/client/v4l/uvc_h264.h b/third_party/FreeRDP/channels/rdpecam/client/v4l/uvc_h264.h new file mode 100644 index 0000000..b599e82 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpecam/client/v4l/uvc_h264.h @@ -0,0 +1,173 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * MS-RDPECAM Implementation, UVC H264 support + * + * See USB_Video_Payload_H 264_1 0.pdf for more details + * + * Credits: + * guvcview http://guvcview.sourceforge.net + * Paulo Assis + * + * Copyright 2025 Oleg Turovski + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UVC_H264_H +#define UVC_H264_H + +#include + +#include "../camera.h" +#include "camera_v4l.h" + +/* UVC H.264 control selectors */ +#define UVCX_VIDEO_CONFIG_PROBE 0x01 +#define UVCX_VIDEO_CONFIG_COMMIT 0x02 +#define UVCX_RATE_CONTROL_MODE 0x03 +#define UVCX_TEMPORAL_SCALE_MODE 0x04 +#define UVCX_SPATIAL_SCALE_MODE 0x05 +#define UVCX_SNR_SCALE_MODE 0x06 +#define UVCX_LTR_BUFFER_SIZE_CONTROL 0x07 +#define UVCX_LTR_PICTURE_CONTROL 0x08 +#define UVCX_PICTURE_TYPE_CONTROL 0x09 +#define UVCX_VERSION 0x0A +#define UVCX_ENCODER_RESET 0x0B +#define UVCX_FRAMERATE_CONFIG 0x0C +#define UVCX_VIDEO_ADVANCE_CONFIG 0x0D +#define UVCX_BITRATE_LAYERS 0x0E +#define UVCX_QP_STEPS_LAYERS 0x0F + +/* Video Class-Specific Request Codes */ +#define UVC_RC_UNDEFINED 0x00 +#define UVC_SET_CUR 0x01 +#define UVC_GET_CUR 0x81 +#define UVC_GET_MIN 0x82 +#define UVC_GET_MAX 0x83 +#define UVC_GET_RES 0x84 +#define UVC_GET_LEN 0x85 +#define UVC_GET_INFO 0x86 +#define UVC_GET_DEF 0x87 + +/* bStreamMuxOption defines */ +#define STREAMMUX_H264 (1 << 0) | (1 << 1) + +/* wProfile defines */ +#define PROFILE_BASELINE 0x4200 +#define PROFILE_MAIN 0x4D00 +#define PROFILE_HIGH 0x6400 + +/* bUsageType defines */ +#define USAGETYPE_REALTIME 0x01 + +/* bRateControlMode defines */ +#define RATECONTROL_CBR 0x01 +#define RATECONTROL_VBR 0x02 +#define RATECONTROL_CONST_QP 0x03 + +/* bEntropyCABAC defines */ +#define ENTROPY_CABAC 0x01 + +/* bmHints defines */ +#define BMHINTS_RESOLUTION 0x0001 +#define BMHINTS_PROFILE 0x0002 +#define BMHINTS_RATECONTROL 0x0004 +#define BMHINTS_USAGE 0x0008 +#define BMHINTS_SLICEMODE 0x0010 +#define BMHINTS_SLICEUNITS 0x0020 +#define BMHINTS_MVCVIEW 0x0040 +#define BMHINTS_TEMPORAL 0x0080 +#define BMHINTS_SNR 0x0100 +#define BMHINTS_SPATIAL 0x0200 +#define BMHINTS_SPATIAL_RATIO 0x0400 +#define BMHINTS_FRAME_INTERVAL 0x0800 +#define BMHINTS_LEAKY_BKT_SIZE 0x1000 +#define BMHINTS_BITRATE 0x2000 +#define BMHINTS_ENTROPY 0x4000 +#define BMHINTS_IFRAMEPERIOD 0x8000 + +/* USB related defines */ +#define USB_VIDEO_CONTROL 0x01 +#define USB_VIDEO_CONTROL_INTERFACE 0x24 +#define USB_VIDEO_CONTROL_XU_TYPE 0x06 + +#define MAX_DEVPATH_DEPTH 8 +#define MAX_DEVPATH_STR_SIZE 32 + +#define WINPR_PACK_PUSH +#include + +/* h264 probe commit struct (uvc 1.1) - packed */ +typedef struct +{ + uint32_t dwFrameInterval; + uint32_t dwBitRate; + uint16_t bmHints; + uint16_t wConfigurationIndex; + uint16_t wWidth; + uint16_t wHeight; + uint16_t wSliceUnits; + uint16_t wSliceMode; + uint16_t wProfile; + uint16_t wIFramePeriod; + uint16_t wEstimatedVideoDelay; + uint16_t wEstimatedMaxConfigDelay; + uint8_t bUsageType; + uint8_t bRateControlMode; + uint8_t bTemporalScaleMode; + uint8_t bSpatialScaleMode; + uint8_t bSNRScaleMode; + uint8_t bStreamMuxOption; + uint8_t bStreamFormat; + uint8_t bEntropyCABAC; + uint8_t bTimestamp; + uint8_t bNumOfReorderFrames; + uint8_t bPreviewFlipped; + uint8_t bView; + uint8_t bReserved1; + uint8_t bReserved2; + uint8_t bStreamID; + uint8_t bSpatialLayerRatio; + uint16_t wLeakyBucketSize; + +} uvcx_video_config_probe_commit_t; + +/* encoder reset struct - packed */ +typedef struct +{ + uint16_t wLayerID; + +} uvcx_encoder_reset; + +/* xu_descriptor struct - packed */ +typedef struct +{ + int8_t bLength; + int8_t bDescriptorType; + int8_t bDescriptorSubType; + int8_t bUnitID; + uint8_t guidExtensionCode[16]; + +} xu_descriptor; + +#define WINPR_PACK_POP +#include + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL uint8_t get_uvc_h624_unit_id(const char* deviceId); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL BOOL set_h264_muxed_format(CamV4lStream* stream, + const CAM_MEDIA_TYPE_DESCRIPTION* mediaType); + +#endif /* UVC_H264_H */ diff --git a/third_party/FreeRDP/channels/rdpecam/common/rdpecam-utils.h b/third_party/FreeRDP/channels/rdpecam/common/rdpecam-utils.h new file mode 100644 index 0000000..f12041e --- /dev/null +++ b/third_party/FreeRDP/channels/rdpecam/common/rdpecam-utils.h @@ -0,0 +1,330 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Capture Virtual Channel Extension + * + * Copyright 2026 Armin Novak + * Copyright 2026 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 + +#if defined(CHANNEL_RDPECAM) +#include +#include +#include + +WINPR_ATTR_FORMAT_ARG(5, 6) +static inline void rdpecam_PrintWarning(wLog* log, const char* file, const char* fkt, size_t line, + WINPR_FORMAT_ARG const char* fmt, ...) +{ + const DWORD level = WLOG_WARN; + va_list ap = WINPR_C_ARRAY_INIT; + + va_start(ap, fmt); + if (WLog_IsLevelActive(log, level)) + WLog_PrintTextMessageVA(log, level, line, file, fkt, fmt, ap); + va_end(ap); +} + +/** @brief check input data is a valid \ref CAM_MSG_ID value + * + * @param id The message id to check + * @param log The logger to use + * @param file The file name the function is called from + * @param fkt The function calling this function + * @param line The line number where the function is called + * + * @return \b true for success, \b false if invalid + * @since version 3.20.1 + */ +#define rdpecam_valid_messageId(id) \ + rdpecam_valid_messageId_((id), WLog_Get(TAG), __FILE__, __func__, __LINE__) +WINPR_ATTR_NODISCARD +static inline bool rdpecam_valid_messageId_(UINT8 id, wLog* log, const char* file, const char* fkt, + size_t line) +{ + switch (id) + { + case CAM_MSG_ID_SuccessResponse: + case CAM_MSG_ID_ErrorResponse: + case CAM_MSG_ID_SelectVersionRequest: + case CAM_MSG_ID_SelectVersionResponse: + case CAM_MSG_ID_DeviceAddedNotification: + case CAM_MSG_ID_DeviceRemovedNotification: + case CAM_MSG_ID_ActivateDeviceRequest: + case CAM_MSG_ID_DeactivateDeviceRequest: + case CAM_MSG_ID_StreamListRequest: + case CAM_MSG_ID_StreamListResponse: + case CAM_MSG_ID_MediaTypeListRequest: + case CAM_MSG_ID_MediaTypeListResponse: + case CAM_MSG_ID_CurrentMediaTypeRequest: + case CAM_MSG_ID_CurrentMediaTypeResponse: + case CAM_MSG_ID_StartStreamsRequest: + case CAM_MSG_ID_StopStreamsRequest: + case CAM_MSG_ID_SampleRequest: + case CAM_MSG_ID_SampleResponse: + case CAM_MSG_ID_SampleErrorResponse: + case CAM_MSG_ID_PropertyListRequest: + case CAM_MSG_ID_PropertyListResponse: + case CAM_MSG_ID_PropertyValueRequest: + case CAM_MSG_ID_PropertyValueResponse: + case CAM_MSG_ID_SetPropertyValueRequest: + return true; + default: + rdpecam_PrintWarning(log, file, fkt, line, "Invalid CAM_MSG_ID %" PRIu8, id); + return false; + } +} + +/** @brief check input data is a valid \ref CAM_ERROR_CODE value + * + * @param code The code to check + * @param log The logger to use + * @param file The file name the function is called from + * @param fkt The function calling this function + * @param line The line number where the function is called + * + * @return \b true for success, \b false if invalid + * @since version 3.20.1 + */ +#define rdpecam_valid_CamErrorCode(code) \ + rdpecam_valid_CamErrorCode_((code), WLog_Get(TAG), __FILE__, __func__, __LINE__) +WINPR_ATTR_NODISCARD +static inline bool rdpecam_valid_CamErrorCode_(UINT32 code, wLog* log, const char* file, + const char* fkt, size_t line) +{ + switch (code) + { + case CAM_ERROR_CODE_UnexpectedError: + case CAM_ERROR_CODE_InvalidMessage: + case CAM_ERROR_CODE_NotInitialized: + case CAM_ERROR_CODE_InvalidRequest: + case CAM_ERROR_CODE_InvalidStreamNumber: + case CAM_ERROR_CODE_InvalidMediaType: + case CAM_ERROR_CODE_OutOfMemory: + case CAM_ERROR_CODE_ItemNotFound: + case CAM_ERROR_CODE_SetNotFound: + case CAM_ERROR_CODE_OperationNotSupported: + return true; + default: + rdpecam_PrintWarning(log, file, fkt, line, "Invalid CAM_ERROR_CODE %" PRIu32, code); + return false; + } +} + +/** @brief check input data is a valid \ref CAM_STREAM_FRAME_SOURCE_TYPES value + * + * @param val The value to check + * @param log The logger to use + * @param file The file name the function is called from + * @param fkt The function calling this function + * @param line The line number where the function is called + * + * @return \b true for success, \b false if invalid + * @since version 3.20.1 + */ +#define rdpecam_valid_CamStreamFrameSourceType(val) \ + rdpecam_valid_CamStreamFrameSourceType_((val), WLog_Get(TAG), __FILE__, __func__, __LINE__) +WINPR_ATTR_NODISCARD +static inline bool rdpecam_valid_CamStreamFrameSourceType_(UINT16 val, wLog* log, const char* file, + const char* fkt, size_t line) +{ + switch (val) + { + case CAM_STREAM_FRAME_SOURCE_TYPE_Color: + case CAM_STREAM_FRAME_SOURCE_TYPE_Infrared: + case CAM_STREAM_FRAME_SOURCE_TYPE_Custom: + return true; + default: + rdpecam_PrintWarning(log, file, fkt, line, + "Invalid CAM_STREAM_FRAME_SOURCE_TYPES %" PRIu16, val); + return false; + } +} + +/** @brief check input data is a valid \ref CAM_STREAM_CATEGORY value + * + * @param val The value to check + * @param log The logger to use + * @param file The file name the function is called from + * @param fkt The function calling this function + * @param line The line number where the function is called + * + * @return \b true for success, \b false if invalid + * @since version 3.20.1 + */ +#define rdpecam_valid_CamStreamCategory(val) \ + rdpecam_valid_CamStreamCategory_((val), WLog_Get(TAG), __FILE__, __func__, __LINE__) +WINPR_ATTR_NODISCARD +static inline bool rdpecam_valid_CamStreamCategory_(UINT8 val, wLog* log, const char* file, + const char* fkt, size_t line) +{ + switch (val) + { + case CAM_STREAM_CATEGORY_Capture: + return true; + default: + rdpecam_PrintWarning(log, file, fkt, line, "Invalid CAM_STREAM_CATEGORY %" PRIu8, val); + return false; + } +} + +/** @brief check input data is a valid \ref CAM_MEDIA_FORMAT value + * + * @param val The value to check + * @param log The logger to use + * @param file The file name the function is called from + * @param fkt The function calling this function + * @param line The line number where the function is called + * + * @return \b true for success, \b false if invalid + * @since version 3.20.1 + */ +#define rdpecam_valid_CamMediaFormat(val) \ + rdpecam_valid_CamMediaFormat_((val), WLog_Get(TAG), __FILE__, __func__, __LINE__) +WINPR_ATTR_NODISCARD +static inline bool rdpecam_valid_CamMediaFormat_(UINT8 val, wLog* log, const char* file, + const char* fkt, size_t line) +{ + switch (val) + { + case CAM_MEDIA_FORMAT_INVALID: + case CAM_MEDIA_FORMAT_H264: + case CAM_MEDIA_FORMAT_MJPG: + case CAM_MEDIA_FORMAT_YUY2: + case CAM_MEDIA_FORMAT_NV12: + case CAM_MEDIA_FORMAT_I420: + case CAM_MEDIA_FORMAT_RGB24: + case CAM_MEDIA_FORMAT_RGB32: + return true; + default: + rdpecam_PrintWarning(log, file, fkt, line, "Invalid CAM_MEDIA_FORMAT %" PRIu8, val); + return false; + } +} + +/** @brief check input data is a valid \ref CAM_MEDIA_TYPE_DESCRIPTION_FLAGS value + * + * @param val The value to check + * @param log The logger to use + * @param file The file name the function is called from + * @param fkt The function calling this function + * @param line The line number where the function is called + * + * @return \b true for success, \b false if invalid + * @since version 3.20.1 + */ +#define rdpecam_valid_MediaTypeDescriptionFlags(val) \ + rdpecam_valid_MediaTypeDescriptionFlags_((val), WLog_Get(TAG), __FILE__, __func__, __LINE__) +WINPR_ATTR_NODISCARD +static inline bool rdpecam_valid_MediaTypeDescriptionFlags_(UINT8 val, wLog* log, const char* file, + const char* fkt, size_t line) +{ + switch (val) + { + case CAM_MEDIA_TYPE_DESCRIPTION_FLAG_DecodingRequired: + case CAM_MEDIA_TYPE_DESCRIPTION_FLAG_BottomUpImage: + return true; + default: + rdpecam_PrintWarning(log, file, fkt, line, + "Invalid CAM_MEDIA_TYPE_DESCRIPTION_FLAGS %" PRIu8, val); + return false; + } +} + +/** @brief check input data is a valid \ref CAM_PROPERTY_MODE value + * + * @param val The value to check + * @param log The logger to use + * @param file The file name the function is called from + * @param fkt The function calling this function + * @param line The line number where the function is called + * + * @return \b true for success, \b false if invalid + * @since version 3.20.1 + */ +#define rdpecam_valid_CamPropertyMode(val) \ + rdpecam_valid_CamPropertyMode_((val), WLog_Get(TAG), __FILE__, __func__, __LINE__) +static inline bool rdpecam_valid_CamPropertyMode_(UINT8 val, wLog* log, const char* file, + const char* fkt, size_t line) +{ + switch (val) + { + case CAM_PROPERTY_MODE_Manual: + case CAM_PROPERTY_MODE_Auto: + return true; + default: + rdpecam_PrintWarning(log, file, fkt, line, "Invalid CAM_PROPERTY_MODE %" PRIu8, val); + return false; + } +} + +/** @brief check input data is a valid \ref CAM_PROPERTY_SET value + * + * @param val The value to check + * @param log The logger to use + * @param file The file name the function is called from + * @param fkt The function calling this function + * @param line The line number where the function is called + * + * @return \b true for success, \b false if invalid + * @since version 3.20.1 + */ +#define rdpecam_valid_CamPropertySet(val) \ + rdpecam_valid_CamPropertySet_((val), WLog_Get(TAG), __FILE__, __func__, __LINE__) +WINPR_ATTR_NODISCARD +static inline bool rdpecam_valid_CamPropertySet_(UINT8 val, wLog* log, const char* file, + const char* fkt, size_t line) +{ + switch (val) + { + case CAM_PROPERTY_SET_CameraControl: + case CAM_PROPERTY_SET_VideoProcAmp: + return true; + default: + rdpecam_PrintWarning(log, file, fkt, line, "Invalid CAM_PROPERTY_SET %" PRIu8, val); + return false; + } +} + +/** @brief check input data is a valid \ref CAM_PROPERTY_CAPABILITIES value + * + * @param val The value to check + * @param log The logger to use + * @param file The file name the function is called from + * @param fkt The function calling this function + * @param line The line number where the function is called + * + * @return \b true for success, \b false if invalid + * @since version 3.20.1 + */ +#define rdpecam_valid_CamPropertyCapabilities(val) \ + rdpecam_valid_CamPropertyCapabilities_((val), WLog_Get(TAG), __FILE__, __func__, __LINE__) +WINPR_ATTR_NODISCARD +static inline bool rdpecam_valid_CamPropertyCapabilities_(UINT32 val, wLog* log, const char* file, + const char* fkt, size_t line) +{ + if ((val & ~((UINT32)CAM_PROPERTY_CAPABILITY_Manual | CAM_PROPERTY_CAPABILITY_Auto)) != 0) + { + rdpecam_PrintWarning(log, file, fkt, line, "Invalid CAM_PROPERTY_CAPABILITIES %" PRIu8, + val); + return false; + } + return true; +} + +#endif diff --git a/third_party/FreeRDP/channels/rdpecam/server/CMakeLists.txt b/third_party/FreeRDP/channels/rdpecam/server/CMakeLists.txt new file mode 100644 index 0000000..bd84337 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpecam/server/CMakeLists.txt @@ -0,0 +1,24 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2022 Pascal Nowack +# +# 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. + +define_channel_server("rdpecam") + +set(${MODULE_PREFIX}_SRCS camera_device_enumerator_main.c camera_device_main.c) + +set(${MODULE_PREFIX}_LIBS freerdp) + +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry") diff --git a/third_party/FreeRDP/channels/rdpecam/server/camera_device_enumerator_main.c b/third_party/FreeRDP/channels/rdpecam/server/camera_device_enumerator_main.c new file mode 100644 index 0000000..da005a1 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpecam/server/camera_device_enumerator_main.c @@ -0,0 +1,627 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Capture Virtual Channel Extension + * + * Copyright 2022 Pascal Nowack + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include +#include +#include + +#include "rdpecam-utils.h" + +#define TAG CHANNELS_TAG("rdpecam-enumerator.server") + +typedef enum +{ + ENUMERATOR_INITIAL, + ENUMERATOR_OPENED, +} eEnumeratorChannelState; + +typedef struct +{ + CamDevEnumServerContext context; + + HANDLE stopEvent; + + HANDLE thread; + void* enumerator_channel; + + DWORD SessionId; + + BOOL isOpened; + BOOL externalThread; + + /* Channel state */ + eEnumeratorChannelState state; + + wStream* buffer; +} enumerator_server; + +static UINT enumerator_server_initialize(CamDevEnumServerContext* context, BOOL externalThread) +{ + UINT error = CHANNEL_RC_OK; + enumerator_server* enumerator = (enumerator_server*)context; + + WINPR_ASSERT(enumerator); + + if (enumerator->isOpened) + { + WLog_WARN(TAG, "Application error: Camera Device Enumerator channel already initialized, " + "calling in this state is not possible!"); + return ERROR_INVALID_STATE; + } + + enumerator->externalThread = externalThread; + + return error; +} + +static UINT enumerator_server_open_channel(enumerator_server* enumerator) +{ + CamDevEnumServerContext* context = &enumerator->context; + DWORD Error = ERROR_SUCCESS; + HANDLE hEvent = nullptr; + DWORD BytesReturned = 0; + PULONG pSessionId = nullptr; + UINT32 channelId = 0; + BOOL status = TRUE; + + WINPR_ASSERT(enumerator); + + if (WTSQuerySessionInformationA(enumerator->context.vcm, WTS_CURRENT_SESSION, WTSSessionId, + (LPSTR*)&pSessionId, &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSQuerySessionInformationA failed!"); + return ERROR_INTERNAL_ERROR; + } + + enumerator->SessionId = (DWORD)*pSessionId; + WTSFreeMemory(pSessionId); + hEvent = WTSVirtualChannelManagerGetEventHandle(enumerator->context.vcm); + + if (WaitForSingleObject(hEvent, 1000) == WAIT_FAILED) + { + Error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", Error); + return Error; + } + + enumerator->enumerator_channel = WTSVirtualChannelOpenEx( + enumerator->SessionId, RDPECAM_CONTROL_DVC_CHANNEL_NAME, WTS_CHANNEL_OPTION_DYNAMIC); + if (!enumerator->enumerator_channel) + { + Error = GetLastError(); + WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed with error %" PRIu32 "!", Error); + return Error; + } + + channelId = WTSChannelGetIdByHandle(enumerator->enumerator_channel); + + IFCALLRET(context->ChannelIdAssigned, status, context, channelId); + if (!status) + { + WLog_ERR(TAG, "context->ChannelIdAssigned failed!"); + return ERROR_INTERNAL_ERROR; + } + + return Error; +} + +static UINT enumerator_server_handle_select_version_request(CamDevEnumServerContext* context, + WINPR_ATTR_UNUSED wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_SELECT_VERSION_REQUEST pdu = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + IFCALLRET(context->SelectVersionRequest, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->SelectVersionRequest failed with error %" PRIu32 "", error); + + return error; +} + +static UINT enumerator_server_recv_device_added_notification(CamDevEnumServerContext* context, + wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_DEVICE_ADDED_NOTIFICATION pdu; + UINT error = CHANNEL_RC_OK; + size_t remaining_length = 0; + WCHAR* channel_name_start = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + /* + * RequiredLength 4: + * + * Nullterminator DeviceName (2), + * VirtualChannelName (>= 1), + * Nullterminator VirtualChannelName (1) + */ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_NO_DATA; + + pdu.DeviceName = Stream_Pointer(s); + + remaining_length = Stream_GetRemainingLength(s); + channel_name_start = Stream_Pointer(s); + + /* Search for null terminator of DeviceName */ + size_t i = 0; + for (; i < remaining_length; i += sizeof(WCHAR), ++channel_name_start) + { + if (*channel_name_start == L'\0') + break; + } + + if (*channel_name_start != L'\0') + { + WLog_ERR(TAG, "enumerator_server_recv_device_added_notification: Invalid DeviceName!"); + return ERROR_INVALID_DATA; + } + + pdu.VirtualChannelName = (char*)++channel_name_start; + ++i; + + if (i >= remaining_length || *pdu.VirtualChannelName == '\0') + { + WLog_ERR(TAG, + "enumerator_server_recv_device_added_notification: Invalid VirtualChannelName!"); + return ERROR_INVALID_DATA; + } + + char* tmp = pdu.VirtualChannelName; + for (; i < remaining_length; ++i, ++tmp) + { + if (*tmp == '\0') + break; + } + + if (*tmp != '\0') + { + WLog_ERR(TAG, + "enumerator_server_recv_device_added_notification: Invalid VirtualChannelName!"); + return ERROR_INVALID_DATA; + } + + IFCALLRET(context->DeviceAddedNotification, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->DeviceAddedNotification failed with error %" PRIu32 "", error); + + return error; +} + +static UINT enumerator_server_recv_device_removed_notification(CamDevEnumServerContext* context, + wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_DEVICE_REMOVED_NOTIFICATION pdu; + UINT error = CHANNEL_RC_OK; + size_t remaining_length = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 2)) + return ERROR_NO_DATA; + + pdu.VirtualChannelName = Stream_Pointer(s); + + remaining_length = Stream_GetRemainingLength(s); + char* tmp = pdu.VirtualChannelName + 1; + + for (size_t i = 1; i < remaining_length; ++i, ++tmp) + { + if (*tmp == '\0') + break; + } + + if (*tmp != '\0') + { + WLog_ERR(TAG, + "enumerator_server_recv_device_removed_notification: Invalid VirtualChannelName!"); + return ERROR_INVALID_DATA; + } + + IFCALLRET(context->DeviceRemovedNotification, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->DeviceRemovedNotification failed with error %" PRIu32 "", error); + + return error; +} + +static UINT enumerator_process_message(enumerator_server* enumerator) +{ + BOOL rc = 0; + UINT error = ERROR_INTERNAL_ERROR; + ULONG BytesReturned = 0; + CAM_SHARED_MSG_HEADER header = WINPR_C_ARRAY_INIT; + wStream* s = nullptr; + + WINPR_ASSERT(enumerator); + WINPR_ASSERT(enumerator->enumerator_channel); + + s = enumerator->buffer; + WINPR_ASSERT(s); + + Stream_ResetPosition(s); + rc = WTSVirtualChannelRead(enumerator->enumerator_channel, 0, nullptr, 0, &BytesReturned); + if (!rc) + goto out; + + if (BytesReturned < 1) + { + error = CHANNEL_RC_OK; + goto out; + } + + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + if (WTSVirtualChannelRead(enumerator->enumerator_channel, 0, Stream_BufferAs(s, char), + (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + goto out; + } + + Stream_SetLength(s, BytesReturned); + if (!Stream_CheckAndLogRequiredLength(TAG, s, CAM_HEADER_SIZE)) + return ERROR_NO_DATA; + + Stream_Read_UINT8(s, header.Version); + { + const UINT8 id = Stream_Get_UINT8(s); + if (!rdpecam_valid_messageId(id)) + return ERROR_INVALID_DATA; + header.MessageId = (CAM_MSG_ID)id; + } + + switch (header.MessageId) + { + case CAM_MSG_ID_SelectVersionRequest: + error = + enumerator_server_handle_select_version_request(&enumerator->context, s, &header); + break; + case CAM_MSG_ID_DeviceAddedNotification: + error = + enumerator_server_recv_device_added_notification(&enumerator->context, s, &header); + break; + case CAM_MSG_ID_DeviceRemovedNotification: + error = enumerator_server_recv_device_removed_notification(&enumerator->context, s, + &header); + break; + default: + WLog_ERR(TAG, "enumerator_process_message: unknown or invalid MessageId %" PRIu8 "", + header.MessageId); + break; + } + +out: + if (error) + WLog_ERR(TAG, "Response failed with error %" PRIu32 "!", error); + + return error; +} + +static UINT enumerator_server_context_poll_int(CamDevEnumServerContext* context) +{ + enumerator_server* enumerator = (enumerator_server*)context; + UINT error = ERROR_INTERNAL_ERROR; + + WINPR_ASSERT(enumerator); + + switch (enumerator->state) + { + case ENUMERATOR_INITIAL: + error = enumerator_server_open_channel(enumerator); + if (error) + WLog_ERR(TAG, "enumerator_server_open_channel failed with error %" PRIu32 "!", + error); + else + enumerator->state = ENUMERATOR_OPENED; + break; + case ENUMERATOR_OPENED: + error = enumerator_process_message(enumerator); + break; + default: + break; + } + + return error; +} + +static HANDLE enumerator_server_get_channel_handle(enumerator_server* enumerator) +{ + void* buffer = nullptr; + DWORD BytesReturned = 0; + HANDLE ChannelEvent = nullptr; + + WINPR_ASSERT(enumerator); + + if (WTSVirtualChannelQuery(enumerator->enumerator_channel, WTSVirtualEventHandle, &buffer, + &BytesReturned) == TRUE) + { + if (BytesReturned == sizeof(HANDLE)) + ChannelEvent = *(HANDLE*)buffer; + + WTSFreeMemory(buffer); + } + + return ChannelEvent; +} + +static DWORD WINAPI enumerator_server_thread_func(LPVOID arg) +{ + DWORD nCount = 0; + HANDLE events[2] = WINPR_C_ARRAY_INIT; + enumerator_server* enumerator = (enumerator_server*)arg; + UINT error = CHANNEL_RC_OK; + DWORD status = 0; + + WINPR_ASSERT(enumerator); + + nCount = 0; + events[nCount++] = enumerator->stopEvent; + + while ((error == CHANNEL_RC_OK) && (WaitForSingleObject(events[0], 0) != WAIT_OBJECT_0)) + { + switch (enumerator->state) + { + case ENUMERATOR_INITIAL: + error = enumerator_server_context_poll_int(&enumerator->context); + if (error == CHANNEL_RC_OK) + { + events[1] = enumerator_server_get_channel_handle(enumerator); + nCount = 2; + } + break; + case ENUMERATOR_OPENED: + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + switch (status) + { + case WAIT_OBJECT_0: + break; + case WAIT_OBJECT_0 + 1: + case WAIT_TIMEOUT: + error = enumerator_server_context_poll_int(&enumerator->context); + break; + + case WAIT_FAILED: + default: + error = ERROR_INTERNAL_ERROR; + break; + } + break; + default: + break; + } + } + + (void)WTSVirtualChannelClose(enumerator->enumerator_channel); + enumerator->enumerator_channel = nullptr; + + if (error && enumerator->context.rdpcontext) + setChannelError(enumerator->context.rdpcontext, error, + "enumerator_server_thread_func reported an error"); + + ExitThread(error); + return error; +} + +static UINT enumerator_server_open(CamDevEnumServerContext* context) +{ + enumerator_server* enumerator = (enumerator_server*)context; + + WINPR_ASSERT(enumerator); + + if (!enumerator->externalThread && (enumerator->thread == nullptr)) + { + enumerator->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + if (!enumerator->stopEvent) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + enumerator->thread = + CreateThread(nullptr, 0, enumerator_server_thread_func, enumerator, 0, nullptr); + if (!enumerator->thread) + { + WLog_ERR(TAG, "CreateThread failed!"); + (void)CloseHandle(enumerator->stopEvent); + enumerator->stopEvent = nullptr; + return ERROR_INTERNAL_ERROR; + } + } + enumerator->isOpened = TRUE; + + return CHANNEL_RC_OK; +} + +static UINT enumerator_server_close(CamDevEnumServerContext* context) +{ + UINT error = CHANNEL_RC_OK; + enumerator_server* enumerator = (enumerator_server*)context; + + WINPR_ASSERT(enumerator); + + if (!enumerator->externalThread && enumerator->thread) + { + (void)SetEvent(enumerator->stopEvent); + + if (WaitForSingleObject(enumerator->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + return error; + } + + (void)CloseHandle(enumerator->thread); + (void)CloseHandle(enumerator->stopEvent); + enumerator->thread = nullptr; + enumerator->stopEvent = nullptr; + } + if (enumerator->externalThread) + { + if (enumerator->state != ENUMERATOR_INITIAL) + { + (void)WTSVirtualChannelClose(enumerator->enumerator_channel); + enumerator->enumerator_channel = nullptr; + enumerator->state = ENUMERATOR_INITIAL; + } + } + enumerator->isOpened = FALSE; + + return error; +} + +static UINT enumerator_server_context_poll(CamDevEnumServerContext* context) +{ + enumerator_server* enumerator = (enumerator_server*)context; + + WINPR_ASSERT(enumerator); + + if (!enumerator->externalThread) + return ERROR_INTERNAL_ERROR; + + return enumerator_server_context_poll_int(context); +} + +static BOOL enumerator_server_context_handle(CamDevEnumServerContext* context, HANDLE* handle) +{ + enumerator_server* enumerator = (enumerator_server*)context; + + WINPR_ASSERT(enumerator); + WINPR_ASSERT(handle); + + if (!enumerator->externalThread) + return FALSE; + if (enumerator->state == ENUMERATOR_INITIAL) + return FALSE; + + *handle = enumerator_server_get_channel_handle(enumerator); + + return TRUE; +} + +static UINT enumerator_server_packet_send(CamDevEnumServerContext* context, wStream* s) +{ + enumerator_server* enumerator = (enumerator_server*)context; + UINT error = CHANNEL_RC_OK; + ULONG written = 0; + + const size_t len = Stream_GetPosition(s); + WINPR_ASSERT(len <= UINT32_MAX); + if (!WTSVirtualChannelWrite(enumerator->enumerator_channel, Stream_BufferAs(s, char), + (UINT32)len, &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + error = ERROR_INTERNAL_ERROR; + goto out; + } + + if (written < Stream_GetPosition(s)) + { + WLog_WARN(TAG, "Unexpected bytes written: %" PRIu32 "/%" PRIuz "", written, + Stream_GetPosition(s)); + } + +out: + Stream_Free(s, TRUE); + return error; +} + +static UINT enumerator_send_select_version_response_pdu( + CamDevEnumServerContext* context, const CAM_SELECT_VERSION_RESPONSE* selectVersionResponse) +{ + wStream* s = nullptr; + + s = Stream_New(nullptr, CAM_HEADER_SIZE); + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return ERROR_NOT_ENOUGH_MEMORY; + } + + Stream_Write_UINT8(s, selectVersionResponse->Header.Version); + Stream_Write_UINT8(s, + WINPR_ASSERTING_INT_CAST(uint8_t, selectVersionResponse->Header.MessageId)); + + return enumerator_server_packet_send(context, s); +} + +CamDevEnumServerContext* cam_dev_enum_server_context_new(HANDLE vcm) +{ + enumerator_server* enumerator = (enumerator_server*)calloc(1, sizeof(enumerator_server)); + + if (!enumerator) + return nullptr; + + enumerator->context.vcm = vcm; + enumerator->context.Initialize = enumerator_server_initialize; + enumerator->context.Open = enumerator_server_open; + enumerator->context.Close = enumerator_server_close; + enumerator->context.Poll = enumerator_server_context_poll; + enumerator->context.ChannelHandle = enumerator_server_context_handle; + + enumerator->context.SelectVersionResponse = enumerator_send_select_version_response_pdu; + + enumerator->buffer = Stream_New(nullptr, 4096); + if (!enumerator->buffer) + goto fail; + + return &enumerator->context; +fail: + WINPR_PRAGMA_DIAG_PUSH + WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC + cam_dev_enum_server_context_free(&enumerator->context); + WINPR_PRAGMA_DIAG_POP + return nullptr; +} + +void cam_dev_enum_server_context_free(CamDevEnumServerContext* context) +{ + enumerator_server* enumerator = (enumerator_server*)context; + + if (enumerator) + { + enumerator_server_close(context); + Stream_Free(enumerator->buffer, TRUE); + } + + free(enumerator); +} diff --git a/third_party/FreeRDP/channels/rdpecam/server/camera_device_main.c b/third_party/FreeRDP/channels/rdpecam/server/camera_device_main.c new file mode 100644 index 0000000..d211286 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpecam/server/camera_device_main.c @@ -0,0 +1,1051 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Capture Virtual Channel Extension + * + * Copyright 2022 Pascal Nowack + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include +#include +#include + +#include "rdpecam-utils.h" + +#define TAG CHANNELS_TAG("rdpecam.server") + +typedef enum +{ + CAMERA_DEVICE_INITIAL, + CAMERA_DEVICE_OPENED, +} eCameraDeviceChannelState; + +typedef struct +{ + CameraDeviceServerContext context; + + HANDLE stopEvent; + + HANDLE thread; + void* device_channel; + + DWORD SessionId; + + BOOL isOpened; + BOOL externalThread; + + /* Channel state */ + eCameraDeviceChannelState state; + + wStream* buffer; +} device_server; + +static UINT device_server_initialize(CameraDeviceServerContext* context, BOOL externalThread) +{ + UINT error = CHANNEL_RC_OK; + device_server* device = (device_server*)context; + + WINPR_ASSERT(device); + + if (device->isOpened) + { + WLog_WARN(TAG, "Application error: Camera channel already initialized, " + "calling in this state is not possible!"); + return ERROR_INVALID_STATE; + } + + device->externalThread = externalThread; + + return error; +} + +static UINT device_server_open_channel(device_server* device) +{ + CameraDeviceServerContext* context = &device->context; + DWORD Error = ERROR_SUCCESS; + HANDLE hEvent = nullptr; + DWORD BytesReturned = 0; + PULONG pSessionId = nullptr; + UINT32 channelId = 0; + BOOL status = TRUE; + + WINPR_ASSERT(device); + + if (WTSQuerySessionInformationA(device->context.vcm, WTS_CURRENT_SESSION, WTSSessionId, + (LPSTR*)&pSessionId, &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSQuerySessionInformationA failed!"); + return ERROR_INTERNAL_ERROR; + } + + device->SessionId = (DWORD)*pSessionId; + WTSFreeMemory(pSessionId); + hEvent = WTSVirtualChannelManagerGetEventHandle(device->context.vcm); + + if (WaitForSingleObject(hEvent, 1000) == WAIT_FAILED) + { + Error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", Error); + return Error; + } + + device->device_channel = WTSVirtualChannelOpenEx(device->SessionId, context->virtualChannelName, + WTS_CHANNEL_OPTION_DYNAMIC); + if (!device->device_channel) + { + Error = GetLastError(); + WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed with error %" PRIu32 "!", Error); + return Error; + } + + channelId = WTSChannelGetIdByHandle(device->device_channel); + + IFCALLRET(context->ChannelIdAssigned, status, context, channelId); + if (!status) + { + WLog_ERR(TAG, "context->ChannelIdAssigned failed!"); + return ERROR_INTERNAL_ERROR; + } + + return Error; +} + +static UINT device_server_handle_success_response(CameraDeviceServerContext* context, + WINPR_ATTR_UNUSED wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_SUCCESS_RESPONSE pdu = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + IFCALLRET(context->SuccessResponse, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->SuccessResponse failed with error %" PRIu32 "", error); + + return error; +} + +static UINT device_server_recv_error_response(CameraDeviceServerContext* context, wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_ERROR_RESPONSE pdu = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_NO_DATA; + + { + const UINT32 val = Stream_Get_UINT32(s); + if (!rdpecam_valid_CamErrorCode(val)) + return ERROR_INVALID_DATA; + pdu.ErrorCode = (CAM_ERROR_CODE)val; + } + + IFCALLRET(context->ErrorResponse, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->ErrorResponse failed with error %" PRIu32 "", error); + + return error; +} + +static UINT device_server_recv_stream_list_response(CameraDeviceServerContext* context, wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_STREAM_LIST_RESPONSE pdu = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 5)) + return ERROR_NO_DATA; + + pdu.N_Descriptions = 255; + const size_t len = Stream_GetRemainingLength(s) / 5; + if (len < 255) + pdu.N_Descriptions = (BYTE)len; + + for (BYTE i = 0; i < pdu.N_Descriptions; ++i) + { + CAM_STREAM_DESCRIPTION* StreamDescription = &pdu.StreamDescriptions[i]; + + { + const UINT16 val = Stream_Get_UINT16(s); + if (!rdpecam_valid_CamStreamFrameSourceType(val)) + return ERROR_INVALID_DATA; + StreamDescription->FrameSourceTypes = (CAM_STREAM_FRAME_SOURCE_TYPES)val; + } + { + const UINT8 val = Stream_Get_UINT8(s); + if (!rdpecam_valid_CamStreamCategory(val)) + return ERROR_INVALID_DATA; + StreamDescription->StreamCategory = (CAM_STREAM_CATEGORY)val; + } + Stream_Read_UINT8(s, StreamDescription->Selected); + Stream_Read_UINT8(s, StreamDescription->CanBeShared); + } + + IFCALLRET(context->StreamListResponse, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->StreamListResponse failed with error %" PRIu32 "", error); + + return error; +} + +static UINT device_server_recv_media_type_list_response(CameraDeviceServerContext* context, + wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_MEDIA_TYPE_LIST_RESPONSE pdu = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 26)) + return ERROR_NO_DATA; + + pdu.N_Descriptions = Stream_GetRemainingLength(s) / 26; + + pdu.MediaTypeDescriptions = calloc(pdu.N_Descriptions, sizeof(CAM_MEDIA_TYPE_DESCRIPTION)); + if (!pdu.MediaTypeDescriptions) + { + WLog_ERR(TAG, "Failed to allocate %zu CAM_MEDIA_TYPE_DESCRIPTION structs", + pdu.N_Descriptions); + return ERROR_NOT_ENOUGH_MEMORY; + } + + for (size_t i = 0; i < pdu.N_Descriptions; ++i) + { + CAM_MEDIA_TYPE_DESCRIPTION* MediaTypeDescriptions = &pdu.MediaTypeDescriptions[i]; + + { + const UINT8 val = Stream_Get_UINT8(s); + if (!rdpecam_valid_CamMediaFormat(val)) + { + free(pdu.MediaTypeDescriptions); + return ERROR_INVALID_DATA; + } + MediaTypeDescriptions->Format = (CAM_MEDIA_FORMAT)val; + } + Stream_Read_UINT32(s, MediaTypeDescriptions->Width); + Stream_Read_UINT32(s, MediaTypeDescriptions->Height); + Stream_Read_UINT32(s, MediaTypeDescriptions->FrameRateNumerator); + Stream_Read_UINT32(s, MediaTypeDescriptions->FrameRateDenominator); + Stream_Read_UINT32(s, MediaTypeDescriptions->PixelAspectRatioNumerator); + Stream_Read_UINT32(s, MediaTypeDescriptions->PixelAspectRatioDenominator); + { + const UINT8 val = Stream_Get_UINT8(s); + if (!rdpecam_valid_MediaTypeDescriptionFlags(val)) + { + free(pdu.MediaTypeDescriptions); + return ERROR_INVALID_DATA; + } + MediaTypeDescriptions->Flags = (CAM_MEDIA_TYPE_DESCRIPTION_FLAGS)val; + } + } + + IFCALLRET(context->MediaTypeListResponse, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->MediaTypeListResponse failed with error %" PRIu32 "", error); + + free(pdu.MediaTypeDescriptions); + + return error; +} + +static UINT device_server_recv_current_media_type_response(CameraDeviceServerContext* context, + wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_CURRENT_MEDIA_TYPE_RESPONSE pdu = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 26)) + return ERROR_NO_DATA; + + { + const UINT8 val = Stream_Get_UINT8(s); + if (!rdpecam_valid_CamMediaFormat(val)) + return ERROR_INVALID_DATA; + pdu.MediaTypeDescription.Format = (CAM_MEDIA_FORMAT)val; + } + + Stream_Read_UINT32(s, pdu.MediaTypeDescription.Width); + Stream_Read_UINT32(s, pdu.MediaTypeDescription.Height); + Stream_Read_UINT32(s, pdu.MediaTypeDescription.FrameRateNumerator); + Stream_Read_UINT32(s, pdu.MediaTypeDescription.FrameRateDenominator); + Stream_Read_UINT32(s, pdu.MediaTypeDescription.PixelAspectRatioNumerator); + Stream_Read_UINT32(s, pdu.MediaTypeDescription.PixelAspectRatioDenominator); + { + const UINT8 val = Stream_Get_UINT8(s); + if (!rdpecam_valid_MediaTypeDescriptionFlags(val)) + return ERROR_INVALID_DATA; + pdu.MediaTypeDescription.Flags = (CAM_MEDIA_TYPE_DESCRIPTION_FLAGS)val; + } + + IFCALLRET(context->CurrentMediaTypeResponse, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->CurrentMediaTypeResponse failed with error %" PRIu32 "", error); + + return error; +} + +static UINT device_server_recv_sample_response(CameraDeviceServerContext* context, wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_SAMPLE_RESPONSE pdu = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return ERROR_NO_DATA; + + Stream_Read_UINT8(s, pdu.StreamIndex); + + pdu.SampleSize = Stream_GetRemainingLength(s); + pdu.Sample = Stream_Pointer(s); + + IFCALLRET(context->SampleResponse, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->SampleResponse failed with error %" PRIu32 "", error); + + return error; +} + +static UINT device_server_recv_sample_error_response(CameraDeviceServerContext* context, wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_SAMPLE_ERROR_RESPONSE pdu = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 5)) + return ERROR_NO_DATA; + + Stream_Read_UINT8(s, pdu.StreamIndex); + { + const UINT32 val = Stream_Get_UINT32(s); + if (!rdpecam_valid_CamErrorCode(val)) + return ERROR_INVALID_DATA; + pdu.ErrorCode = (CAM_ERROR_CODE)val; + } + + IFCALLRET(context->SampleErrorResponse, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->SampleErrorResponse failed with error %" PRIu32 "", error); + + return error; +} + +static UINT device_server_recv_property_list_response(CameraDeviceServerContext* context, + wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_PROPERTY_LIST_RESPONSE pdu = WINPR_C_ARRAY_INIT; + UINT error = ERROR_INVALID_DATA; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + pdu.N_Properties = Stream_GetRemainingLength(s) / 19; + + if (pdu.N_Properties > 0) + { + pdu.Properties = calloc(pdu.N_Properties, sizeof(CAM_PROPERTY_DESCRIPTION)); + if (!pdu.Properties) + { + WLog_ERR(TAG, "Failed to allocate %zu CAM_PROPERTY_DESCRIPTION structs", + pdu.N_Properties); + return ERROR_NOT_ENOUGH_MEMORY; + } + + for (size_t i = 0; i < pdu.N_Properties; ++i) + { + CAM_PROPERTY_DESCRIPTION* cur = &pdu.Properties[i]; + { + const UINT8 val = Stream_Get_UINT8(s); + if (!rdpecam_valid_CamPropertySet(val)) + goto fail; + cur->PropertySet = (CAM_PROPERTY_SET)val; + } + cur->PropertyId = Stream_Get_UINT8(s); + cur->Capabilities = Stream_Get_UINT8(s); + if (!rdpecam_valid_CamPropertyCapabilities(cur->Capabilities)) + goto fail; + cur->MinValue = Stream_Get_INT32(s); + cur->MaxValue = Stream_Get_INT32(s); + cur->Step = Stream_Get_INT32(s); + cur->DefaultValue = Stream_Get_INT32(s); + } + } + + error = IFCALLRESULT(CHANNEL_RC_OK, context->PropertyListResponse, context, &pdu); + +fail: + if (error) + WLog_ERR(TAG, "context->PropertyListResponse failed with error %" PRIu32 "", error); + + free(pdu.Properties); + + return error; +} + +static UINT device_server_recv_property_value_response(CameraDeviceServerContext* context, + wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_PROPERTY_VALUE_RESPONSE pdu = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 5)) + return ERROR_NO_DATA; + + { + const UINT8 val = Stream_Get_UINT8(s); + if (!rdpecam_valid_CamPropertyMode(val)) + return ERROR_INVALID_DATA; + pdu.PropertyValue.Mode = (CAM_PROPERTY_MODE)val; + } + + Stream_Read_INT32(s, pdu.PropertyValue.Value); + + IFCALLRET(context->PropertyValueResponse, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->PropertyValueResponse failed with error %" PRIu32 "", error); + + return error; +} + +static UINT device_process_message(device_server* device) +{ + BOOL rc = 0; + UINT error = ERROR_INTERNAL_ERROR; + ULONG BytesReturned = 0; + CAM_SHARED_MSG_HEADER header = WINPR_C_ARRAY_INIT; + wStream* s = nullptr; + + WINPR_ASSERT(device); + WINPR_ASSERT(device->device_channel); + + s = device->buffer; + WINPR_ASSERT(s); + + Stream_ResetPosition(s); + rc = WTSVirtualChannelRead(device->device_channel, 0, nullptr, 0, &BytesReturned); + if (!rc) + goto out; + + if (BytesReturned < 1) + { + error = CHANNEL_RC_OK; + goto out; + } + + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + if (WTSVirtualChannelRead(device->device_channel, 0, Stream_BufferAs(s, char), + (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + goto out; + } + + Stream_SetLength(s, BytesReturned); + if (!Stream_CheckAndLogRequiredLength(TAG, s, CAM_HEADER_SIZE)) + return ERROR_NO_DATA; + + Stream_Read_UINT8(s, header.Version); + { + const UINT8 id = Stream_Get_UINT8(s); + if (!rdpecam_valid_messageId(id)) + return ERROR_INVALID_DATA; + header.MessageId = (CAM_MSG_ID)id; + } + + switch (header.MessageId) + { + case CAM_MSG_ID_SuccessResponse: + error = device_server_handle_success_response(&device->context, s, &header); + break; + case CAM_MSG_ID_ErrorResponse: + error = device_server_recv_error_response(&device->context, s, &header); + break; + case CAM_MSG_ID_StreamListResponse: + error = device_server_recv_stream_list_response(&device->context, s, &header); + break; + case CAM_MSG_ID_MediaTypeListResponse: + error = device_server_recv_media_type_list_response(&device->context, s, &header); + break; + case CAM_MSG_ID_CurrentMediaTypeResponse: + error = device_server_recv_current_media_type_response(&device->context, s, &header); + break; + case CAM_MSG_ID_SampleResponse: + error = device_server_recv_sample_response(&device->context, s, &header); + break; + case CAM_MSG_ID_SampleErrorResponse: + error = device_server_recv_sample_error_response(&device->context, s, &header); + break; + case CAM_MSG_ID_PropertyListResponse: + error = device_server_recv_property_list_response(&device->context, s, &header); + break; + case CAM_MSG_ID_PropertyValueResponse: + error = device_server_recv_property_value_response(&device->context, s, &header); + break; + default: + WLog_ERR(TAG, "device_process_message: unknown or invalid MessageId %" PRIu8 "", + header.MessageId); + break; + } + +out: + if (error) + WLog_ERR(TAG, "Response failed with error %" PRIu32 "!", error); + + return error; +} + +static UINT device_server_context_poll_int(CameraDeviceServerContext* context) +{ + device_server* device = (device_server*)context; + UINT error = ERROR_INTERNAL_ERROR; + + WINPR_ASSERT(device); + + switch (device->state) + { + case CAMERA_DEVICE_INITIAL: + error = device_server_open_channel(device); + if (error) + WLog_ERR(TAG, "device_server_open_channel failed with error %" PRIu32 "!", error); + else + device->state = CAMERA_DEVICE_OPENED; + break; + case CAMERA_DEVICE_OPENED: + error = device_process_message(device); + break; + default: + break; + } + + return error; +} + +static HANDLE device_server_get_channel_handle(device_server* device) +{ + void* buffer = nullptr; + DWORD BytesReturned = 0; + HANDLE ChannelEvent = nullptr; + + WINPR_ASSERT(device); + + if (WTSVirtualChannelQuery(device->device_channel, WTSVirtualEventHandle, &buffer, + &BytesReturned) == TRUE) + { + if (BytesReturned == sizeof(HANDLE)) + ChannelEvent = *(HANDLE*)buffer; + + WTSFreeMemory(buffer); + } + + return ChannelEvent; +} + +static DWORD WINAPI device_server_thread_func(LPVOID arg) +{ + DWORD nCount = 0; + HANDLE events[2] = WINPR_C_ARRAY_INIT; + device_server* device = (device_server*)arg; + UINT error = CHANNEL_RC_OK; + DWORD status = 0; + + WINPR_ASSERT(device); + + nCount = 0; + events[nCount++] = device->stopEvent; + + while ((error == CHANNEL_RC_OK) && (WaitForSingleObject(events[0], 0) != WAIT_OBJECT_0)) + { + switch (device->state) + { + case CAMERA_DEVICE_INITIAL: + error = device_server_context_poll_int(&device->context); + if (error == CHANNEL_RC_OK) + { + events[1] = device_server_get_channel_handle(device); + nCount = 2; + } + break; + case CAMERA_DEVICE_OPENED: + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + switch (status) + { + case WAIT_OBJECT_0: + break; + case WAIT_OBJECT_0 + 1: + case WAIT_TIMEOUT: + error = device_server_context_poll_int(&device->context); + break; + + case WAIT_FAILED: + default: + error = ERROR_INTERNAL_ERROR; + break; + } + break; + default: + break; + } + } + + (void)WTSVirtualChannelClose(device->device_channel); + device->device_channel = nullptr; + + if (error && device->context.rdpcontext) + setChannelError(device->context.rdpcontext, error, + "device_server_thread_func reported an error"); + + ExitThread(error); + return error; +} + +static UINT device_server_open(CameraDeviceServerContext* context) +{ + device_server* device = (device_server*)context; + + WINPR_ASSERT(device); + + if (!device->externalThread && (device->thread == nullptr)) + { + device->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + if (!device->stopEvent) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + device->thread = CreateThread(nullptr, 0, device_server_thread_func, device, 0, nullptr); + if (!device->thread) + { + WLog_ERR(TAG, "CreateThread failed!"); + (void)CloseHandle(device->stopEvent); + device->stopEvent = nullptr; + return ERROR_INTERNAL_ERROR; + } + } + device->isOpened = TRUE; + + return CHANNEL_RC_OK; +} + +static UINT device_server_close(CameraDeviceServerContext* context) +{ + UINT error = CHANNEL_RC_OK; + device_server* device = (device_server*)context; + + WINPR_ASSERT(device); + + if (!device->externalThread && device->thread) + { + (void)SetEvent(device->stopEvent); + + if (WaitForSingleObject(device->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + return error; + } + + (void)CloseHandle(device->thread); + (void)CloseHandle(device->stopEvent); + device->thread = nullptr; + device->stopEvent = nullptr; + } + if (device->externalThread) + { + if (device->state != CAMERA_DEVICE_INITIAL) + { + (void)WTSVirtualChannelClose(device->device_channel); + device->device_channel = nullptr; + device->state = CAMERA_DEVICE_INITIAL; + } + } + device->isOpened = FALSE; + + return error; +} + +static UINT device_server_context_poll(CameraDeviceServerContext* context) +{ + device_server* device = (device_server*)context; + + WINPR_ASSERT(device); + + if (!device->externalThread) + return ERROR_INTERNAL_ERROR; + + return device_server_context_poll_int(context); +} + +static BOOL device_server_context_handle(CameraDeviceServerContext* context, HANDLE* handle) +{ + device_server* device = (device_server*)context; + + WINPR_ASSERT(device); + WINPR_ASSERT(handle); + + if (!device->externalThread) + return FALSE; + if (device->state == CAMERA_DEVICE_INITIAL) + return FALSE; + + *handle = device_server_get_channel_handle(device); + + return TRUE; +} + +static wStream* device_server_packet_new(size_t size, BYTE version, BYTE messageId) +{ + wStream* s = nullptr; + + /* Allocate what we need plus header bytes */ + s = Stream_New(nullptr, size + CAM_HEADER_SIZE); + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return nullptr; + } + + Stream_Write_UINT8(s, version); + Stream_Write_UINT8(s, messageId); + + return s; +} + +static UINT device_server_packet_send(CameraDeviceServerContext* context, wStream* s) +{ + device_server* device = (device_server*)context; + UINT error = CHANNEL_RC_OK; + ULONG written = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(s); + + const size_t len = Stream_GetPosition(s); + WINPR_ASSERT(len <= UINT32_MAX); + if (!WTSVirtualChannelWrite(device->device_channel, Stream_BufferAs(s, char), (UINT32)len, + &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + error = ERROR_INTERNAL_ERROR; + goto out; + } + + if (written < Stream_GetPosition(s)) + { + WLog_WARN(TAG, "Unexpected bytes written: %" PRIu32 "/%" PRIuz "", written, + Stream_GetPosition(s)); + } + +out: + Stream_Free(s, TRUE); + return error; +} + +static UINT device_server_write_and_send_header(CameraDeviceServerContext* context, BYTE messageId) +{ + wStream* s = nullptr; + + WINPR_ASSERT(context); + + s = device_server_packet_new(0, context->protocolVersion, messageId); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + return device_server_packet_send(context, s); +} + +static UINT device_send_activate_device_request_pdu( + CameraDeviceServerContext* context, + WINPR_ATTR_UNUSED const CAM_ACTIVATE_DEVICE_REQUEST* activateDeviceRequest) +{ + WINPR_ASSERT(context); + + return device_server_write_and_send_header(context, CAM_MSG_ID_ActivateDeviceRequest); +} + +static UINT device_send_deactivate_device_request_pdu( + CameraDeviceServerContext* context, + WINPR_ATTR_UNUSED const CAM_DEACTIVATE_DEVICE_REQUEST* deactivateDeviceRequest) +{ + WINPR_ASSERT(context); + + return device_server_write_and_send_header(context, CAM_MSG_ID_DeactivateDeviceRequest); +} + +static UINT device_send_stream_list_request_pdu( + CameraDeviceServerContext* context, + WINPR_ATTR_UNUSED const CAM_STREAM_LIST_REQUEST* streamListRequest) +{ + WINPR_ASSERT(context); + + return device_server_write_and_send_header(context, CAM_MSG_ID_StreamListRequest); +} + +static UINT +device_send_media_type_list_request_pdu(CameraDeviceServerContext* context, + const CAM_MEDIA_TYPE_LIST_REQUEST* mediaTypeListRequest) +{ + wStream* s = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(mediaTypeListRequest); + + s = device_server_packet_new(1, context->protocolVersion, CAM_MSG_ID_MediaTypeListRequest); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + Stream_Write_UINT8(s, mediaTypeListRequest->StreamIndex); + + return device_server_packet_send(context, s); +} + +static UINT device_send_current_media_type_request_pdu( + CameraDeviceServerContext* context, + const CAM_CURRENT_MEDIA_TYPE_REQUEST* currentMediaTypeRequest) +{ + wStream* s = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(currentMediaTypeRequest); + + s = device_server_packet_new(1, context->protocolVersion, CAM_MSG_ID_CurrentMediaTypeRequest); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + Stream_Write_UINT8(s, currentMediaTypeRequest->StreamIndex); + + return device_server_packet_send(context, s); +} + +static UINT +device_send_start_streams_request_pdu(CameraDeviceServerContext* context, + const CAM_START_STREAMS_REQUEST* startStreamsRequest) +{ + wStream* s = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(startStreamsRequest); + + s = device_server_packet_new(startStreamsRequest->N_Infos * 27ul, context->protocolVersion, + CAM_MSG_ID_StartStreamsRequest); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + for (size_t i = 0; i < startStreamsRequest->N_Infos; ++i) + { + const CAM_START_STREAM_INFO* info = &startStreamsRequest->StartStreamsInfo[i]; + const CAM_MEDIA_TYPE_DESCRIPTION* description = &info->MediaTypeDescription; + + Stream_Write_UINT8(s, info->StreamIndex); + + Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, description->Format)); + Stream_Write_UINT32(s, description->Width); + Stream_Write_UINT32(s, description->Height); + Stream_Write_UINT32(s, description->FrameRateNumerator); + Stream_Write_UINT32(s, description->FrameRateDenominator); + Stream_Write_UINT32(s, description->PixelAspectRatioNumerator); + Stream_Write_UINT32(s, description->PixelAspectRatioDenominator); + Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, description->Flags)); + } + + return device_server_packet_send(context, s); +} + +static UINT device_send_stop_streams_request_pdu( + CameraDeviceServerContext* context, + WINPR_ATTR_UNUSED const CAM_STOP_STREAMS_REQUEST* stopStreamsRequest) +{ + WINPR_ASSERT(context); + + return device_server_write_and_send_header(context, CAM_MSG_ID_StopStreamsRequest); +} + +static UINT device_send_sample_request_pdu(CameraDeviceServerContext* context, + const CAM_SAMPLE_REQUEST* sampleRequest) +{ + wStream* s = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(sampleRequest); + + s = device_server_packet_new(1, context->protocolVersion, CAM_MSG_ID_SampleRequest); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + Stream_Write_UINT8(s, sampleRequest->StreamIndex); + + return device_server_packet_send(context, s); +} + +static UINT device_send_property_list_request_pdu( + CameraDeviceServerContext* context, + WINPR_ATTR_UNUSED const CAM_PROPERTY_LIST_REQUEST* propertyListRequest) +{ + WINPR_ASSERT(context); + + return device_server_write_and_send_header(context, CAM_MSG_ID_PropertyListRequest); +} + +static UINT +device_send_property_value_request_pdu(CameraDeviceServerContext* context, + const CAM_PROPERTY_VALUE_REQUEST* propertyValueRequest) +{ + wStream* s = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(propertyValueRequest); + + s = device_server_packet_new(2, context->protocolVersion, CAM_MSG_ID_PropertyValueRequest); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, propertyValueRequest->PropertySet)); + Stream_Write_UINT8(s, propertyValueRequest->PropertyId); + + return device_server_packet_send(context, s); +} + +static UINT device_send_set_property_value_request_pdu( + CameraDeviceServerContext* context, + const CAM_SET_PROPERTY_VALUE_REQUEST* setPropertyValueRequest) +{ + wStream* s = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(setPropertyValueRequest); + + s = device_server_packet_new(2 + 5, context->protocolVersion, + CAM_MSG_ID_SetPropertyValueRequest); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, setPropertyValueRequest->PropertySet)); + Stream_Write_UINT8(s, setPropertyValueRequest->PropertyId); + + Stream_Write_UINT8( + s, WINPR_ASSERTING_INT_CAST(uint8_t, setPropertyValueRequest->PropertyValue.Mode)); + Stream_Write_INT32(s, setPropertyValueRequest->PropertyValue.Value); + + return device_server_packet_send(context, s); +} + +CameraDeviceServerContext* camera_device_server_context_new(HANDLE vcm) +{ + device_server* device = (device_server*)calloc(1, sizeof(device_server)); + + if (!device) + return nullptr; + + device->context.vcm = vcm; + device->context.Initialize = device_server_initialize; + device->context.Open = device_server_open; + device->context.Close = device_server_close; + device->context.Poll = device_server_context_poll; + device->context.ChannelHandle = device_server_context_handle; + + device->context.ActivateDeviceRequest = device_send_activate_device_request_pdu; + device->context.DeactivateDeviceRequest = device_send_deactivate_device_request_pdu; + + device->context.StreamListRequest = device_send_stream_list_request_pdu; + device->context.MediaTypeListRequest = device_send_media_type_list_request_pdu; + device->context.CurrentMediaTypeRequest = device_send_current_media_type_request_pdu; + + device->context.StartStreamsRequest = device_send_start_streams_request_pdu; + device->context.StopStreamsRequest = device_send_stop_streams_request_pdu; + device->context.SampleRequest = device_send_sample_request_pdu; + + device->context.PropertyListRequest = device_send_property_list_request_pdu; + device->context.PropertyValueRequest = device_send_property_value_request_pdu; + device->context.SetPropertyValueRequest = device_send_set_property_value_request_pdu; + + device->buffer = Stream_New(nullptr, 4096); + if (!device->buffer) + goto fail; + + return &device->context; +fail: + WINPR_PRAGMA_DIAG_PUSH + WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC + camera_device_server_context_free(&device->context); + WINPR_PRAGMA_DIAG_POP + return nullptr; +} + +void camera_device_server_context_free(CameraDeviceServerContext* context) +{ + device_server* device = (device_server*)context; + + if (device) + { + device_server_close(context); + Stream_Free(device->buffer, TRUE); + } + + free(context->virtualChannelName); + + free(device); +} diff --git a/third_party/FreeRDP/channels/rdpei/CMakeLists.txt b/third_party/FreeRDP/channels/rdpei/CMakeLists.txt new file mode 100644 index 0000000..5ddabd7 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpei/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel("rdpei") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/third_party/FreeRDP/channels/rdpei/ChannelOptions.cmake b/third_party/FreeRDP/channels/rdpei/ChannelOptions.cmake new file mode 100644 index 0000000..6f6d568 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpei/ChannelOptions.cmake @@ -0,0 +1,20 @@ +set(OPTION_DEFAULT ON) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options( + NAME + "rdpei" + TYPE + "dynamic" + DESCRIPTION + "Input Virtual Channel Extension" + SPECIFICATIONS + "[MS-RDPEI]" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/rdpei/client/CMakeLists.txt b/third_party/FreeRDP/channels/rdpei/client/CMakeLists.txt new file mode 100644 index 0000000..1b153a7 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpei/client/CMakeLists.txt @@ -0,0 +1,25 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2013 Marc-Andre Moreau +# +# 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. + +define_channel_client("rdpei") + +set(${MODULE_PREFIX}_SRCS rdpei_main.c rdpei_main.h ../rdpei_common.c ../rdpei_common.h) + +set(${MODULE_PREFIX}_LIBS winpr freerdp) +include_directories(..) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") diff --git a/third_party/FreeRDP/channels/rdpei/client/rdpei_main.c b/third_party/FreeRDP/channels/rdpei/client/rdpei_main.c new file mode 100644 index 0000000..10d0059 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpei/client/rdpei_main.c @@ -0,0 +1,1614 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Input Virtual Channel Extension + * + * Copyright 2013 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "rdpei_common.h" + +#include "rdpei_main.h" + +#define RDPEI_TAG CHANNELS_TAG("rdpei.client") + +/** + * Touch Input + * http://msdn.microsoft.com/en-us/library/windows/desktop/dd562197/ + * + * Windows Touch Input + * http://msdn.microsoft.com/en-us/library/windows/desktop/dd317321/ + * + * Input: Touch injection sample + * http://code.msdn.microsoft.com/windowsdesktop/Touch-Injection-Sample-444d9bf7 + * + * Pointer Input Message Reference + * http://msdn.microsoft.com/en-us/library/hh454916/ + * + * POINTER_INFO Structure + * http://msdn.microsoft.com/en-us/library/hh454907/ + * + * POINTER_TOUCH_INFO Structure + * http://msdn.microsoft.com/en-us/library/hh454910/ + */ + +#define MAX_CONTACTS 64 +#define MAX_PEN_CONTACTS 4 + +typedef struct +{ + GENERIC_DYNVC_PLUGIN base; + + RdpeiClientContext* context; + + UINT32 version; + UINT32 features; /* SC_READY_MULTIPEN_INJECTION_SUPPORTED */ + UINT16 maxTouchContacts; + UINT64 currentFrameTime; + UINT64 previousFrameTime; + RDPINPUT_CONTACT_POINT contactPoints[MAX_CONTACTS]; + + UINT64 currentPenFrameTime; + UINT64 previousPenFrameTime; + UINT16 maxPenContacts; + RDPINPUT_PEN_CONTACT_POINT penContactPoints[MAX_PEN_CONTACTS]; + + CRITICAL_SECTION lock; + rdpContext* rdpcontext; + + HANDLE thread; + + HANDLE event; + UINT64 lastPollEventTime; + BOOL running; + BOOL async; +} RDPEI_PLUGIN; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_send_frame(RdpeiClientContext* context, RDPINPUT_TOUCH_FRAME* frame); + +#ifdef WITH_DEBUG_RDPEI +static const char* rdpei_eventid_string(UINT16 event) +{ + switch (event) + { + case EVENTID_SC_READY: + return "EVENTID_SC_READY"; + case EVENTID_CS_READY: + return "EVENTID_CS_READY"; + case EVENTID_TOUCH: + return "EVENTID_TOUCH"; + case EVENTID_SUSPEND_TOUCH: + return "EVENTID_SUSPEND_TOUCH"; + case EVENTID_RESUME_TOUCH: + return "EVENTID_RESUME_TOUCH"; + case EVENTID_DISMISS_HOVERING_CONTACT: + return "EVENTID_DISMISS_HOVERING_CONTACT"; + case EVENTID_PEN: + return "EVENTID_PEN"; + default: + return "EVENTID_UNKNOWN"; + } +} +#endif + +static RDPINPUT_CONTACT_POINT* rdpei_contact(RDPEI_PLUGIN* rdpei, INT32 externalId, BOOL active) +{ + for (UINT16 i = 0; i < rdpei->maxTouchContacts; i++) + { + RDPINPUT_CONTACT_POINT* contactPoint = &rdpei->contactPoints[i]; + + if (!contactPoint->active && active) + continue; + else if (!contactPoint->active && !active) + { + contactPoint->contactId = i; + contactPoint->externalId = externalId; + contactPoint->active = TRUE; + return contactPoint; + } + else if (contactPoint->externalId == externalId) + { + return contactPoint; + } + } + return nullptr; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_add_frame(RdpeiClientContext* context) +{ + RDPINPUT_TOUCH_FRAME frame = WINPR_C_ARRAY_INIT; + RDPINPUT_CONTACT_DATA contacts[MAX_CONTACTS] = WINPR_C_ARRAY_INIT; + + if (!context || !context->handle) + return ERROR_INTERNAL_ERROR; + + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle; + frame.contacts = contacts; + + for (UINT16 i = 0; i < rdpei->maxTouchContacts; i++) + { + RDPINPUT_CONTACT_POINT* contactPoint = &rdpei->contactPoints[i]; + RDPINPUT_CONTACT_DATA* contact = &contactPoint->data; + + if (contactPoint->dirty) + { + contacts[frame.contactCount] = *contact; + rdpei->contactPoints[i].dirty = FALSE; + frame.contactCount++; + } + else if (contactPoint->active) + { + if (contact->contactFlags & RDPINPUT_CONTACT_FLAG_DOWN) + { + contact->contactFlags = RDPINPUT_CONTACT_FLAG_UPDATE; + contact->contactFlags |= RDPINPUT_CONTACT_FLAG_INRANGE; + contact->contactFlags |= RDPINPUT_CONTACT_FLAG_INCONTACT; + } + + contacts[frame.contactCount] = *contact; + frame.contactCount++; + } + if (contact->contactFlags & RDPINPUT_CONTACT_FLAG_UP) + { + contactPoint->active = FALSE; + contactPoint->externalId = 0; + contactPoint->contactId = 0; + } + } + + if (frame.contactCount > 0) + { + UINT error = rdpei_send_frame(context, &frame); + if (error != CHANNEL_RC_OK) + { + WLog_Print(rdpei->base.log, WLOG_ERROR, + "rdpei_send_frame failed with error %" PRIu32 "!", error); + return error; + } + } + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_send_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s, UINT16 eventId, + size_t pduLength) +{ + if (!callback || !s || !callback->channel || !callback->channel->Write) + return ERROR_INTERNAL_ERROR; + + if (pduLength > UINT32_MAX) + return ERROR_INVALID_PARAMETER; + + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)callback->plugin; + if (!rdpei) + return ERROR_INTERNAL_ERROR; + + Stream_ResetPosition(s); + Stream_Write_UINT16(s, eventId); /* eventId (2 bytes) */ + Stream_Write_UINT32(s, (UINT32)pduLength); /* pduLength (4 bytes) */ + Stream_SetPosition(s, Stream_Length(s)); + const UINT status = callback->channel->Write(callback->channel, (UINT32)Stream_Length(s), + Stream_Buffer(s), nullptr); +#ifdef WITH_DEBUG_RDPEI + WLog_Print(rdpei->base.log, WLOG_DEBUG, + "rdpei_send_pdu: eventId: %" PRIu16 " (%s) length: %" PRIu32 " status: %" PRIu32 "", + eventId, rdpei_eventid_string(eventId), pduLength, status); +#endif + return status; +} + +static UINT rdpei_write_pen_frame(wStream* s, const RDPINPUT_PEN_FRAME* frame) +{ + if (!s || !frame) + return ERROR_INTERNAL_ERROR; + + if (!rdpei_write_2byte_unsigned(s, frame->contactCount)) + return ERROR_OUTOFMEMORY; + if (!rdpei_write_8byte_unsigned(s, frame->frameOffset)) + return ERROR_OUTOFMEMORY; + for (UINT16 x = 0; x < frame->contactCount; x++) + { + const RDPINPUT_PEN_CONTACT* contact = &frame->contacts[x]; + + if (!Stream_EnsureRemainingCapacity(s, 1)) + return ERROR_OUTOFMEMORY; + Stream_Write_UINT8(s, contact->deviceId); + if (!rdpei_write_2byte_unsigned(s, contact->fieldsPresent)) + return ERROR_OUTOFMEMORY; + if (!rdpei_write_4byte_signed(s, contact->x)) + return ERROR_OUTOFMEMORY; + if (!rdpei_write_4byte_signed(s, contact->y)) + return ERROR_OUTOFMEMORY; + if (!rdpei_write_4byte_unsigned(s, contact->contactFlags)) + return ERROR_OUTOFMEMORY; + if (contact->fieldsPresent & RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT) + { + if (!rdpei_write_4byte_unsigned(s, contact->penFlags)) + return ERROR_OUTOFMEMORY; + } + if (contact->fieldsPresent & RDPINPUT_PEN_CONTACT_PRESSURE_PRESENT) + { + if (!rdpei_write_4byte_unsigned(s, contact->pressure)) + return ERROR_OUTOFMEMORY; + } + if (contact->fieldsPresent & RDPINPUT_PEN_CONTACT_ROTATION_PRESENT) + { + if (!rdpei_write_2byte_unsigned(s, contact->rotation)) + return ERROR_OUTOFMEMORY; + } + if (contact->fieldsPresent & RDPINPUT_PEN_CONTACT_TILTX_PRESENT) + { + if (!rdpei_write_2byte_signed(s, contact->tiltX)) + return ERROR_OUTOFMEMORY; + } + if (contact->fieldsPresent & RDPINPUT_PEN_CONTACT_TILTY_PRESENT) + { + if (!rdpei_write_2byte_signed(s, contact->tiltY)) + return ERROR_OUTOFMEMORY; + } + } + return CHANNEL_RC_OK; +} + +static UINT rdpei_send_pen_event_pdu(GENERIC_CHANNEL_CALLBACK* callback, size_t frameOffset, + const RDPINPUT_PEN_FRAME* frames, size_t count) +{ + UINT status = ERROR_OUTOFMEMORY; + WINPR_ASSERT(callback); + + if (frameOffset > UINT32_MAX) + return ERROR_INVALID_PARAMETER; + if (count > UINT16_MAX) + return ERROR_INVALID_PARAMETER; + + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)callback->plugin; + if (!rdpei) + return ERROR_INTERNAL_ERROR; + + if (!frames || (count == 0)) + return ERROR_INTERNAL_ERROR; + + wStream* s = Stream_New(nullptr, 64); + + if (!s) + { + WLog_Print(rdpei->base.log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Seek(s, RDPINPUT_HEADER_LENGTH); + /** + * the time that has elapsed (in milliseconds) from when the oldest touch frame + * was generated to when it was encoded for transmission by the client. + */ + if (!rdpei_write_4byte_unsigned( + s, (UINT32)frameOffset)) /* encodeTime (FOUR_BYTE_UNSIGNED_INTEGER) */ + goto fail; + if (!rdpei_write_2byte_unsigned(s, (UINT16)count)) /* (frameCount) TWO_BYTE_UNSIGNED_INTEGER */ + goto fail; + + for (size_t x = 0; x < count; x++) + { + status = rdpei_write_pen_frame(s, &frames[x]); + if (status) + { + WLog_Print(rdpei->base.log, WLOG_ERROR, + "rdpei_write_pen_frame failed with error %" PRIu32 "!", status); + goto fail; + } + } + Stream_SealLength(s); + + status = rdpei_send_pdu(callback, s, EVENTID_PEN, Stream_Length(s)); +fail: + Stream_Free(s, TRUE); + return status; +} + +static UINT rdpei_send_pen_frame(RdpeiClientContext* context, RDPINPUT_PEN_FRAME* frame) +{ + const UINT64 currentTime = GetTickCount64(); + + if (!context) + return ERROR_INTERNAL_ERROR; + + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle; + if (!rdpei || !rdpei->base.listener_callback) + return ERROR_INTERNAL_ERROR; + if (!rdpei || !rdpei->rdpcontext) + return ERROR_INTERNAL_ERROR; + if (freerdp_settings_get_bool(rdpei->rdpcontext->settings, FreeRDP_SuspendInput)) + return CHANNEL_RC_OK; + + GENERIC_CHANNEL_CALLBACK* callback = rdpei->base.listener_callback->channel_callback; + /* Just ignore the event if the channel is not connected */ + if (!callback) + return CHANNEL_RC_OK; + + if (!rdpei->previousPenFrameTime && !rdpei->currentPenFrameTime) + { + rdpei->currentPenFrameTime = currentTime; + frame->frameOffset = 0; + } + else + { + rdpei->currentPenFrameTime = currentTime; + frame->frameOffset = rdpei->currentPenFrameTime - rdpei->previousPenFrameTime; + } + + const size_t off = WINPR_ASSERTING_INT_CAST(size_t, frame->frameOffset); + const UINT error = rdpei_send_pen_event_pdu(callback, off, frame, 1); + if (error) + return error; + + rdpei->previousPenFrameTime = rdpei->currentPenFrameTime; + return error; +} + +static UINT rdpei_add_pen_frame(RdpeiClientContext* context) +{ + RDPINPUT_PEN_FRAME penFrame = WINPR_C_ARRAY_INIT; + RDPINPUT_PEN_CONTACT penContacts[MAX_PEN_CONTACTS] = WINPR_C_ARRAY_INIT; + + if (!context || !context->handle) + return ERROR_INTERNAL_ERROR; + + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle; + + penFrame.contacts = penContacts; + + for (UINT16 i = 0; i < rdpei->maxPenContacts; i++) + { + RDPINPUT_PEN_CONTACT_POINT* contact = &(rdpei->penContactPoints[i]); + + if (contact->dirty) + { + penContacts[penFrame.contactCount++] = contact->data; + contact->dirty = FALSE; + } + else if (contact->active) + { + if (contact->data.contactFlags & RDPINPUT_CONTACT_FLAG_DOWN) + { + contact->data.contactFlags = RDPINPUT_CONTACT_FLAG_UPDATE; + contact->data.contactFlags |= RDPINPUT_CONTACT_FLAG_INRANGE; + contact->data.contactFlags |= RDPINPUT_CONTACT_FLAG_INCONTACT; + } + + penContacts[penFrame.contactCount++] = contact->data; + } + if (contact->data.contactFlags & RDPINPUT_CONTACT_FLAG_CANCELED) + { + contact->externalId = 0; + contact->active = FALSE; + } + } + + if (penFrame.contactCount > 0) + return rdpei_send_pen_frame(context, &penFrame); + return CHANNEL_RC_OK; +} + +static UINT rdpei_update(wLog* log, RdpeiClientContext* context) +{ + UINT error = rdpei_add_frame(context); + if (error != CHANNEL_RC_OK) + { + WLog_Print(log, WLOG_ERROR, "rdpei_add_frame failed with error %" PRIu32 "!", error); + return error; + } + + return rdpei_add_pen_frame(context); +} + +static BOOL rdpei_poll_run_unlocked(rdpContext* context, void* userdata) +{ + RDPEI_PLUGIN* rdpei = userdata; + WINPR_ASSERT(rdpei); + WINPR_ASSERT(context); + + const UINT64 now = GetTickCount64(); + + /* Send an event every ~20ms */ + if ((now < rdpei->lastPollEventTime) || (now - rdpei->lastPollEventTime < 20ULL)) + return TRUE; + + rdpei->lastPollEventTime = now; + + const UINT error = rdpei_update(rdpei->base.log, rdpei->context); + + (void)ResetEvent(rdpei->event); + + if (error != CHANNEL_RC_OK) + { + WLog_Print(rdpei->base.log, WLOG_ERROR, "rdpei_add_frame failed with error %" PRIu32 "!", + error); + setChannelError(context, error, "rdpei_add_frame reported an error"); + return FALSE; + } + + return TRUE; +} + +static BOOL rdpei_poll_run(rdpContext* context, void* userdata) +{ + RDPEI_PLUGIN* rdpei = userdata; + WINPR_ASSERT(rdpei); + + EnterCriticalSection(&rdpei->lock); + BOOL rc = rdpei_poll_run_unlocked(context, userdata); + LeaveCriticalSection(&rdpei->lock); + return rc; +} + +static DWORD WINAPI rdpei_periodic_update(LPVOID arg) +{ + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)arg; + UINT error = CHANNEL_RC_OK; + + if (!rdpei) + { + error = ERROR_INVALID_PARAMETER; + goto out; + } + + if (!rdpei->context) + { + error = ERROR_INVALID_PARAMETER; + goto out; + } + + while (rdpei->running) + { + const DWORD status = WaitForSingleObject(rdpei->event, 20); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_Print(rdpei->base.log, WLOG_ERROR, + "WaitForMultipleObjects failed with error %" PRIu32 "!", error); + break; + } + + if (!rdpei_poll_run(rdpei->rdpcontext, rdpei)) + error = ERROR_INTERNAL_ERROR; + } + +out: + + if (error && rdpei && rdpei->rdpcontext) + setChannelError(rdpei->rdpcontext, error, "rdpei_schedule_thread reported an error"); + + if (rdpei) + rdpei->running = FALSE; + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_send_cs_ready_pdu(GENERIC_CHANNEL_CALLBACK* callback) +{ + if (!callback || !callback->plugin) + return ERROR_INTERNAL_ERROR; + + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)callback->plugin; + + UINT32 flags = CS_READY_FLAGS_SHOW_TOUCH_VISUALS & rdpei->context->clientFeaturesMask; + if (rdpei->version > RDPINPUT_PROTOCOL_V10) + flags |= CS_READY_FLAGS_DISABLE_TIMESTAMP_INJECTION & rdpei->context->clientFeaturesMask; + if (rdpei->features & SC_READY_MULTIPEN_INJECTION_SUPPORTED) + flags |= CS_READY_FLAGS_ENABLE_MULTIPEN_INJECTION & rdpei->context->clientFeaturesMask; + + UINT32 pduLength = RDPINPUT_HEADER_LENGTH + 10; + wStream* s = Stream_New(nullptr, pduLength); + + if (!s) + { + WLog_Print(rdpei->base.log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Seek(s, RDPINPUT_HEADER_LENGTH); + Stream_Write_UINT32(s, flags); /* flags (4 bytes) */ + Stream_Write_UINT32(s, rdpei->version); /* protocolVersion (4 bytes) */ + Stream_Write_UINT16(s, rdpei->maxTouchContacts); /* maxTouchContacts (2 bytes) */ + Stream_SealLength(s); + + const UINT status = rdpei_send_pdu(callback, s, EVENTID_CS_READY, pduLength); + Stream_Free(s, TRUE); + return status; +} + +#if defined(WITH_DEBUG_RDPEI) +static void rdpei_print_contact_flags(wLog* log, UINT32 contactFlags) +{ + if (contactFlags & RDPINPUT_CONTACT_FLAG_DOWN) + WLog_Print(log, WLOG_DEBUG, " RDPINPUT_CONTACT_FLAG_DOWN"); + + if (contactFlags & RDPINPUT_CONTACT_FLAG_UPDATE) + WLog_Print(log, WLOG_DEBUG, " RDPINPUT_CONTACT_FLAG_UPDATE"); + + if (contactFlags & RDPINPUT_CONTACT_FLAG_UP) + WLog_Print(log, WLOG_DEBUG, " RDPINPUT_CONTACT_FLAG_UP"); + + if (contactFlags & RDPINPUT_CONTACT_FLAG_INRANGE) + WLog_Print(log, WLOG_DEBUG, " RDPINPUT_CONTACT_FLAG_INRANGE"); + + if (contactFlags & RDPINPUT_CONTACT_FLAG_INCONTACT) + WLog_Print(log, WLOG_DEBUG, " RDPINPUT_CONTACT_FLAG_INCONTACT"); + + if (contactFlags & RDPINPUT_CONTACT_FLAG_CANCELED) + WLog_Print(log, WLOG_DEBUG, " RDPINPUT_CONTACT_FLAG_CANCELED"); +} +#endif + +static INT16 bounded(INT32 val) +{ + if (val < INT16_MIN) + return INT16_MIN; + if (val > INT16_MAX) + return INT16_MAX; + return (INT16)val; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_write_touch_frame(wLog* log, wStream* s, RDPINPUT_TOUCH_FRAME* frame) +{ + int rectSize = 2; + if (!s || !frame) + return ERROR_INTERNAL_ERROR; +#ifdef WITH_DEBUG_RDPEI + WLog_Print(log, WLOG_DEBUG, "contactCount: %" PRIu32 "", frame->contactCount); + WLog_Print(log, WLOG_DEBUG, "frameOffset: 0x%016" PRIX64 "", frame->frameOffset); +#endif + if (!rdpei_write_2byte_unsigned( + s, frame->contactCount)) /* contactCount (TWO_BYTE_UNSIGNED_INTEGER) */ + return ERROR_OUTOFMEMORY; + /** + * the time offset from the previous frame (in microseconds). + * If this is the first frame being transmitted then this field MUST be set to zero. + */ + if (!rdpei_write_8byte_unsigned(s, frame->frameOffset * + 1000)) /* frameOffset (EIGHT_BYTE_UNSIGNED_INTEGER) */ + return ERROR_OUTOFMEMORY; + + if (!Stream_EnsureRemainingCapacity(s, (size_t)frame->contactCount * 64)) + { + WLog_Print(log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + for (UINT32 index = 0; index < frame->contactCount; index++) + { + RDPINPUT_CONTACT_DATA* contact = &frame->contacts[index]; + + contact->fieldsPresent |= CONTACT_DATA_CONTACTRECT_PRESENT; + contact->contactRectLeft = bounded(contact->x - rectSize); + contact->contactRectTop = bounded(contact->y - rectSize); + contact->contactRectRight = bounded(contact->x + rectSize); + contact->contactRectBottom = bounded(contact->y + rectSize); +#ifdef WITH_DEBUG_RDPEI + WLog_Print(log, WLOG_DEBUG, "contact[%" PRIu32 "].contactId: %" PRIu32 "", index, + contact->contactId); + WLog_Print(log, WLOG_DEBUG, "contact[%" PRIu32 "].fieldsPresent: %" PRIu32 "", index, + contact->fieldsPresent); + WLog_Print(log, WLOG_DEBUG, "contact[%" PRIu32 "].x: %" PRId32 "", index, contact->x); + WLog_Print(log, WLOG_DEBUG, "contact[%" PRIu32 "].y: %" PRId32 "", index, contact->y); + WLog_Print(log, WLOG_DEBUG, "contact[%" PRIu32 "].contactFlags: 0x%08" PRIX32 "", index, + contact->contactFlags); + rdpei_print_contact_flags(log, contact->contactFlags); +#endif + Stream_Write_UINT8( + s, WINPR_ASSERTING_INT_CAST(uint8_t, contact->contactId)); /* contactId (1 byte) */ + /* fieldsPresent (TWO_BYTE_UNSIGNED_INTEGER) */ + if (!rdpei_write_2byte_unsigned(s, contact->fieldsPresent)) + return ERROR_OUTOFMEMORY; + if (!rdpei_write_4byte_signed(s, contact->x)) /* x (FOUR_BYTE_SIGNED_INTEGER) */ + return ERROR_OUTOFMEMORY; + if (!rdpei_write_4byte_signed(s, contact->y)) /* y (FOUR_BYTE_SIGNED_INTEGER) */ + return ERROR_OUTOFMEMORY; + /* contactFlags (FOUR_BYTE_UNSIGNED_INTEGER) */ + if (!rdpei_write_4byte_unsigned(s, contact->contactFlags)) + return ERROR_OUTOFMEMORY; + + if (contact->fieldsPresent & CONTACT_DATA_CONTACTRECT_PRESENT) + { + /* contactRectLeft (TWO_BYTE_SIGNED_INTEGER) */ + if (!rdpei_write_2byte_signed(s, contact->contactRectLeft)) + return ERROR_OUTOFMEMORY; + /* contactRectTop (TWO_BYTE_SIGNED_INTEGER) */ + if (!rdpei_write_2byte_signed(s, contact->contactRectTop)) + return ERROR_OUTOFMEMORY; + /* contactRectRight (TWO_BYTE_SIGNED_INTEGER) */ + if (!rdpei_write_2byte_signed(s, contact->contactRectRight)) + return ERROR_OUTOFMEMORY; + /* contactRectBottom (TWO_BYTE_SIGNED_INTEGER) */ + if (!rdpei_write_2byte_signed(s, contact->contactRectBottom)) + return ERROR_OUTOFMEMORY; + } + + if (contact->fieldsPresent & CONTACT_DATA_ORIENTATION_PRESENT) + { + /* orientation (FOUR_BYTE_UNSIGNED_INTEGER) */ + if (!rdpei_write_4byte_unsigned(s, contact->orientation)) + return ERROR_OUTOFMEMORY; + } + + if (contact->fieldsPresent & CONTACT_DATA_PRESSURE_PRESENT) + { + /* pressure (FOUR_BYTE_UNSIGNED_INTEGER) */ + if (!rdpei_write_4byte_unsigned(s, contact->pressure)) + return ERROR_OUTOFMEMORY; + } + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_send_touch_event_pdu(GENERIC_CHANNEL_CALLBACK* callback, + RDPINPUT_TOUCH_FRAME* frame) +{ + UINT status = ERROR_OUTOFMEMORY; + WINPR_ASSERT(callback); + + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)callback->plugin; + if (!rdpei || !rdpei->rdpcontext) + return ERROR_INTERNAL_ERROR; + if (freerdp_settings_get_bool(rdpei->rdpcontext->settings, FreeRDP_SuspendInput)) + return CHANNEL_RC_OK; + + if (!frame) + return ERROR_INTERNAL_ERROR; + + size_t pduLength = 64ULL + (64ULL * frame->contactCount); + wStream* s = Stream_New(nullptr, pduLength); + + if (!s) + { + WLog_Print(rdpei->base.log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if (!Stream_SafeSeek(s, RDPINPUT_HEADER_LENGTH)) + goto fail; + /** + * the time that has elapsed (in milliseconds) from when the oldest touch frame + * was generated to when it was encoded for transmission by the client. + */ + if (!rdpei_write_4byte_unsigned( + s, (UINT32)frame->frameOffset)) /* encodeTime (FOUR_BYTE_UNSIGNED_INTEGER) */ + goto fail; + if (!rdpei_write_2byte_unsigned(s, 1)) /* (frameCount) TWO_BYTE_UNSIGNED_INTEGER */ + goto fail; + + const UINT rc = rdpei_write_touch_frame(rdpei->base.log, s, frame); + if (rc) + { + WLog_Print(rdpei->base.log, WLOG_ERROR, + "rdpei_write_touch_frame failed with error %" PRIu32 "!", rc); + status = rc; + goto fail; + } + + Stream_SealLength(s); + + status = rdpei_send_pdu(callback, s, EVENTID_TOUCH, Stream_Length(s)); +fail: + Stream_Free(s, TRUE); + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_recv_sc_ready_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + UINT32 features = 0; + UINT32 protocolVersion = 0; + + if (!callback || !callback->plugin) + return ERROR_INTERNAL_ERROR; + + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)callback->plugin; + + if (!Stream_CheckAndLogRequiredLengthWLog(rdpei->base.log, s, 4)) + return ERROR_INVALID_DATA; + Stream_Read_UINT32(s, protocolVersion); /* protocolVersion (4 bytes) */ + + if (protocolVersion >= RDPINPUT_PROTOCOL_V300) + { + if (!Stream_CheckAndLogRequiredLengthWLog(rdpei->base.log, s, 4)) + return ERROR_INVALID_DATA; + } + + if (Stream_GetRemainingLength(s) >= 4) + Stream_Read_UINT32(s, features); + + if (rdpei->version > protocolVersion) + rdpei->version = protocolVersion; + rdpei->features = features; + + if (protocolVersion > RDPINPUT_PROTOCOL_V300) + { + WLog_Print(rdpei->base.log, WLOG_WARN, + "Unknown [MS-RDPEI] protocolVersion: 0x%08" PRIX32 "", protocolVersion); + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_recv_suspend_touch_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + UINT error = CHANNEL_RC_OK; + + WINPR_UNUSED(s); + + if (!callback || !callback->plugin) + return ERROR_INTERNAL_ERROR; + + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)callback->plugin; + RdpeiClientContext* context = rdpei->context; + if (!rdpei) + return ERROR_INTERNAL_ERROR; + + IFCALLRET(context->SuspendTouch, error, context); + + if (error) + WLog_Print(rdpei->base.log, WLOG_ERROR, + "rdpei->SuspendTouch failed with error %" PRIu32 "!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_recv_resume_touch_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + UINT error = CHANNEL_RC_OK; + if (!s || !callback) + return ERROR_INTERNAL_ERROR; + + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)callback->plugin; + if (!rdpei) + return ERROR_INTERNAL_ERROR; + + RdpeiClientContext* context = (RdpeiClientContext*)callback->plugin->pInterface; + if (!context) + return ERROR_INTERNAL_ERROR; + + IFCALLRET(context->ResumeTouch, error, context); + + if (error) + WLog_Print(rdpei->base.log, WLOG_ERROR, "rdpei->ResumeTouch failed with error %" PRIu32 "!", + error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_recv_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + UINT16 eventId = 0; + UINT32 pduLength = 0; + UINT error = 0; + + if (!callback || !s) + return ERROR_INTERNAL_ERROR; + + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)callback->plugin; + if (!rdpei) + return ERROR_INTERNAL_ERROR; + + if (!Stream_CheckAndLogRequiredLengthWLog(rdpei->base.log, s, 6)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, eventId); /* eventId (2 bytes) */ + Stream_Read_UINT32(s, pduLength); /* pduLength (4 bytes) */ +#ifdef WITH_DEBUG_RDPEI + WLog_Print(rdpei->base.log, WLOG_DEBUG, + "rdpei_recv_pdu: eventId: %" PRIu16 " (%s) length: %" PRIu32 "", eventId, + rdpei_eventid_string(eventId), pduLength); +#endif + + if ((pduLength < 6) || !Stream_CheckAndLogRequiredLengthWLog(rdpei->base.log, s, pduLength - 6)) + return ERROR_INVALID_DATA; + + switch (eventId) + { + case EVENTID_SC_READY: + if ((error = rdpei_recv_sc_ready_pdu(callback, s))) + { + WLog_Print(rdpei->base.log, WLOG_ERROR, + "rdpei_recv_sc_ready_pdu failed with error %" PRIu32 "!", error); + return error; + } + + if ((error = rdpei_send_cs_ready_pdu(callback))) + { + WLog_Print(rdpei->base.log, WLOG_ERROR, + "rdpei_send_cs_ready_pdu failed with error %" PRIu32 "!", error); + return error; + } + + break; + + case EVENTID_SUSPEND_TOUCH: + if ((error = rdpei_recv_suspend_touch_pdu(callback, s))) + { + WLog_Print(rdpei->base.log, WLOG_ERROR, + "rdpei_recv_suspend_touch_pdu failed with error %" PRIu32 "!", error); + return error; + } + + break; + + case EVENTID_RESUME_TOUCH: + if ((error = rdpei_recv_resume_touch_pdu(callback, s))) + { + WLog_Print(rdpei->base.log, WLOG_ERROR, + "rdpei_recv_resume_touch_pdu failed with error %" PRIu32 "!", error); + return error; + } + + break; + + default: + break; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data) +{ + GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + return rdpei_recv_pdu(callback, data); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + if (callback) + { + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)callback->plugin; + if (rdpei && rdpei->base.listener_callback) + { + if (rdpei->base.listener_callback->channel_callback == callback) + rdpei->base.listener_callback->channel_callback = nullptr; + } + } + free(callback); + return CHANNEL_RC_OK; +} + +/** + * Channel Client Interface + */ + +static UINT32 rdpei_get_version(RdpeiClientContext* context) +{ + if (!context || !context->handle) + return 0; + + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle; + return rdpei->version; +} + +static UINT32 rdpei_get_features(RdpeiClientContext* context) +{ + if (!context || !context->handle) + return 0; + + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle; + return rdpei->features; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_send_frame(RdpeiClientContext* context, RDPINPUT_TOUCH_FRAME* frame) +{ + UINT64 currentTime = GetTickCount64(); + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle; + + GENERIC_CHANNEL_CALLBACK* callback = rdpei->base.listener_callback->channel_callback; + + /* Just ignore the event if the channel is not connected */ + if (!callback) + return CHANNEL_RC_OK; + + if (!rdpei->previousFrameTime && !rdpei->currentFrameTime) + { + rdpei->currentFrameTime = currentTime; + frame->frameOffset = 0; + } + else + { + rdpei->currentFrameTime = currentTime; + frame->frameOffset = rdpei->currentFrameTime - rdpei->previousFrameTime; + } + + const UINT error = rdpei_send_touch_event_pdu(callback, frame); + if (error) + { + WLog_Print(rdpei->base.log, WLOG_ERROR, + "rdpei_send_touch_event_pdu failed with error %" PRIu32 "!", error); + return error; + } + + rdpei->previousFrameTime = rdpei->currentFrameTime; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_add_contact(RdpeiClientContext* context, const RDPINPUT_CONTACT_DATA* contact) +{ + UINT error = CHANNEL_RC_OK; + if (!context || !contact || !context->handle) + return ERROR_INTERNAL_ERROR; + + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle; + + EnterCriticalSection(&rdpei->lock); + RDPINPUT_CONTACT_POINT* contactPoint = &rdpei->contactPoints[contact->contactId]; + + if (contactPoint->dirty && contactPoint->data.contactFlags != contact->contactFlags) + { + const INT32 externalId = contactPoint->externalId; + error = rdpei_add_frame(context); + if (!contactPoint->active) + { + contactPoint->active = TRUE; + contactPoint->externalId = externalId; + contactPoint->contactId = contact->contactId; + } + } + + contactPoint->data = *contact; + contactPoint->dirty = TRUE; + (void)SetEvent(rdpei->event); + LeaveCriticalSection(&rdpei->lock); + + return error; +} + +static UINT rdpei_touch_process(RdpeiClientContext* context, INT32 externalId, UINT32 contactFlags, + INT32 x, INT32 y, INT32* contactId, UINT32 fieldFlags, va_list ap) +{ + INT64 contactIdlocal = -1; + UINT error = CHANNEL_RC_OK; + + if (!context || !contactId || !context->handle) + return ERROR_INTERNAL_ERROR; + + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle; + /* Create a new contact point in an empty slot */ + EnterCriticalSection(&rdpei->lock); + const BOOL begin = (contactFlags & RDPINPUT_CONTACT_FLAG_DOWN) != 0; + RDPINPUT_CONTACT_POINT* contactPoint = rdpei_contact(rdpei, externalId, !begin); + if (contactPoint) + contactIdlocal = contactPoint->contactId; + + if (contactIdlocal > UINT32_MAX) + { + error = ERROR_INVALID_PARAMETER; + goto fail; + } + + if (contactIdlocal >= 0) + { + RDPINPUT_CONTACT_DATA contact = WINPR_C_ARRAY_INIT; + contact.x = x; + contact.y = y; + contact.contactId = (UINT32)contactIdlocal; + contact.contactFlags = contactFlags; + contact.fieldsPresent = WINPR_ASSERTING_INT_CAST(UINT16, fieldFlags); + + if (fieldFlags & CONTACT_DATA_CONTACTRECT_PRESENT) + { + INT32 val = va_arg(ap, INT32); + contact.contactRectLeft = WINPR_ASSERTING_INT_CAST(INT16, val); + + val = va_arg(ap, INT32); + contact.contactRectTop = WINPR_ASSERTING_INT_CAST(INT16, val); + + val = va_arg(ap, INT32); + contact.contactRectRight = WINPR_ASSERTING_INT_CAST(INT16, val); + + val = va_arg(ap, INT32); + contact.contactRectBottom = WINPR_ASSERTING_INT_CAST(INT16, val); + } + if (fieldFlags & CONTACT_DATA_ORIENTATION_PRESENT) + { + UINT32 p = va_arg(ap, UINT32); + if (p >= 360) + { + WLog_Print(rdpei->base.log, WLOG_WARN, + "TouchContact %" PRId64 ": Invalid orientation value %" PRIu32 + "degree, clamping to 359 degree", + contactIdlocal, p); + p = 359; + } + contact.orientation = p; + } + if (fieldFlags & CONTACT_DATA_PRESSURE_PRESENT) + { + UINT32 p = va_arg(ap, UINT32); + if (p > 1024) + { + WLog_Print(rdpei->base.log, WLOG_WARN, + "TouchContact %" PRId64 ": Invalid pressure value %" PRIu32 + ", clamping to 1024", + contactIdlocal, p); + p = 1024; + } + contact.pressure = p; + } + + error = context->AddContact(context, &contact); + } + +fail: + if (contactId) + *contactId = (INT32)contactIdlocal; + + LeaveCriticalSection(&rdpei->lock); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_touch_begin(RdpeiClientContext* context, INT32 externalId, INT32 x, INT32 y, + INT32* contactId) +{ + UINT rc = 0; + va_list ap = WINPR_C_ARRAY_INIT; + rc = rdpei_touch_process(context, externalId, + RDPINPUT_CONTACT_FLAG_DOWN | RDPINPUT_CONTACT_FLAG_INRANGE | + RDPINPUT_CONTACT_FLAG_INCONTACT, + x, y, contactId, 0, ap); + return rc; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_touch_update(RdpeiClientContext* context, INT32 externalId, INT32 x, INT32 y, + INT32* contactId) +{ + UINT rc = 0; + va_list ap = WINPR_C_ARRAY_INIT; + rc = rdpei_touch_process(context, externalId, + RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE | + RDPINPUT_CONTACT_FLAG_INCONTACT, + x, y, contactId, 0, ap); + return rc; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_touch_end(RdpeiClientContext* context, INT32 externalId, INT32 x, INT32 y, + INT32* contactId) +{ + UINT error = 0; + va_list ap = WINPR_C_ARRAY_INIT; + error = rdpei_touch_process(context, externalId, + RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE | + RDPINPUT_CONTACT_FLAG_INCONTACT, + x, y, contactId, 0, ap); + if (error != CHANNEL_RC_OK) + return error; + error = + rdpei_touch_process(context, externalId, RDPINPUT_CONTACT_FLAG_UP, x, y, contactId, 0, ap); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_touch_cancel(RdpeiClientContext* context, INT32 externalId, INT32 x, INT32 y, + INT32* contactId) +{ + UINT rc = 0; + va_list ap = WINPR_C_ARRAY_INIT; + rc = rdpei_touch_process(context, externalId, + RDPINPUT_CONTACT_FLAG_UP | RDPINPUT_CONTACT_FLAG_CANCELED, x, y, + contactId, 0, ap); + return rc; +} + +static UINT rdpei_touch_raw_event(RdpeiClientContext* context, INT32 externalId, INT32 x, INT32 y, + INT32* contactId, UINT32 flags, UINT32 fieldFlags, ...) +{ + UINT rc = 0; + va_list ap = WINPR_C_ARRAY_INIT; + va_start(ap, fieldFlags); + rc = rdpei_touch_process(context, externalId, flags, x, y, contactId, fieldFlags, ap); + va_end(ap); + return rc; +} + +static UINT rdpei_touch_raw_event_va(RdpeiClientContext* context, INT32 externalId, INT32 x, + INT32 y, INT32* contactId, UINT32 flags, UINT32 fieldFlags, + va_list args) +{ + return rdpei_touch_process(context, externalId, flags, x, y, contactId, fieldFlags, args); +} + +static RDPINPUT_PEN_CONTACT_POINT* rdpei_pen_contact(RDPEI_PLUGIN* rdpei, INT32 externalId, + BOOL active) +{ + if (!rdpei) + return nullptr; + + for (UINT32 x = 0; x < rdpei->maxPenContacts; x++) + { + RDPINPUT_PEN_CONTACT_POINT* contact = &rdpei->penContactPoints[x]; + if (active) + { + if (contact->active) + { + if (contact->externalId == externalId) + return contact; + } + } + else + { + if (!contact->active) + { + contact->externalId = externalId; + contact->active = TRUE; + return contact; + } + } + } + return nullptr; +} + +static UINT rdpei_add_pen(RdpeiClientContext* context, INT32 externalId, + const RDPINPUT_PEN_CONTACT* contact) +{ + if (!context || !contact || !context->handle) + return ERROR_INTERNAL_ERROR; + + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle; + + EnterCriticalSection(&rdpei->lock); + + RDPINPUT_PEN_CONTACT_POINT* contactPoint = rdpei_pen_contact(rdpei, externalId, TRUE); + if (contactPoint) + { + contactPoint->data = *contact; + contactPoint->dirty = TRUE; + (void)SetEvent(rdpei->event); + } + + LeaveCriticalSection(&rdpei->lock); + + return CHANNEL_RC_OK; +} + +static UINT rdpei_pen_process(RdpeiClientContext* context, INT32 externalId, UINT32 contactFlags, + UINT32 fieldFlags, INT32 x, INT32 y, va_list ap) +{ + RDPINPUT_PEN_CONTACT_POINT* contactPoint = nullptr; + UINT error = CHANNEL_RC_OK; + + if (!context || !context->handle) + return ERROR_INTERNAL_ERROR; + + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle; + + EnterCriticalSection(&rdpei->lock); + // Start a new contact only when it is not active. + contactPoint = rdpei_pen_contact(rdpei, externalId, TRUE); + if (!contactPoint) + { + const UINT32 mask = RDPINPUT_CONTACT_FLAG_INRANGE; + if ((contactFlags & mask) == mask) + { + contactPoint = rdpei_pen_contact(rdpei, externalId, FALSE); + } + } + + if (contactPoint != nullptr) + { + RDPINPUT_PEN_CONTACT contact = WINPR_C_ARRAY_INIT; + + contact.x = x; + contact.y = y; + contact.fieldsPresent = WINPR_ASSERTING_INT_CAST(UINT16, fieldFlags); + + contact.contactFlags = contactFlags; + if (fieldFlags & RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT) + { + const UINT32 val = va_arg(ap, UINT32); + contact.penFlags = WINPR_ASSERTING_INT_CAST(UINT16, val); + } + if (fieldFlags & RDPINPUT_PEN_CONTACT_PRESSURE_PRESENT) + { + const UINT32 val = va_arg(ap, UINT32); + contact.pressure = WINPR_ASSERTING_INT_CAST(UINT16, val); + } + if (fieldFlags & RDPINPUT_PEN_CONTACT_ROTATION_PRESENT) + { + const UINT32 val = va_arg(ap, UINT32); + contact.rotation = WINPR_ASSERTING_INT_CAST(UINT16, val); + } + if (fieldFlags & RDPINPUT_PEN_CONTACT_TILTX_PRESENT) + { + const INT32 val = va_arg(ap, INT32); + contact.tiltX = WINPR_ASSERTING_INT_CAST(INT16, val); + } + if (fieldFlags & RDPINPUT_PEN_CONTACT_TILTY_PRESENT) + { + const INT32 val = va_arg(ap, INT32); + WINPR_ASSERT((val >= INT16_MIN) && (val <= INT16_MAX)); + contact.tiltY = WINPR_ASSERTING_INT_CAST(INT16, val); + } + + error = context->AddPen(context, externalId, &contact); + } + + LeaveCriticalSection(&rdpei->lock); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_pen_begin(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags, + INT32 x, INT32 y, ...) +{ + UINT error = 0; + va_list ap = WINPR_C_ARRAY_INIT; + + va_start(ap, y); + error = rdpei_pen_process(context, externalId, + RDPINPUT_CONTACT_FLAG_DOWN | RDPINPUT_CONTACT_FLAG_INRANGE | + RDPINPUT_CONTACT_FLAG_INCONTACT, + fieldFlags, x, y, ap); + va_end(ap); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_pen_update(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags, + INT32 x, INT32 y, ...) +{ + UINT error = 0; + va_list ap = WINPR_C_ARRAY_INIT; + + va_start(ap, y); + error = rdpei_pen_process(context, externalId, + RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE | + RDPINPUT_CONTACT_FLAG_INCONTACT, + fieldFlags, x, y, ap); + va_end(ap); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_pen_end(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags, INT32 x, + INT32 y, ...) +{ + UINT error = 0; + va_list ap = WINPR_C_ARRAY_INIT; + va_start(ap, y); + error = rdpei_pen_process(context, externalId, + RDPINPUT_CONTACT_FLAG_UP | RDPINPUT_CONTACT_FLAG_INRANGE, fieldFlags, + x, y, ap); + va_end(ap); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_pen_hover_begin(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags, + INT32 x, INT32 y, ...) +{ + UINT error = 0; + va_list ap = WINPR_C_ARRAY_INIT; + + va_start(ap, y); + error = rdpei_pen_process(context, externalId, + RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE, + fieldFlags, x, y, ap); + va_end(ap); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_pen_hover_update(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags, + INT32 x, INT32 y, ...) +{ + UINT error = 0; + va_list ap = WINPR_C_ARRAY_INIT; + + va_start(ap, y); + error = rdpei_pen_process(context, externalId, + RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE, + fieldFlags, x, y, ap); + va_end(ap); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpei_pen_hover_cancel(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags, + INT32 x, INT32 y, ...) +{ + UINT error = 0; + va_list ap = WINPR_C_ARRAY_INIT; + + va_start(ap, y); + error = rdpei_pen_process(context, externalId, + RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_CANCELED, + fieldFlags, x, y, ap); + va_end(ap); + + return error; +} + +static UINT rdpei_pen_raw_event(RdpeiClientContext* context, INT32 externalId, UINT32 contactFlags, + UINT32 fieldFlags, INT32 x, INT32 y, ...) +{ + UINT error = 0; + va_list ap = WINPR_C_ARRAY_INIT; + + va_start(ap, y); + error = rdpei_pen_process(context, externalId, contactFlags, fieldFlags, x, y, ap); + va_end(ap); + return error; +} + +static UINT rdpei_pen_raw_event_va(RdpeiClientContext* context, INT32 externalId, + UINT32 contactFlags, UINT32 fieldFlags, INT32 x, INT32 y, + va_list args) +{ + return rdpei_pen_process(context, externalId, contactFlags, fieldFlags, x, y, args); +} + +static UINT init_plugin_cb(GENERIC_DYNVC_PLUGIN* base, rdpContext* rcontext, rdpSettings* settings) +{ + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)base; + + WINPR_ASSERT(base); + WINPR_UNUSED(settings); + + rdpei->version = RDPINPUT_PROTOCOL_V300; + rdpei->currentFrameTime = 0; + rdpei->previousFrameTime = 0; + rdpei->maxTouchContacts = MAX_CONTACTS; + rdpei->maxPenContacts = MAX_PEN_CONTACTS; + rdpei->rdpcontext = rcontext; + + WINPR_ASSERT(rdpei->base.log); + + InitializeCriticalSection(&rdpei->lock); + rdpei->event = CreateEventA(nullptr, TRUE, FALSE, nullptr); + if (!rdpei->event) + { + WLog_Print(rdpei->base.log, WLOG_ERROR, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + RdpeiClientContext* context = (RdpeiClientContext*)calloc(1, sizeof(RdpeiClientContext)); + if (!context) + { + WLog_Print(rdpei->base.log, WLOG_ERROR, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + context->clientFeaturesMask = UINT32_MAX; + context->handle = (void*)rdpei; + context->GetVersion = rdpei_get_version; + context->GetFeatures = rdpei_get_features; + context->AddContact = rdpei_add_contact; + context->TouchBegin = rdpei_touch_begin; + context->TouchUpdate = rdpei_touch_update; + context->TouchEnd = rdpei_touch_end; + context->TouchCancel = rdpei_touch_cancel; + context->TouchRawEvent = rdpei_touch_raw_event; + context->TouchRawEventVA = rdpei_touch_raw_event_va; + context->AddPen = rdpei_add_pen; + context->PenBegin = rdpei_pen_begin; + context->PenUpdate = rdpei_pen_update; + context->PenEnd = rdpei_pen_end; + context->PenHoverBegin = rdpei_pen_hover_begin; + context->PenHoverUpdate = rdpei_pen_hover_update; + context->PenHoverCancel = rdpei_pen_hover_cancel; + context->PenRawEvent = rdpei_pen_raw_event; + context->PenRawEventVA = rdpei_pen_raw_event_va; + + rdpei->context = context; + rdpei->base.iface.pInterface = (void*)context; + + rdpei->async = + !freerdp_settings_get_bool(rdpei->rdpcontext->settings, FreeRDP_SynchronousDynamicChannels); + if (rdpei->async) + { + rdpei->running = TRUE; + + rdpei->thread = CreateThread(nullptr, 0, rdpei_periodic_update, rdpei, 0, nullptr); + if (!rdpei->thread) + { + WLog_Print(rdpei->base.log, WLOG_ERROR, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + } + else + { + if (!freerdp_client_channel_register(rdpei->rdpcontext->channels, rdpei->event, + rdpei_poll_run, rdpei)) + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +static void terminate_plugin_cb(GENERIC_DYNVC_PLUGIN* base) +{ + RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)base; + WINPR_ASSERT(rdpei); + + rdpei->running = FALSE; + if (rdpei->event) + (void)SetEvent(rdpei->event); + + if (rdpei->thread) + { + (void)WaitForSingleObject(rdpei->thread, INFINITE); + (void)CloseHandle(rdpei->thread); + } + + if (rdpei->event && !rdpei->async) + freerdp_client_channel_unregister(rdpei->rdpcontext->channels, rdpei->event); + + if (rdpei->event) + (void)CloseHandle(rdpei->event); + + DeleteCriticalSection(&rdpei->lock); + free(rdpei->context); +} + +static const IWTSVirtualChannelCallback rdpei_callbacks = { rdpei_on_data_received, + nullptr, /* Open */ + rdpei_on_close, nullptr }; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE rdpei_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)) +{ + return freerdp_generic_DVCPluginEntry(pEntryPoints, RDPEI_TAG, RDPEI_DVC_CHANNEL_NAME, + sizeof(RDPEI_PLUGIN), sizeof(GENERIC_CHANNEL_CALLBACK), + &rdpei_callbacks, init_plugin_cb, terminate_plugin_cb); +} diff --git a/third_party/FreeRDP/channels/rdpei/client/rdpei_main.h b/third_party/FreeRDP/channels/rdpei/client/rdpei_main.h new file mode 100644 index 0000000..c46709a --- /dev/null +++ b/third_party/FreeRDP/channels/rdpei/client/rdpei_main.h @@ -0,0 +1,87 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Input Virtual Channel Extension + * + * Copyright 2013 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPEI_CLIENT_MAIN_H +#define FREERDP_CHANNEL_RDPEI_CLIENT_MAIN_H + +#include + +#include +#include +#include +#include + +#include +#include + +/** + * Touch Contact State Transitions + * + * ENGAGED -> UPDATE | INRANGE | INCONTACT -> ENGAGED + * ENGAGED -> UP | INRANGE -> HOVERING + * ENGAGED -> UP -> OUT_OF_RANGE + * ENGAGED -> UP | CANCELED -> OUT_OF_RANGE + * + * HOVERING -> UPDATE | INRANGE -> HOVERING + * HOVERING -> DOWN | INRANGE | INCONTACT -> ENGAGED + * HOVERING -> UPDATE -> OUT_OF_RANGE + * HOVERING -> UPDATE | CANCELED -> OUT_OF_RANGE + * + * OUT_OF_RANGE -> DOWN | INRANGE | INCONTACT -> ENGAGED + * OUT_OF_RANGE -> UPDATE | INRANGE -> HOVERING + * + * When a contact is in the "hovering" or "engaged" state, it is referred to as being "active". + * "Hovering" contacts are in range of the digitizer, while "engaged" contacts are in range of + * the digitizer and in contact with the digitizer surface. MS-RDPEI remotes only active contacts + * and contacts that are transitioning to the "out of range" state; see section 2.2.3.3.1.1 for + * an enumeration of valid state flags combinations. + * + * When transitioning from the "engaged" state to the "hovering" state, or from the "engaged" + * state to the "out of range" state, the contact position cannot change; it is only allowed + * to change after the transition has taken place. + * + */ + +typedef struct +{ + BOOL dirty; + BOOL active; + UINT32 contactId; + INT32 externalId; + RDPINPUT_CONTACT_DATA data; +} RDPINPUT_CONTACT_POINT; + +typedef struct +{ + BOOL dirty; + BOOL active; + INT32 externalId; + RDPINPUT_PEN_CONTACT data; +} RDPINPUT_PEN_CONTACT_POINT; + +#ifdef WITH_DEBUG_DVC +#define DEBUG_DVC(...) WLog_DBG(TAG, __VA_ARGS__) +#else +#define DEBUG_DVC(...) \ + do \ + { \ + } while (0) +#endif + +#endif /* FREERDP_CHANNEL_RDPEI_CLIENT_MAIN_H */ diff --git a/third_party/FreeRDP/channels/rdpei/rdpei_common.c b/third_party/FreeRDP/channels/rdpei/rdpei_common.c new file mode 100644 index 0000000..e970828 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpei/rdpei_common.c @@ -0,0 +1,645 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Input Virtual Channel Extension + * + * Copyright 2013 Marc-Andre Moreau + * Copyright 2014 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include "rdpei_common.h" + +#include + +#define TAG FREERDP_TAG("channels.rdpei.common") + +BOOL rdpei_read_2byte_unsigned(wStream* s, UINT16* value) +{ + BYTE byte = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return FALSE; + + Stream_Read_UINT8(s, byte); + + if (byte & 0x80) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return FALSE; + + const INT32 ibyte = ((byte & 0x7F) << 8); + *value = WINPR_ASSERTING_INT_CAST(UINT16, ibyte); + Stream_Read_UINT8(s, byte); + *value |= byte; + } + else + { + *value = (byte & 0x7F); + } + + return TRUE; +} + +BOOL rdpei_write_2byte_unsigned(wStream* s, UINT16 value) +{ + BYTE byte = 0; + + if (!Stream_EnsureRemainingCapacity(s, 2)) + return FALSE; + + if (value > 0x7FFF) + return FALSE; + + if (value >= 0x7F) + { + byte = ((value & 0x7F00) >> 8); + Stream_Write_UINT8(s, byte | 0x80); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else + { + byte = (value & 0x7F); + Stream_Write_UINT8(s, byte); + } + + return TRUE; +} + +BOOL rdpei_read_2byte_signed(wStream* s, INT16* value) +{ + BYTE byte = 0; + BOOL negative = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return FALSE; + + Stream_Read_UINT8(s, byte); + + negative = (byte & 0x40) != 0; + + const BYTE val = (byte & 0x3F); + + if (byte & 0x80) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return FALSE; + + Stream_Read_UINT8(s, byte); + *value = (INT16)((val << 8) | byte); + } + else + *value = val; + + if (negative) + *value *= -1; + + return TRUE; +} + +BOOL rdpei_write_2byte_signed(wStream* s, INT16 value) +{ + BYTE byte = 0; + BOOL negative = FALSE; + + if (!Stream_EnsureRemainingCapacity(s, 2)) + return FALSE; + + if (value < 0) + { + negative = TRUE; + value *= -1; + } + + if (value > 0x3FFF) + return FALSE; + + if (value >= 0x3F) + { + byte = ((value & 0x3F00) >> 8); + + if (negative) + byte |= 0x40; + + Stream_Write_UINT8(s, byte | 0x80); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else + { + byte = (value & 0x3F); + + if (negative) + byte |= 0x40; + + Stream_Write_UINT8(s, byte); + } + + return TRUE; +} + +BOOL rdpei_read_4byte_unsigned(wStream* s, UINT32* value) +{ + BYTE byte = 0; + BYTE count = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return FALSE; + + Stream_Read_UINT8(s, byte); + + count = (byte & 0xC0) >> 6; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, count)) + return FALSE; + + switch (count) + { + case 0: + *value = (byte & 0x3F); + break; + + case 1: + *value = ((byte & 0x3F) << 8) & 0xFF00; + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 2: + *value = ((byte & 0x3F) << 16) & 0xFF0000; + Stream_Read_UINT8(s, byte); + *value |= ((byte << 8) & 0xFF00); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 3: + *value = ((UINT32)(byte & 0x3F) << 24) & 0xFF000000; + Stream_Read_UINT8(s, byte); + *value |= ((UINT32)(byte << 16) & 0xFF0000); + Stream_Read_UINT8(s, byte); + *value |= ((UINT32)(byte << 8) & 0xFF00); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + default: + break; + } + + return TRUE; +} + +BOOL rdpei_write_4byte_unsigned(wStream* s, UINT32 value) +{ + BYTE byte = 0; + + if (!Stream_EnsureRemainingCapacity(s, 4)) + return FALSE; + + if (value <= 0x3FUL) + { + Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, value)); + } + else if (value <= 0x3FFFUL) + { + byte = (value >> 8) & 0x3F; + Stream_Write_UINT8(s, byte | 0x40); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x3FFFFFUL) + { + byte = (value >> 16) & 0x3F; + Stream_Write_UINT8(s, byte | 0x80); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x3FFFFFFFUL) + { + byte = (value >> 24) & 0x3F; + Stream_Write_UINT8(s, byte | 0xC0); + byte = (value >> 16) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else + { + return FALSE; + } + + return TRUE; +} + +BOOL rdpei_read_4byte_signed(wStream* s, INT32* value) +{ + BYTE byte = 0; + BYTE count = 0; + BOOL negative = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return FALSE; + + Stream_Read_UINT8(s, byte); + + count = (byte & 0xC0) >> 6; + negative = (byte & 0x20) != 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, count)) + return FALSE; + + switch (count) + { + case 0: + *value = (byte & 0x1F); + break; + + case 1: + *value = (byte & 0x1F) << 8; + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 2: + *value = (byte & 0x1F) << 16; + Stream_Read_UINT8(s, byte); + *value |= (byte << 8); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 3: + *value = (byte & 0x1F) << 24; + Stream_Read_UINT8(s, byte); + *value |= (byte << 16); + Stream_Read_UINT8(s, byte); + *value |= (byte << 8); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + default: + break; + } + + if (negative) + *value *= -1; + + return TRUE; +} + +BOOL rdpei_write_4byte_signed(wStream* s, INT32 value) +{ + BYTE byte = 0; + BOOL negative = FALSE; + + if (!Stream_EnsureRemainingCapacity(s, 4)) + return FALSE; + + if (value < 0) + { + negative = TRUE; + value *= -1; + } + + if (value <= 0x1FL) + { + byte = value & 0x1F; + + if (negative) + byte |= 0x20; + + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFL) + { + byte = (value >> 8) & 0x1F; + + if (negative) + byte |= 0x20; + + Stream_Write_UINT8(s, byte | 0x40); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFFFL) + { + byte = (value >> 16) & 0x1F; + + if (negative) + byte |= 0x20; + + Stream_Write_UINT8(s, byte | 0x80); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFFFFFL) + { + byte = (value >> 24) & 0x1F; + + if (negative) + byte |= 0x20; + + Stream_Write_UINT8(s, byte | 0xC0); + byte = (value >> 16) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else + { + return FALSE; + } + + return TRUE; +} + +BOOL rdpei_read_8byte_unsigned(wStream* s, UINT64* value) +{ + UINT64 byte = 0; + BYTE count = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return FALSE; + + Stream_Read_UINT8(s, byte); + + count = (byte & 0xE0) >> 5; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, count)) + return FALSE; + + switch (count) + { + case 0: + *value = (byte & 0x1F); + break; + + case 1: + *value = (byte & 0x1FU) << 8U; + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 2: + *value = (byte & 0x1FU) << 16U; + Stream_Read_UINT8(s, byte); + *value |= (byte << 8U); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 3: + *value = (byte & 0x1FU) << 24U; + Stream_Read_UINT8(s, byte); + *value |= (byte << 16U); + Stream_Read_UINT8(s, byte); + *value |= (byte << 8U); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 4: + *value = ((byte & 0x1FU)) << 32U; + Stream_Read_UINT8(s, byte); + *value |= (byte << 24U); + Stream_Read_UINT8(s, byte); + *value |= (byte << 16U); + Stream_Read_UINT8(s, byte); + *value |= (byte << 8U); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 5: + *value = ((byte & 0x1FU)) << 40U; + Stream_Read_UINT8(s, byte); + *value |= ((byte) << 32U); + Stream_Read_UINT8(s, byte); + *value |= (byte << 24U); + Stream_Read_UINT8(s, byte); + *value |= (byte << 16U); + Stream_Read_UINT8(s, byte); + *value |= (byte << 8U); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 6: + *value = ((byte & 0x1FU)) << 48U; + Stream_Read_UINT8(s, byte); + *value |= ((byte) << 40U); + Stream_Read_UINT8(s, byte); + *value |= ((byte) << 32U); + Stream_Read_UINT8(s, byte); + *value |= (byte << 24U); + Stream_Read_UINT8(s, byte); + *value |= (byte << 16U); + Stream_Read_UINT8(s, byte); + *value |= (byte << 8U); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + case 7: + *value = ((byte & 0x1FU)) << 56U; + Stream_Read_UINT8(s, byte); + *value |= ((byte) << 48U); + Stream_Read_UINT8(s, byte); + *value |= ((byte) << 40U); + Stream_Read_UINT8(s, byte); + *value |= ((byte) << 32U); + Stream_Read_UINT8(s, byte); + *value |= (byte << 24U); + Stream_Read_UINT8(s, byte); + *value |= (byte << 16U); + Stream_Read_UINT8(s, byte); + *value |= (byte << 8U); + Stream_Read_UINT8(s, byte); + *value |= byte; + break; + + default: + break; + } + + return TRUE; +} + +BOOL rdpei_write_8byte_unsigned(wStream* s, UINT64 value) +{ + BYTE byte = 0; + + if (!Stream_EnsureRemainingCapacity(s, 8)) + return FALSE; + + if (value <= 0x1FULL) + { + byte = value & 0x1F; + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFULL) + { + byte = (value >> 8) & 0x1F; + byte |= (1 << 5); + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFFFULL) + { + byte = (value >> 16) & 0x1F; + byte |= (2 << 5); + Stream_Write_UINT8(s, byte); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFFFFFULL) + { + byte = (value >> 24) & 0x1F; + byte |= (3 << 5); + Stream_Write_UINT8(s, byte); + byte = (value >> 16) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFFFFFFFULL) + { + byte = (value >> 32) & 0x1F; + byte |= (4 << 5); + Stream_Write_UINT8(s, byte); + byte = (value >> 24) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 16) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFFFFFFFFFULL) + { + byte = (value >> 40) & 0x1F; + byte |= (5 << 5); + Stream_Write_UINT8(s, byte); + byte = (value >> 32) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 24) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 16) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFFFFFFFFFFFULL) + { + byte = (value >> 48) & 0x1F; + byte |= (6 << 5); + Stream_Write_UINT8(s, byte); + byte = (value >> 40) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 32) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 24) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 16) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else if (value <= 0x1FFFFFFFFFFFFFFFULL) + { + byte = (value >> 56) & 0x1F; + byte |= (7 << 5); + Stream_Write_UINT8(s, byte); + byte = (value >> 48) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 40) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 32) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 24) & 0x1F; + Stream_Write_UINT8(s, byte); + byte = (value >> 16) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value >> 8) & 0xFF; + Stream_Write_UINT8(s, byte); + byte = (value & 0xFF); + Stream_Write_UINT8(s, byte); + } + else + { + return FALSE; + } + + return TRUE; +} + +void touch_event_reset(RDPINPUT_TOUCH_EVENT* event) +{ + for (UINT16 i = 0; i < event->frameCount; i++) + touch_frame_reset(&event->frames[i]); + + free(event->frames); + event->frames = nullptr; + event->frameCount = 0; +} + +void touch_frame_reset(RDPINPUT_TOUCH_FRAME* frame) +{ + free(frame->contacts); + frame->contacts = nullptr; + frame->contactCount = 0; +} + +void pen_event_reset(RDPINPUT_PEN_EVENT* event) +{ + for (UINT16 i = 0; i < event->frameCount; i++) + pen_frame_reset(&event->frames[i]); + + free(event->frames); + event->frames = nullptr; + event->frameCount = 0; +} + +void pen_frame_reset(RDPINPUT_PEN_FRAME* frame) +{ + free(frame->contacts); + frame->contacts = nullptr; + frame->contactCount = 0; +} diff --git a/third_party/FreeRDP/channels/rdpei/rdpei_common.h b/third_party/FreeRDP/channels/rdpei/rdpei_common.h new file mode 100644 index 0000000..f9d8511 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpei/rdpei_common.h @@ -0,0 +1,74 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Input Virtual Channel Extension + * + * Copyright 2013 Marc-Andre Moreau + * Copyright 2014 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPEI_COMMON_H +#define FREERDP_CHANNEL_RDPEI_COMMON_H + +#include +#include + +#include +#include + +/** @brief input event ids */ +enum +{ + EVENTID_SC_READY = 0x0001, + EVENTID_CS_READY = 0x0002, + EVENTID_TOUCH = 0x0003, + EVENTID_SUSPEND_TOUCH = 0x0004, + EVENTID_RESUME_TOUCH = 0x0005, + EVENTID_DISMISS_HOVERING_CONTACT = 0x0006, + EVENTID_PEN = 0x0008 +}; + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL rdpei_read_2byte_unsigned(wStream* s, UINT16* value); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL rdpei_write_2byte_unsigned(wStream* s, UINT16 value); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL rdpei_read_2byte_signed(wStream* s, INT16* value); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL rdpei_write_2byte_signed(wStream* s, INT16 value); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL rdpei_read_4byte_unsigned(wStream* s, UINT32* value); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL rdpei_write_4byte_unsigned(wStream* s, UINT32 value); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL rdpei_read_4byte_signed(wStream* s, INT32* value); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL rdpei_write_4byte_signed(wStream* s, INT32 value); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL rdpei_read_8byte_unsigned(wStream* s, UINT64* value); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL rdpei_write_8byte_unsigned(wStream* s, UINT64 value); + +FREERDP_LOCAL +void touch_event_reset(RDPINPUT_TOUCH_EVENT* event); + +FREERDP_LOCAL +void touch_frame_reset(RDPINPUT_TOUCH_FRAME* frame); + +FREERDP_LOCAL +void pen_event_reset(RDPINPUT_PEN_EVENT* event); + +FREERDP_LOCAL +void pen_frame_reset(RDPINPUT_PEN_FRAME* frame); + +#endif /* FREERDP_CHANNEL_RDPEI_COMMON_H */ diff --git a/third_party/FreeRDP/channels/rdpei/server/CMakeLists.txt b/third_party/FreeRDP/channels/rdpei/server/CMakeLists.txt new file mode 100644 index 0000000..346c2ff --- /dev/null +++ b/third_party/FreeRDP/channels/rdpei/server/CMakeLists.txt @@ -0,0 +1,25 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2014 Thincast Technologies Gmbh. +# Copyright 2014 David FORT +# +# 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. + +define_channel_server("rdpei") + +set(${MODULE_PREFIX}_SRCS rdpei_main.c rdpei_main.h ../rdpei_common.c ../rdpei_common.h) + +set(${MODULE_PREFIX}_LIBS winpr) + +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntry") diff --git a/third_party/FreeRDP/channels/rdpei/server/rdpei_main.c b/third_party/FreeRDP/channels/rdpei/server/rdpei_main.c new file mode 100644 index 0000000..34ae6c5 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpei/server/rdpei_main.c @@ -0,0 +1,928 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Extended Input channel server-side implementation + * + * Copyright 2014 Thincast Technologies Gmbh. + * Copyright 2014 David FORT + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include + +#include "rdpei_main.h" +#include "../rdpei_common.h" +#include +#include + +typedef enum +{ + RDPEI_INITIAL, + RDPEI_OPENED, +} eRdpEiChannelState; + +enum RdpEiState +{ + STATE_INITIAL, + STATE_WAITING_CLIENT_READY, + STATE_WAITING_FRAME, + STATE_SUSPENDED, +}; + +struct s_rdpei_server_private +{ + HANDLE channelHandle; + HANDLE eventHandle; + + HANDLE stopEvent; + HANDLE thread; + + /* Channel state */ + eRdpEiChannelState channelState; + + UINT32 expectedBytes; + BOOL waitingHeaders; + wStream* inputStream; + wStream* outputStream; + + UINT16 currentMsgType; + + RDPINPUT_TOUCH_EVENT touchEvent; + RDPINPUT_PEN_EVENT penEvent; + + enum RdpEiState automataState; +}; + +static UINT rdpei_server_open_channel(RdpeiServerContext* context) +{ + DWORD error = ERROR_SUCCESS; + DWORD bytesReturned = 0; + PULONG pSessionId = nullptr; + BOOL status = TRUE; + + WINPR_ASSERT(context); + + RdpeiServerPrivate* priv = context->priv; + WINPR_ASSERT(priv); + + if (WTSQuerySessionInformationA(context->vcm, WTS_CURRENT_SESSION, WTSSessionId, + (LPSTR*)&pSessionId, &bytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSQuerySessionInformationA failed!"); + return ERROR_INTERNAL_ERROR; + } + + DWORD sessionId = (DWORD)*pSessionId; + WTSFreeMemory(pSessionId); + + priv->channelHandle = + WTSVirtualChannelOpenEx(sessionId, RDPEI_DVC_CHANNEL_NAME, WTS_CHANNEL_OPTION_DYNAMIC); + if (!priv->channelHandle) + { + error = GetLastError(); + WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed with error %" PRIu32 "!", error); + return error; + } + + const UINT32 channelId = WTSChannelGetIdByHandle(priv->channelHandle); + + IFCALLRET(context->onChannelIdAssigned, status, context, channelId); + if (!status) + { + WLog_ERR(TAG, "context->onChannelIdAssigned failed!"); + return ERROR_INTERNAL_ERROR; + } + + return error; +} + +static UINT rdpei_server_context_poll_int(RdpeiServerContext* context) +{ + RdpeiServerPrivate* priv = nullptr; + UINT error = ERROR_INTERNAL_ERROR; + + WINPR_ASSERT(context); + priv = context->priv; + WINPR_ASSERT(priv); + + switch (priv->channelState) + { + case RDPEI_INITIAL: + error = rdpei_server_open_channel(context); + if (error) + WLog_ERR(TAG, "rdpei_server_open_channel failed with error %" PRIu32 "!", error); + else + priv->channelState = RDPEI_OPENED; + break; + case RDPEI_OPENED: + error = rdpei_server_handle_messages(context); + break; + default: + break; + } + + return error; +} + +static HANDLE rdpei_server_get_channel_handle(RdpeiServerContext* context) +{ + RdpeiServerPrivate* priv = nullptr; + void* buffer = nullptr; + DWORD bytesReturned = 0; + HANDLE channelEvent = nullptr; + + WINPR_ASSERT(context); + priv = context->priv; + WINPR_ASSERT(priv); + + if (WTSVirtualChannelQuery(priv->channelHandle, WTSVirtualEventHandle, &buffer, + &bytesReturned) == TRUE) + { + if (bytesReturned == sizeof(HANDLE)) + channelEvent = *(HANDLE*)buffer; + + WTSFreeMemory(buffer); + } + + return channelEvent; +} + +static DWORD WINAPI rdpei_server_thread_func(LPVOID arg) +{ + RdpeiServerContext* context = (RdpeiServerContext*)arg; + RdpeiServerPrivate* priv = nullptr; + HANDLE events[2] = WINPR_C_ARRAY_INIT; + DWORD nCount = 0; + UINT error = CHANNEL_RC_OK; + DWORD status = 0; + + WINPR_ASSERT(context); + priv = context->priv; + WINPR_ASSERT(priv); + + events[nCount++] = priv->stopEvent; + + while ((error == CHANNEL_RC_OK) && (WaitForSingleObject(events[0], 0) != WAIT_OBJECT_0)) + { + switch (priv->channelState) + { + case RDPEI_INITIAL: + error = rdpei_server_context_poll_int(context); + if (error == CHANNEL_RC_OK) + { + events[1] = rdpei_server_get_channel_handle(context); + nCount = 2; + } + break; + case RDPEI_OPENED: + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + switch (status) + { + case WAIT_OBJECT_0: + break; + case WAIT_OBJECT_0 + 1: + case WAIT_TIMEOUT: + error = rdpei_server_context_poll_int(context); + break; + + case WAIT_FAILED: + default: + error = ERROR_INTERNAL_ERROR; + break; + } + break; + default: + break; + } + } + + (void)WTSVirtualChannelClose(priv->channelHandle); + priv->channelHandle = nullptr; + + ExitThread(error); + return error; +} + +static UINT rdpei_server_open(RdpeiServerContext* context) +{ + RdpeiServerPrivate* priv = nullptr; + + priv = context->priv; + WINPR_ASSERT(priv); + + if (!priv->thread) + { + priv->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + if (!priv->stopEvent) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + priv->thread = CreateThread(nullptr, 0, rdpei_server_thread_func, context, 0, nullptr); + if (!priv->thread) + { + WLog_ERR(TAG, "CreateThread failed!"); + (void)CloseHandle(priv->stopEvent); + priv->stopEvent = nullptr; + return ERROR_INTERNAL_ERROR; + } + } + + return CHANNEL_RC_OK; +} + +static UINT rdpei_server_close(RdpeiServerContext* context) +{ + RdpeiServerPrivate* priv = nullptr; + UINT error = CHANNEL_RC_OK; + + priv = context->priv; + WINPR_ASSERT(priv); + + if (priv->thread) + { + (void)SetEvent(priv->stopEvent); + + if (WaitForSingleObject(priv->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + return error; + } + + (void)CloseHandle(priv->thread); + (void)CloseHandle(priv->stopEvent); + priv->thread = nullptr; + priv->stopEvent = nullptr; + } + + return error; +} + +RdpeiServerContext* rdpei_server_context_new(HANDLE vcm) +{ + RdpeiServerContext* ret = calloc(1, sizeof(*ret)); + + if (!ret) + return nullptr; + + ret->Open = rdpei_server_open; + ret->Close = rdpei_server_close; + + ret->priv = calloc(1, sizeof(*ret->priv)); + if (!ret->priv) + goto fail; + + ret->priv->inputStream = Stream_New(nullptr, 256); + if (!ret->priv->inputStream) + goto fail; + + ret->priv->outputStream = Stream_New(nullptr, 200); + if (!ret->priv->outputStream) + goto fail; + + ret->vcm = vcm; + rdpei_server_context_reset(ret); + return ret; + +fail: + WINPR_PRAGMA_DIAG_PUSH + WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC + rdpei_server_context_free(ret); + WINPR_PRAGMA_DIAG_POP + return nullptr; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_server_init(RdpeiServerContext* context) +{ + RdpeiServerPrivate* priv = context->priv; + UINT error = rdpei_server_open_channel(context); + if (error) + return error; + + priv->eventHandle = rdpei_server_get_channel_handle(context); + if (!priv->eventHandle) + { + WLog_ERR(TAG, "Failed to get channel handle!"); + goto out_close; + } + + return CHANNEL_RC_OK; + +out_close: + (void)WTSVirtualChannelClose(priv->channelHandle); + return CHANNEL_RC_INITIALIZATION_ERROR; +} + +void rdpei_server_context_reset(RdpeiServerContext* context) +{ + RdpeiServerPrivate* priv = context->priv; + + priv->channelHandle = INVALID_HANDLE_VALUE; + priv->expectedBytes = RDPINPUT_HEADER_LENGTH; + priv->waitingHeaders = TRUE; + priv->automataState = STATE_INITIAL; + Stream_ResetPosition(priv->inputStream); +} + +void rdpei_server_context_free(RdpeiServerContext* context) +{ + if (!context) + return; + RdpeiServerPrivate* priv = context->priv; + if (priv) + { + if (priv->channelHandle && priv->channelHandle != INVALID_HANDLE_VALUE) + (void)WTSVirtualChannelClose(priv->channelHandle); + Stream_Free(priv->inputStream, TRUE); + Stream_Free(priv->outputStream, TRUE); + } + free(priv); + free(context); +} + +HANDLE rdpei_server_get_event_handle(RdpeiServerContext* context) +{ + return context->priv->eventHandle; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT read_cs_ready_message(RdpeiServerContext* context, wStream* s) +{ + UINT error = CHANNEL_RC_OK; + if (!Stream_CheckAndLogRequiredLength(TAG, s, 10)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, context->protocolFlags); + Stream_Read_UINT32(s, context->clientVersion); + Stream_Read_UINT16(s, context->maxTouchPoints); + + switch (context->clientVersion) + { + case RDPINPUT_PROTOCOL_V10: + case RDPINPUT_PROTOCOL_V101: + case RDPINPUT_PROTOCOL_V200: + case RDPINPUT_PROTOCOL_V300: + break; + default: + WLog_ERR(TAG, "unhandled RPDEI protocol version 0x%" PRIx32 "", context->clientVersion); + break; + } + + IFCALLRET(context->onClientReady, error, context); + if (error) + WLog_ERR(TAG, "context->onClientReady failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT read_touch_contact_data(RdpeiServerContext* context, wStream* s, + RDPINPUT_CONTACT_DATA* contactData) +{ + WINPR_UNUSED(context); + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT8(s, contactData->contactId); + if (!rdpei_read_2byte_unsigned(s, &contactData->fieldsPresent) || + !rdpei_read_4byte_signed(s, &contactData->x) || + !rdpei_read_4byte_signed(s, &contactData->y) || + !rdpei_read_4byte_unsigned(s, &contactData->contactFlags)) + { + WLog_ERR(TAG, "rdpei_read_ failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (contactData->fieldsPresent & CONTACT_DATA_CONTACTRECT_PRESENT) + { + if (!rdpei_read_2byte_signed(s, &contactData->contactRectLeft) || + !rdpei_read_2byte_signed(s, &contactData->contactRectTop) || + !rdpei_read_2byte_signed(s, &contactData->contactRectRight) || + !rdpei_read_2byte_signed(s, &contactData->contactRectBottom)) + { + WLog_ERR(TAG, "rdpei_read_ failed!"); + return ERROR_INTERNAL_ERROR; + } + } + + if ((contactData->fieldsPresent & CONTACT_DATA_ORIENTATION_PRESENT) && + !rdpei_read_4byte_unsigned(s, &contactData->orientation)) + { + WLog_ERR(TAG, "rdpei_read_ failed!"); + return ERROR_INTERNAL_ERROR; + } + + if ((contactData->fieldsPresent & CONTACT_DATA_PRESSURE_PRESENT) && + !rdpei_read_4byte_unsigned(s, &contactData->pressure)) + { + WLog_ERR(TAG, "rdpei_read_ failed!"); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +static UINT read_pen_contact(RdpeiServerContext* context, wStream* s, + RDPINPUT_PEN_CONTACT* contactData) +{ + WINPR_UNUSED(context); + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT8(s, contactData->deviceId); + if (!rdpei_read_2byte_unsigned(s, &contactData->fieldsPresent) || + !rdpei_read_4byte_signed(s, &contactData->x) || + !rdpei_read_4byte_signed(s, &contactData->y) || + !rdpei_read_4byte_unsigned(s, &contactData->contactFlags)) + { + WLog_ERR(TAG, "rdpei_read_ failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT) + { + if (!rdpei_read_4byte_unsigned(s, &contactData->penFlags)) + return ERROR_INVALID_DATA; + } + if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_PRESSURE_PRESENT) + { + if (!rdpei_read_4byte_unsigned(s, &contactData->pressure)) + return ERROR_INVALID_DATA; + } + if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_ROTATION_PRESENT) + { + if (!rdpei_read_2byte_unsigned(s, &contactData->rotation)) + return ERROR_INVALID_DATA; + } + if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_TILTX_PRESENT) + { + if (!rdpei_read_2byte_signed(s, &contactData->tiltX)) + return ERROR_INVALID_DATA; + } + if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_TILTY_PRESENT) + { + if (!rdpei_read_2byte_signed(s, &contactData->tiltY)) + return ERROR_INVALID_DATA; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT read_touch_frame(RdpeiServerContext* context, wStream* s, RDPINPUT_TOUCH_FRAME* frame) +{ + RDPINPUT_CONTACT_DATA* contact = nullptr; + UINT error = 0; + + if (!rdpei_read_2byte_unsigned(s, &frame->contactCount) || + !rdpei_read_8byte_unsigned(s, &frame->frameOffset)) + { + WLog_ERR(TAG, "rdpei_read_ failed!"); + return ERROR_INTERNAL_ERROR; + } + + frame->contacts = contact = calloc(frame->contactCount, sizeof(RDPINPUT_CONTACT_DATA)); + if (!frame->contacts) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + for (UINT32 i = 0; i < frame->contactCount; i++, contact++) + { + if ((error = read_touch_contact_data(context, s, contact))) + { + WLog_ERR(TAG, "read_touch_contact_data failed with error %" PRIu32 "!", error); + frame->contactCount = WINPR_ASSERTING_INT_CAST(UINT16, i); + touch_frame_reset(frame); + return error; + } + } + return CHANNEL_RC_OK; +} + +static UINT read_pen_frame(RdpeiServerContext* context, wStream* s, RDPINPUT_PEN_FRAME* frame) +{ + RDPINPUT_PEN_CONTACT* contact = nullptr; + UINT error = 0; + + if (!rdpei_read_2byte_unsigned(s, &frame->contactCount) || + !rdpei_read_8byte_unsigned(s, &frame->frameOffset)) + { + WLog_ERR(TAG, "rdpei_read_ failed!"); + return ERROR_INTERNAL_ERROR; + } + + frame->contacts = contact = calloc(frame->contactCount, sizeof(RDPINPUT_PEN_CONTACT)); + if (!frame->contacts) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + for (UINT32 i = 0; i < frame->contactCount; i++, contact++) + { + if ((error = read_pen_contact(context, s, contact))) + { + WLog_ERR(TAG, "read_touch_contact_data failed with error %" PRIu32 "!", error); + frame->contactCount = WINPR_ASSERTING_INT_CAST(UINT16, i); + + pen_frame_reset(frame); + return error; + } + } + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT read_touch_event(RdpeiServerContext* context, wStream* s) +{ + UINT16 frameCount = 0; + RDPINPUT_TOUCH_EVENT* event = &context->priv->touchEvent; + RDPINPUT_TOUCH_FRAME* frame = nullptr; + UINT error = CHANNEL_RC_OK; + + if (!rdpei_read_4byte_unsigned(s, &event->encodeTime) || + !rdpei_read_2byte_unsigned(s, &frameCount)) + { + WLog_ERR(TAG, "rdpei_read_ failed!"); + return ERROR_INTERNAL_ERROR; + } + + event->frameCount = frameCount; + event->frames = frame = calloc(event->frameCount, sizeof(RDPINPUT_TOUCH_FRAME)); + if (!event->frames) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + for (UINT32 i = 0; i < frameCount; i++, frame++) + { + if ((error = read_touch_frame(context, s, frame))) + { + WLog_ERR(TAG, "read_touch_contact_data failed with error %" PRIu32 "!", error); + event->frameCount = WINPR_ASSERTING_INT_CAST(UINT16, i); + + goto out_cleanup; + } + } + + IFCALLRET(context->onTouchEvent, error, context, event); + if (error) + WLog_ERR(TAG, "context->onTouchEvent failed with error %" PRIu32 "", error); + +out_cleanup: + touch_event_reset(event); + return error; +} + +static UINT read_pen_event(RdpeiServerContext* context, wStream* s) +{ + UINT16 frameCount = 0; + RDPINPUT_PEN_EVENT* event = &context->priv->penEvent; + RDPINPUT_PEN_FRAME* frame = nullptr; + UINT error = CHANNEL_RC_OK; + + if (!rdpei_read_4byte_unsigned(s, &event->encodeTime) || + !rdpei_read_2byte_unsigned(s, &frameCount)) + { + WLog_ERR(TAG, "rdpei_read_ failed!"); + return ERROR_INTERNAL_ERROR; + } + + event->frameCount = frameCount; + event->frames = frame = calloc(event->frameCount, sizeof(RDPINPUT_PEN_FRAME)); + if (!event->frames) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + for (UINT32 i = 0; i < frameCount; i++, frame++) + { + if ((error = read_pen_frame(context, s, frame))) + { + WLog_ERR(TAG, "read_pen_frame failed with error %" PRIu32 "!", error); + event->frameCount = WINPR_ASSERTING_INT_CAST(UINT16, i); + + goto out_cleanup; + } + } + + IFCALLRET(context->onPenEvent, error, context, event); + if (error) + WLog_ERR(TAG, "context->onPenEvent failed with error %" PRIu32 "", error); + +out_cleanup: + pen_event_reset(event); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT read_dismiss_hovering_contact(RdpeiServerContext* context, wStream* s) +{ + BYTE contactId = 0; + UINT error = CHANNEL_RC_OK; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT8(s, contactId); + + IFCALLRET(context->onTouchReleased, error, context, contactId); + if (error) + WLog_ERR(TAG, "context->onTouchReleased failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_server_handle_messages(RdpeiServerContext* context) +{ + DWORD bytesReturned = 0; + RdpeiServerPrivate* priv = context->priv; + wStream* s = priv->inputStream; + UINT error = CHANNEL_RC_OK; + + if (!WTSVirtualChannelRead(priv->channelHandle, 0, Stream_Pointer(s), priv->expectedBytes, + &bytesReturned)) + { + if (GetLastError() == ERROR_NO_DATA) + return ERROR_READ_FAULT; + + WLog_DBG(TAG, "channel connection closed"); + return CHANNEL_RC_OK; + } + priv->expectedBytes -= bytesReturned; + Stream_Seek(s, bytesReturned); + + if (priv->expectedBytes) + return CHANNEL_RC_OK; + + Stream_SealLength(s); + Stream_ResetPosition(s); + + if (priv->waitingHeaders) + { + UINT32 pduLen = 0; + + /* header case */ + Stream_Read_UINT16(s, priv->currentMsgType); + Stream_Read_UINT32(s, pduLen); + + if (pduLen < RDPINPUT_HEADER_LENGTH) + { + WLog_ERR(TAG, "invalid pduLength %" PRIu32 "", pduLen); + return ERROR_INVALID_DATA; + } + priv->expectedBytes = pduLen - RDPINPUT_HEADER_LENGTH; + priv->waitingHeaders = FALSE; + Stream_ResetPosition(s); + if (priv->expectedBytes) + { + if (!Stream_EnsureCapacity(s, priv->expectedBytes)) + { + WLog_ERR(TAG, "Stream_EnsureCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + return CHANNEL_RC_OK; + } + } + + /* when here we have the header + the body */ + switch (priv->currentMsgType) + { + case EVENTID_CS_READY: + if (priv->automataState != STATE_WAITING_CLIENT_READY) + { + WLog_ERR(TAG, "not expecting a CS_READY packet in this state(%u)", + priv->automataState); + return ERROR_INVALID_STATE; + } + + if ((error = read_cs_ready_message(context, s))) + { + WLog_ERR(TAG, "read_cs_ready_message failed with error %" PRIu32 "", error); + return error; + } + break; + + case EVENTID_TOUCH: + if ((error = read_touch_event(context, s))) + { + WLog_ERR(TAG, "read_touch_event failed with error %" PRIu32 "", error); + return error; + } + break; + case EVENTID_DISMISS_HOVERING_CONTACT: + if ((error = read_dismiss_hovering_contact(context, s))) + { + WLog_ERR(TAG, "read_dismiss_hovering_contact failed with error %" PRIu32 "", error); + return error; + } + break; + case EVENTID_PEN: + if ((error = read_pen_event(context, s))) + { + WLog_ERR(TAG, "read_pen_event failed with error %" PRIu32 "", error); + return error; + } + break; + default: + WLog_ERR(TAG, "unexpected message type 0x%" PRIx16 "", priv->currentMsgType); + } + + Stream_ResetPosition(s); + priv->waitingHeaders = TRUE; + priv->expectedBytes = RDPINPUT_HEADER_LENGTH; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_server_send_sc_ready(RdpeiServerContext* context, UINT32 version, UINT32 features) +{ + ULONG written = 0; + RdpeiServerPrivate* priv = context->priv; + UINT32 pduLen = 4; + + if (priv->automataState != STATE_INITIAL) + { + WLog_ERR(TAG, "called from unexpected state %u", priv->automataState); + return ERROR_INVALID_STATE; + } + + Stream_ResetPosition(priv->outputStream); + + if (version >= RDPINPUT_PROTOCOL_V300) + pduLen += 4; + + if (!Stream_EnsureCapacity(priv->outputStream, RDPINPUT_HEADER_LENGTH + pduLen)) + { + WLog_ERR(TAG, "Stream_EnsureCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(priv->outputStream, EVENTID_SC_READY); + Stream_Write_UINT32(priv->outputStream, RDPINPUT_HEADER_LENGTH + pduLen); + Stream_Write_UINT32(priv->outputStream, version); + if (version >= RDPINPUT_PROTOCOL_V300) + Stream_Write_UINT32(priv->outputStream, features); + + const size_t pos = Stream_GetPosition(priv->outputStream); + + WINPR_ASSERT(pos <= UINT32_MAX); + if (!WTSVirtualChannelWrite(priv->channelHandle, Stream_BufferAs(priv->outputStream, char), + (ULONG)pos, &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + return ERROR_INTERNAL_ERROR; + } + + priv->automataState = STATE_WAITING_CLIENT_READY; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_server_suspend(RdpeiServerContext* context) +{ + ULONG written = 0; + RdpeiServerPrivate* priv = context->priv; + + switch (priv->automataState) + { + case STATE_SUSPENDED: + WLog_ERR(TAG, "already suspended"); + return CHANNEL_RC_OK; + case STATE_WAITING_FRAME: + break; + default: + WLog_ERR(TAG, "called from unexpected state %u", priv->automataState); + return ERROR_INVALID_STATE; + } + + Stream_ResetPosition(priv->outputStream); + if (!Stream_EnsureCapacity(priv->outputStream, RDPINPUT_HEADER_LENGTH)) + { + WLog_ERR(TAG, "Stream_EnsureCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(priv->outputStream, EVENTID_SUSPEND_TOUCH); + Stream_Write_UINT32(priv->outputStream, RDPINPUT_HEADER_LENGTH); + + const size_t pos = Stream_GetPosition(priv->outputStream); + + WINPR_ASSERT(pos <= UINT32_MAX); + if (!WTSVirtualChannelWrite(priv->channelHandle, Stream_BufferAs(priv->outputStream, char), + (ULONG)pos, &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + return ERROR_INTERNAL_ERROR; + } + + priv->automataState = STATE_SUSPENDED; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpei_server_resume(RdpeiServerContext* context) +{ + ULONG written = 0; + RdpeiServerPrivate* priv = context->priv; + + switch (priv->automataState) + { + case STATE_WAITING_FRAME: + WLog_ERR(TAG, "not suspended"); + return CHANNEL_RC_OK; + case STATE_SUSPENDED: + break; + default: + WLog_ERR(TAG, "called from unexpected state %u", priv->automataState); + return ERROR_INVALID_STATE; + } + + Stream_ResetPosition(priv->outputStream); + if (!Stream_EnsureCapacity(priv->outputStream, RDPINPUT_HEADER_LENGTH)) + { + WLog_ERR(TAG, "Stream_EnsureCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(priv->outputStream, EVENTID_RESUME_TOUCH); + Stream_Write_UINT32(priv->outputStream, RDPINPUT_HEADER_LENGTH); + + const size_t pos = Stream_GetPosition(priv->outputStream); + + WINPR_ASSERT(pos <= UINT32_MAX); + if (!WTSVirtualChannelWrite(priv->channelHandle, Stream_BufferAs(priv->outputStream, char), + (ULONG)pos, &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + return ERROR_INTERNAL_ERROR; + } + + priv->automataState = STATE_WAITING_FRAME; + return CHANNEL_RC_OK; +} diff --git a/third_party/FreeRDP/channels/rdpei/server/rdpei_main.h b/third_party/FreeRDP/channels/rdpei/server/rdpei_main.h new file mode 100644 index 0000000..cf3e3cb --- /dev/null +++ b/third_party/FreeRDP/channels/rdpei/server/rdpei_main.h @@ -0,0 +1,32 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Extended Input channel server-side implementation + * + * Copyright 2014 David Fort + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPEI_SERVER_MAIN_H +#define FREERDP_CHANNEL_RDPEI_SERVER_MAIN_H + +#include +#include +#include +#include + +#define TAG CHANNELS_TAG("rdpei.server") + +#endif /* FREERDP_CHANNEL_RDPEI_SERVER_MAIN_H */ diff --git a/third_party/FreeRDP/channels/rdpemsc/CMakeLists.txt b/third_party/FreeRDP/channels/rdpemsc/CMakeLists.txt new file mode 100644 index 0000000..b17d26e --- /dev/null +++ b/third_party/FreeRDP/channels/rdpemsc/CMakeLists.txt @@ -0,0 +1,22 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2023 Pascal Nowack +# +# 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. + +define_channel("rdpemsc") + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/third_party/FreeRDP/channels/rdpemsc/ChannelOptions.cmake b/third_party/FreeRDP/channels/rdpemsc/ChannelOptions.cmake new file mode 100644 index 0000000..df2563f --- /dev/null +++ b/third_party/FreeRDP/channels/rdpemsc/ChannelOptions.cmake @@ -0,0 +1,20 @@ +set(OPTION_DEFAULT ON) +set(OPTION_CLIENT_DEFAULT OFF) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options( + NAME + "rdpemsc" + TYPE + "dynamic" + DESCRIPTION + "Mouse Cursor Virtual Channel Extension" + SPECIFICATIONS + "[MS-RDPEMSC]" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/rdpemsc/server/CMakeLists.txt b/third_party/FreeRDP/channels/rdpemsc/server/CMakeLists.txt new file mode 100644 index 0000000..5844b5d --- /dev/null +++ b/third_party/FreeRDP/channels/rdpemsc/server/CMakeLists.txt @@ -0,0 +1,24 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2023 Pascal Nowack +# +# 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. + +define_channel_server("rdpemsc") + +set(${MODULE_PREFIX}_SRCS mouse_cursor_main.c) + +set(${MODULE_PREFIX}_LIBS freerdp) + +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry") diff --git a/third_party/FreeRDP/channels/rdpemsc/server/mouse_cursor_main.c b/third_party/FreeRDP/channels/rdpemsc/server/mouse_cursor_main.c new file mode 100644 index 0000000..6dcbef6 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpemsc/server/mouse_cursor_main.c @@ -0,0 +1,771 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Mouse Cursor Virtual Channel Extension + * + * Copyright 2023 Pascal Nowack + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#define TAG CHANNELS_TAG("rdpemsc.server") + +typedef enum +{ + MOUSE_CURSOR_INITIAL, + MOUSE_CURSOR_OPENED, +} eMouseCursorChannelState; + +typedef struct +{ + MouseCursorServerContext context; + + HANDLE stopEvent; + + HANDLE thread; + void* mouse_cursor_channel; + + DWORD SessionId; + + BOOL isOpened; + BOOL externalThread; + + /* Channel state */ + eMouseCursorChannelState state; + + wStream* buffer; +} mouse_cursor_server; + +static UINT mouse_cursor_server_initialize(MouseCursorServerContext* context, BOOL externalThread) +{ + UINT error = CHANNEL_RC_OK; + mouse_cursor_server* mouse_cursor = (mouse_cursor_server*)context; + + WINPR_ASSERT(mouse_cursor); + + if (mouse_cursor->isOpened) + { + WLog_WARN(TAG, "Application error: Mouse Cursor channel already initialized, " + "calling in this state is not possible!"); + return ERROR_INVALID_STATE; + } + + mouse_cursor->externalThread = externalThread; + + return error; +} + +static UINT mouse_cursor_server_open_channel(mouse_cursor_server* mouse_cursor) +{ + MouseCursorServerContext* context = nullptr; + DWORD Error = ERROR_SUCCESS; + DWORD BytesReturned = 0; + PULONG pSessionId = nullptr; + UINT32 channelId = 0; + BOOL status = TRUE; + + WINPR_ASSERT(mouse_cursor); + context = &mouse_cursor->context; + WINPR_ASSERT(context); + + if (WTSQuerySessionInformationA(mouse_cursor->context.vcm, WTS_CURRENT_SESSION, WTSSessionId, + (LPSTR*)&pSessionId, &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSQuerySessionInformationA failed!"); + return ERROR_INTERNAL_ERROR; + } + + mouse_cursor->SessionId = (DWORD)*pSessionId; + WTSFreeMemory(pSessionId); + + mouse_cursor->mouse_cursor_channel = WTSVirtualChannelOpenEx( + mouse_cursor->SessionId, RDPEMSC_DVC_CHANNEL_NAME, WTS_CHANNEL_OPTION_DYNAMIC); + if (!mouse_cursor->mouse_cursor_channel) + { + Error = GetLastError(); + WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed with error %" PRIu32 "!", Error); + return Error; + } + + channelId = WTSChannelGetIdByHandle(mouse_cursor->mouse_cursor_channel); + + IFCALLRET(context->ChannelIdAssigned, status, context, channelId); + if (!status) + { + WLog_ERR(TAG, "context->ChannelIdAssigned failed!"); + return ERROR_INTERNAL_ERROR; + } + + return Error; +} + +static BOOL read_cap_set(wStream* s, wArrayList* capsSets) +{ + RDP_MOUSE_CURSOR_CAPSET* capsSet = nullptr; + UINT32 signature = 0; + UINT32 size = 0; + size_t capsDataSize = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 12)) + return FALSE; + + Stream_Read_UINT32(s, signature); + + RDP_MOUSE_CURSOR_CAPVERSION version = RDP_MOUSE_CURSOR_CAPVERSION_INVALID; + { + const UINT32 val = Stream_Get_UINT32(s); + switch (val) + { + case RDP_MOUSE_CURSOR_CAPVERSION_1: + version = RDP_MOUSE_CURSOR_CAPVERSION_1; + break; + default: + WLog_WARN(TAG, "Received caps set with unknown version %" PRIu32, val); + break; + } + } + + Stream_Read_UINT32(s, size); + + if (size < 12) + { + WLog_ERR(TAG, "Size of caps set is invalid: %u", size); + return FALSE; + } + + capsDataSize = size - 12; + if (!Stream_CheckAndLogRequiredLength(TAG, s, capsDataSize)) + return FALSE; + + switch (version) + { + case RDP_MOUSE_CURSOR_CAPVERSION_1: + { + RDP_MOUSE_CURSOR_CAPSET_VERSION1* capsSetV1 = nullptr; + + capsSetV1 = calloc(1, sizeof(RDP_MOUSE_CURSOR_CAPSET_VERSION1)); + if (!capsSetV1) + return FALSE; + + capsSet = (RDP_MOUSE_CURSOR_CAPSET*)capsSetV1; + break; + } + default: + Stream_Seek(s, capsDataSize); + return TRUE; + } + WINPR_ASSERT(capsSet); + + capsSet->signature = signature; + capsSet->version = version; + capsSet->size = size; + + if (!ArrayList_Append(capsSets, capsSet)) + { + WLog_ERR(TAG, "Failed to append caps set to arraylist"); + free(capsSet); + return FALSE; + } + + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): ArrayList_Append owns capsSet + return TRUE; +} + +static UINT mouse_cursor_server_recv_cs_caps_advertise(MouseCursorServerContext* context, + wStream* s, + const RDP_MOUSE_CURSOR_HEADER* header) +{ + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(s); + WINPR_ASSERT(header); + + /* There must be at least one capability set present */ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 12)) + return ERROR_NO_DATA; + + RDP_MOUSE_CURSOR_CAPS_ADVERTISE_PDU pdu = { + .header = *header, + .capsSets = ArrayList_New(FALSE), + }; + + if (!pdu.capsSets) + { + WLog_ERR(TAG, "Failed to allocate arraylist"); + return ERROR_NOT_ENOUGH_MEMORY; + } + + wObject* aobj = ArrayList_Object(pdu.capsSets); + WINPR_ASSERT(aobj); + aobj->fnObjectFree = free; + + while (Stream_GetRemainingLength(s) > 0) + { + if (!read_cap_set(s, pdu.capsSets)) + { + ArrayList_Free(pdu.capsSets); + return ERROR_INVALID_DATA; + } + } + + IFCALLRET(context->CapsAdvertise, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->CapsAdvertise failed with error %" PRIu32 "", error); + + ArrayList_Free(pdu.capsSets); + + return error; +} + +static UINT mouse_cursor_process_message(mouse_cursor_server* mouse_cursor) +{ + BOOL rc = 0; + UINT error = ERROR_INTERNAL_ERROR; + ULONG BytesReturned = 0; + wStream* s = nullptr; + + WINPR_ASSERT(mouse_cursor); + WINPR_ASSERT(mouse_cursor->mouse_cursor_channel); + + s = mouse_cursor->buffer; + WINPR_ASSERT(s); + + Stream_ResetPosition(s); + rc = WTSVirtualChannelRead(mouse_cursor->mouse_cursor_channel, 0, nullptr, 0, &BytesReturned); + if (!rc) + goto out; + + if (BytesReturned < 1) + { + error = CHANNEL_RC_OK; + goto out; + } + + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + if (WTSVirtualChannelRead(mouse_cursor->mouse_cursor_channel, 0, Stream_BufferAs(s, char), + (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + goto out; + } + + Stream_SetLength(s, BytesReturned); + if (!Stream_CheckAndLogRequiredLength(TAG, s, RDPEMSC_HEADER_SIZE)) + return ERROR_NO_DATA; + + { + const UINT8 pduType = Stream_Get_UINT8(s); + const UINT8 updateType = Stream_Get_UINT8(s); + switch (updateType) + { + case TS_UPDATETYPE_MOUSEPTR_SYSTEM_NULL: + case TS_UPDATETYPE_MOUSEPTR_SYSTEM_DEFAULT: + case TS_UPDATETYPE_MOUSEPTR_POSITION: + case TS_UPDATETYPE_MOUSEPTR_CACHED: + case TS_UPDATETYPE_MOUSEPTR_POINTER: + case TS_UPDATETYPE_MOUSEPTR_LARGE_POINTER: + break; + default: + WLog_ERR(TAG, + "mouse_cursor_process_message: unknown or invalid updateType %" PRIu8 "", + updateType); + return ERROR_INVALID_DATA; + } + + RDP_MOUSE_CURSOR_HEADER header = { .updateType = (TS_UPDATETYPE_MOUSEPTR)updateType, + .reserved = Stream_Get_UINT16(s), + .pduType = PDUTYPE_EMSC_RESERVED }; + + switch (pduType) + { + case PDUTYPE_CS_CAPS_ADVERTISE: + header.pduType = PDUTYPE_CS_CAPS_ADVERTISE; + error = + mouse_cursor_server_recv_cs_caps_advertise(&mouse_cursor->context, s, &header); + break; + default: + WLog_ERR(TAG, "mouse_cursor_process_message: unknown or invalid pduType %" PRIu8 "", + pduType); + break; + } + } + +out: + if (error) + WLog_ERR(TAG, "Response failed with error %" PRIu32 "!", error); + + return error; +} + +static UINT mouse_cursor_server_context_poll_int(MouseCursorServerContext* context) +{ + mouse_cursor_server* mouse_cursor = (mouse_cursor_server*)context; + UINT error = ERROR_INTERNAL_ERROR; + + WINPR_ASSERT(mouse_cursor); + + switch (mouse_cursor->state) + { + case MOUSE_CURSOR_INITIAL: + error = mouse_cursor_server_open_channel(mouse_cursor); + if (error) + WLog_ERR(TAG, "mouse_cursor_server_open_channel failed with error %" PRIu32 "!", + error); + else + mouse_cursor->state = MOUSE_CURSOR_OPENED; + break; + case MOUSE_CURSOR_OPENED: + error = mouse_cursor_process_message(mouse_cursor); + break; + default: + break; + } + + return error; +} + +static HANDLE mouse_cursor_server_get_channel_handle(mouse_cursor_server* mouse_cursor) +{ + void* buffer = nullptr; + DWORD BytesReturned = 0; + HANDLE ChannelEvent = nullptr; + + WINPR_ASSERT(mouse_cursor); + + if (WTSVirtualChannelQuery(mouse_cursor->mouse_cursor_channel, WTSVirtualEventHandle, &buffer, + &BytesReturned) == TRUE) + { + if (BytesReturned == sizeof(HANDLE)) + ChannelEvent = *(HANDLE*)buffer; + + WTSFreeMemory(buffer); + } + + return ChannelEvent; +} + +static DWORD WINAPI mouse_cursor_server_thread_func(LPVOID arg) +{ + DWORD nCount = 0; + HANDLE events[2] = WINPR_C_ARRAY_INIT; + mouse_cursor_server* mouse_cursor = (mouse_cursor_server*)arg; + UINT error = CHANNEL_RC_OK; + DWORD status = 0; + + WINPR_ASSERT(mouse_cursor); + + nCount = 0; + events[nCount++] = mouse_cursor->stopEvent; + + while ((error == CHANNEL_RC_OK) && (WaitForSingleObject(events[0], 0) != WAIT_OBJECT_0)) + { + switch (mouse_cursor->state) + { + case MOUSE_CURSOR_INITIAL: + error = mouse_cursor_server_context_poll_int(&mouse_cursor->context); + if (error == CHANNEL_RC_OK) + { + events[1] = mouse_cursor_server_get_channel_handle(mouse_cursor); + nCount = 2; + } + break; + case MOUSE_CURSOR_OPENED: + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + switch (status) + { + case WAIT_OBJECT_0: + break; + case WAIT_OBJECT_0 + 1: + case WAIT_TIMEOUT: + error = mouse_cursor_server_context_poll_int(&mouse_cursor->context); + break; + + case WAIT_FAILED: + default: + error = ERROR_INTERNAL_ERROR; + break; + } + break; + default: + break; + } + } + + (void)WTSVirtualChannelClose(mouse_cursor->mouse_cursor_channel); + mouse_cursor->mouse_cursor_channel = nullptr; + + if (error && mouse_cursor->context.rdpcontext) + setChannelError(mouse_cursor->context.rdpcontext, error, + "mouse_cursor_server_thread_func reported an error"); + + ExitThread(error); + return error; +} + +static UINT mouse_cursor_server_open(MouseCursorServerContext* context) +{ + mouse_cursor_server* mouse_cursor = (mouse_cursor_server*)context; + + WINPR_ASSERT(mouse_cursor); + + if (!mouse_cursor->externalThread && (mouse_cursor->thread == nullptr)) + { + mouse_cursor->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + if (!mouse_cursor->stopEvent) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + mouse_cursor->thread = + CreateThread(nullptr, 0, mouse_cursor_server_thread_func, mouse_cursor, 0, nullptr); + if (!mouse_cursor->thread) + { + WLog_ERR(TAG, "CreateThread failed!"); + (void)CloseHandle(mouse_cursor->stopEvent); + mouse_cursor->stopEvent = nullptr; + return ERROR_INTERNAL_ERROR; + } + } + mouse_cursor->isOpened = TRUE; + + return CHANNEL_RC_OK; +} + +static UINT mouse_cursor_server_close(MouseCursorServerContext* context) +{ + UINT error = CHANNEL_RC_OK; + mouse_cursor_server* mouse_cursor = (mouse_cursor_server*)context; + + WINPR_ASSERT(mouse_cursor); + + if (!mouse_cursor->externalThread && mouse_cursor->thread) + { + (void)SetEvent(mouse_cursor->stopEvent); + + if (WaitForSingleObject(mouse_cursor->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + return error; + } + + (void)CloseHandle(mouse_cursor->thread); + (void)CloseHandle(mouse_cursor->stopEvent); + mouse_cursor->thread = nullptr; + mouse_cursor->stopEvent = nullptr; + } + if (mouse_cursor->externalThread) + { + if (mouse_cursor->state != MOUSE_CURSOR_INITIAL) + { + (void)WTSVirtualChannelClose(mouse_cursor->mouse_cursor_channel); + mouse_cursor->mouse_cursor_channel = nullptr; + mouse_cursor->state = MOUSE_CURSOR_INITIAL; + } + } + mouse_cursor->isOpened = FALSE; + + return error; +} + +static UINT mouse_cursor_server_context_poll(MouseCursorServerContext* context) +{ + mouse_cursor_server* mouse_cursor = (mouse_cursor_server*)context; + + WINPR_ASSERT(mouse_cursor); + + if (!mouse_cursor->externalThread) + return ERROR_INTERNAL_ERROR; + + return mouse_cursor_server_context_poll_int(context); +} + +static BOOL mouse_cursor_server_context_handle(MouseCursorServerContext* context, HANDLE* handle) +{ + mouse_cursor_server* mouse_cursor = (mouse_cursor_server*)context; + + WINPR_ASSERT(mouse_cursor); + WINPR_ASSERT(handle); + + if (!mouse_cursor->externalThread) + return FALSE; + if (mouse_cursor->state == MOUSE_CURSOR_INITIAL) + return FALSE; + + *handle = mouse_cursor_server_get_channel_handle(mouse_cursor); + + return TRUE; +} + +static wStream* mouse_cursor_server_packet_new(size_t size, RDP_MOUSE_CURSOR_PDUTYPE pduType, + const RDP_MOUSE_CURSOR_HEADER* header) +{ + wStream* s = nullptr; + + /* Allocate what we need plus header bytes */ + s = Stream_New(nullptr, size + RDPEMSC_HEADER_SIZE); + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return nullptr; + } + + WINPR_ASSERT(pduType <= UINT8_MAX); + Stream_Write_UINT8(s, (BYTE)pduType); + + WINPR_ASSERT(header->updateType <= UINT8_MAX); + Stream_Write_UINT8(s, (BYTE)header->updateType); + Stream_Write_UINT16(s, header->reserved); + + return s; +} + +static UINT mouse_cursor_server_packet_send(MouseCursorServerContext* context, wStream* s) +{ + mouse_cursor_server* mouse_cursor = (mouse_cursor_server*)context; + UINT error = CHANNEL_RC_OK; + ULONG written = 0; + + WINPR_ASSERT(mouse_cursor); + WINPR_ASSERT(s); + + const size_t pos = Stream_GetPosition(s); + + WINPR_ASSERT(pos <= UINT32_MAX); + if (!WTSVirtualChannelWrite(mouse_cursor->mouse_cursor_channel, Stream_BufferAs(s, char), + (ULONG)pos, &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + error = ERROR_INTERNAL_ERROR; + goto out; + } + + if (written < Stream_GetPosition(s)) + { + WLog_WARN(TAG, "Unexpected bytes written: %" PRIu32 "/%" PRIuz "", written, + Stream_GetPosition(s)); + } + +out: + Stream_Free(s, TRUE); + return error; +} + +static UINT +mouse_cursor_server_send_sc_caps_confirm(MouseCursorServerContext* context, + const RDP_MOUSE_CURSOR_CAPS_CONFIRM_PDU* capsConfirm) +{ + RDP_MOUSE_CURSOR_CAPSET* capsetHeader = nullptr; + RDP_MOUSE_CURSOR_PDUTYPE pduType = PDUTYPE_EMSC_RESERVED; + size_t caps_size = 0; + wStream* s = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(capsConfirm); + + capsetHeader = capsConfirm->capsSet; + WINPR_ASSERT(capsetHeader); + + caps_size = 12; + switch (capsetHeader->version) + { + case RDP_MOUSE_CURSOR_CAPVERSION_1: + break; + default: + WINPR_ASSERT(FALSE); + break; + } + + pduType = PDUTYPE_SC_CAPS_CONFIRM; + s = mouse_cursor_server_packet_new(caps_size, pduType, &capsConfirm->header); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + Stream_Write_UINT32(s, capsetHeader->signature); + Stream_Write_UINT32(s, capsetHeader->version); + Stream_Write_UINT32(s, capsetHeader->size); + + /* Write capsData */ + switch (capsetHeader->version) + { + case RDP_MOUSE_CURSOR_CAPVERSION_1: + break; + default: + WINPR_ASSERT(FALSE); + break; + } + + return mouse_cursor_server_packet_send(context, s); +} + +static void write_point16(wStream* s, const TS_POINT16* point16) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(point16); + + Stream_Write_UINT16(s, point16->xPos); + Stream_Write_UINT16(s, point16->yPos); +} + +static UINT mouse_cursor_server_send_sc_mouseptr_update( + MouseCursorServerContext* context, const RDP_MOUSE_CURSOR_MOUSEPTR_UPDATE_PDU* mouseptrUpdate) +{ + TS_POINT16* position = nullptr; + TS_POINTERATTRIBUTE* pointerAttribute = nullptr; + TS_LARGEPOINTERATTRIBUTE* largePointerAttribute = nullptr; + RDP_MOUSE_CURSOR_PDUTYPE pduType = PDUTYPE_EMSC_RESERVED; + size_t update_size = 0; + wStream* s = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(mouseptrUpdate); + + position = mouseptrUpdate->position; + pointerAttribute = mouseptrUpdate->pointerAttribute; + largePointerAttribute = mouseptrUpdate->largePointerAttribute; + + switch (mouseptrUpdate->header.updateType) + { + case TS_UPDATETYPE_MOUSEPTR_SYSTEM_NULL: + case TS_UPDATETYPE_MOUSEPTR_SYSTEM_DEFAULT: + update_size = 0; + break; + case TS_UPDATETYPE_MOUSEPTR_POSITION: + WINPR_ASSERT(position); + update_size = 4; + break; + case TS_UPDATETYPE_MOUSEPTR_CACHED: + WINPR_ASSERT(mouseptrUpdate->cachedPointerIndex); + update_size = 2; + break; + case TS_UPDATETYPE_MOUSEPTR_POINTER: + WINPR_ASSERT(pointerAttribute); + update_size = 2 + 2 + 4 + 2 + 2 + 2 + 2; + update_size += pointerAttribute->lengthAndMask; + update_size += pointerAttribute->lengthXorMask; + break; + case TS_UPDATETYPE_MOUSEPTR_LARGE_POINTER: + WINPR_ASSERT(largePointerAttribute); + update_size = 2 + 2 + 4 + 2 + 2 + 4 + 4; + update_size += largePointerAttribute->lengthAndMask; + update_size += largePointerAttribute->lengthXorMask; + break; + default: + WINPR_ASSERT(FALSE); + break; + } + + pduType = PDUTYPE_SC_MOUSEPTR_UPDATE; + s = mouse_cursor_server_packet_new(update_size, pduType, &mouseptrUpdate->header); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + switch (mouseptrUpdate->header.updateType) + { + case TS_UPDATETYPE_MOUSEPTR_SYSTEM_NULL: + case TS_UPDATETYPE_MOUSEPTR_SYSTEM_DEFAULT: + break; + case TS_UPDATETYPE_MOUSEPTR_POSITION: + write_point16(s, position); + break; + case TS_UPDATETYPE_MOUSEPTR_CACHED: + Stream_Write_UINT16(s, *mouseptrUpdate->cachedPointerIndex); + break; + case TS_UPDATETYPE_MOUSEPTR_POINTER: + Stream_Write_UINT16(s, pointerAttribute->xorBpp); + Stream_Write_UINT16(s, pointerAttribute->cacheIndex); + write_point16(s, &pointerAttribute->hotSpot); + Stream_Write_UINT16(s, pointerAttribute->width); + Stream_Write_UINT16(s, pointerAttribute->height); + Stream_Write_UINT16(s, pointerAttribute->lengthAndMask); + Stream_Write_UINT16(s, pointerAttribute->lengthXorMask); + Stream_Write(s, pointerAttribute->xorMaskData, pointerAttribute->lengthXorMask); + Stream_Write(s, pointerAttribute->andMaskData, pointerAttribute->lengthAndMask); + break; + case TS_UPDATETYPE_MOUSEPTR_LARGE_POINTER: + Stream_Write_UINT16(s, largePointerAttribute->xorBpp); + Stream_Write_UINT16(s, largePointerAttribute->cacheIndex); + write_point16(s, &largePointerAttribute->hotSpot); + Stream_Write_UINT16(s, largePointerAttribute->width); + Stream_Write_UINT16(s, largePointerAttribute->height); + Stream_Write_UINT32(s, largePointerAttribute->lengthAndMask); + Stream_Write_UINT32(s, largePointerAttribute->lengthXorMask); + Stream_Write(s, largePointerAttribute->xorMaskData, + largePointerAttribute->lengthXorMask); + Stream_Write(s, largePointerAttribute->andMaskData, + largePointerAttribute->lengthAndMask); + break; + default: + WINPR_ASSERT(FALSE); + break; + } + + return mouse_cursor_server_packet_send(context, s); +} + +MouseCursorServerContext* mouse_cursor_server_context_new(HANDLE vcm) +{ + mouse_cursor_server* mouse_cursor = + (mouse_cursor_server*)calloc(1, sizeof(mouse_cursor_server)); + + if (!mouse_cursor) + return nullptr; + + mouse_cursor->context.vcm = vcm; + mouse_cursor->context.Initialize = mouse_cursor_server_initialize; + mouse_cursor->context.Open = mouse_cursor_server_open; + mouse_cursor->context.Close = mouse_cursor_server_close; + mouse_cursor->context.Poll = mouse_cursor_server_context_poll; + mouse_cursor->context.ChannelHandle = mouse_cursor_server_context_handle; + + mouse_cursor->context.CapsConfirm = mouse_cursor_server_send_sc_caps_confirm; + mouse_cursor->context.MouseptrUpdate = mouse_cursor_server_send_sc_mouseptr_update; + + mouse_cursor->buffer = Stream_New(nullptr, 4096); + if (!mouse_cursor->buffer) + goto fail; + + return &mouse_cursor->context; +fail: + WINPR_PRAGMA_DIAG_PUSH + WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC + mouse_cursor_server_context_free(&mouse_cursor->context); + WINPR_PRAGMA_DIAG_POP + return nullptr; +} + +void mouse_cursor_server_context_free(MouseCursorServerContext* context) +{ + mouse_cursor_server* mouse_cursor = (mouse_cursor_server*)context; + + if (mouse_cursor) + { + mouse_cursor_server_close(context); + Stream_Free(mouse_cursor->buffer, TRUE); + } + + free(mouse_cursor); +} diff --git a/third_party/FreeRDP/channels/rdpgfx/CMakeLists.txt b/third_party/FreeRDP/channels/rdpgfx/CMakeLists.txt new file mode 100644 index 0000000..4745eb8 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpgfx/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel("rdpgfx") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/third_party/FreeRDP/channels/rdpgfx/ChannelOptions.cmake b/third_party/FreeRDP/channels/rdpgfx/ChannelOptions.cmake new file mode 100644 index 0000000..ce3b5e3 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpgfx/ChannelOptions.cmake @@ -0,0 +1,20 @@ +set(OPTION_DEFAULT ON) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options( + NAME + "rdpgfx" + TYPE + "dynamic" + DESCRIPTION + "Graphics Pipeline Extension" + SPECIFICATIONS + "[MS-RDPEGFX]" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/rdpgfx/client/CMakeLists.txt b/third_party/FreeRDP/channels/rdpgfx/client/CMakeLists.txt new file mode 100644 index 0000000..69cec9b --- /dev/null +++ b/third_party/FreeRDP/channels/rdpgfx/client/CMakeLists.txt @@ -0,0 +1,27 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2013 Marc-Andre Moreau +# +# 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. + +define_channel_client("rdpgfx") + +set(${MODULE_PREFIX}_SRCS rdpgfx_main.c rdpgfx_main.h rdpgfx_codec.c rdpgfx_codec.h ../rdpgfx_common.c + ../rdpgfx_common.h +) + +set(${MODULE_PREFIX}_LIBS winpr freerdp) +include_directories(..) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") diff --git a/third_party/FreeRDP/channels/rdpgfx/client/rdpgfx_codec.c b/third_party/FreeRDP/channels/rdpgfx/client/rdpgfx_codec.c new file mode 100644 index 0000000..7fc4f15 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpgfx/client/rdpgfx_codec.c @@ -0,0 +1,305 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Graphics Pipeline Extension + * + * Copyright 2014 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include + +#include "rdpgfx_common.h" + +#include "rdpgfx_codec.h" + +#define TAG CHANNELS_TAG("rdpgfx.client") + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_read_h264_metablock(WINPR_ATTR_UNUSED RDPGFX_PLUGIN* gfx, wStream* s, + RDPGFX_H264_METABLOCK* meta) +{ + RECTANGLE_16* regionRect = nullptr; + RDPGFX_H264_QUANT_QUALITY* quantQualityVal = nullptr; + UINT error = ERROR_INVALID_DATA; + meta->regionRects = nullptr; + meta->quantQualityVals = nullptr; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + goto error_out; + + Stream_Read_UINT32(s, meta->numRegionRects); /* numRegionRects (4 bytes) */ + + if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, meta->numRegionRects, 8ull)) + goto error_out; + + meta->regionRects = (RECTANGLE_16*)calloc(meta->numRegionRects, sizeof(RECTANGLE_16)); + + if (!meta->regionRects) + { + WLog_ERR(TAG, "malloc failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + + meta->quantQualityVals = + (RDPGFX_H264_QUANT_QUALITY*)calloc(meta->numRegionRects, sizeof(RDPGFX_H264_QUANT_QUALITY)); + + if (!meta->quantQualityVals) + { + WLog_ERR(TAG, "malloc failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + + WLog_DBG(TAG, "H264_METABLOCK: numRegionRects: %" PRIu32 "", meta->numRegionRects); + + for (UINT32 index = 0; index < meta->numRegionRects; index++) + { + regionRect = &(meta->regionRects[index]); + + if ((error = rdpgfx_read_rect16(s, regionRect))) + { + WLog_ERR(TAG, "rdpgfx_read_rect16 failed with error %" PRIu32 "!", error); + goto error_out; + } + + WLog_DBG(TAG, + "regionRects[%" PRIu32 "]: left: %" PRIu16 " top: %" PRIu16 " right: %" PRIu16 + " bottom: %" PRIu16 "", + index, regionRect->left, regionRect->top, regionRect->right, regionRect->bottom); + } + + if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, meta->numRegionRects, 2ull)) + { + error = ERROR_INVALID_DATA; + goto error_out; + } + + for (UINT32 index = 0; index < meta->numRegionRects; index++) + { + quantQualityVal = &(meta->quantQualityVals[index]); + Stream_Read_UINT8(s, quantQualityVal->qpVal); /* qpVal (1 byte) */ + Stream_Read_UINT8(s, quantQualityVal->qualityVal); /* qualityVal (1 byte) */ + quantQualityVal->qp = quantQualityVal->qpVal & 0x3F; + quantQualityVal->r = (quantQualityVal->qpVal >> 6) & 1; + quantQualityVal->p = (quantQualityVal->qpVal >> 7) & 1; + WLog_DBG(TAG, + "quantQualityVals[%" PRIu32 "]: qp: %" PRIu8 " r: %" PRIu8 " p: %" PRIu8 + " qualityVal: %" PRIu8 "", + index, quantQualityVal->qp, quantQualityVal->r, quantQualityVal->p, + quantQualityVal->qualityVal); + } + + return CHANNEL_RC_OK; +error_out: + free_h264_metablock(meta); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_decode_AVC420(RDPGFX_PLUGIN* gfx, RDPGFX_SURFACE_COMMAND* cmd) +{ + UINT error = 0; + RDPGFX_AVC420_BITMAP_STREAM h264 = WINPR_C_ARRAY_INIT; + RdpgfxClientContext* context = gfx->context; + wStream* s = Stream_New(cmd->data, cmd->length); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if ((error = rdpgfx_read_h264_metablock(gfx, s, &(h264.meta)))) + { + Stream_Free(s, FALSE); + WLog_ERR(TAG, "rdpgfx_read_h264_metablock failed with error %" PRIu32 "!", error); + return error; + } + + h264.data = Stream_Pointer(s); + h264.length = (UINT32)Stream_GetRemainingLength(s); + Stream_Free(s, FALSE); + cmd->extra = (void*)&h264; + + if (context) + { + IFCALLRET(context->SurfaceCommand, error, context, cmd); + + if (error) + WLog_ERR(TAG, "context->SurfaceCommand failed with error %" PRIu32 "", error); + } + + free_h264_metablock(&h264.meta); + cmd->extra = nullptr; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_decode_AVC444(RDPGFX_PLUGIN* gfx, RDPGFX_SURFACE_COMMAND* cmd) +{ + UINT error = 0; + UINT32 tmp = 0; + size_t pos1 = 0; + size_t pos2 = 0; + + RDPGFX_AVC444_BITMAP_STREAM h264 = WINPR_C_ARRAY_INIT; + RdpgfxClientContext* context = gfx->context; + wStream* s = Stream_New(cmd->data, cmd->length); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + { + error = ERROR_INVALID_DATA; + goto fail; + } + + Stream_Read_UINT32(s, tmp); + h264.cbAvc420EncodedBitstream1 = tmp & 0x3FFFFFFFUL; + h264.LC = (tmp >> 30UL) & 0x03UL; + + if (h264.LC == 0x03) + { + error = ERROR_INVALID_DATA; + goto fail; + } + + pos1 = Stream_GetPosition(s); + + if ((error = rdpgfx_read_h264_metablock(gfx, s, &(h264.bitstream[0].meta)))) + { + WLog_ERR(TAG, "rdpgfx_read_h264_metablock failed with error %" PRIu32 "!", error); + goto fail; + } + + pos2 = Stream_GetPosition(s); + h264.bitstream[0].data = Stream_Pointer(s); + + if (h264.LC == 0) + { + const size_t bitstreamLen = 1ULL * h264.cbAvc420EncodedBitstream1 - pos2 + pos1; + + if ((bitstreamLen > UINT32_MAX) || !Stream_CheckAndLogRequiredLength(TAG, s, bitstreamLen)) + { + error = ERROR_INVALID_DATA; + goto fail; + } + + h264.bitstream[0].length = (UINT32)bitstreamLen; + Stream_Seek(s, bitstreamLen); + + if ((error = rdpgfx_read_h264_metablock(gfx, s, &(h264.bitstream[1].meta)))) + { + WLog_ERR(TAG, "rdpgfx_read_h264_metablock failed with error %" PRIu32 "!", error); + goto fail; + } + + h264.bitstream[1].data = Stream_Pointer(s); + + const size_t len = Stream_GetRemainingLength(s); + if (len > UINT32_MAX) + goto fail; + h264.bitstream[1].length = (UINT32)len; + } + else + { + const size_t len = Stream_GetRemainingLength(s); + if (len > UINT32_MAX) + goto fail; + h264.bitstream[0].length = (UINT32)len; + } + + cmd->extra = (void*)&h264; + + if (context) + { + IFCALLRET(context->SurfaceCommand, error, context, cmd); + + if (error) + WLog_ERR(TAG, "context->SurfaceCommand failed with error %" PRIu32 "", error); + } + +fail: + Stream_Free(s, FALSE); + free_h264_metablock(&h264.bitstream[0].meta); + free_h264_metablock(&h264.bitstream[1].meta); + cmd->extra = nullptr; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpgfx_decode(RDPGFX_PLUGIN* gfx, RDPGFX_SURFACE_COMMAND* cmd) +{ + UINT error = CHANNEL_RC_OK; + RdpgfxClientContext* context = gfx->context; + PROFILER_ENTER(context->SurfaceProfiler) + + switch (cmd->codecId) + { + case RDPGFX_CODECID_AVC420: + if ((error = rdpgfx_decode_AVC420(gfx, cmd))) + WLog_ERR(TAG, "rdpgfx_decode_AVC420 failed with error %" PRIu32 "", error); + + break; + + case RDPGFX_CODECID_AVC444: + case RDPGFX_CODECID_AVC444v2: + if ((error = rdpgfx_decode_AVC444(gfx, cmd))) + WLog_ERR(TAG, "rdpgfx_decode_AVC444 failed with error %" PRIu32 "", error); + + break; + + default: + if (context) + { + IFCALLRET(context->SurfaceCommand, error, context, cmd); + + if (error) + WLog_ERR(TAG, "context->SurfaceCommand failed with error %" PRIu32 "", error); + } + + break; + } + + PROFILER_EXIT(context->SurfaceProfiler) + return error; +} diff --git a/third_party/FreeRDP/channels/rdpgfx/client/rdpgfx_codec.h b/third_party/FreeRDP/channels/rdpgfx/client/rdpgfx_codec.h new file mode 100644 index 0000000..c1a0538 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpgfx/client/rdpgfx_codec.h @@ -0,0 +1,36 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Graphics Pipeline Extension + * + * Copyright 2014 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPGFX_CLIENT_CODEC_H +#define FREERDP_CHANNEL_RDPGFX_CLIENT_CODEC_H + +#include +#include + +#include +#include + +#include "rdpgfx_main.h" + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT rdpgfx_decode(RDPGFX_PLUGIN* gfx, RDPGFX_SURFACE_COMMAND* cmd); + +#endif /* FREERDP_CHANNEL_RDPGFX_CLIENT_CODEC_H */ diff --git a/third_party/FreeRDP/channels/rdpgfx/client/rdpgfx_main.c b/third_party/FreeRDP/channels/rdpgfx/client/rdpgfx_main.c new file mode 100644 index 0000000..c18a91d --- /dev/null +++ b/third_party/FreeRDP/channels/rdpgfx/client/rdpgfx_main.c @@ -0,0 +1,2384 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Graphics Pipeline Extension + * + * Copyright 2013-2014 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 Thincast Technologies GmbH + * Copyright 2016 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "rdpgfx_common.h" +#include "rdpgfx_codec.h" + +#include "rdpgfx_main.h" + +#define TAG CHANNELS_TAG("rdpgfx.client") + +static BOOL delete_surface(const void* key, void* value, void* arg) +{ + const UINT16 id = (UINT16)(uintptr_t)(key); + RdpgfxClientContext* context = arg; + RDPGFX_DELETE_SURFACE_PDU pdu = WINPR_C_ARRAY_INIT; + + WINPR_UNUSED(value); + pdu.surfaceId = id - 1; + + if (context) + { + UINT error = CHANNEL_RC_OK; + IFCALLRET(context->DeleteSurface, error, context, &pdu); + + if (error) + { + WLog_ERR(TAG, "context->DeleteSurface failed with error %" PRIu32 "", error); + } + } + return TRUE; +} + +static void free_surfaces(RdpgfxClientContext* context, wHashTable* SurfaceTable) +{ + HashTable_Foreach(SurfaceTable, delete_surface, context); +} + +static UINT evict_cache_slots(RdpgfxClientContext* context, UINT16 MaxCacheSlots, void** CacheSlots) +{ + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(CacheSlots); + for (UINT16 index = 0; index < MaxCacheSlots; index++) + { + if (CacheSlots[index]) + { + RDPGFX_EVICT_CACHE_ENTRY_PDU pdu = WINPR_C_ARRAY_INIT; + pdu.cacheSlot = index + 1; + + if (context && context->EvictCacheEntry) + { + const UINT rc = context->EvictCacheEntry(context, &pdu); + if (rc != CHANNEL_RC_OK) + error = rc; + } + + CacheSlots[index] = nullptr; + } + } + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_caps_advertise_pdu(RdpgfxClientContext* context, + const RDPGFX_CAPS_ADVERTISE_PDU* pdu) +{ + UINT error = CHANNEL_RC_OK; + RDPGFX_HEADER header = WINPR_C_ARRAY_INIT; + RDPGFX_PLUGIN* gfx = nullptr; + GENERIC_CHANNEL_CALLBACK* callback = nullptr; + wStream* s = nullptr; + + WINPR_ASSERT(pdu); + WINPR_ASSERT(context); + + gfx = (RDPGFX_PLUGIN*)context->handle; + + if (!gfx || !gfx->base.listener_callback) + return ERROR_BAD_ARGUMENTS; + + callback = gfx->base.listener_callback->channel_callback; + + header.flags = 0; + header.cmdId = RDPGFX_CMDID_CAPSADVERTISE; + header.pduLength = RDPGFX_HEADER_SIZE + 2; + + for (UINT16 index = 0; index < pdu->capsSetCount; index++) + { + const RDPGFX_CAPSET* capsSet = &(pdu->capsSets[index]); + header.pduLength += RDPGFX_CAPSET_BASE_SIZE + capsSet->length; + } + + DEBUG_RDPGFX(gfx->log, "SendCapsAdvertisePdu %" PRIu16 "", pdu->capsSetCount); + s = Stream_New(nullptr, header.pduLength); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if ((error = rdpgfx_write_header(s, &header))) + goto fail; + + /* RDPGFX_CAPS_ADVERTISE_PDU */ + Stream_Write_UINT16(s, pdu->capsSetCount); /* capsSetCount (2 bytes) */ + + for (UINT16 index = 0; index < pdu->capsSetCount; index++) + { + const RDPGFX_CAPSET* capsSet = &(pdu->capsSets[index]); + + DEBUG_RDPGFX(gfx->log, "Sending %s [0x%08" PRIx32 "] flags=0x%08" PRIx32, + rdpgfx_caps_version_str(capsSet->version), capsSet->version, capsSet->flags); + + Stream_Write_UINT32(s, capsSet->version); /* version (4 bytes) */ + Stream_Write_UINT32(s, capsSet->length); /* capsDataLength (4 bytes) */ + Stream_Write_UINT32(s, capsSet->flags); /* capsData (4 bytes) */ + Stream_Zero(s, capsSet->length - 4); + } + + Stream_SealLength(s); + error = callback->channel->Write(callback->channel, (UINT32)Stream_Length(s), Stream_Buffer(s), + nullptr); +fail: + Stream_Free(s, TRUE); + return error; +} + +static BOOL rdpgfx_is_capability_filtered(RDPGFX_PLUGIN* gfx, UINT32 caps) +{ + WINPR_ASSERT(gfx); + const UINT32 filter = + freerdp_settings_get_uint32(gfx->rdpcontext->settings, FreeRDP_GfxCapsFilter); + const UINT32 capList[] = { RDPGFX_CAPVERSION_8, RDPGFX_CAPVERSION_81, + RDPGFX_CAPVERSION_10, RDPGFX_CAPVERSION_101, + RDPGFX_CAPVERSION_102, RDPGFX_CAPVERSION_103, + RDPGFX_CAPVERSION_104, RDPGFX_CAPVERSION_105, + RDPGFX_CAPVERSION_106, RDPGFX_CAPVERSION_106_ERR, + RDPGFX_CAPVERSION_107 }; + + for (size_t x = 0; x < ARRAYSIZE(capList); x++) + { + if (caps == capList[x]) + return (filter & (1 << x)) != 0; + } + + return TRUE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_supported_caps(GENERIC_CHANNEL_CALLBACK* callback) +{ + RDPGFX_PLUGIN* gfx = nullptr; + RdpgfxClientContext* context = nullptr; + RDPGFX_CAPSET* capsSet = nullptr; + RDPGFX_CAPSET capsSets[RDPGFX_NUMBER_CAPSETS] = WINPR_C_ARRAY_INIT; + RDPGFX_CAPS_ADVERTISE_PDU pdu = WINPR_C_ARRAY_INIT; + + if (!callback) + return ERROR_BAD_ARGUMENTS; + + gfx = (RDPGFX_PLUGIN*)callback->plugin; + + if (!gfx) + return ERROR_BAD_CONFIGURATION; + + context = gfx->context; + + if (!context) + return ERROR_BAD_CONFIGURATION; + + pdu.capsSetCount = 0; + pdu.capsSets = (RDPGFX_CAPSET*)capsSets; + + if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_8)) + { + capsSet = &capsSets[pdu.capsSetCount++]; + capsSet->version = RDPGFX_CAPVERSION_8; + capsSet->length = 4; + capsSet->flags = 0; + + if (freerdp_settings_get_bool(gfx->rdpcontext->settings, FreeRDP_GfxThinClient)) + capsSet->flags |= RDPGFX_CAPS_FLAG_THINCLIENT; + + /* in CAPVERSION_8 the spec says that we should not have both + * thinclient and smallcache (and thinclient implies a small cache) + */ + if (freerdp_settings_get_bool(gfx->rdpcontext->settings, FreeRDP_GfxSmallCache) && + !freerdp_settings_get_bool(gfx->rdpcontext->settings, FreeRDP_GfxThinClient)) + capsSet->flags |= RDPGFX_CAPS_FLAG_SMALL_CACHE; + } + + if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_81)) + { + capsSet = &capsSets[pdu.capsSetCount++]; + capsSet->version = RDPGFX_CAPVERSION_81; + capsSet->length = 4; + capsSet->flags = 0; + + if (freerdp_settings_get_bool(gfx->rdpcontext->settings, FreeRDP_GfxThinClient)) + capsSet->flags |= RDPGFX_CAPS_FLAG_THINCLIENT; + + if (freerdp_settings_get_bool(gfx->rdpcontext->settings, FreeRDP_GfxSmallCache)) + capsSet->flags |= RDPGFX_CAPS_FLAG_SMALL_CACHE; + +#ifdef WITH_GFX_H264 + + if (freerdp_settings_get_bool(gfx->rdpcontext->settings, FreeRDP_GfxH264)) + capsSet->flags |= RDPGFX_CAPS_FLAG_AVC420_ENABLED; + +#endif + } + + if (!freerdp_settings_get_bool(gfx->rdpcontext->settings, FreeRDP_GfxH264) || + freerdp_settings_get_bool(gfx->rdpcontext->settings, FreeRDP_GfxAVC444)) + { + UINT32 caps10Flags = 0; + + if (freerdp_settings_get_bool(gfx->rdpcontext->settings, FreeRDP_GfxSmallCache)) + caps10Flags |= RDPGFX_CAPS_FLAG_SMALL_CACHE; + +#ifdef WITH_GFX_H264 + + if (!freerdp_settings_get_bool(gfx->rdpcontext->settings, FreeRDP_GfxAVC444)) + caps10Flags |= RDPGFX_CAPS_FLAG_AVC_DISABLED; + +#else + caps10Flags |= RDPGFX_CAPS_FLAG_AVC_DISABLED; +#endif + + if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_10)) + { + capsSet = &capsSets[pdu.capsSetCount++]; + capsSet->version = RDPGFX_CAPVERSION_10; + capsSet->length = 4; + capsSet->flags = caps10Flags; + } + + if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_101)) + { + capsSet = &capsSets[pdu.capsSetCount++]; + capsSet->version = RDPGFX_CAPVERSION_101; + capsSet->length = 0x10; + capsSet->flags = 0; + } + + if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_102)) + { + capsSet = &capsSets[pdu.capsSetCount++]; + capsSet->version = RDPGFX_CAPVERSION_102; + capsSet->length = 0x4; + capsSet->flags = caps10Flags; + } + + if (freerdp_settings_get_bool(gfx->rdpcontext->settings, FreeRDP_GfxThinClient)) + { + if ((caps10Flags & RDPGFX_CAPS_FLAG_AVC_DISABLED) == 0) + caps10Flags |= RDPGFX_CAPS_FLAG_AVC_THINCLIENT; + } + + if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_103)) + { + capsSet = &capsSets[pdu.capsSetCount++]; + capsSet->version = RDPGFX_CAPVERSION_103; + capsSet->length = 0x4; + capsSet->flags = caps10Flags & ~RDPGFX_CAPS_FLAG_SMALL_CACHE; + } + + if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_104)) + { + capsSet = &capsSets[pdu.capsSetCount++]; + capsSet->version = RDPGFX_CAPVERSION_104; + capsSet->length = 0x4; + capsSet->flags = caps10Flags; + } + + /* The following capabilities expect support for image scaling. + * Disable these for builds that do not have support for that. + */ +#if defined(WITH_CAIRO) || defined(WITH_SWSCALE) + if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_105)) + { + capsSet = &capsSets[pdu.capsSetCount++]; + capsSet->version = RDPGFX_CAPVERSION_105; + capsSet->length = 0x4; + capsSet->flags = caps10Flags; + } + + if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_106)) + { + capsSet = &capsSets[pdu.capsSetCount++]; + capsSet->version = RDPGFX_CAPVERSION_106; + capsSet->length = 0x4; + capsSet->flags = caps10Flags; + } + + if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_106_ERR)) + { + capsSet = &capsSets[pdu.capsSetCount++]; + capsSet->version = RDPGFX_CAPVERSION_106_ERR; + capsSet->length = 0x4; + capsSet->flags = caps10Flags; + } +#endif + + if (!rdpgfx_is_capability_filtered(gfx, RDPGFX_CAPVERSION_107)) + { + capsSet = &capsSets[pdu.capsSetCount++]; + capsSet->version = RDPGFX_CAPVERSION_107; + capsSet->length = 0x4; + capsSet->flags = caps10Flags; +#if !defined(WITH_CAIRO) && !defined(WITH_SWSCALE) + capsSet->flags |= RDPGFX_CAPS_FLAG_SCALEDMAP_DISABLE; +#endif + } + } + + return IFCALLRESULT(ERROR_BAD_CONFIGURATION, context->CapsAdvertise, context, &pdu); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_caps_confirm_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + RDPGFX_CAPSET capsSet = WINPR_C_ARRAY_INIT; + RDPGFX_CAPS_CONFIRM_PDU pdu = WINPR_C_ARRAY_INIT; + WINPR_ASSERT(callback); + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; + + WINPR_ASSERT(gfx); + RdpgfxClientContext* context = gfx->context; + + pdu.capsSet = &capsSet; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 12)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, capsSet.version); /* version (4 bytes) */ + Stream_Read_UINT32(s, capsSet.length); /* capsDataLength (4 bytes) */ + Stream_Read_UINT32(s, capsSet.flags); /* capsData (4 bytes) */ + gfx->TotalDecodedFrames = 0; + gfx->ConnectionCaps = capsSet; + WLog_Print(gfx->log, WLOG_DEBUG, + "RecvCapsConfirmPdu: version: %s [0x%08" PRIX32 "] flags: 0x%08" PRIX32 "", + rdpgfx_caps_version_str(capsSet.version), capsSet.version, capsSet.flags); + + if (!context) + return ERROR_BAD_CONFIGURATION; + + return IFCALLRESULT(CHANNEL_RC_OK, context->CapsConfirm, context, &pdu); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_frame_acknowledge_pdu(RdpgfxClientContext* context, + const RDPGFX_FRAME_ACKNOWLEDGE_PDU* pdu) +{ + UINT error = 0; + wStream* s = nullptr; + RDPGFX_HEADER header = WINPR_C_ARRAY_INIT; + RDPGFX_PLUGIN* gfx = nullptr; + GENERIC_CHANNEL_CALLBACK* callback = nullptr; + + if (!context || !pdu) + return ERROR_BAD_ARGUMENTS; + + gfx = (RDPGFX_PLUGIN*)context->handle; + + if (!gfx || !gfx->base.listener_callback) + return ERROR_BAD_CONFIGURATION; + + callback = gfx->base.listener_callback->channel_callback; + + if (!callback) + return ERROR_BAD_CONFIGURATION; + + header.flags = 0; + header.cmdId = RDPGFX_CMDID_FRAMEACKNOWLEDGE; + header.pduLength = RDPGFX_HEADER_SIZE + 12; + DEBUG_RDPGFX(gfx->log, "SendFrameAcknowledgePdu: %" PRIu32 "", pdu->frameId); + s = Stream_New(nullptr, header.pduLength); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if ((error = rdpgfx_write_header(s, &header))) + goto fail; + + /* RDPGFX_FRAME_ACKNOWLEDGE_PDU */ + Stream_Write_UINT32(s, pdu->queueDepth); /* queueDepth (4 bytes) */ + Stream_Write_UINT32(s, pdu->frameId); /* frameId (4 bytes) */ + Stream_Write_UINT32(s, pdu->totalFramesDecoded); /* totalFramesDecoded (4 bytes) */ + error = callback->channel->Write(callback->channel, (UINT32)Stream_Length(s), Stream_Buffer(s), + nullptr); + + if (error == CHANNEL_RC_OK) /* frame successfully acked */ + gfx->UnacknowledgedFrames--; + +fail: + Stream_Free(s, TRUE); + return error; +} + +static UINT rdpgfx_send_qoe_frame_acknowledge_pdu(RdpgfxClientContext* context, + const RDPGFX_QOE_FRAME_ACKNOWLEDGE_PDU* pdu) +{ + UINT error = 0; + wStream* s = nullptr; + RDPGFX_HEADER header = WINPR_C_ARRAY_INIT; + GENERIC_CHANNEL_CALLBACK* callback = nullptr; + RDPGFX_PLUGIN* gfx = nullptr; + + header.flags = 0; + header.cmdId = RDPGFX_CMDID_QOEFRAMEACKNOWLEDGE; + header.pduLength = RDPGFX_HEADER_SIZE + 12; + + if (!context || !pdu) + return ERROR_BAD_ARGUMENTS; + + gfx = (RDPGFX_PLUGIN*)context->handle; + + if (!gfx || !gfx->base.listener_callback) + return ERROR_BAD_CONFIGURATION; + + callback = gfx->base.listener_callback->channel_callback; + + if (!callback) + return ERROR_BAD_CONFIGURATION; + + DEBUG_RDPGFX(gfx->log, "SendQoeFrameAcknowledgePdu: %" PRIu32 "", pdu->frameId); + s = Stream_New(nullptr, header.pduLength); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if ((error = rdpgfx_write_header(s, &header))) + goto fail; + + /* RDPGFX_FRAME_ACKNOWLEDGE_PDU */ + Stream_Write_UINT32(s, pdu->frameId); + Stream_Write_UINT32(s, pdu->timestamp); + Stream_Write_UINT16(s, pdu->timeDiffSE); + Stream_Write_UINT16(s, pdu->timeDiffEDR); + error = callback->channel->Write(callback->channel, (UINT32)Stream_Length(s), Stream_Buffer(s), + nullptr); +fail: + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_reset_graphics_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + RDPGFX_RESET_GRAPHICS_PDU pdu = WINPR_C_ARRAY_INIT; + WINPR_ASSERT(callback); + + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; + + WINPR_ASSERT(gfx); + + RdpgfxClientContext* context = gfx->context; + UINT error = CHANNEL_RC_OK; + GraphicsResetEventArgs graphicsReset = WINPR_C_ARRAY_INIT; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 12)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, pdu.width); /* width (4 bytes) */ + Stream_Read_UINT32(s, pdu.height); /* height (4 bytes) */ + Stream_Read_UINT32(s, pdu.monitorCount); /* monitorCount (4 bytes) */ + + if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, pdu.monitorCount, 20ull)) + return ERROR_INVALID_DATA; + + pdu.monitorDefArray = (MONITOR_DEF*)calloc(pdu.monitorCount, sizeof(MONITOR_DEF)); + + if (!pdu.monitorDefArray) + { + WLog_Print(gfx->log, WLOG_ERROR, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + for (UINT32 index = 0; index < pdu.monitorCount; index++) + { + MONITOR_DEF* monitor = &(pdu.monitorDefArray[index]); + Stream_Read_INT32(s, monitor->left); /* left (4 bytes) */ + Stream_Read_INT32(s, monitor->top); /* top (4 bytes) */ + Stream_Read_INT32(s, monitor->right); /* right (4 bytes) */ + Stream_Read_INT32(s, monitor->bottom); /* bottom (4 bytes) */ + Stream_Read_UINT32(s, monitor->flags); /* flags (4 bytes) */ + } + + const size_t size = (RDPGFX_HEADER_SIZE + 12ULL + (pdu.monitorCount * 20ULL)); + if (size > 340) + { + free(pdu.monitorDefArray); + return CHANNEL_RC_NULL_DATA; + } + const size_t pad = 340ULL - size; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)pad)) + { + free(pdu.monitorDefArray); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Seek(s, pad); /* pad (total size is 340 bytes) */ + WLog_Print(gfx->log, WLOG_DEBUG, + "RecvResetGraphicsPdu: width: %" PRIu32 " height: %" PRIu32 " count: %" PRIu32 "", + pdu.width, pdu.height, pdu.monitorCount); + +#if defined(WITH_DEBUG_RDPGFX) + for (UINT32 index = 0; index < pdu.monitorCount; index++) + { + MONITOR_DEF* monitor = &(pdu.monitorDefArray[index]); + DEBUG_RDPGFX(gfx->log, + "RecvResetGraphicsPdu: monitor left:%" PRIi32 " top:%" PRIi32 " right:%" PRIi32 + " bottom:%" PRIi32 " flags:0x%" PRIx32 "", + monitor->left, monitor->top, monitor->right, monitor->bottom, monitor->flags); + } +#endif + + if (context) + { + IFCALLRET(context->ResetGraphics, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, "context->ResetGraphics failed with error %" PRIu32 "", + error); + } + + /* some listeners may be interested (namely the display channel) */ + EventArgsInit(&graphicsReset, "libfreerdp"); + graphicsReset.width = pdu.width; + graphicsReset.height = pdu.height; + PubSub_OnGraphicsReset(gfx->rdpcontext->pubSub, gfx->rdpcontext, &graphicsReset); + free(pdu.monitorDefArray); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_evict_cache_entry_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + RDPGFX_EVICT_CACHE_ENTRY_PDU pdu = WINPR_C_ARRAY_INIT; + WINPR_ASSERT(callback); + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; + WINPR_ASSERT(gfx); + RdpgfxClientContext* context = gfx->context; + UINT error = CHANNEL_RC_OK; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 2)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, pdu.cacheSlot); /* cacheSlot (2 bytes) */ + WLog_Print(gfx->log, WLOG_DEBUG, "RecvEvictCacheEntryPdu: cacheSlot: %" PRIu16 "", + pdu.cacheSlot); + + if (context) + { + IFCALLRET(context->EvictCacheEntry, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, + "context->EvictCacheEntry failed with error %" PRIu32 "", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_save_persistent_cache(RDPGFX_PLUGIN* gfx) +{ + UINT error = CHANNEL_RC_OK; + PERSISTENT_CACHE_ENTRY cacheEntry; + rdpPersistentCache* persistent = nullptr; + WINPR_ASSERT(gfx); + WINPR_ASSERT(gfx->rdpcontext); + rdpSettings* settings = gfx->rdpcontext->settings; + RdpgfxClientContext* context = gfx->context; + + WINPR_ASSERT(context); + WINPR_ASSERT(settings); + + if (!freerdp_settings_get_bool(settings, FreeRDP_BitmapCachePersistEnabled)) + return CHANNEL_RC_OK; + + const char* BitmapCachePersistFile = + freerdp_settings_get_string(settings, FreeRDP_BitmapCachePersistFile); + if (!BitmapCachePersistFile) + return CHANNEL_RC_OK; + + if (!context->ExportCacheEntry) + return CHANNEL_RC_INITIALIZATION_ERROR; + + persistent = persistent_cache_new(); + + if (!persistent) + return CHANNEL_RC_NO_MEMORY; + + if (persistent_cache_open(persistent, BitmapCachePersistFile, TRUE, 3) < 1) + { + error = CHANNEL_RC_INITIALIZATION_ERROR; + goto fail; + } + + for (UINT16 idx = 0; idx < gfx->MaxCacheSlots; idx++) + { + if (gfx->CacheSlots[idx]) + { + UINT16 cacheSlot = idx; + + if (context->ExportCacheEntry(context, cacheSlot, &cacheEntry) != CHANNEL_RC_OK) + continue; + + if (persistent_cache_write_entry(persistent, &cacheEntry) < 0) + goto fail; + } + } + + persistent_cache_free(persistent); + + return error; +fail: + persistent_cache_free(persistent); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_cache_import_offer_pdu(RdpgfxClientContext* context, + const RDPGFX_CACHE_IMPORT_OFFER_PDU* pdu) +{ + UINT error = CHANNEL_RC_OK; + wStream* s = nullptr; + RDPGFX_HEADER header; + GENERIC_CHANNEL_CALLBACK* callback = nullptr; + + if (!context || !pdu) + return ERROR_BAD_ARGUMENTS; + + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)context->handle; + + if (!gfx || !gfx->base.listener_callback) + return ERROR_BAD_CONFIGURATION; + + callback = gfx->base.listener_callback->channel_callback; + + if (!callback) + return ERROR_BAD_CONFIGURATION; + + header.flags = 0; + header.cmdId = RDPGFX_CMDID_CACHEIMPORTOFFER; + header.pduLength = RDPGFX_HEADER_SIZE + 2ul + pdu->cacheEntriesCount * 12ul; + WLog_Print(gfx->log, WLOG_DEBUG, "SendCacheImportOfferPdu: cacheEntriesCount: %" PRIu16 "", + pdu->cacheEntriesCount); + s = Stream_New(nullptr, header.pduLength); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if ((error = rdpgfx_write_header(s, &header))) + goto fail; + + if (pdu->cacheEntriesCount <= 0) + { + WLog_ERR(TAG, "Invalid cacheEntriesCount: %" PRIu16 "", pdu->cacheEntriesCount); + error = ERROR_INVALID_DATA; + goto fail; + } + + /* cacheEntriesCount (2 bytes) */ + Stream_Write_UINT16(s, pdu->cacheEntriesCount); + + for (UINT16 index = 0; index < pdu->cacheEntriesCount; index++) + { + const RDPGFX_CACHE_ENTRY_METADATA* cacheEntry = &(pdu->cacheEntries[index]); + Stream_Write_UINT64(s, cacheEntry->cacheKey); /* cacheKey (8 bytes) */ + Stream_Write_UINT32(s, cacheEntry->bitmapLength); /* bitmapLength (4 bytes) */ + } + + error = callback->channel->Write(callback->channel, (UINT32)Stream_Length(s), Stream_Buffer(s), + nullptr); + +fail: + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_cache_offer(RDPGFX_PLUGIN* gfx) +{ + int count = 0; + UINT error = CHANNEL_RC_OK; + PERSISTENT_CACHE_ENTRY entry; + RDPGFX_CACHE_IMPORT_OFFER_PDU* offer = nullptr; + rdpPersistentCache* persistent = nullptr; + + WINPR_ASSERT(gfx); + WINPR_ASSERT(gfx->rdpcontext); + + RdpgfxClientContext* context = gfx->context; + rdpSettings* settings = gfx->rdpcontext->settings; + + if (!freerdp_settings_get_bool(settings, FreeRDP_BitmapCachePersistEnabled)) + return CHANNEL_RC_OK; + + const char* BitmapCachePersistFile = + freerdp_settings_get_string(settings, FreeRDP_BitmapCachePersistFile); + if (!BitmapCachePersistFile) + return CHANNEL_RC_OK; + + persistent = persistent_cache_new(); + + if (!persistent) + return CHANNEL_RC_NO_MEMORY; + + if (persistent_cache_open(persistent, BitmapCachePersistFile, FALSE, 3) < 1) + { + error = CHANNEL_RC_INITIALIZATION_ERROR; + goto fail; + } + + if (persistent_cache_get_version(persistent) != 3) + { + error = ERROR_INVALID_DATA; + goto fail; + } + + count = persistent_cache_get_count(persistent); + if (count < 0) + { + error = ERROR_INVALID_DATA; + goto fail; + } + + if (count >= RDPGFX_CACHE_ENTRY_MAX_COUNT) + count = RDPGFX_CACHE_ENTRY_MAX_COUNT - 1; + + if (count > gfx->MaxCacheSlots) + count = gfx->MaxCacheSlots; + + offer = (RDPGFX_CACHE_IMPORT_OFFER_PDU*)calloc(1, sizeof(RDPGFX_CACHE_IMPORT_OFFER_PDU)); + if (!offer) + { + error = CHANNEL_RC_NO_MEMORY; + goto fail; + } + + WINPR_ASSERT(count <= UINT16_MAX); + offer->cacheEntriesCount = (UINT16)count; + + WLog_DBG(TAG, "Sending Cache Import Offer: %d", count); + + for (int idx = 0; idx < count; idx++) + { + if (persistent_cache_read_entry(persistent, &entry) < 1) + { + error = ERROR_INVALID_DATA; + goto fail; + } + + offer->cacheEntries[idx].cacheKey = entry.key64; + offer->cacheEntries[idx].bitmapLength = entry.size; + } + + if (offer->cacheEntriesCount > 0) + { + error = rdpgfx_send_cache_import_offer_pdu(context, offer); + if (error != CHANNEL_RC_OK) + { + WLog_Print(gfx->log, WLOG_ERROR, "Failed to send cache import offer PDU"); + goto fail; + } + } + +fail: + persistent_cache_free(persistent); + free(offer); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_load_cache_import_reply(RDPGFX_PLUGIN* gfx, + const RDPGFX_CACHE_IMPORT_REPLY_PDU* reply) +{ + UINT error = CHANNEL_RC_OK; + rdpPersistentCache* persistent = nullptr; + WINPR_ASSERT(gfx); + WINPR_ASSERT(gfx->rdpcontext); + rdpSettings* settings = gfx->rdpcontext->settings; + RdpgfxClientContext* context = gfx->context; + + WINPR_ASSERT(settings); + WINPR_ASSERT(reply); + if (!freerdp_settings_get_bool(settings, FreeRDP_BitmapCachePersistEnabled)) + return CHANNEL_RC_OK; + + const char* BitmapCachePersistFile = + freerdp_settings_get_string(settings, FreeRDP_BitmapCachePersistFile); + if (!BitmapCachePersistFile) + return CHANNEL_RC_OK; + + persistent = persistent_cache_new(); + + if (!persistent) + return CHANNEL_RC_NO_MEMORY; + + if (persistent_cache_open(persistent, BitmapCachePersistFile, FALSE, 3) < 1) + { + error = CHANNEL_RC_INITIALIZATION_ERROR; + goto fail; + } + + if (persistent_cache_get_version(persistent) != 3) + { + error = ERROR_INVALID_DATA; + goto fail; + } + + int count = persistent_cache_get_count(persistent); + + count = (count < reply->importedEntriesCount) ? count : reply->importedEntriesCount; + + WLog_DBG(TAG, "Receiving Cache Import Reply: %d", count); + + for (int idx = 0; idx < count; idx++) + { + PERSISTENT_CACHE_ENTRY entry = WINPR_C_ARRAY_INIT; + if (persistent_cache_read_entry(persistent, &entry) < 1) + { + error = ERROR_INVALID_DATA; + goto fail; + } + + const UINT16 cacheSlot = reply->cacheSlots[idx]; + if (context && context->ImportCacheEntry) + { + error = context->ImportCacheEntry(context, cacheSlot, &entry); + if (error != CHANNEL_RC_OK) + break; + } + } + + persistent_cache_free(persistent); + + return error; +fail: + persistent_cache_free(persistent); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_cache_import_reply_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + RDPGFX_CACHE_IMPORT_REPLY_PDU pdu = WINPR_C_ARRAY_INIT; + WINPR_ASSERT(callback); + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; + WINPR_ASSERT(gfx); + RdpgfxClientContext* context = gfx->context; + UINT error = CHANNEL_RC_OK; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 2)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, pdu.importedEntriesCount); /* cacheSlot (2 bytes) */ + + if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, pdu.importedEntriesCount, 2ull)) + return ERROR_INVALID_DATA; + + if (pdu.importedEntriesCount > RDPGFX_CACHE_ENTRY_MAX_COUNT) + return ERROR_INVALID_DATA; + + for (UINT16 idx = 0; idx < pdu.importedEntriesCount; idx++) + { + Stream_Read_UINT16(s, pdu.cacheSlots[idx]); /* cacheSlot (2 bytes) */ + } + + DEBUG_RDPGFX(gfx->log, "RecvCacheImportReplyPdu: importedEntriesCount: %" PRIu16 "", + pdu.importedEntriesCount); + + error = rdpgfx_load_cache_import_reply(gfx, &pdu); + + if (error) + { + WLog_Print(gfx->log, WLOG_ERROR, + "rdpgfx_load_cache_import_reply failed with error %" PRIu32 "", error); + return error; + } + + if (context) + { + IFCALLRET(context->CacheImportReply, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, + "context->CacheImportReply failed with error %" PRIu32 "", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_create_surface_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + RDPGFX_CREATE_SURFACE_PDU pdu = WINPR_C_ARRAY_INIT; + WINPR_ASSERT(callback); + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; + WINPR_ASSERT(gfx); + RdpgfxClientContext* context = gfx->context; + UINT error = CHANNEL_RC_OK; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 7)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */ + Stream_Read_UINT16(s, pdu.width); /* width (2 bytes) */ + Stream_Read_UINT16(s, pdu.height); /* height (2 bytes) */ + Stream_Read_UINT8(s, pdu.pixelFormat); /* RDPGFX_PIXELFORMAT (1 byte) */ + DEBUG_RDPGFX(gfx->log, + "RecvCreateSurfacePdu: surfaceId: %" PRIu16 " width: %" PRIu16 " height: %" PRIu16 + " pixelFormat: 0x%02" PRIX8 "", + pdu.surfaceId, pdu.width, pdu.height, pdu.pixelFormat); + + if (context) + { + /* create surface PDU sometimes happens for surface ID that are already in use and have not + * been removed yet. Ensure that there is no surface with the new ID by trying to remove it + * manually. + */ + RDPGFX_DELETE_SURFACE_PDU deletePdu = { pdu.surfaceId }; + const UINT drc = IFCALLRESULT(CHANNEL_RC_OK, context->DeleteSurface, context, &deletePdu); + if (drc != CHANNEL_RC_OK) + WLog_Print(gfx->log, WLOG_WARN, + "context->DeleteSurface failed with error %" PRIu32 ", ignoring", error); + + IFCALLRET(context->CreateSurface, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, "context->CreateSurface failed with error %" PRIu32 "", + error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_delete_surface_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + RDPGFX_DELETE_SURFACE_PDU pdu = WINPR_C_ARRAY_INIT; + WINPR_ASSERT(callback); + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; + WINPR_ASSERT(gfx); + RdpgfxClientContext* context = gfx->context; + UINT error = CHANNEL_RC_OK; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 2)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */ + DEBUG_RDPGFX(gfx->log, "RecvDeleteSurfacePdu: surfaceId: %" PRIu16 "", pdu.surfaceId); + + if (context) + { + IFCALLRET(context->DeleteSurface, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, "context->DeleteSurface failed with error %" PRIu32 "", + error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_start_frame_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + RDPGFX_START_FRAME_PDU pdu = WINPR_C_ARRAY_INIT; + WINPR_ASSERT(callback); + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; + WINPR_ASSERT(gfx); + RdpgfxClientContext* context = gfx->context; + UINT error = CHANNEL_RC_OK; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, RDPGFX_START_FRAME_PDU_SIZE)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, pdu.timestamp); /* timestamp (4 bytes) */ + Stream_Read_UINT32(s, pdu.frameId); /* frameId (4 bytes) */ + DEBUG_RDPGFX(gfx->log, "RecvStartFramePdu: frameId: %" PRIu32 " timestamp: 0x%08" PRIX32 "", + pdu.frameId, pdu.timestamp); + gfx->StartDecodingTime = GetTickCount64(); + + if (context) + { + IFCALLRET(context->StartFrame, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, "context->StartFrame failed with error %" PRIu32 "", + error); + } + + gfx->UnacknowledgedFrames++; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_end_frame_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + RDPGFX_END_FRAME_PDU pdu = WINPR_C_ARRAY_INIT; + RDPGFX_FRAME_ACKNOWLEDGE_PDU ack = WINPR_C_ARRAY_INIT; + WINPR_ASSERT(callback); + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; + WINPR_ASSERT(gfx); + RdpgfxClientContext* context = gfx->context; + UINT error = CHANNEL_RC_OK; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, RDPGFX_END_FRAME_PDU_SIZE)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, pdu.frameId); /* frameId (4 bytes) */ + DEBUG_RDPGFX(gfx->log, "RecvEndFramePdu: frameId: %" PRIu32 "", pdu.frameId); + + const UINT64 start = GetTickCount64(); + if (context) + { + IFCALLRET(context->EndFrame, error, context, &pdu); + + if (error) + { + WLog_Print(gfx->log, WLOG_ERROR, "context->EndFrame failed with error %" PRIu32 "", + error); + return error; + } + } + const UINT64 end = GetTickCount64(); + const UINT64 EndFrameTime = end - start; + gfx->TotalDecodedFrames++; + + if (!gfx->sendFrameAcks) + return error; + + ack.frameId = pdu.frameId; + ack.totalFramesDecoded = gfx->TotalDecodedFrames; + + if (gfx->suspendFrameAcks) + { + ack.queueDepth = SUSPEND_FRAME_ACKNOWLEDGEMENT; + + if (gfx->TotalDecodedFrames == 1) + if ((error = rdpgfx_send_frame_acknowledge_pdu(context, &ack))) + WLog_Print(gfx->log, WLOG_ERROR, + "rdpgfx_send_frame_acknowledge_pdu failed with error %" PRIu32 "", + error); + } + else + { + ack.queueDepth = QUEUE_DEPTH_UNAVAILABLE; + + if ((error = rdpgfx_send_frame_acknowledge_pdu(context, &ack))) + WLog_Print(gfx->log, WLOG_ERROR, + "rdpgfx_send_frame_acknowledge_pdu failed with error %" PRIu32 "", error); + } + + switch (gfx->ConnectionCaps.version) + { + case RDPGFX_CAPVERSION_10: + case RDPGFX_CAPVERSION_102: + case RDPGFX_CAPVERSION_103: + case RDPGFX_CAPVERSION_104: + case RDPGFX_CAPVERSION_105: + case RDPGFX_CAPVERSION_106: + case RDPGFX_CAPVERSION_106_ERR: + case RDPGFX_CAPVERSION_107: + if (freerdp_settings_get_bool(gfx->rdpcontext->settings, FreeRDP_GfxSendQoeAck)) + { + RDPGFX_QOE_FRAME_ACKNOWLEDGE_PDU qoe = WINPR_C_ARRAY_INIT; + UINT64 diff = (GetTickCount64() - gfx->StartDecodingTime); + + if (diff > 65000) + diff = 0; + + qoe.frameId = pdu.frameId; + qoe.timestamp = gfx->StartDecodingTime % UINT32_MAX; + qoe.timeDiffSE = WINPR_ASSERTING_INT_CAST(UINT16, diff); + qoe.timeDiffEDR = WINPR_ASSERTING_INT_CAST(UINT16, EndFrameTime); + + if ((error = rdpgfx_send_qoe_frame_acknowledge_pdu(context, &qoe))) + WLog_Print(gfx->log, WLOG_ERROR, + "rdpgfx_send_qoe_frame_acknowledge_pdu failed with error %" PRIu32 + "", + error); + } + + break; + + default: + break; + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_wire_to_surface_1_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + RDPGFX_SURFACE_COMMAND cmd = WINPR_C_ARRAY_INIT; + RDPGFX_WIRE_TO_SURFACE_PDU_1 pdu = WINPR_C_ARRAY_INIT; + WINPR_ASSERT(callback); + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; + UINT error = 0; + + WINPR_ASSERT(gfx); + if (!Stream_CheckAndLogRequiredLength(TAG, s, RDPGFX_WIRE_TO_SURFACE_PDU_1_SIZE)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */ + Stream_Read_UINT16(s, pdu.codecId); /* codecId (2 bytes) */ + Stream_Read_UINT8(s, pdu.pixelFormat); /* pixelFormat (1 byte) */ + + if ((error = rdpgfx_read_rect16(s, &(pdu.destRect)))) /* destRect (8 bytes) */ + { + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_read_rect16 failed with error %" PRIu32 "", error); + return error; + } + + Stream_Read_UINT32(s, pdu.bitmapDataLength); /* bitmapDataLength (4 bytes) */ + + if (!Stream_CheckAndLogRequiredLength(TAG, s, pdu.bitmapDataLength)) + return ERROR_INVALID_DATA; + + pdu.bitmapData = Stream_Pointer(s); + Stream_Seek(s, pdu.bitmapDataLength); + + DEBUG_RDPGFX(gfx->log, + "RecvWireToSurface1Pdu: surfaceId: %" PRIu16 " codecId: %s (0x%04" PRIX16 + ") pixelFormat: 0x%02" PRIX8 " " + "destRect: left: %" PRIu16 " top: %" PRIu16 " right: %" PRIu16 " bottom: %" PRIu16 + " bitmapDataLength: %" PRIu32 "", + pdu.surfaceId, rdpgfx_get_codec_id_string(pdu.codecId), pdu.codecId, + pdu.pixelFormat, pdu.destRect.left, pdu.destRect.top, pdu.destRect.right, + pdu.destRect.bottom, pdu.bitmapDataLength); + cmd.surfaceId = pdu.surfaceId; + cmd.codecId = pdu.codecId; + cmd.contextId = 0; + + switch (pdu.pixelFormat) + { + case GFX_PIXEL_FORMAT_XRGB_8888: + cmd.format = PIXEL_FORMAT_BGRX32; + break; + + case GFX_PIXEL_FORMAT_ARGB_8888: + cmd.format = PIXEL_FORMAT_BGRA32; + break; + + default: + return ERROR_INVALID_DATA; + } + + cmd.left = pdu.destRect.left; + cmd.top = pdu.destRect.top; + cmd.right = pdu.destRect.right; + cmd.bottom = pdu.destRect.bottom; + cmd.width = cmd.right - cmd.left; + cmd.height = cmd.bottom - cmd.top; + cmd.length = pdu.bitmapDataLength; + cmd.data = pdu.bitmapData; + cmd.extra = nullptr; + + if (cmd.right < cmd.left) + { + WLog_Print(gfx->log, WLOG_ERROR, "RecvWireToSurface1Pdu right=%" PRIu32 " < left=%" PRIu32, + cmd.right, cmd.left); + return ERROR_INVALID_DATA; + } + if (cmd.bottom < cmd.top) + { + WLog_Print(gfx->log, WLOG_ERROR, "RecvWireToSurface1Pdu bottom=%" PRIu32 " < top=%" PRIu32, + cmd.bottom, cmd.top); + return ERROR_INVALID_DATA; + } + + if ((error = rdpgfx_decode(gfx, &cmd))) + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_decode failed with error %" PRIu32 "!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_wire_to_surface_2_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + RDPGFX_SURFACE_COMMAND cmd = WINPR_C_ARRAY_INIT; + RDPGFX_WIRE_TO_SURFACE_PDU_2 pdu = WINPR_C_ARRAY_INIT; + WINPR_ASSERT(callback); + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; + WINPR_ASSERT(gfx); + RdpgfxClientContext* context = gfx->context; + UINT error = CHANNEL_RC_OK; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, RDPGFX_WIRE_TO_SURFACE_PDU_2_SIZE)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */ + Stream_Read_UINT16(s, pdu.codecId); /* codecId (2 bytes) */ + Stream_Read_UINT32(s, pdu.codecContextId); /* codecContextId (4 bytes) */ + Stream_Read_UINT8(s, pdu.pixelFormat); /* pixelFormat (1 byte) */ + Stream_Read_UINT32(s, pdu.bitmapDataLength); /* bitmapDataLength (4 bytes) */ + pdu.bitmapData = Stream_Pointer(s); + if (!Stream_SafeSeek(s, pdu.bitmapDataLength)) + return ERROR_INVALID_DATA; + + DEBUG_RDPGFX(gfx->log, + "RecvWireToSurface2Pdu: surfaceId: %" PRIu16 " codecId: %s (0x%04" PRIX16 ") " + "codecContextId: %" PRIu32 " pixelFormat: 0x%02" PRIX8 + " bitmapDataLength: %" PRIu32 "", + pdu.surfaceId, rdpgfx_get_codec_id_string(pdu.codecId), pdu.codecId, + pdu.codecContextId, pdu.pixelFormat, pdu.bitmapDataLength); + + cmd.surfaceId = pdu.surfaceId; + cmd.codecId = pdu.codecId; + cmd.contextId = pdu.codecContextId; + + switch (pdu.pixelFormat) + { + case GFX_PIXEL_FORMAT_XRGB_8888: + cmd.format = PIXEL_FORMAT_BGRX32; + break; + + case GFX_PIXEL_FORMAT_ARGB_8888: + cmd.format = PIXEL_FORMAT_BGRA32; + break; + + default: + return ERROR_INVALID_DATA; + } + + cmd.left = 0; + cmd.top = 0; + cmd.right = 0; + cmd.bottom = 0; + cmd.width = 0; + cmd.height = 0; + cmd.length = pdu.bitmapDataLength; + cmd.data = pdu.bitmapData; + cmd.extra = nullptr; + + if (context) + { + IFCALLRET(context->SurfaceCommand, error, context, &cmd); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, + "context->SurfaceCommand failed with error %" PRIu32 "", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_delete_encoding_context_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + RDPGFX_DELETE_ENCODING_CONTEXT_PDU pdu = WINPR_C_ARRAY_INIT; + WINPR_ASSERT(callback); + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; + WINPR_ASSERT(gfx); + RdpgfxClientContext* context = gfx->context; + UINT error = CHANNEL_RC_OK; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 6)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */ + Stream_Read_UINT32(s, pdu.codecContextId); /* codecContextId (4 bytes) */ + + DEBUG_RDPGFX(gfx->log, + "RecvDeleteEncodingContextPdu: surfaceId: %" PRIu16 " codecContextId: %" PRIu32 "", + pdu.surfaceId, pdu.codecContextId); + + if (context) + { + IFCALLRET(context->DeleteEncodingContext, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, + "context->DeleteEncodingContext failed with error %" PRIu32 "", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_solid_fill_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + RECTANGLE_16* fillRect = nullptr; + RDPGFX_SOLID_FILL_PDU pdu = WINPR_C_ARRAY_INIT; + WINPR_ASSERT(callback); + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; + WINPR_ASSERT(gfx); + RdpgfxClientContext* context = gfx->context; + UINT error = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */ + + if ((error = rdpgfx_read_color32(s, &(pdu.fillPixel)))) /* fillPixel (4 bytes) */ + { + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_read_color32 failed with error %" PRIu32 "!", + error); + return error; + } + + Stream_Read_UINT16(s, pdu.fillRectCount); /* fillRectCount (2 bytes) */ + + if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, pdu.fillRectCount, 8ull)) + return ERROR_INVALID_DATA; + + pdu.fillRects = (RECTANGLE_16*)calloc(pdu.fillRectCount, sizeof(RECTANGLE_16)); + + if (!pdu.fillRects) + { + WLog_Print(gfx->log, WLOG_ERROR, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + for (UINT16 index = 0; index < pdu.fillRectCount; index++) + { + fillRect = &(pdu.fillRects[index]); + + if ((error = rdpgfx_read_rect16(s, fillRect))) + { + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_read_rect16 failed with error %" PRIu32 "!", + error); + free(pdu.fillRects); + return error; + } + } + DEBUG_RDPGFX(gfx->log, "RecvSolidFillPdu: surfaceId: %" PRIu16 " fillRectCount: %" PRIu16 "", + pdu.surfaceId, pdu.fillRectCount); + + if (context) + { + IFCALLRET(context->SolidFill, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, "context->SolidFill failed with error %" PRIu32 "", + error); + } + + free(pdu.fillRects); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_surface_to_surface_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + RDPGFX_POINT16* destPt = nullptr; + RDPGFX_SURFACE_TO_SURFACE_PDU pdu = WINPR_C_ARRAY_INIT; + WINPR_ASSERT(callback); + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; + WINPR_ASSERT(gfx); + RdpgfxClientContext* context = gfx->context; + UINT error = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 14)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, pdu.surfaceIdSrc); /* surfaceIdSrc (2 bytes) */ + Stream_Read_UINT16(s, pdu.surfaceIdDest); /* surfaceIdDest (2 bytes) */ + + if ((error = rdpgfx_read_rect16(s, &(pdu.rectSrc)))) /* rectSrc (8 bytes ) */ + { + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_read_rect16 failed with error %" PRIu32 "!", + error); + return error; + } + + Stream_Read_UINT16(s, pdu.destPtsCount); /* destPtsCount (2 bytes) */ + + if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, pdu.destPtsCount, 4ull)) + return ERROR_INVALID_DATA; + + pdu.destPts = (RDPGFX_POINT16*)calloc(pdu.destPtsCount, sizeof(RDPGFX_POINT16)); + + if (!pdu.destPts) + { + WLog_Print(gfx->log, WLOG_ERROR, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + for (UINT16 index = 0; index < pdu.destPtsCount; index++) + { + destPt = &(pdu.destPts[index]); + + if ((error = rdpgfx_read_point16(s, destPt))) + { + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_read_point16 failed with error %" PRIu32 "!", + error); + free(pdu.destPts); + return error; + } + } + + DEBUG_RDPGFX(gfx->log, + "RecvSurfaceToSurfacePdu: surfaceIdSrc: %" PRIu16 " surfaceIdDest: %" PRIu16 " " + "left: %" PRIu16 " top: %" PRIu16 " right: %" PRIu16 " bottom: %" PRIu16 + " destPtsCount: %" PRIu16 "", + pdu.surfaceIdSrc, pdu.surfaceIdDest, pdu.rectSrc.left, pdu.rectSrc.top, + pdu.rectSrc.right, pdu.rectSrc.bottom, pdu.destPtsCount); + + if (context) + { + IFCALLRET(context->SurfaceToSurface, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, + "context->SurfaceToSurface failed with error %" PRIu32 "", error); + } + + free(pdu.destPts); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_surface_to_cache_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + RDPGFX_SURFACE_TO_CACHE_PDU pdu = WINPR_C_ARRAY_INIT; + WINPR_ASSERT(callback); + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; + + WINPR_ASSERT(gfx); + RdpgfxClientContext* context = gfx->context; + UINT error = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 20)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */ + Stream_Read_UINT64(s, pdu.cacheKey); /* cacheKey (8 bytes) */ + Stream_Read_UINT16(s, pdu.cacheSlot); /* cacheSlot (2 bytes) */ + + if ((error = rdpgfx_read_rect16(s, &(pdu.rectSrc)))) /* rectSrc (8 bytes ) */ + { + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_read_rect16 failed with error %" PRIu32 "!", + error); + return error; + } + + DEBUG_RDPGFX(gfx->log, + "RecvSurfaceToCachePdu: surfaceId: %" PRIu16 " cacheKey: 0x%016" PRIX64 + " cacheSlot: %" PRIu16 " " + "left: %" PRIu16 " top: %" PRIu16 " right: %" PRIu16 " bottom: %" PRIu16 "", + pdu.surfaceId, pdu.cacheKey, pdu.cacheSlot, pdu.rectSrc.left, pdu.rectSrc.top, + pdu.rectSrc.right, pdu.rectSrc.bottom); + + if (context) + { + IFCALLRET(context->SurfaceToCache, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, + "context->SurfaceToCache failed with error %" PRIu32 "", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_cache_to_surface_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + RDPGFX_POINT16* destPt = nullptr; + RDPGFX_CACHE_TO_SURFACE_PDU pdu = WINPR_C_ARRAY_INIT; + WINPR_ASSERT(callback); + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; + + WINPR_ASSERT(gfx); + RdpgfxClientContext* context = gfx->context; + UINT error = CHANNEL_RC_OK; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 6)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, pdu.cacheSlot); /* cacheSlot (2 bytes) */ + Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */ + Stream_Read_UINT16(s, pdu.destPtsCount); /* destPtsCount (2 bytes) */ + + if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, pdu.destPtsCount, 4ull)) + return ERROR_INVALID_DATA; + + pdu.destPts = (RDPGFX_POINT16*)calloc(pdu.destPtsCount, sizeof(RDPGFX_POINT16)); + + if (!pdu.destPts) + { + WLog_Print(gfx->log, WLOG_ERROR, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + for (UINT16 index = 0; index < pdu.destPtsCount; index++) + { + destPt = &(pdu.destPts[index]); + + if ((error = rdpgfx_read_point16(s, destPt))) + { + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_read_point16 failed with error %" PRIu32 "", + error); + free(pdu.destPts); + return error; + } + } + + DEBUG_RDPGFX(gfx->log, + "RdpGfxRecvCacheToSurfacePdu: cacheSlot: %" PRIu16 " surfaceId: %" PRIu16 + " destPtsCount: %" PRIu16 "", + pdu.cacheSlot, pdu.surfaceId, pdu.destPtsCount); + + if (context) + { + IFCALLRET(context->CacheToSurface, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, + "context->CacheToSurface failed with error %" PRIu32 "", error); + } + + free(pdu.destPts); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_map_surface_to_output_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + RDPGFX_MAP_SURFACE_TO_OUTPUT_PDU pdu = WINPR_C_ARRAY_INIT; + WINPR_ASSERT(callback); + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; + + WINPR_ASSERT(gfx); + RdpgfxClientContext* context = gfx->context; + UINT error = CHANNEL_RC_OK; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 12)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */ + Stream_Read_UINT16(s, pdu.reserved); /* reserved (2 bytes) */ + Stream_Read_UINT32(s, pdu.outputOriginX); /* outputOriginX (4 bytes) */ + Stream_Read_UINT32(s, pdu.outputOriginY); /* outputOriginY (4 bytes) */ + DEBUG_RDPGFX(gfx->log, + "RecvMapSurfaceToOutputPdu: surfaceId: %" PRIu16 " outputOriginX: %" PRIu32 + " outputOriginY: %" PRIu32 "", + pdu.surfaceId, pdu.outputOriginX, pdu.outputOriginY); + + if (context) + { + IFCALLRET(context->MapSurfaceToOutput, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, + "context->MapSurfaceToOutput failed with error %" PRIu32 "", error); + } + + return error; +} + +static UINT rdpgfx_recv_map_surface_to_scaled_output_pdu(GENERIC_CHANNEL_CALLBACK* callback, + wStream* s) +{ + RDPGFX_MAP_SURFACE_TO_SCALED_OUTPUT_PDU pdu = WINPR_C_ARRAY_INIT; + WINPR_ASSERT(callback); + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; + + WINPR_ASSERT(gfx); + RdpgfxClientContext* context = gfx->context; + UINT error = CHANNEL_RC_OK; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 20)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */ + Stream_Read_UINT16(s, pdu.reserved); /* reserved (2 bytes) */ + Stream_Read_UINT32(s, pdu.outputOriginX); /* outputOriginX (4 bytes) */ + Stream_Read_UINT32(s, pdu.outputOriginY); /* outputOriginY (4 bytes) */ + Stream_Read_UINT32(s, pdu.targetWidth); /* targetWidth (4 bytes) */ + Stream_Read_UINT32(s, pdu.targetHeight); /* targetHeight (4 bytes) */ + DEBUG_RDPGFX(gfx->log, + "RecvMapSurfaceToScaledOutputPdu: surfaceId: %" PRIu16 " outputOriginX: %" PRIu32 + " outputOriginY: %" PRIu32 " targetWidth: %" PRIu32 " targetHeight: %" PRIu32, + pdu.surfaceId, pdu.outputOriginX, pdu.outputOriginY, pdu.targetWidth, + pdu.targetHeight); + + if (context) + { + IFCALLRET(context->MapSurfaceToScaledOutput, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, + "context->MapSurfaceToScaledOutput failed with error %" PRIu32 "", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_map_surface_to_window_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + RDPGFX_MAP_SURFACE_TO_WINDOW_PDU pdu = WINPR_C_ARRAY_INIT; + WINPR_ASSERT(callback); + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; + + WINPR_ASSERT(gfx); + RdpgfxClientContext* context = gfx->context; + UINT error = CHANNEL_RC_OK; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 18)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */ + Stream_Read_UINT64(s, pdu.windowId); /* windowId (8 bytes) */ + Stream_Read_UINT32(s, pdu.mappedWidth); /* mappedWidth (4 bytes) */ + Stream_Read_UINT32(s, pdu.mappedHeight); /* mappedHeight (4 bytes) */ + DEBUG_RDPGFX(gfx->log, + "RecvMapSurfaceToWindowPdu: surfaceId: %" PRIu16 " windowId: 0x%016" PRIX64 + " mappedWidth: %" PRIu32 " mappedHeight: %" PRIu32 "", + pdu.surfaceId, pdu.windowId, pdu.mappedWidth, pdu.mappedHeight); + + if (context && context->MapSurfaceToWindow) + { + IFCALLRET(context->MapSurfaceToWindow, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, + "context->MapSurfaceToWindow failed with error %" PRIu32 "", error); + } + + return error; +} + +static UINT rdpgfx_recv_map_surface_to_scaled_window_pdu(GENERIC_CHANNEL_CALLBACK* callback, + wStream* s) +{ + RDPGFX_MAP_SURFACE_TO_SCALED_WINDOW_PDU pdu = WINPR_C_ARRAY_INIT; + WINPR_ASSERT(callback); + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; + WINPR_ASSERT(gfx); + RdpgfxClientContext* context = gfx->context; + UINT error = CHANNEL_RC_OK; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 26)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, pdu.surfaceId); /* surfaceId (2 bytes) */ + Stream_Read_UINT64(s, pdu.windowId); /* windowId (8 bytes) */ + Stream_Read_UINT32(s, pdu.mappedWidth); /* mappedWidth (4 bytes) */ + Stream_Read_UINT32(s, pdu.mappedHeight); /* mappedHeight (4 bytes) */ + Stream_Read_UINT32(s, pdu.targetWidth); /* targetWidth (4 bytes) */ + Stream_Read_UINT32(s, pdu.targetHeight); /* targetHeight (4 bytes) */ + DEBUG_RDPGFX(gfx->log, + "RecvMapSurfaceToScaledWindowPdu: surfaceId: %" PRIu16 " windowId: 0x%016" PRIX64 + " mappedWidth: %" PRIu32 " mappedHeight: %" PRIu32 " targetWidth: %" PRIu32 + " targetHeight: %" PRIu32 "", + pdu.surfaceId, pdu.windowId, pdu.mappedWidth, pdu.mappedHeight, pdu.targetWidth, + pdu.targetHeight); + + if (context && context->MapSurfaceToScaledWindow) + { + IFCALLRET(context->MapSurfaceToScaledWindow, error, context, &pdu); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, + "context->MapSurfaceToScaledWindow failed with error %" PRIu32 "", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s) +{ + size_t end = 0; + RDPGFX_HEADER header = WINPR_C_ARRAY_INIT; + UINT error = 0; + WINPR_ASSERT(callback); + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; + const size_t beg = Stream_GetPosition(s); + + WINPR_ASSERT(gfx); + if ((error = rdpgfx_read_header(s, &header))) + { + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_read_header failed with error %" PRIu32 "!", + error); + return error; + } + + DEBUG_RDPGFX( + gfx->log, "cmdId: %s (0x%04" PRIX16 ") flags: 0x%04" PRIX16 " pduLength: %" PRIu32 "", + rdpgfx_get_cmd_id_string(header.cmdId), header.cmdId, header.flags, header.pduLength); + + switch (header.cmdId) + { + case RDPGFX_CMDID_WIRETOSURFACE_1: + if ((error = rdpgfx_recv_wire_to_surface_1_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, + "rdpgfx_recv_wire_to_surface_1_pdu failed with error %" PRIu32 "!", + error); + + break; + + case RDPGFX_CMDID_WIRETOSURFACE_2: + if ((error = rdpgfx_recv_wire_to_surface_2_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, + "rdpgfx_recv_wire_to_surface_2_pdu failed with error %" PRIu32 "!", + error); + + break; + + case RDPGFX_CMDID_DELETEENCODINGCONTEXT: + if ((error = rdpgfx_recv_delete_encoding_context_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, + "rdpgfx_recv_delete_encoding_context_pdu failed with error %" PRIu32 "!", + error); + + break; + + case RDPGFX_CMDID_SOLIDFILL: + if ((error = rdpgfx_recv_solid_fill_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, + "rdpgfx_recv_solid_fill_pdu failed with error %" PRIu32 "!", error); + + break; + + case RDPGFX_CMDID_SURFACETOSURFACE: + if ((error = rdpgfx_recv_surface_to_surface_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, + "rdpgfx_recv_surface_to_surface_pdu failed with error %" PRIu32 "!", + error); + + break; + + case RDPGFX_CMDID_SURFACETOCACHE: + if ((error = rdpgfx_recv_surface_to_cache_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, + "rdpgfx_recv_surface_to_cache_pdu failed with error %" PRIu32 "!", + error); + + break; + + case RDPGFX_CMDID_CACHETOSURFACE: + if ((error = rdpgfx_recv_cache_to_surface_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, + "rdpgfx_recv_cache_to_surface_pdu failed with error %" PRIu32 "!", + error); + + break; + + case RDPGFX_CMDID_EVICTCACHEENTRY: + if ((error = rdpgfx_recv_evict_cache_entry_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, + "rdpgfx_recv_evict_cache_entry_pdu failed with error %" PRIu32 "!", + error); + + break; + + case RDPGFX_CMDID_CREATESURFACE: + if ((error = rdpgfx_recv_create_surface_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, + "rdpgfx_recv_create_surface_pdu failed with error %" PRIu32 "!", error); + + break; + + case RDPGFX_CMDID_DELETESURFACE: + if ((error = rdpgfx_recv_delete_surface_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, + "rdpgfx_recv_delete_surface_pdu failed with error %" PRIu32 "!", error); + + break; + + case RDPGFX_CMDID_STARTFRAME: + if ((error = rdpgfx_recv_start_frame_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, + "rdpgfx_recv_start_frame_pdu failed with error %" PRIu32 "!", error); + + break; + + case RDPGFX_CMDID_ENDFRAME: + if ((error = rdpgfx_recv_end_frame_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, + "rdpgfx_recv_end_frame_pdu failed with error %" PRIu32 "!", error); + + break; + + case RDPGFX_CMDID_RESETGRAPHICS: + if ((error = rdpgfx_recv_reset_graphics_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, + "rdpgfx_recv_reset_graphics_pdu failed with error %" PRIu32 "!", error); + + break; + + case RDPGFX_CMDID_MAPSURFACETOOUTPUT: + if ((error = rdpgfx_recv_map_surface_to_output_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, + "rdpgfx_recv_map_surface_to_output_pdu failed with error %" PRIu32 "!", + error); + + break; + + case RDPGFX_CMDID_CACHEIMPORTREPLY: + if ((error = rdpgfx_recv_cache_import_reply_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, + "rdpgfx_recv_cache_import_reply_pdu failed with error %" PRIu32 "!", + error); + + break; + + case RDPGFX_CMDID_CAPSCONFIRM: + if ((error = rdpgfx_recv_caps_confirm_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, + "rdpgfx_recv_caps_confirm_pdu failed with error %" PRIu32 "!", error); + + if ((error = rdpgfx_send_cache_offer(gfx))) + WLog_Print(gfx->log, WLOG_ERROR, + "rdpgfx_send_cache_offer failed with error %" PRIu32 "!", error); + + break; + + case RDPGFX_CMDID_MAPSURFACETOWINDOW: + if ((error = rdpgfx_recv_map_surface_to_window_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, + "rdpgfx_recv_map_surface_to_window_pdu failed with error %" PRIu32 "!", + error); + + break; + + case RDPGFX_CMDID_MAPSURFACETOSCALEDWINDOW: + if ((error = rdpgfx_recv_map_surface_to_scaled_window_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, + "rdpgfx_recv_map_surface_to_scaled_window_pdu failed with error %" PRIu32 + "!", + error); + + break; + + case RDPGFX_CMDID_MAPSURFACETOSCALEDOUTPUT: + if ((error = rdpgfx_recv_map_surface_to_scaled_output_pdu(callback, s))) + WLog_Print(gfx->log, WLOG_ERROR, + "rdpgfx_recv_map_surface_to_scaled_output_pdu failed with error %" PRIu32 + "!", + error); + + break; + + default: + error = CHANNEL_RC_BAD_PROC; + break; + } + + if (error) + { + WLog_Print(gfx->log, WLOG_ERROR, "Error while processing GFX cmdId: %s (0x%04" PRIX16 ")", + rdpgfx_get_cmd_id_string(header.cmdId), header.cmdId); + Stream_SetPosition(s, (beg + header.pduLength)); + return error; + } + + end = Stream_GetPosition(s); + + if (end != (beg + header.pduLength)) + { + WLog_Print(gfx->log, WLOG_ERROR, + "Unexpected gfx pdu end: Actual: %" PRIuz ", Expected: %" PRIuz, end, + (beg + header.pduLength)); + Stream_SetPosition(s, (beg + header.pduLength)); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data) +{ + int status = 0; + UINT32 DstSize = 0; + BYTE* pDstData = nullptr; + GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + WINPR_ASSERT(callback); + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(gfx); + status = zgfx_decompress(gfx->zgfx, Stream_ConstPointer(data), + (UINT32)Stream_GetRemainingLength(data), &pDstData, &DstSize, 0); + + if (status < 0) + { + WLog_Print(gfx->log, WLOG_ERROR, "zgfx_decompress failure! status: %d", status); + free(pDstData); + return ERROR_INTERNAL_ERROR; + } + + wStream sbuffer = WINPR_C_ARRAY_INIT; + wStream* s = Stream_StaticConstInit(&sbuffer, pDstData, DstSize); + + if (!s) + { + WLog_Print(gfx->log, WLOG_ERROR, "calloc failed!"); + free(pDstData); + return CHANNEL_RC_NO_MEMORY; + } + + while (Stream_GetPosition(s) < Stream_Length(s)) + { + if ((error = rdpgfx_recv_pdu(callback, s))) + { + WLog_Print(gfx->log, WLOG_ERROR, "rdpgfx_recv_pdu failed with error %" PRIu32 "!", + error); + break; + } + } + + free(pDstData); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_on_open(IWTSVirtualChannelCallback* pChannelCallback) +{ + GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + WINPR_ASSERT(callback); + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; + WINPR_ASSERT(gfx); + RdpgfxClientContext* context = gfx->context; + UINT error = CHANNEL_RC_OK; + BOOL do_caps_advertise = TRUE; + gfx->sendFrameAcks = TRUE; + + if (context) + { + IFCALLRET(context->OnOpen, error, context, &do_caps_advertise, &gfx->sendFrameAcks); + + if (error) + WLog_Print(gfx->log, WLOG_ERROR, "context->OnOpen failed with error %" PRIu32 "", + error); + } + + if (do_caps_advertise) + error = rdpgfx_send_supported_caps(callback); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + UINT error = CHANNEL_RC_OK; + GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + WINPR_ASSERT(callback); + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; + + if (!gfx) + goto fail; + + { + RdpgfxClientContext* context = gfx->context; + + DEBUG_RDPGFX(gfx->log, "OnClose"); + error = rdpgfx_save_persistent_cache(gfx); + + if (error) + { + // print error, but don't consider this a hard failure + WLog_Print(gfx->log, WLOG_ERROR, + "rdpgfx_save_persistent_cache failed with error %" PRIu32 "", error); + } + + free_surfaces(context, gfx->SurfaceTable); + error = evict_cache_slots(context, gfx->MaxCacheSlots, gfx->CacheSlots); + if (error) + { + // print error, but don't consider this a hard failure + WLog_Print(gfx->log, WLOG_ERROR, "evict_cache_slots failed with error %" PRIu32 "", + error); + } + + free(callback); + gfx->UnacknowledgedFrames = 0; + gfx->TotalDecodedFrames = 0; + + if (context) + { + error = IFCALLRESULT(CHANNEL_RC_OK, context->OnClose, context); + if (error) + { + // print error, but don't consider this a hard failure + WLog_Print(gfx->log, WLOG_ERROR, "context->OnClose failed with error %" PRIu32 "", + error); + } + } + } + +fail: + return CHANNEL_RC_OK; +} + +static void terminate_plugin_cb(GENERIC_DYNVC_PLUGIN* base) +{ + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)base; + WINPR_ASSERT(gfx); + RdpgfxClientContext* context = gfx->context; + + DEBUG_RDPGFX(gfx->log, "Terminated"); + rdpgfx_client_context_free(context); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_set_surface_data(RdpgfxClientContext* context, UINT16 surfaceId, void* pData) +{ + ULONG_PTR key = 0; + WINPR_ASSERT(context); + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)context->handle; + WINPR_ASSERT(gfx); + key = ((ULONG_PTR)surfaceId) + 1; + + if (pData) + { + if (!HashTable_Insert(gfx->SurfaceTable, (void*)key, pData)) + return ERROR_BAD_ARGUMENTS; + } + else + HashTable_Remove(gfx->SurfaceTable, (void*)key); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_get_surface_ids(RdpgfxClientContext* context, UINT16** ppSurfaceIds, + UINT16* count_out) +{ + size_t count = 0; + UINT16* pSurfaceIds = nullptr; + ULONG_PTR* pKeys = nullptr; + WINPR_ASSERT(context); + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)context->handle; + WINPR_ASSERT(gfx); + count = HashTable_GetKeys(gfx->SurfaceTable, &pKeys); + + WINPR_ASSERT(ppSurfaceIds); + WINPR_ASSERT(count_out); + if (count < 1) + { + *count_out = 0; + return CHANNEL_RC_OK; + } + + pSurfaceIds = (UINT16*)calloc(count, sizeof(UINT16)); + + if (!pSurfaceIds) + { + WLog_Print(gfx->log, WLOG_ERROR, "calloc failed!"); + free(pKeys); + return CHANNEL_RC_NO_MEMORY; + } + + for (size_t index = 0; index < count; index++) + { + pSurfaceIds[index] = (UINT16)(pKeys[index] - 1); + } + + free(pKeys); + *ppSurfaceIds = pSurfaceIds; + *count_out = (UINT16)count; + return CHANNEL_RC_OK; +} + +static void* rdpgfx_get_surface_data(RdpgfxClientContext* context, UINT16 surfaceId) +{ + ULONG_PTR key = 0; + void* pData = nullptr; + WINPR_ASSERT(context); + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)context->handle; + WINPR_ASSERT(gfx); + key = ((ULONG_PTR)surfaceId) + 1; + pData = HashTable_GetItemValue(gfx->SurfaceTable, (void*)key); + return pData; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_set_cache_slot_data(RdpgfxClientContext* context, UINT16 cacheSlot, void* pData) +{ + WINPR_ASSERT(context); + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)context->handle; + + WINPR_ASSERT(gfx); + /* Microsoft uses 1-based indexing for the egfx bitmap cache ! */ + if (cacheSlot == 0 || cacheSlot > gfx->MaxCacheSlots) + { + WLog_ERR(TAG, "invalid cache slot %" PRIu16 ", must be between 1 and %" PRIu16 "", + cacheSlot, gfx->MaxCacheSlots); + return ERROR_INVALID_INDEX; + } + + gfx->CacheSlots[cacheSlot - 1] = pData; + return CHANNEL_RC_OK; +} + +static void* rdpgfx_get_cache_slot_data(RdpgfxClientContext* context, UINT16 cacheSlot) +{ + void* pData = nullptr; + WINPR_ASSERT(context); + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)context->handle; + WINPR_ASSERT(gfx); + /* Microsoft uses 1-based indexing for the egfx bitmap cache ! */ + if (cacheSlot == 0 || cacheSlot > gfx->MaxCacheSlots) + { + WLog_ERR(TAG, "invalid cache slot %" PRIu16 ", must be between 1 and %" PRIu16 "", + cacheSlot, gfx->MaxCacheSlots); + return nullptr; + } + + pData = gfx->CacheSlots[cacheSlot - 1]; + return pData; +} + +static UINT init_plugin_cb(GENERIC_DYNVC_PLUGIN* base, rdpContext* rcontext, + WINPR_ATTR_UNUSED rdpSettings* settings) +{ + RdpgfxClientContext* context = nullptr; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)base; + + WINPR_ASSERT(base); + gfx->rdpcontext = rcontext; + gfx->log = WLog_Get(TAG); + + gfx->SurfaceTable = HashTable_New(TRUE); + if (!gfx->SurfaceTable) + { + WLog_ERR(TAG, "HashTable_New for surfaces failed !"); + return CHANNEL_RC_NO_MEMORY; + } + + gfx->suspendFrameAcks = + freerdp_settings_get_bool(gfx->rdpcontext->settings, FreeRDP_GfxSuspendFrameAck); + gfx->MaxCacheSlots = + freerdp_settings_get_bool(gfx->rdpcontext->settings, FreeRDP_GfxSmallCache) ? 4096 : 25600; + + context = (RdpgfxClientContext*)calloc(1, sizeof(RdpgfxClientContext)); + if (!context) + { + WLog_ERR(TAG, "context calloc failed!"); + HashTable_Free(gfx->SurfaceTable); + gfx->SurfaceTable = nullptr; + return CHANNEL_RC_NO_MEMORY; + } + + gfx->zgfx = zgfx_context_new(FALSE); + if (!gfx->zgfx) + { + WLog_ERR(TAG, "zgfx_context_new failed!"); + HashTable_Free(gfx->SurfaceTable); + gfx->SurfaceTable = nullptr; + free(context); + return CHANNEL_RC_NO_MEMORY; + } + + context->handle = (void*)gfx; + context->GetSurfaceIds = rdpgfx_get_surface_ids; + context->SetSurfaceData = rdpgfx_set_surface_data; + context->GetSurfaceData = rdpgfx_get_surface_data; + context->SetCacheSlotData = rdpgfx_set_cache_slot_data; + context->GetCacheSlotData = rdpgfx_get_cache_slot_data; + context->CapsAdvertise = rdpgfx_send_caps_advertise_pdu; + context->FrameAcknowledge = rdpgfx_send_frame_acknowledge_pdu; + context->CacheImportOffer = rdpgfx_send_cache_import_offer_pdu; + context->QoeFrameAcknowledge = rdpgfx_send_qoe_frame_acknowledge_pdu; + + gfx->base.iface.pInterface = (void*)context; + gfx->context = context; + return CHANNEL_RC_OK; +} + +void rdpgfx_client_context_free(RdpgfxClientContext* context) +{ + + RDPGFX_PLUGIN* gfx = nullptr; + + if (!context) + return; + + gfx = (RDPGFX_PLUGIN*)context->handle; + + free_surfaces(context, gfx->SurfaceTable); + evict_cache_slots(context, gfx->MaxCacheSlots, gfx->CacheSlots); + + if (gfx->zgfx) + { + zgfx_context_free(gfx->zgfx); + gfx->zgfx = nullptr; + } + + HashTable_Free(gfx->SurfaceTable); + free(context); +} + +static const IWTSVirtualChannelCallback rdpgfx_callbacks = { rdpgfx_on_data_received, + rdpgfx_on_open, rdpgfx_on_close, + nullptr }; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE rdpgfx_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)) +{ + return freerdp_generic_DVCPluginEntry(pEntryPoints, TAG, RDPGFX_DVC_CHANNEL_NAME, + sizeof(RDPGFX_PLUGIN), sizeof(GENERIC_CHANNEL_CALLBACK), + &rdpgfx_callbacks, init_plugin_cb, terminate_plugin_cb); +} diff --git a/third_party/FreeRDP/channels/rdpgfx/client/rdpgfx_main.h b/third_party/FreeRDP/channels/rdpgfx/client/rdpgfx_main.h new file mode 100644 index 0000000..41ce4af --- /dev/null +++ b/third_party/FreeRDP/channels/rdpgfx/client/rdpgfx_main.h @@ -0,0 +1,61 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Graphics Pipeline Extension + * + * Copyright 2013-2014 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPGFX_CLIENT_MAIN_H +#define FREERDP_CHANNEL_RDPGFX_CLIENT_MAIN_H + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +typedef struct +{ + GENERIC_DYNVC_PLUGIN base; + + ZGFX_CONTEXT* zgfx; + UINT32 UnacknowledgedFrames; + UINT32 TotalDecodedFrames; + UINT64 StartDecodingTime; + BOOL suspendFrameAcks; + BOOL sendFrameAcks; + + wHashTable* SurfaceTable; + + UINT16 MaxCacheSlots; + void* CacheSlots[25600]; + rdpPersistentCache* persistent; + + rdpContext* rdpcontext; + + wLog* log; + RDPGFX_CAPSET ConnectionCaps; + RdpgfxClientContext* context; +} RDPGFX_PLUGIN; + +#endif /* FREERDP_CHANNEL_RDPGFX_CLIENT_MAIN_H */ diff --git a/third_party/FreeRDP/channels/rdpgfx/rdpgfx_common.c b/third_party/FreeRDP/channels/rdpgfx/rdpgfx_common.c new file mode 100644 index 0000000..775641c --- /dev/null +++ b/third_party/FreeRDP/channels/rdpgfx/rdpgfx_common.c @@ -0,0 +1,197 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Graphics Pipeline Extension + * + * Copyright 2013 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include + +#define TAG CHANNELS_TAG("rdpgfx.common") + +#include "rdpgfx_common.h" + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpgfx_read_header(wStream* s, RDPGFX_HEADER* header) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(header); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return CHANNEL_RC_NO_MEMORY; + + Stream_Read_UINT16(s, header->cmdId); /* cmdId (2 bytes) */ + Stream_Read_UINT16(s, header->flags); /* flags (2 bytes) */ + Stream_Read_UINT32(s, header->pduLength); /* pduLength (4 bytes) */ + + if (header->pduLength < 8) + { + WLog_ERR(TAG, "header->pduLength %u less than 8!", header->pduLength); + return ERROR_INVALID_DATA; + } + if (!Stream_CheckAndLogRequiredLength(TAG, s, (header->pduLength - 8))) + return ERROR_INVALID_DATA; + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpgfx_write_header(wStream* s, const RDPGFX_HEADER* header) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(header); + + if (!Stream_EnsureRemainingCapacity(s, 8)) + return CHANNEL_RC_NO_MEMORY; + Stream_Write_UINT16(s, header->cmdId); /* cmdId (2 bytes) */ + Stream_Write_UINT16(s, header->flags); /* flags (2 bytes) */ + Stream_Write_UINT32(s, header->pduLength); /* pduLength (4 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpgfx_read_point16(wStream* s, RDPGFX_POINT16* pt16) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(pt16); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, pt16->x); /* x (2 bytes) */ + Stream_Read_UINT16(s, pt16->y); /* y (2 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpgfx_write_point16(wStream* s, const RDPGFX_POINT16* point16) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(point16); + + if (!Stream_EnsureRemainingCapacity(s, 4)) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT16(s, point16->x); /* x (2 bytes) */ + Stream_Write_UINT16(s, point16->y); /* y (2 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpgfx_read_rect16(wStream* s, RECTANGLE_16* rect16) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(rect16); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, rect16->left); /* left (2 bytes) */ + Stream_Read_UINT16(s, rect16->top); /* top (2 bytes) */ + Stream_Read_UINT16(s, rect16->right); /* right (2 bytes) */ + Stream_Read_UINT16(s, rect16->bottom); /* bottom (2 bytes) */ + if (rect16->left >= rect16->right) + return ERROR_INVALID_DATA; + if (rect16->top >= rect16->bottom) + return ERROR_INVALID_DATA; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpgfx_write_rect16(wStream* s, const RECTANGLE_16* rect16) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(rect16); + + if (!Stream_EnsureRemainingCapacity(s, 8)) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT16(s, rect16->left); /* left (2 bytes) */ + Stream_Write_UINT16(s, rect16->top); /* top (2 bytes) */ + Stream_Write_UINT16(s, rect16->right); /* right (2 bytes) */ + Stream_Write_UINT16(s, rect16->bottom); /* bottom (2 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpgfx_read_color32(wStream* s, RDPGFX_COLOR32* color32) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(color32); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT8(s, color32->B); /* B (1 byte) */ + Stream_Read_UINT8(s, color32->G); /* G (1 byte) */ + Stream_Read_UINT8(s, color32->R); /* R (1 byte) */ + Stream_Read_UINT8(s, color32->XA); /* XA (1 byte) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpgfx_write_color32(wStream* s, const RDPGFX_COLOR32* color32) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(color32); + + if (!Stream_EnsureRemainingCapacity(s, 4)) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT8(s, color32->B); /* B (1 byte) */ + Stream_Write_UINT8(s, color32->G); /* G (1 byte) */ + Stream_Write_UINT8(s, color32->R); /* R (1 byte) */ + Stream_Write_UINT8(s, color32->XA); /* XA (1 byte) */ + return CHANNEL_RC_OK; +} diff --git a/third_party/FreeRDP/channels/rdpgfx/rdpgfx_common.h b/third_party/FreeRDP/channels/rdpgfx/rdpgfx_common.h new file mode 100644 index 0000000..5074b04 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpgfx/rdpgfx_common.h @@ -0,0 +1,66 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Graphics Pipeline Extension + * + * Copyright 2013 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPGFX_COMMON_H +#define FREERDP_CHANNEL_RDPGFX_COMMON_H + +#include +#include + +#include +#include +#include +#include + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT rdpgfx_read_header(wStream* s, RDPGFX_HEADER* header); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT rdpgfx_write_header(wStream* s, const RDPGFX_HEADER* header); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT rdpgfx_read_point16(wStream* s, RDPGFX_POINT16* pt16); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT rdpgfx_write_point16(wStream* s, const RDPGFX_POINT16* point16); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT rdpgfx_read_rect16(wStream* s, RECTANGLE_16* rect16); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT rdpgfx_write_rect16(wStream* s, const RECTANGLE_16* rect16); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT rdpgfx_read_color32(wStream* s, RDPGFX_COLOR32* color32); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT rdpgfx_write_color32(wStream* s, const RDPGFX_COLOR32* color32); + +#ifdef WITH_DEBUG_RDPGFX +#define DEBUG_RDPGFX(_LOGGER, ...) WLog_Print(_LOGGER, WLOG_DEBUG, __VA_ARGS__) +#else +#define DEBUG_RDPGFX(_LOGGER, ...) \ + do \ + { \ + } while (0) +#endif + +#endif /* FREERDP_CHANNEL_RDPGFX_COMMON_H */ diff --git a/third_party/FreeRDP/channels/rdpgfx/server/CMakeLists.txt b/third_party/FreeRDP/channels/rdpgfx/server/CMakeLists.txt new file mode 100644 index 0000000..3c47952 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpgfx/server/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2016 Jiang Zihao +# +# 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. + +define_channel_server("rdpgfx") + +set(${MODULE_PREFIX}_SRCS rdpgfx_main.c rdpgfx_main.h ../rdpgfx_common.c ../rdpgfx_common.h) + +set(${MODULE_PREFIX}_LIBS freerdp) + +include_directories(..) + +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry") diff --git a/third_party/FreeRDP/channels/rdpgfx/server/rdpgfx_main.c b/third_party/FreeRDP/channels/rdpgfx/server/rdpgfx_main.c new file mode 100644 index 0000000..26068cb --- /dev/null +++ b/third_party/FreeRDP/channels/rdpgfx/server/rdpgfx_main.c @@ -0,0 +1,1889 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Graphics Pipeline Extension + * + * Copyright 2016 Jiang Zihao + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "rdpgfx_common.h" +#include "rdpgfx_main.h" + +#define TAG CHANNELS_TAG("rdpgfx.server") +#define RDPGFX_RESET_GRAPHICS_PDU_SIZE 340 + +#define checkCapsAreExchanged(context) \ + checkCapsAreExchangedInt(context, __FILE__, __func__, __LINE__) +static BOOL checkCapsAreExchangedInt(RdpgfxServerContext* context, const char* file, + const char* fkt, size_t line) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + const DWORD level = WLOG_TRACE; + if (WLog_IsLevelActive(context->priv->log, level)) + { + WLog_PrintTextMessage(context->priv->log, level, line, file, fkt, + "activeCapSet{Version=0x%08" PRIx32 ", flags=0x%08" PRIx32 "}", + context->priv->activeCapSet.version, + context->priv->activeCapSet.flags); + } + return context->priv->activeCapSet.version > 0; +} + +/** + * Function description + * Calculate packet size from data length. + * It would be data length + header. + * + * @param dataLen estimated data length without header + * + * @return new stream + */ +static inline UINT32 rdpgfx_pdu_length(UINT32 dataLen) +{ + return RDPGFX_HEADER_SIZE + dataLen; +} + +static inline UINT rdpgfx_server_packet_init_header(wStream* s, UINT16 cmdId, UINT32 pduLength) +{ + RDPGFX_HEADER header; + header.flags = 0; + header.cmdId = cmdId; + header.pduLength = pduLength; + /* Write header. Note that actual length might be changed + * after the entire packet has been constructed. */ + return rdpgfx_write_header(s, &header); +} + +/** + * Function description + * Complete the rdpgfx packet header. + * + * @param s stream + * @param start saved start pos of the packet in the stream + */ +static inline BOOL rdpgfx_server_packet_complete_header(wStream* s, size_t start) +{ + const size_t current = Stream_GetPosition(s); + const size_t cap = Stream_Capacity(s); + if (cap < start + RDPGFX_HEADER_SIZE) + return FALSE; + if ((start > UINT32_MAX) || (current < start)) + return FALSE; + /* Fill actual length */ + Stream_SetPosition(s, start + RDPGFX_HEADER_SIZE - sizeof(UINT32)); + Stream_Write_UINT32(s, (UINT32)(current - start)); /* pduLength (4 bytes) */ + Stream_SetPosition(s, current); + return TRUE; +} + +/** + * Function description + * Send the stream for rdpgfx server packet. + * The packet would be compressed according to [MS-RDPEGFX]. + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_server_packet_send(RdpgfxServerContext* context, wStream* s) +{ + UINT error = 0; + UINT32 flags = 0; + ULONG written = 0; + BYTE* pSrcData = Stream_Buffer(s); + const size_t SrcSize = Stream_GetPosition(s); + if (SrcSize > UINT32_MAX) + return ERROR_INTERNAL_ERROR; + + wStream* fs = nullptr; + /* Allocate new stream with enough capacity. Additional overhead is + * descriptor (1 bytes) + segmentCount (2 bytes) + uncompressedSize (4 bytes) + * + segmentCount * size (4 bytes) */ + fs = Stream_New(nullptr, SrcSize + 7 + (SrcSize / ZGFX_SEGMENTED_MAXSIZE + 1) * 4); + + if (!fs) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + if (zgfx_compress_to_stream(context->priv->zgfx, fs, pSrcData, (UINT32)SrcSize, &flags) < 0) + { + WLog_Print(context->priv->log, WLOG_ERROR, "zgfx_compress_to_stream failed!"); + error = ERROR_INTERNAL_ERROR; + goto out; + } + + { + const size_t pos = Stream_GetPosition(fs); + WINPR_ASSERT(pos <= UINT32_MAX); + if (!WTSVirtualChannelWrite(context->priv->rdpgfx_channel, Stream_BufferAs(fs, char), + (UINT32)pos, &written)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "WTSVirtualChannelWrite failed!"); + error = ERROR_INTERNAL_ERROR; + goto out; + } + } + + if (written < Stream_GetPosition(fs)) + { + WLog_Print(context->priv->log, WLOG_WARN, + "Unexpected bytes written: %" PRIu32 "/%" PRIuz "", written, + Stream_GetPosition(fs)); + } + + error = CHANNEL_RC_OK; +out: + Stream_Free(fs, TRUE); + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * Create new stream for single rdpgfx packet. The new stream length + * would be required data length + header. The header will be written + * to the stream before return, but the pduLength field might be + * changed in rdpgfx_server_single_packet_send. + * + * @param cmdId The CommandID to write + * @param dataLen estimated data length without header + * + * @return new stream + */ +static wStream* rdpgfx_server_single_packet_new(wLog* log, UINT16 cmdId, UINT32 dataLen) +{ + UINT error = 0; + wStream* s = nullptr; + UINT32 pduLength = rdpgfx_pdu_length(dataLen); + s = Stream_New(nullptr, pduLength); + + if (!s) + { + WLog_Print(log, WLOG_ERROR, "Stream_New failed!"); + goto error; + } + + if ((error = rdpgfx_server_packet_init_header(s, cmdId, pduLength))) + { + WLog_Print(log, WLOG_ERROR, "Failed to init header with error %" PRIu32 "!", error); + goto error; + } + + return s; +error: + Stream_Free(s, TRUE); + return nullptr; +} + +/** + * Function description + * Send the stream for single rdpgfx packet. + * The header will be filled with actual length. + * The packet would be compressed according to [MS-RDPEGFX]. + * + * @return 0 on success, otherwise a Win32 error code + */ +static inline UINT rdpgfx_server_single_packet_send(RdpgfxServerContext* context, wStream* s) +{ + /* Fill actual length */ + rdpgfx_server_packet_complete_header(s, 0); + return rdpgfx_server_packet_send(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_caps_confirm_pdu(RdpgfxServerContext* context, + const RDPGFX_CAPS_CONFIRM_PDU* capsConfirm) +{ + wStream* s = nullptr; + RDPGFX_CAPSET* capsSet = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(capsConfirm); + + capsSet = capsConfirm->capsSet; + WINPR_ASSERT(capsSet); + + s = rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_CAPSCONFIRM, + RDPGFX_CAPSET_BASE_SIZE + capsSet->length); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + WLog_DBG(TAG, "CAPS version=0x%04" PRIx32 ", flags=0x%04" PRIx32 ", length=%" PRIu32, + capsSet->version, capsSet->flags, capsSet->length); + Stream_Write_UINT32(s, capsSet->version); /* version (4 bytes) */ + Stream_Write_UINT32(s, capsSet->length); /* capsDataLength (4 bytes) */ + + if (capsSet->length >= 4) + { + Stream_Write_UINT32(s, capsSet->flags); /* capsData (4 bytes) */ + Stream_Zero(s, capsSet->length - 4); + } + else + Stream_Zero(s, capsSet->length); + + context->priv->activeCapSet = *capsSet; + return rdpgfx_server_single_packet_send(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_reset_graphics_pdu(RdpgfxServerContext* context, + const RDPGFX_RESET_GRAPHICS_PDU* pdu) +{ + if (!checkCapsAreExchanged(context)) + return CHANNEL_RC_NOT_INITIALIZED; + + wStream* s = nullptr; + + /* Check monitorCount. This ensures total size within 340 bytes) */ + if (pdu->monitorCount >= 16) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "Monitor count MUST be less than or equal to 16: %" PRIu32 "", + pdu->monitorCount); + return ERROR_INVALID_DATA; + } + + s = rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_RESETGRAPHICS, + RDPGFX_RESET_GRAPHICS_PDU_SIZE - RDPGFX_HEADER_SIZE); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT32(s, pdu->width); /* width (4 bytes) */ + Stream_Write_UINT32(s, pdu->height); /* height (4 bytes) */ + Stream_Write_UINT32(s, pdu->monitorCount); /* monitorCount (4 bytes) */ + + for (UINT32 index = 0; index < pdu->monitorCount; index++) + { + const MONITOR_DEF* monitor = &(pdu->monitorDefArray[index]); + Stream_Write_INT32(s, monitor->left); /* left (4 bytes) */ + Stream_Write_INT32(s, monitor->top); /* top (4 bytes) */ + Stream_Write_INT32(s, monitor->right); /* right (4 bytes) */ + Stream_Write_INT32(s, monitor->bottom); /* bottom (4 bytes) */ + Stream_Write_UINT32(s, monitor->flags); /* flags (4 bytes) */ + } + + /* pad (total size must be 340 bytes) */ + Stream_SetPosition(s, RDPGFX_RESET_GRAPHICS_PDU_SIZE); + return rdpgfx_server_single_packet_send(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_evict_cache_entry_pdu(RdpgfxServerContext* context, + const RDPGFX_EVICT_CACHE_ENTRY_PDU* pdu) +{ + if (!checkCapsAreExchanged(context)) + return CHANNEL_RC_NOT_INITIALIZED; + wStream* s = + rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_EVICTCACHEENTRY, 2); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, pdu->cacheSlot); /* cacheSlot (2 bytes) */ + return rdpgfx_server_single_packet_send(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_cache_import_reply_pdu(RdpgfxServerContext* context, + const RDPGFX_CACHE_IMPORT_REPLY_PDU* pdu) +{ + if (!checkCapsAreExchanged(context)) + return CHANNEL_RC_NOT_INITIALIZED; + WINPR_ASSERT(context); + WINPR_ASSERT(pdu); + + WLog_DBG(TAG, "reply with %" PRIu16 " entries", pdu->importedEntriesCount); + wStream* s = rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_CACHEIMPORTREPLY, + 2 + 2 * pdu->importedEntriesCount); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + /* importedEntriesCount (2 bytes) */ + Stream_Write_UINT16(s, pdu->importedEntriesCount); + + for (UINT16 index = 0; index < pdu->importedEntriesCount; index++) + { + Stream_Write_UINT16(s, pdu->cacheSlots[index]); /* cacheSlot (2 bytes) */ + } + + return rdpgfx_server_single_packet_send(context, s); +} + +static UINT +rdpgfx_process_cache_import_offer_pdu(RdpgfxServerContext* context, + const RDPGFX_CACHE_IMPORT_OFFER_PDU* cacheImportOffer) +{ + if (!checkCapsAreExchanged(context)) + return CHANNEL_RC_NOT_INITIALIZED; + WINPR_ASSERT(context); + WINPR_ASSERT(cacheImportOffer); + + RDPGFX_CACHE_IMPORT_REPLY_PDU reply = WINPR_C_ARRAY_INIT; + WLog_DBG(TAG, "received %" PRIu16 " entries, reply with %" PRIu16 " entries", + cacheImportOffer->cacheEntriesCount, reply.importedEntriesCount); + return IFCALLRESULT(CHANNEL_RC_OK, context->CacheImportReply, context, &reply); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_create_surface_pdu(RdpgfxServerContext* context, + const RDPGFX_CREATE_SURFACE_PDU* pdu) +{ + if (!checkCapsAreExchanged(context)) + return CHANNEL_RC_NOT_INITIALIZED; + wStream* s = rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_CREATESURFACE, 7); + + WINPR_ASSERT(context); + WINPR_ASSERT(pdu); + WINPR_ASSERT((pdu->pixelFormat == GFX_PIXEL_FORMAT_XRGB_8888) || + (pdu->pixelFormat == GFX_PIXEL_FORMAT_ARGB_8888)); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, pdu->surfaceId); /* surfaceId (2 bytes) */ + Stream_Write_UINT16(s, pdu->width); /* width (2 bytes) */ + Stream_Write_UINT16(s, pdu->height); /* height (2 bytes) */ + Stream_Write_UINT8(s, pdu->pixelFormat); /* RDPGFX_PIXELFORMAT (1 byte) */ + return rdpgfx_server_single_packet_send(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_delete_surface_pdu(RdpgfxServerContext* context, + const RDPGFX_DELETE_SURFACE_PDU* pdu) +{ + if (!checkCapsAreExchanged(context)) + return CHANNEL_RC_NOT_INITIALIZED; + wStream* s = rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_DELETESURFACE, 2); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, pdu->surfaceId); /* surfaceId (2 bytes) */ + return rdpgfx_server_single_packet_send(context, s); +} + +static inline BOOL rdpgfx_write_start_frame_pdu(wStream* s, const RDPGFX_START_FRAME_PDU* pdu) +{ + if (!Stream_EnsureRemainingCapacity(s, 8)) + return FALSE; + Stream_Write_UINT32(s, pdu->timestamp); /* timestamp (4 bytes) */ + Stream_Write_UINT32(s, pdu->frameId); /* frameId (4 bytes) */ + return TRUE; +} + +static inline BOOL rdpgfx_write_end_frame_pdu(wStream* s, const RDPGFX_END_FRAME_PDU* pdu) +{ + if (!Stream_EnsureRemainingCapacity(s, 4)) + return FALSE; + Stream_Write_UINT32(s, pdu->frameId); /* frameId (4 bytes) */ + return TRUE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_start_frame_pdu(RdpgfxServerContext* context, + const RDPGFX_START_FRAME_PDU* pdu) +{ + if (!checkCapsAreExchanged(context)) + return CHANNEL_RC_NOT_INITIALIZED; + wStream* s = rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_STARTFRAME, + RDPGFX_START_FRAME_PDU_SIZE); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rdpgfx_write_start_frame_pdu(s, pdu); + return rdpgfx_server_single_packet_send(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_end_frame_pdu(RdpgfxServerContext* context, const RDPGFX_END_FRAME_PDU* pdu) +{ + if (!checkCapsAreExchanged(context)) + return CHANNEL_RC_NOT_INITIALIZED; + wStream* s = rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_ENDFRAME, + RDPGFX_END_FRAME_PDU_SIZE); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + rdpgfx_write_end_frame_pdu(s, pdu); + return rdpgfx_server_single_packet_send(context, s); +} + +/** + * Function description + * Estimate RFX_AVC420_BITMAP_STREAM structure size in stream + * + * @return estimated size + */ +static inline UINT32 rdpgfx_estimate_h264_avc420(const RDPGFX_AVC420_BITMAP_STREAM* havc420) +{ + /* H264 metadata + H264 stream. See rdpgfx_write_h264_avc420 */ + return sizeof(UINT32) /* numRegionRects */ + + 10ULL /* regionRects + quantQualityVals */ + * havc420->meta.numRegionRects + + havc420->length; +} + +/** + * Function description + * Estimate surface command packet size in stream without header + * + * @return estimated size + */ +static inline UINT32 rdpgfx_estimate_surface_command(const RDPGFX_SURFACE_COMMAND* cmd) +{ + RDPGFX_AVC420_BITMAP_STREAM* havc420 = nullptr; + RDPGFX_AVC444_BITMAP_STREAM* havc444 = nullptr; + UINT32 h264Size = 0; + + /* Estimate stream size according to codec. */ + switch (cmd->codecId) + { + case RDPGFX_CODECID_CAPROGRESSIVE: + case RDPGFX_CODECID_CAPROGRESSIVE_V2: + return RDPGFX_WIRE_TO_SURFACE_PDU_2_SIZE + cmd->length; + + case RDPGFX_CODECID_AVC420: + havc420 = (RDPGFX_AVC420_BITMAP_STREAM*)cmd->extra; + h264Size = rdpgfx_estimate_h264_avc420(havc420); + return RDPGFX_WIRE_TO_SURFACE_PDU_1_SIZE + h264Size; + + case RDPGFX_CODECID_AVC444: + havc444 = (RDPGFX_AVC444_BITMAP_STREAM*)cmd->extra; + h264Size = sizeof(UINT32); /* cbAvc420EncodedBitstream1 */ + /* avc420EncodedBitstream1 */ + havc420 = &(havc444->bitstream[0]); + h264Size += rdpgfx_estimate_h264_avc420(havc420); + + /* avc420EncodedBitstream2 */ + if (havc444->LC == 0) + { + havc420 = &(havc444->bitstream[1]); + h264Size += rdpgfx_estimate_h264_avc420(havc420); + } + + return RDPGFX_WIRE_TO_SURFACE_PDU_1_SIZE + h264Size; + + default: + return RDPGFX_WIRE_TO_SURFACE_PDU_1_SIZE + cmd->length; + } +} + +/** + * Function description + * Resolve RDPGFX_CMDID_WIRETOSURFACE_1 or RDPGFX_CMDID_WIRETOSURFACE_2 + * according to codecId + * + * @return 0 on success, otherwise a Win32 error code + */ +static inline UINT16 rdpgfx_surface_command_cmdid(const RDPGFX_SURFACE_COMMAND* cmd) +{ + if (cmd->codecId == RDPGFX_CODECID_CAPROGRESSIVE || + cmd->codecId == RDPGFX_CODECID_CAPROGRESSIVE_V2) + { + return RDPGFX_CMDID_WIRETOSURFACE_2; + } + + return RDPGFX_CMDID_WIRETOSURFACE_1; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_write_h264_metablock(wLog* log, wStream* s, const RDPGFX_H264_METABLOCK* meta) +{ + RECTANGLE_16* regionRect = nullptr; + RDPGFX_H264_QUANT_QUALITY* quantQualityVal = nullptr; + UINT error = CHANNEL_RC_OK; + + if (!Stream_EnsureRemainingCapacity(s, 4 + meta->numRegionRects * 10)) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(s, meta->numRegionRects); /* numRegionRects (4 bytes) */ + + for (UINT32 index = 0; index < meta->numRegionRects; index++) + { + regionRect = &(meta->regionRects[index]); + + if ((error = rdpgfx_write_rect16(s, regionRect))) + { + WLog_Print(log, WLOG_ERROR, "rdpgfx_write_rect16 failed with error %" PRIu32 "!", + error); + return error; + } + } + + for (UINT32 index = 0; index < meta->numRegionRects; index++) + { + quantQualityVal = &(meta->quantQualityVals[index]); + Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST( + uint8_t, quantQualityVal->qp | (quantQualityVal->r << 6) | + (quantQualityVal->p << 7))); /* qpVal (1 byte) */ + /* qualityVal (1 byte) */ + Stream_Write_UINT8(s, quantQualityVal->qualityVal); + } + + return error; +} + +/** + * Function description + * Write RFX_AVC420_BITMAP_STREAM structure to stream + * + * @return 0 on success, otherwise a Win32 error code + */ +static inline UINT rdpgfx_write_h264_avc420(wLog* log, wStream* s, + RDPGFX_AVC420_BITMAP_STREAM* havc420) +{ + UINT error = CHANNEL_RC_OK; + + if ((error = rdpgfx_write_h264_metablock(log, s, &(havc420->meta)))) + { + WLog_Print(log, WLOG_ERROR, "rdpgfx_write_h264_metablock failed with error %" PRIu32 "!", + error); + return error; + } + + if (!Stream_EnsureRemainingCapacity(s, havc420->length)) + return ERROR_OUTOFMEMORY; + + Stream_Write(s, havc420->data, havc420->length); + return error; +} + +/** + * Function description + * Write RDPGFX_CMDID_WIRETOSURFACE_1 or RDPGFX_CMDID_WIRETOSURFACE_2 + * to the stream according to RDPGFX_SURFACE_COMMAND message + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_write_surface_command(wLog* log, wStream* s, const RDPGFX_SURFACE_COMMAND* cmd) +{ + UINT error = CHANNEL_RC_OK; + RDPGFX_AVC420_BITMAP_STREAM* havc420 = nullptr; + RDPGFX_AVC444_BITMAP_STREAM* havc444 = nullptr; + UINT8 pixelFormat = 0; + + switch (cmd->format) + { + case PIXEL_FORMAT_BGRX32: + pixelFormat = GFX_PIXEL_FORMAT_XRGB_8888; + break; + + case PIXEL_FORMAT_BGRA32: + pixelFormat = GFX_PIXEL_FORMAT_ARGB_8888; + break; + + default: + WLog_Print(log, WLOG_ERROR, "Format %s not supported!", + FreeRDPGetColorFormatName(cmd->format)); + return ERROR_INVALID_DATA; + } + + if (cmd->codecId == RDPGFX_CODECID_CAPROGRESSIVE || + cmd->codecId == RDPGFX_CODECID_CAPROGRESSIVE_V2) + { + if (!Stream_EnsureRemainingCapacity(s, 13 + cmd->length)) + return ERROR_INTERNAL_ERROR; + /* Write RDPGFX_CMDID_WIRETOSURFACE_2 format for CAPROGRESSIVE */ + Stream_Write_UINT16( + s, WINPR_ASSERTING_INT_CAST(uint16_t, cmd->surfaceId)); /* surfaceId (2 bytes) */ + Stream_Write_UINT16( + s, WINPR_ASSERTING_INT_CAST(uint16_t, cmd->codecId)); /* codecId (2 bytes) */ + Stream_Write_UINT32(s, cmd->contextId); /* codecContextId (4 bytes) */ + Stream_Write_UINT8(s, pixelFormat); /* pixelFormat (1 byte) */ + Stream_Write_UINT32(s, cmd->length); /* bitmapDataLength (4 bytes) */ + Stream_Write(s, cmd->data, cmd->length); + } + else + { + /* Write RDPGFX_CMDID_WIRETOSURFACE_1 format for others */ + if (!Stream_EnsureRemainingCapacity(s, 17)) + return ERROR_INTERNAL_ERROR; + Stream_Write_UINT16( + s, WINPR_ASSERTING_INT_CAST(uint16_t, cmd->surfaceId)); /* surfaceId (2 bytes) */ + Stream_Write_UINT16( + s, WINPR_ASSERTING_INT_CAST(uint16_t, cmd->codecId)); /* codecId (2 bytes) */ + Stream_Write_UINT8(s, pixelFormat); /* pixelFormat (1 byte) */ + Stream_Write_UINT16(s, WINPR_ASSERTING_INT_CAST(uint16_t, cmd->left)); /* left (2 bytes) */ + Stream_Write_UINT16(s, WINPR_ASSERTING_INT_CAST(uint16_t, cmd->top)); /* top (2 bytes) */ + Stream_Write_UINT16(s, + WINPR_ASSERTING_INT_CAST(uint16_t, cmd->right)); /* right (2 bytes) */ + Stream_Write_UINT16(s, + WINPR_ASSERTING_INT_CAST(uint16_t, cmd->bottom)); /* bottom (2 bytes) */ + Stream_Write_UINT32(s, cmd->length); /* bitmapDataLength (4 bytes) */ + const size_t bitmapDataStart = Stream_GetPosition(s); + + if (cmd->codecId == RDPGFX_CODECID_AVC420) + { + havc420 = (RDPGFX_AVC420_BITMAP_STREAM*)cmd->extra; + error = rdpgfx_write_h264_avc420(log, s, havc420); + + if (error != CHANNEL_RC_OK) + { + WLog_Print(log, WLOG_ERROR, "rdpgfx_write_h264_avc420 failed!"); + return error; + } + } + else if ((cmd->codecId == RDPGFX_CODECID_AVC444) || + (cmd->codecId == RDPGFX_CODECID_AVC444v2)) + { + havc444 = (RDPGFX_AVC444_BITMAP_STREAM*)cmd->extra; + havc420 = &(havc444->bitstream[0]); /* avc420EncodedBitstreamInfo (4 bytes) */ + if (!Stream_EnsureRemainingCapacity(s, 4)) + return ERROR_INTERNAL_ERROR; + Stream_Write_UINT32(s, havc444->cbAvc420EncodedBitstream1 | + ((uint32_t)havc444->LC << 30UL)); + /* avc420EncodedBitstream1 */ + error = rdpgfx_write_h264_avc420(log, s, havc420); + + if (error != CHANNEL_RC_OK) + { + WLog_Print(log, WLOG_ERROR, "rdpgfx_write_h264_avc420 failed!"); + return error; + } + + /* avc420EncodedBitstream2 */ + if (havc444->LC == 0) + { + havc420 = &(havc444->bitstream[1]); + error = rdpgfx_write_h264_avc420(log, s, havc420); + + if (error != CHANNEL_RC_OK) + { + WLog_Print(log, WLOG_ERROR, "rdpgfx_write_h264_avc420 failed!"); + return error; + } + } + } + else + { + if (!Stream_EnsureRemainingCapacity(s, cmd->length)) + return ERROR_INTERNAL_ERROR; + Stream_Write(s, cmd->data, cmd->length); + } + + /* Fill actual bitmap data length */ + const size_t bitmapDataLength = Stream_GetPosition(s) - bitmapDataStart; + if (bitmapDataLength > UINT32_MAX) + return ERROR_INTERNAL_ERROR; + + Stream_SetPosition(s, bitmapDataStart - sizeof(UINT32)); + if (!Stream_EnsureRemainingCapacity(s, 4)) + return ERROR_INTERNAL_ERROR; + Stream_Write_UINT32(s, (UINT32)bitmapDataLength); /* bitmapDataLength (4 bytes) */ + if (!Stream_SafeSeek(s, bitmapDataLength)) + return ERROR_INTERNAL_ERROR; + } + + return error; +} + +/** + * Function description + * Send RDPGFX_CMDID_WIRETOSURFACE_1 or RDPGFX_CMDID_WIRETOSURFACE_2 + * message according to codecId + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_surface_command(RdpgfxServerContext* context, + const RDPGFX_SURFACE_COMMAND* cmd) +{ + if (!checkCapsAreExchanged(context)) + return CHANNEL_RC_NOT_INITIALIZED; + UINT error = CHANNEL_RC_OK; + wStream* s = nullptr; + s = rdpgfx_server_single_packet_new(context->priv->log, rdpgfx_surface_command_cmdid(cmd), + rdpgfx_estimate_surface_command(cmd)); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + error = rdpgfx_write_surface_command(context->priv->log, s, cmd); + + if (error != CHANNEL_RC_OK) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_write_surface_command failed!"); + goto error; + } + + return rdpgfx_server_single_packet_send(context, s); +error: + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * Send RDPGFX_CMDID_WIRETOSURFACE_1 or RDPGFX_CMDID_WIRETOSURFACE_2 + * message according to codecId. + * Prepend/append start/end frame message in same packet if exists. + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_surface_frame_command(RdpgfxServerContext* context, + const RDPGFX_SURFACE_COMMAND* cmd, + const RDPGFX_START_FRAME_PDU* startFrame, + const RDPGFX_END_FRAME_PDU* endFrame) + +{ + if (!checkCapsAreExchanged(context)) + return CHANNEL_RC_NOT_INITIALIZED; + UINT error = CHANNEL_RC_OK; + UINT32 size = rdpgfx_pdu_length(rdpgfx_estimate_surface_command(cmd)); + + if (startFrame) + { + size += rdpgfx_pdu_length(RDPGFX_START_FRAME_PDU_SIZE); + } + + if (endFrame) + { + size += rdpgfx_pdu_length(RDPGFX_END_FRAME_PDU_SIZE); + } + + wStream* s = Stream_New(nullptr, size); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + /* Write start frame if exists */ + if (startFrame) + { + const size_t position = Stream_GetPosition(s); + error = rdpgfx_server_packet_init_header(s, RDPGFX_CMDID_STARTFRAME, 0); + + if (error != CHANNEL_RC_OK) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "Failed to init header with error %" PRIu32 "!", error); + goto error; + } + + if (!rdpgfx_write_start_frame_pdu(s, startFrame) || + !rdpgfx_server_packet_complete_header(s, position)) + goto error; + } + + /* Write RDPGFX_CMDID_WIRETOSURFACE_1 or RDPGFX_CMDID_WIRETOSURFACE_2 */ + { + const size_t pos = Stream_GetPosition(s); + error = rdpgfx_server_packet_init_header(s, rdpgfx_surface_command_cmdid(cmd), + 0); // Actual length will be filled later + + if (error != CHANNEL_RC_OK) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "Failed to init header with error %" PRIu32 "!", error); + goto error; + } + + error = rdpgfx_write_surface_command(context->priv->log, s, cmd); + + if (error != CHANNEL_RC_OK) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_write_surface_command failed!"); + goto error; + } + + if (!rdpgfx_server_packet_complete_header(s, pos)) + goto error; + } + + /* Write end frame if exists */ + if (endFrame) + { + const size_t position = Stream_GetPosition(s); + error = rdpgfx_server_packet_init_header(s, RDPGFX_CMDID_ENDFRAME, 0); + + if (error != CHANNEL_RC_OK) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "Failed to init header with error %" PRIu32 "!", error); + goto error; + } + + if (!rdpgfx_write_end_frame_pdu(s, endFrame) || + !rdpgfx_server_packet_complete_header(s, position)) + goto error; + } + + return rdpgfx_server_packet_send(context, s); +error: + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_delete_encoding_context_pdu(RdpgfxServerContext* context, + const RDPGFX_DELETE_ENCODING_CONTEXT_PDU* pdu) +{ + if (!checkCapsAreExchanged(context)) + return CHANNEL_RC_NOT_INITIALIZED; + wStream* s = + rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_DELETEENCODINGCONTEXT, 6); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, pdu->surfaceId); /* surfaceId (2 bytes) */ + Stream_Write_UINT32(s, pdu->codecContextId); /* codecContextId (4 bytes) */ + return rdpgfx_server_single_packet_send(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_solid_fill_pdu(RdpgfxServerContext* context, + const RDPGFX_SOLID_FILL_PDU* pdu) +{ + if (!checkCapsAreExchanged(context)) + return CHANNEL_RC_NOT_INITIALIZED; + UINT error = CHANNEL_RC_OK; + RECTANGLE_16* fillRect = nullptr; + wStream* s = rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_SOLIDFILL, + 8 + 8 * pdu->fillRectCount); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, pdu->surfaceId); /* surfaceId (2 bytes) */ + + /* fillPixel (4 bytes) */ + if ((error = rdpgfx_write_color32(s, &(pdu->fillPixel)))) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpgfx_write_color32 failed with error %" PRIu32 "!", error); + goto error; + } + + Stream_Write_UINT16(s, pdu->fillRectCount); /* fillRectCount (2 bytes) */ + + for (UINT16 index = 0; index < pdu->fillRectCount; index++) + { + fillRect = &(pdu->fillRects[index]); + + if ((error = rdpgfx_write_rect16(s, fillRect))) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpgfx_write_rect16 failed with error %" PRIu32 "!", error); + goto error; + } + } + + return rdpgfx_server_single_packet_send(context, s); +error: + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_surface_to_surface_pdu(RdpgfxServerContext* context, + const RDPGFX_SURFACE_TO_SURFACE_PDU* pdu) +{ + if (!checkCapsAreExchanged(context)) + return CHANNEL_RC_NOT_INITIALIZED; + UINT error = CHANNEL_RC_OK; + RDPGFX_POINT16* destPt = nullptr; + wStream* s = rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_SURFACETOSURFACE, + 14 + 4 * pdu->destPtsCount); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, pdu->surfaceIdSrc); /* surfaceIdSrc (2 bytes) */ + Stream_Write_UINT16(s, pdu->surfaceIdDest); /* surfaceIdDest (2 bytes) */ + + /* rectSrc (8 bytes ) */ + if ((error = rdpgfx_write_rect16(s, &(pdu->rectSrc)))) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpgfx_write_rect16 failed with error %" PRIu32 "!", error); + goto error; + } + + Stream_Write_UINT16(s, pdu->destPtsCount); /* destPtsCount (2 bytes) */ + + for (UINT16 index = 0; index < pdu->destPtsCount; index++) + { + destPt = &(pdu->destPts[index]); + + if ((error = rdpgfx_write_point16(s, destPt))) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpgfx_write_point16 failed with error %" PRIu32 "!", error); + goto error; + } + } + + return rdpgfx_server_single_packet_send(context, s); +error: + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_surface_to_cache_pdu(RdpgfxServerContext* context, + const RDPGFX_SURFACE_TO_CACHE_PDU* pdu) +{ + if (!checkCapsAreExchanged(context)) + return CHANNEL_RC_NOT_INITIALIZED; + UINT error = CHANNEL_RC_OK; + wStream* s = + rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_SURFACETOCACHE, 20); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, pdu->surfaceId); /* surfaceId (2 bytes) */ + Stream_Write_UINT64(s, pdu->cacheKey); /* cacheKey (8 bytes) */ + Stream_Write_UINT16(s, pdu->cacheSlot); /* cacheSlot (2 bytes) */ + + /* rectSrc (8 bytes ) */ + if ((error = rdpgfx_write_rect16(s, &(pdu->rectSrc)))) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpgfx_write_rect16 failed with error %" PRIu32 "!", error); + goto error; + } + + return rdpgfx_server_single_packet_send(context, s); +error: + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_cache_to_surface_pdu(RdpgfxServerContext* context, + const RDPGFX_CACHE_TO_SURFACE_PDU* pdu) +{ + if (!checkCapsAreExchanged(context)) + return CHANNEL_RC_NOT_INITIALIZED; + UINT error = CHANNEL_RC_OK; + RDPGFX_POINT16* destPt = nullptr; + wStream* s = rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_CACHETOSURFACE, + 6 + 4 * pdu->destPtsCount); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, pdu->cacheSlot); /* cacheSlot (2 bytes) */ + Stream_Write_UINT16(s, pdu->surfaceId); /* surfaceId (2 bytes) */ + Stream_Write_UINT16(s, pdu->destPtsCount); /* destPtsCount (2 bytes) */ + + for (UINT16 index = 0; index < pdu->destPtsCount; index++) + { + destPt = &(pdu->destPts[index]); + + if ((error = rdpgfx_write_point16(s, destPt))) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpgfx_write_point16 failed with error %" PRIu32 "", error); + goto error; + } + } + + return rdpgfx_server_single_packet_send(context, s); +error: + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_map_surface_to_output_pdu(RdpgfxServerContext* context, + const RDPGFX_MAP_SURFACE_TO_OUTPUT_PDU* pdu) +{ + if (!checkCapsAreExchanged(context)) + return CHANNEL_RC_NOT_INITIALIZED; + wStream* s = + rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_MAPSURFACETOOUTPUT, 12); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, pdu->surfaceId); /* surfaceId (2 bytes) */ + Stream_Write_UINT16(s, 0); /* reserved (2 bytes). Must be 0 */ + Stream_Write_UINT32(s, pdu->outputOriginX); /* outputOriginX (4 bytes) */ + Stream_Write_UINT32(s, pdu->outputOriginY); /* outputOriginY (4 bytes) */ + return rdpgfx_server_single_packet_send(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_send_map_surface_to_window_pdu(RdpgfxServerContext* context, + const RDPGFX_MAP_SURFACE_TO_WINDOW_PDU* pdu) +{ + if (!checkCapsAreExchanged(context)) + return CHANNEL_RC_NOT_INITIALIZED; + wStream* s = + rdpgfx_server_single_packet_new(context->priv->log, RDPGFX_CMDID_MAPSURFACETOWINDOW, 18); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, pdu->surfaceId); /* surfaceId (2 bytes) */ + Stream_Write_UINT64(s, pdu->windowId); /* windowId (8 bytes) */ + Stream_Write_UINT32(s, pdu->mappedWidth); /* mappedWidth (4 bytes) */ + Stream_Write_UINT32(s, pdu->mappedHeight); /* mappedHeight (4 bytes) */ + return rdpgfx_server_single_packet_send(context, s); +} + +static UINT +rdpgfx_send_map_surface_to_scaled_window_pdu(RdpgfxServerContext* context, + const RDPGFX_MAP_SURFACE_TO_SCALED_WINDOW_PDU* pdu) +{ + if (!checkCapsAreExchanged(context)) + return CHANNEL_RC_NOT_INITIALIZED; + wStream* s = rdpgfx_server_single_packet_new(context->priv->log, + RDPGFX_CMDID_MAPSURFACETOSCALEDWINDOW, 26); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, pdu->surfaceId); /* surfaceId (2 bytes) */ + Stream_Write_UINT64(s, pdu->windowId); /* windowId (8 bytes) */ + Stream_Write_UINT32(s, pdu->mappedWidth); /* mappedWidth (4 bytes) */ + Stream_Write_UINT32(s, pdu->mappedHeight); /* mappedHeight (4 bytes) */ + Stream_Write_UINT32(s, pdu->targetWidth); /* targetWidth (4 bytes) */ + Stream_Write_UINT32(s, pdu->targetHeight); /* targetHeight (4 bytes) */ + return rdpgfx_server_single_packet_send(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_frame_acknowledge_pdu(RdpgfxServerContext* context, wStream* s) +{ + if (!checkCapsAreExchanged(context)) + return CHANNEL_RC_NOT_INITIALIZED; + RDPGFX_FRAME_ACKNOWLEDGE_PDU pdu; + UINT error = CHANNEL_RC_OK; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 12)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, pdu.queueDepth); /* queueDepth (4 bytes) */ + Stream_Read_UINT32(s, pdu.frameId); /* frameId (4 bytes) */ + Stream_Read_UINT32(s, pdu.totalFramesDecoded); /* totalFramesDecoded (4 bytes) */ + + if (context) + { + IFCALLRET(context->FrameAcknowledge, error, context, &pdu); + + if (error) + WLog_Print(context->priv->log, WLOG_ERROR, + "context->FrameAcknowledge failed with error %" PRIu32 "", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_cache_import_offer_pdu(RdpgfxServerContext* context, wStream* s) +{ + if (!checkCapsAreExchanged(context)) + return CHANNEL_RC_NOT_INITIALIZED; + + RDPGFX_CACHE_IMPORT_OFFER_PDU pdu = WINPR_C_ARRAY_INIT; + RDPGFX_CACHE_ENTRY_METADATA* cacheEntry = nullptr; + UINT error = CHANNEL_RC_OK; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 2)) + return ERROR_INVALID_DATA; + + /* cacheEntriesCount (2 bytes) */ + Stream_Read_UINT16(s, pdu.cacheEntriesCount); + + /* 2.2.2.16 RDPGFX_CACHE_IMPORT_OFFER_PDU */ + if (pdu.cacheEntriesCount >= 5462) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Invalid cacheEntriesCount: %" PRIu16 "", + pdu.cacheEntriesCount); + return ERROR_INVALID_DATA; + } + + if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, pdu.cacheEntriesCount, 12ull)) + return ERROR_INVALID_DATA; + + for (UINT16 index = 0; index < pdu.cacheEntriesCount; index++) + { + cacheEntry = &(pdu.cacheEntries[index]); + Stream_Read_UINT64(s, cacheEntry->cacheKey); /* cacheKey (8 bytes) */ + Stream_Read_UINT32(s, cacheEntry->bitmapLength); /* bitmapLength (4 bytes) */ + } + + if (context) + { + IFCALLRET(context->CacheImportOffer, error, context, &pdu); + + if (error) + WLog_Print(context->priv->log, WLOG_ERROR, + "context->CacheImportOffer failed with error %" PRIu32 "", error); + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_caps_advertise_pdu(RdpgfxServerContext* context, wStream* s) +{ + RDPGFX_CAPSET* capsSets = nullptr; + RDPGFX_CAPS_ADVERTISE_PDU pdu = WINPR_C_ARRAY_INIT; + UINT error = ERROR_INVALID_DATA; + + if (!context) + return ERROR_BAD_ARGUMENTS; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 2)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, pdu.capsSetCount); /* capsSetCount (2 bytes) */ + if (pdu.capsSetCount > 0) + { + capsSets = calloc(pdu.capsSetCount, (RDPGFX_CAPSET_BASE_SIZE + 4)); + if (!capsSets) + return ERROR_OUTOFMEMORY; + } + + pdu.capsSets = capsSets; + + for (UINT16 index = 0; index < pdu.capsSetCount; index++) + { + RDPGFX_CAPSET* capsSet = &(pdu.capsSets[index]); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + goto fail; + + Stream_Read_UINT32(s, capsSet->version); /* version (4 bytes) */ + Stream_Read_UINT32(s, capsSet->length); /* capsDataLength (4 bytes) */ + + if (capsSet->length >= 4) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + goto fail; + + Stream_Peek_UINT32(s, capsSet->flags); /* capsData (4 bytes) */ + } + + if (!Stream_SafeSeek(s, capsSet->length)) + goto fail; + } + + error = ERROR_BAD_CONFIGURATION; + IFCALLRET(context->CapsAdvertise, error, context, &pdu); + + if (error) + WLog_Print(context->priv->log, WLOG_ERROR, + "context->CapsAdvertise failed with error %" PRIu32 "", error); + +fail: + free(capsSets); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_recv_qoe_frame_acknowledge_pdu(RdpgfxServerContext* context, wStream* s) +{ + if (!checkCapsAreExchanged(context)) + return CHANNEL_RC_NOT_INITIALIZED; + RDPGFX_QOE_FRAME_ACKNOWLEDGE_PDU pdu; + UINT error = CHANNEL_RC_OK; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 12)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, pdu.frameId); /* frameId (4 bytes) */ + Stream_Read_UINT32(s, pdu.timestamp); /* timestamp (4 bytes) */ + Stream_Read_UINT16(s, pdu.timeDiffSE); /* timeDiffSE (2 bytes) */ + Stream_Read_UINT16(s, pdu.timeDiffEDR); /* timeDiffEDR (2 bytes) */ + + if (context) + { + IFCALLRET(context->QoeFrameAcknowledge, error, context, &pdu); + + if (error) + WLog_Print(context->priv->log, WLOG_ERROR, + "context->QoeFrameAcknowledge failed with error %" PRIu32 "", error); + } + + return error; +} + +static UINT +rdpgfx_send_map_surface_to_scaled_output_pdu(RdpgfxServerContext* context, + const RDPGFX_MAP_SURFACE_TO_SCALED_OUTPUT_PDU* pdu) +{ + if (!checkCapsAreExchanged(context)) + return CHANNEL_RC_NOT_INITIALIZED; + wStream* s = rdpgfx_server_single_packet_new(context->priv->log, + RDPGFX_CMDID_MAPSURFACETOSCALEDOUTPUT, 20); + + if (!s) + { + WLog_Print(context->priv->log, WLOG_ERROR, "rdpgfx_server_single_packet_new failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT16(s, pdu->surfaceId); /* surfaceId (2 bytes) */ + Stream_Write_UINT16(s, 0); /* reserved (2 bytes). Must be 0 */ + Stream_Write_UINT32(s, pdu->outputOriginX); /* outputOriginX (4 bytes) */ + Stream_Write_UINT32(s, pdu->outputOriginY); /* outputOriginY (4 bytes) */ + Stream_Write_UINT32(s, pdu->targetWidth); /* targetWidth (4 bytes) */ + Stream_Write_UINT32(s, pdu->targetHeight); /* targetHeight (4 bytes) */ + return rdpgfx_server_single_packet_send(context, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_server_receive_pdu(RdpgfxServerContext* context, wStream* s) +{ + size_t beg = 0; + size_t end = 0; + RDPGFX_HEADER header; + UINT error = CHANNEL_RC_OK; + beg = Stream_GetPosition(s); + + if ((error = rdpgfx_read_header(s, &header))) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpgfx_read_header failed with error %" PRIu32 "!", error); + return error; + } + +#ifdef WITH_DEBUG_RDPGFX + WLog_DBG(TAG, "cmdId: %s (0x%04" PRIX16 ") flags: 0x%04" PRIX16 " pduLength: %" PRIu32 "", + rdpgfx_get_cmd_id_string(header.cmdId), header.cmdId, header.flags, header.pduLength); +#endif + + switch (header.cmdId) + { + case RDPGFX_CMDID_FRAMEACKNOWLEDGE: + if ((error = rdpgfx_recv_frame_acknowledge_pdu(context, s))) + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpgfx_recv_frame_acknowledge_pdu " + "failed with error %" PRIu32 "!", + error); + + break; + + case RDPGFX_CMDID_CACHEIMPORTOFFER: + if ((error = rdpgfx_recv_cache_import_offer_pdu(context, s))) + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpgfx_recv_cache_import_offer_pdu " + "failed with error %" PRIu32 "!", + error); + + break; + + case RDPGFX_CMDID_CAPSADVERTISE: + if ((error = rdpgfx_recv_caps_advertise_pdu(context, s))) + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpgfx_recv_caps_advertise_pdu " + "failed with error %" PRIu32 "!", + error); + + break; + + case RDPGFX_CMDID_QOEFRAMEACKNOWLEDGE: + if ((error = rdpgfx_recv_qoe_frame_acknowledge_pdu(context, s))) + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpgfx_recv_qoe_frame_acknowledge_pdu " + "failed with error %" PRIu32 "!", + error); + + break; + + default: + error = CHANNEL_RC_BAD_PROC; + break; + } + + if (error) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "Error while parsing GFX cmdId: %s (0x%04" PRIX16 ")", + rdpgfx_get_cmd_id_string(header.cmdId), header.cmdId); + return error; + } + + end = Stream_GetPosition(s); + + if (end != (beg + header.pduLength)) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "Unexpected gfx pdu end: Actual: %" PRIuz ", Expected: %" PRIuz "", end, + (beg + header.pduLength)); + Stream_SetPosition(s, (beg + header.pduLength)); + } + + return error; +} + +static BOOL rdpgfx_server_close(RdpgfxServerContext* context); + +static DWORD WINAPI rdpgfx_server_thread_func(LPVOID arg) +{ + RdpgfxServerContext* context = (RdpgfxServerContext*)arg; + WINPR_ASSERT(context); + + RdpgfxServerPrivate* priv = context->priv; + DWORD status = 0; + DWORD nCount = 0; + HANDLE events[8] = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(priv); + + if (priv->ownThread) + { + WINPR_ASSERT(priv->stopEvent); + events[nCount++] = priv->stopEvent; + } + + WINPR_ASSERT(priv->channelEvent); + events[nCount++] = priv->channelEvent; + + /* Main virtual channel loop. RDPGFX do not need version negotiation */ + while (TRUE) + { + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_Print(context->priv->log, WLOG_ERROR, + "WaitForMultipleObjects failed with error %" PRIu32 "", error); + break; + } + + /* Stop Event */ + if (status == WAIT_OBJECT_0) + break; + + if ((error = rdpgfx_server_handle_messages(context))) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpgfx_server_handle_messages failed with error %" PRIu32 "", error); + break; + } + } + + if (error && context->rdpcontext) + setChannelError(context->rdpcontext, error, "rdpgfx_server_thread_func reported an error"); + + ExitThread(error); + return error; +} + +static BOOL rdpgfx_server_open(RdpgfxServerContext* context) +{ + WINPR_ASSERT(context); + RdpgfxServerPrivate* priv = context->priv; + void* buffer = nullptr; + + WINPR_ASSERT(priv); + + if (!priv->isOpened) + { + PULONG pSessionId = nullptr; + DWORD BytesReturned = 0; + priv->SessionId = WTS_CURRENT_SESSION; + UINT32 channelId = 0; + BOOL status = TRUE; + + if (WTSQuerySessionInformationA(context->vcm, WTS_CURRENT_SESSION, WTSSessionId, + (LPSTR*)&pSessionId, &BytesReturned) == FALSE) + { + WLog_Print(context->priv->log, WLOG_ERROR, "WTSQuerySessionInformationA failed!"); + return FALSE; + } + + priv->SessionId = (DWORD)*pSessionId; + WTSFreeMemory(pSessionId); + priv->rdpgfx_channel = WTSVirtualChannelOpenEx(priv->SessionId, RDPGFX_DVC_CHANNEL_NAME, + WTS_CHANNEL_OPTION_DYNAMIC); + + if (!priv->rdpgfx_channel) + { + WLog_Print(context->priv->log, WLOG_ERROR, "WTSVirtualChannelOpenEx failed!"); + return FALSE; + } + + channelId = WTSChannelGetIdByHandle(priv->rdpgfx_channel); + + IFCALLRET(context->ChannelIdAssigned, status, context, channelId); + if (!status) + { + WLog_Print(context->priv->log, WLOG_ERROR, "context->ChannelIdAssigned failed!"); + goto fail; + } + + /* Query for channel event handle */ + if (!WTSVirtualChannelQuery(priv->rdpgfx_channel, WTSVirtualEventHandle, &buffer, + &BytesReturned) || + (BytesReturned != sizeof(HANDLE))) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "WTSVirtualChannelQuery failed " + "or invalid returned size(%" PRIu32 ")", + BytesReturned); + + if (buffer) + WTSFreeMemory(buffer); + + goto fail; + } + + priv->channelEvent = *(HANDLE*)buffer; + WTSFreeMemory(buffer); + + if (!(priv->zgfx = zgfx_context_new(TRUE))) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Create zgfx context failed!"); + goto fail; + } + + priv->isReady = FALSE; + const RDPGFX_CAPSET empty = WINPR_C_ARRAY_INIT; + priv->activeCapSet = empty; + if (priv->ownThread) + { + if (!(priv->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr))) + { + WLog_Print(context->priv->log, WLOG_ERROR, "CreateEvent failed!"); + goto fail; + } + + if (!(priv->thread = CreateThread(nullptr, 0, rdpgfx_server_thread_func, (void*)context, + 0, nullptr))) + { + WLog_Print(context->priv->log, WLOG_ERROR, "CreateThread failed!"); + goto fail; + } + } + + priv->isOpened = TRUE; + return TRUE; + } + + WLog_Print(context->priv->log, WLOG_ERROR, "RDPGFX channel is already opened!"); + return FALSE; +fail: + rdpgfx_server_close(context); + return FALSE; +} + +BOOL rdpgfx_server_close(RdpgfxServerContext* context) +{ + WINPR_ASSERT(context); + + RdpgfxServerPrivate* priv = context->priv; + WINPR_ASSERT(priv); + + if (priv->ownThread && priv->thread) + { + (void)SetEvent(priv->stopEvent); + + if (WaitForSingleObject(priv->thread, INFINITE) == WAIT_FAILED) + { + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", GetLastError()); + return FALSE; + } + + (void)CloseHandle(priv->thread); + (void)CloseHandle(priv->stopEvent); + priv->thread = nullptr; + priv->stopEvent = nullptr; + } + + zgfx_context_free(priv->zgfx); + priv->zgfx = nullptr; + + if (priv->rdpgfx_channel) + { + (void)WTSVirtualChannelClose(priv->rdpgfx_channel); + priv->rdpgfx_channel = nullptr; + } + + priv->channelEvent = nullptr; + priv->isOpened = FALSE; + priv->isReady = FALSE; + const RDPGFX_CAPSET empty = WINPR_C_ARRAY_INIT; + priv->activeCapSet = empty; + return TRUE; +} + +static BOOL rdpgfx_server_initialize(RdpgfxServerContext* context, BOOL externalThread) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (context->priv->isOpened) + { + WLog_Print(context->priv->log, WLOG_WARN, + "Application error: RDPEGFX channel already initialized, " + "calling in this state is not possible!"); + return FALSE; + } + + context->priv->ownThread = !externalThread; + return TRUE; +} + +RdpgfxServerContext* rdpgfx_server_context_new(HANDLE vcm) +{ + RdpgfxServerContext* context = (RdpgfxServerContext*)calloc(1, sizeof(RdpgfxServerContext)); + + if (!context) + { + WLog_ERR(TAG, "calloc failed!"); + return nullptr; + } + + context->vcm = vcm; + context->Initialize = rdpgfx_server_initialize; + context->Open = rdpgfx_server_open; + context->Close = rdpgfx_server_close; + context->ResetGraphics = rdpgfx_send_reset_graphics_pdu; + context->StartFrame = rdpgfx_send_start_frame_pdu; + context->EndFrame = rdpgfx_send_end_frame_pdu; + context->SurfaceCommand = rdpgfx_send_surface_command; + context->SurfaceFrameCommand = rdpgfx_send_surface_frame_command; + context->DeleteEncodingContext = rdpgfx_send_delete_encoding_context_pdu; + context->CreateSurface = rdpgfx_send_create_surface_pdu; + context->DeleteSurface = rdpgfx_send_delete_surface_pdu; + context->SolidFill = rdpgfx_send_solid_fill_pdu; + context->SurfaceToSurface = rdpgfx_send_surface_to_surface_pdu; + context->SurfaceToCache = rdpgfx_send_surface_to_cache_pdu; + context->CacheToSurface = rdpgfx_send_cache_to_surface_pdu; + context->CacheImportOffer = rdpgfx_process_cache_import_offer_pdu; + context->CacheImportReply = rdpgfx_send_cache_import_reply_pdu; + context->EvictCacheEntry = rdpgfx_send_evict_cache_entry_pdu; + context->MapSurfaceToOutput = rdpgfx_send_map_surface_to_output_pdu; + context->MapSurfaceToWindow = rdpgfx_send_map_surface_to_window_pdu; + context->MapSurfaceToScaledOutput = rdpgfx_send_map_surface_to_scaled_output_pdu; + context->MapSurfaceToScaledWindow = rdpgfx_send_map_surface_to_scaled_window_pdu; + context->CapsAdvertise = nullptr; + context->CapsConfirm = rdpgfx_send_caps_confirm_pdu; + context->FrameAcknowledge = nullptr; + context->QoeFrameAcknowledge = nullptr; + RdpgfxServerPrivate* priv = context->priv = + (RdpgfxServerPrivate*)calloc(1, sizeof(RdpgfxServerPrivate)); + + if (!priv) + { + WLog_ERR(TAG, "calloc failed!"); + goto fail; + } + + priv->log = WLog_Get(TAG); + if (!priv->log) + goto fail; + + /* Create shared input stream */ + priv->input_stream = Stream_New(nullptr, 4); + + if (!priv->input_stream) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Stream_New failed!"); + goto fail; + } + + priv->isOpened = FALSE; + priv->isReady = FALSE; + priv->ownThread = TRUE; + + { + const RDPGFX_CAPSET empty = WINPR_C_ARRAY_INIT; + priv->activeCapSet = empty; + } + + return context; +fail: + WINPR_PRAGMA_DIAG_PUSH + WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC + rdpgfx_server_context_free(context); + WINPR_PRAGMA_DIAG_POP + return nullptr; +} + +void rdpgfx_server_context_free(RdpgfxServerContext* context) +{ + if (!context) + return; + + rdpgfx_server_close(context); + + if (context->priv) + Stream_Free(context->priv->input_stream, TRUE); + + free(context->priv); + free(context); +} + +HANDLE rdpgfx_server_get_event_handle(RdpgfxServerContext* context) +{ + if (!context) + return nullptr; + if (!context->priv) + return nullptr; + return context->priv->channelEvent; +} + +/* + * Handle rpdgfx messages - server side + * + * @param Server side context + * + * @return 0 on success + * ERROR_NO_DATA if no data could be read this time + * otherwise a Win32 error code + */ +UINT rdpgfx_server_handle_messages(RdpgfxServerContext* context) +{ + DWORD BytesReturned = 0; + void* buffer = nullptr; + UINT ret = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + RdpgfxServerPrivate* priv = context->priv; + wStream* s = priv->input_stream; + + /* Check whether the dynamic channel is ready */ + if (!priv->isReady) + { + if (WTSVirtualChannelQuery(priv->rdpgfx_channel, WTSVirtualChannelReady, &buffer, + &BytesReturned) == FALSE) + { + if (GetLastError() == ERROR_NO_DATA) + return ERROR_NO_DATA; + + WLog_Print(context->priv->log, WLOG_ERROR, "WTSVirtualChannelQuery failed"); + return ERROR_INTERNAL_ERROR; + } + + priv->isReady = *((BOOL*)buffer); + WTSFreeMemory(buffer); + } + + /* Consume channel event only after the gfx dynamic channel is ready */ + if (priv->isReady) + { + Stream_ResetPosition(s); + + if (!WTSVirtualChannelRead(priv->rdpgfx_channel, 0, nullptr, 0, &BytesReturned)) + { + if (GetLastError() == ERROR_NO_DATA) + return ERROR_NO_DATA; + + WLog_Print(context->priv->log, WLOG_ERROR, "WTSVirtualChannelRead failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (BytesReturned < 1) + return CHANNEL_RC_OK; + + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + { + WLog_Print(context->priv->log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + const size_t len = Stream_Capacity(s); + if (len > UINT32_MAX) + return ERROR_INTERNAL_ERROR; + if (WTSVirtualChannelRead(priv->rdpgfx_channel, 0, Stream_BufferAs(s, char), (UINT32)len, + &BytesReturned) == FALSE) + { + WLog_Print(context->priv->log, WLOG_ERROR, "WTSVirtualChannelRead failed!"); + return ERROR_INTERNAL_ERROR; + } + + Stream_SetLength(s, BytesReturned); + Stream_ResetPosition(s); + + while (Stream_GetPosition(s) < Stream_Length(s)) + { + if ((ret = rdpgfx_server_receive_pdu(context, s))) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "rdpgfx_server_receive_pdu " + "failed with error %" PRIu32 "!", + ret); + return ret; + } + } + } + + return ret; +} diff --git a/third_party/FreeRDP/channels/rdpgfx/server/rdpgfx_main.h b/third_party/FreeRDP/channels/rdpgfx/server/rdpgfx_main.h new file mode 100644 index 0000000..8b184bb --- /dev/null +++ b/third_party/FreeRDP/channels/rdpgfx/server/rdpgfx_main.h @@ -0,0 +1,42 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Graphics Pipeline Extension + * + * Copyright 2016 Jiang Zihao + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPGFX_SERVER_MAIN_H +#define FREERDP_CHANNEL_RDPGFX_SERVER_MAIN_H + +#include +#include + +struct s_rdpgfx_server_private +{ + ZGFX_CONTEXT* zgfx; + BOOL ownThread; + HANDLE thread; + HANDLE stopEvent; + HANDLE channelEvent; + void* rdpgfx_channel; + DWORD SessionId; + wStream* input_stream; + BOOL isOpened; + BOOL isReady; + wLog* log; + RDPGFX_CAPSET activeCapSet; +}; + +#endif /* FREERDP_CHANNEL_RDPGFX_SERVER_MAIN_H */ diff --git a/third_party/FreeRDP/channels/rdpsnd/CMakeLists.txt b/third_party/FreeRDP/channels/rdpsnd/CMakeLists.txt new file mode 100644 index 0000000..c749552 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/CMakeLists.txt @@ -0,0 +1,29 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel("rdpsnd") + +include_directories(common) +add_subdirectory(common) + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/third_party/FreeRDP/channels/rdpsnd/ChannelOptions.cmake b/third_party/FreeRDP/channels/rdpsnd/ChannelOptions.cmake new file mode 100644 index 0000000..f4a7633 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/ChannelOptions.cmake @@ -0,0 +1,20 @@ +set(OPTION_DEFAULT ON) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options( + NAME + "rdpsnd" + TYPE + "static;dynamic" + DESCRIPTION + "Audio Output Virtual Channel Extension" + SPECIFICATIONS + "[MS-RDPEA]" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/rdpsnd/client/CMakeLists.txt b/third_party/FreeRDP/channels/rdpsnd/client/CMakeLists.txt new file mode 100644 index 0000000..760f826 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/client/CMakeLists.txt @@ -0,0 +1,58 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_client("rdpsnd") + +set(${MODULE_PREFIX}_SRCS rdpsnd_main.c rdpsnd_main.h) + +set(${MODULE_PREFIX}_LIBS winpr freerdp ${CMAKE_THREAD_LIBS_INIT} rdpsnd-common) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntryEx;DVCPluginEntry") + +if(WITH_OSS) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "oss" "") +endif() + +if(WITH_ALSA) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "alsa" "") +endif() + +if(WITH_IOSAUDIO) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "ios" "") +endif() + +if(WITH_PULSE) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "pulse" "") +endif() + +if(WITH_MACAUDIO) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "mac" "") +endif() + +if(WITH_WINMM) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "winmm" "") +endif() + +if(WITH_OPENSLES) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "opensles" "") +endif() + +if(WITH_SNDIO) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "sndio" "") +endif() + +add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "fake" "") diff --git a/third_party/FreeRDP/channels/rdpsnd/client/alsa/CMakeLists.txt b/third_party/FreeRDP/channels/rdpsnd/client/alsa/CMakeLists.txt new file mode 100644 index 0000000..1686de3 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/client/alsa/CMakeLists.txt @@ -0,0 +1,30 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_client_subsystem("rdpsnd" "alsa" "") + +find_package(ALSA REQUIRED) +freerdp_client_pc_add_requires_private("alsa") + +set(${MODULE_PREFIX}_SRCS rdpsnd_alsa.c) + +set(${MODULE_PREFIX}_LIBS winpr freerdp ${ALSA_LIBRARIES}) + +include_directories(..) +include_directories(SYSTEM ${ALSA_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") diff --git a/third_party/FreeRDP/channels/rdpsnd/client/alsa/rdpsnd_alsa.c b/third_party/FreeRDP/channels/rdpsnd/client/alsa/rdpsnd_alsa.c new file mode 100644 index 0000000..3bf4a21 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/client/alsa/rdpsnd_alsa.c @@ -0,0 +1,571 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Output Virtual Channel + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "rdpsnd_main.h" + +typedef struct +{ + rdpsndDevicePlugin device; + + UINT32 latency; + AUDIO_FORMAT aformat; + char* device_name; + snd_pcm_t* pcm_handle; + snd_mixer_t* mixer_handle; + + UINT32 actual_rate; + snd_pcm_format_t format; + UINT32 actual_channels; + + snd_pcm_uframes_t buffer_size; + snd_pcm_uframes_t period_size; +} rdpsndAlsaPlugin; + +#define SND_PCM_CHECK(_func, _status) \ + do \ + { \ + if ((_status) < 0) \ + { \ + WLog_ERR(TAG, "%s: %d\n", (_func), (_status)); \ + return -1; \ + } \ + } while (0) + +static int rdpsnd_alsa_set_hw_params(rdpsndAlsaPlugin* alsa) +{ + int status = 0; + snd_pcm_hw_params_t* hw_params = nullptr; + snd_pcm_uframes_t buffer_size_max = 0; + status = snd_pcm_hw_params_malloc(&hw_params); + SND_PCM_CHECK("snd_pcm_hw_params_malloc", status); + status = snd_pcm_hw_params_any(alsa->pcm_handle, hw_params); + SND_PCM_CHECK("snd_pcm_hw_params_any", status); + /* Set interleaved read/write access */ + status = + snd_pcm_hw_params_set_access(alsa->pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); + SND_PCM_CHECK("snd_pcm_hw_params_set_access", status); + /* Set sample format */ + status = snd_pcm_hw_params_set_format(alsa->pcm_handle, hw_params, alsa->format); + SND_PCM_CHECK("snd_pcm_hw_params_set_format", status); + /* Set sample rate */ + status = + snd_pcm_hw_params_set_rate_near(alsa->pcm_handle, hw_params, &alsa->actual_rate, nullptr); + SND_PCM_CHECK("snd_pcm_hw_params_set_rate_near", status); + /* Set number of channels */ + status = snd_pcm_hw_params_set_channels(alsa->pcm_handle, hw_params, alsa->actual_channels); + SND_PCM_CHECK("snd_pcm_hw_params_set_channels", status); + /* Get maximum buffer size */ + status = snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size_max); + SND_PCM_CHECK("snd_pcm_hw_params_get_buffer_size_max", status); + /** + * ALSA Parameters + * + * http://www.alsa-project.org/main/index.php/FramesPeriods + * + * buffer_size = period_size * periods + * period_bytes = period_size * bytes_per_frame + * bytes_per_frame = channels * bytes_per_sample + * + * A frame is equivalent of one sample being played, + * irrespective of the number of channels or the number of bits + * + * A period is the number of frames in between each hardware interrupt. + * + * The buffer size always has to be greater than one period size. + * Commonly this is (2 * period_size), but some hardware can do 8 periods per buffer. + * It is also possible for the buffer size to not be an integer multiple of the period size. + */ + const size_t interrupts_per_sec_near = 50; + const size_t bytes_per_sec = + (1ull * alsa->actual_rate * alsa->aformat.wBitsPerSample / 8 * alsa->actual_channels); + alsa->buffer_size = buffer_size_max; + alsa->period_size = (bytes_per_sec / interrupts_per_sec_near); + + if (alsa->period_size > buffer_size_max) + { + WLog_ERR(TAG, "Warning: requested sound buffer size %lu, got %lu instead\n", + alsa->buffer_size, buffer_size_max); + alsa->period_size = (buffer_size_max / 8); + } + + /* Set buffer size */ + status = + snd_pcm_hw_params_set_buffer_size_near(alsa->pcm_handle, hw_params, &alsa->buffer_size); + SND_PCM_CHECK("snd_pcm_hw_params_set_buffer_size_near", status); + /* Set period size */ + status = snd_pcm_hw_params_set_period_size_near(alsa->pcm_handle, hw_params, &alsa->period_size, + nullptr); + SND_PCM_CHECK("snd_pcm_hw_params_set_period_size_near", status); + status = snd_pcm_hw_params(alsa->pcm_handle, hw_params); + SND_PCM_CHECK("snd_pcm_hw_params", status); + snd_pcm_hw_params_free(hw_params); + return 0; +} + +static int rdpsnd_alsa_set_sw_params(rdpsndAlsaPlugin* alsa) +{ + int status = 0; + snd_pcm_sw_params_t* sw_params = nullptr; + status = snd_pcm_sw_params_malloc(&sw_params); + SND_PCM_CHECK("snd_pcm_sw_params_malloc", status); + status = snd_pcm_sw_params_current(alsa->pcm_handle, sw_params); + SND_PCM_CHECK("snd_pcm_sw_params_current", status); + status = snd_pcm_sw_params_set_avail_min( + alsa->pcm_handle, sw_params, (1ULL * alsa->aformat.nChannels * alsa->actual_channels)); + SND_PCM_CHECK("snd_pcm_sw_params_set_avail_min", status); + status = snd_pcm_sw_params_set_start_threshold(alsa->pcm_handle, sw_params, + alsa->aformat.nBlockAlign); + SND_PCM_CHECK("snd_pcm_sw_params_set_start_threshold", status); + status = snd_pcm_sw_params(alsa->pcm_handle, sw_params); + SND_PCM_CHECK("snd_pcm_sw_params", status); + snd_pcm_sw_params_free(sw_params); + status = snd_pcm_prepare(alsa->pcm_handle); + SND_PCM_CHECK("snd_pcm_prepare", status); + return 0; +} + +static int rdpsnd_alsa_validate_params(rdpsndAlsaPlugin* alsa) +{ + int status = 0; + snd_pcm_uframes_t buffer_size = 0; + snd_pcm_uframes_t period_size = 0; + status = snd_pcm_get_params(alsa->pcm_handle, &buffer_size, &period_size); + SND_PCM_CHECK("snd_pcm_get_params", status); + return 0; +} + +static int rdpsnd_alsa_set_params(rdpsndAlsaPlugin* alsa) +{ + snd_pcm_drop(alsa->pcm_handle); + + if (rdpsnd_alsa_set_hw_params(alsa) < 0) + return -1; + + if (rdpsnd_alsa_set_sw_params(alsa) < 0) + return -1; + + return rdpsnd_alsa_validate_params(alsa); +} + +static BOOL rdpsnd_alsa_set_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, + UINT32 latency) +{ + rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device; + + if (format) + { + alsa->aformat = *format; + alsa->actual_rate = format->nSamplesPerSec; + alsa->actual_channels = format->nChannels; + + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + switch (format->wBitsPerSample) + { + case 8: + alsa->format = SND_PCM_FORMAT_S8; + break; + + case 16: + alsa->format = SND_PCM_FORMAT_S16_LE; + break; + + default: + return FALSE; + } + + break; + + default: + return FALSE; + } + } + + alsa->latency = latency; + return (rdpsnd_alsa_set_params(alsa) == 0); +} + +static void rdpsnd_alsa_close_mixer(rdpsndAlsaPlugin* alsa) +{ + if (alsa && alsa->mixer_handle) + { + snd_mixer_close(alsa->mixer_handle); + alsa->mixer_handle = nullptr; + } +} + +static BOOL rdpsnd_alsa_open_mixer(rdpsndAlsaPlugin* alsa) +{ + int status = 0; + + if (alsa->mixer_handle) + return TRUE; + + status = snd_mixer_open(&alsa->mixer_handle, 0); + + if (status < 0) + { + WLog_ERR(TAG, "snd_mixer_open failed"); + goto fail; + } + + status = snd_mixer_attach(alsa->mixer_handle, alsa->device_name); + + if (status < 0) + { + WLog_ERR(TAG, "snd_mixer_attach failed"); + goto fail; + } + + status = snd_mixer_selem_register(alsa->mixer_handle, nullptr, nullptr); + + if (status < 0) + { + WLog_ERR(TAG, "snd_mixer_selem_register failed"); + goto fail; + } + + status = snd_mixer_load(alsa->mixer_handle); + + if (status < 0) + { + WLog_ERR(TAG, "snd_mixer_load failed"); + goto fail; + } + + return TRUE; +fail: + rdpsnd_alsa_close_mixer(alsa); + return FALSE; +} + +static void rdpsnd_alsa_pcm_close(rdpsndAlsaPlugin* alsa) +{ + if (alsa && alsa->pcm_handle) + { + snd_pcm_drain(alsa->pcm_handle); + snd_pcm_close(alsa->pcm_handle); + alsa->pcm_handle = nullptr; + } +} + +static BOOL rdpsnd_alsa_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, UINT32 latency) +{ + int mode = 0; + int status = 0; + rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device; + + if (alsa->pcm_handle) + return TRUE; + + mode = 0; + /*mode |= SND_PCM_NONBLOCK;*/ + status = snd_pcm_open(&alsa->pcm_handle, alsa->device_name, SND_PCM_STREAM_PLAYBACK, mode); + + if (status < 0) + { + WLog_ERR(TAG, "snd_pcm_open failed"); + return FALSE; + } + + return rdpsnd_alsa_set_format(device, format, latency) && rdpsnd_alsa_open_mixer(alsa); +} + +static void rdpsnd_alsa_close(rdpsndDevicePlugin* device) +{ + rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device; + + if (!alsa) + return; + + rdpsnd_alsa_close_mixer(alsa); +} + +static void rdpsnd_alsa_free(rdpsndDevicePlugin* device) +{ + rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device; + rdpsnd_alsa_pcm_close(alsa); + rdpsnd_alsa_close_mixer(alsa); + free(alsa->device_name); + free(alsa); +} + +static BOOL rdpsnd_alsa_format_supported(WINPR_ATTR_UNUSED rdpsndDevicePlugin* device, + const AUDIO_FORMAT* format) +{ + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + if (format->cbSize == 0 && format->nSamplesPerSec <= 48000 && + (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) && + (format->nChannels == 1 || format->nChannels == 2)) + { + return TRUE; + } + + break; + default: + break; + } + + return FALSE; +} + +static UINT32 rdpsnd_alsa_get_volume(rdpsndDevicePlugin* device) +{ + long volume_min = 0; + long volume_max = 0; + long volume_left = 0; + long volume_right = 0; + + rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device; + UINT32 dwVolumeLeft = ((50 * 0xFFFF) / 100); /* 50% */ + UINT32 dwVolumeRight = ((50 * 0xFFFF) / 100); /* 50% */ + + if (!rdpsnd_alsa_open_mixer(alsa)) + return 0; + + for (snd_mixer_elem_t* elem = snd_mixer_first_elem(alsa->mixer_handle); elem; + elem = snd_mixer_elem_next(elem)) + { + if (snd_mixer_selem_has_playback_volume(elem)) + { + snd_mixer_selem_get_playback_volume_range(elem, &volume_min, &volume_max); + snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, &volume_left); + snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, &volume_right); + dwVolumeLeft = + (UINT16)(((volume_left * 0xFFFF) - volume_min) / (volume_max - volume_min)); + dwVolumeRight = + (UINT16)(((volume_right * 0xFFFF) - volume_min) / (volume_max - volume_min)); + break; + } + } + + return (dwVolumeLeft << 16) | dwVolumeRight; +} + +static BOOL rdpsnd_alsa_set_volume(rdpsndDevicePlugin* device, UINT32 value) +{ + long left = 0; + long right = 0; + long volume_min = 0; + long volume_max = 0; + long volume_left = 0; + long volume_right = 0; + rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device; + + if (!rdpsnd_alsa_open_mixer(alsa)) + return FALSE; + + left = (value & 0xFFFF); + right = ((value >> 16) & 0xFFFF); + + for (snd_mixer_elem_t* elem = snd_mixer_first_elem(alsa->mixer_handle); elem; + elem = snd_mixer_elem_next(elem)) + { + if (snd_mixer_selem_has_playback_volume(elem)) + { + snd_mixer_selem_get_playback_volume_range(elem, &volume_min, &volume_max); + volume_left = volume_min + (left * (volume_max - volume_min)) / 0xFFFF; + volume_right = volume_min + (right * (volume_max - volume_min)) / 0xFFFF; + + if ((snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, volume_left) < + 0) || + (snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, + volume_right) < 0)) + { + WLog_ERR(TAG, "error setting the volume\n"); + return FALSE; + } + } + } + + return TRUE; +} + +static UINT rdpsnd_alsa_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size) +{ + UINT latency = 0; + size_t offset = 0; + rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device; + WINPR_ASSERT(alsa); + WINPR_ASSERT(data || (size == 0)); + const size_t frame_size = 1ull * alsa->actual_channels * alsa->aformat.wBitsPerSample / 8; + if (frame_size == 0) + return 0; + + while (offset < size) + { + snd_pcm_sframes_t status = + snd_pcm_writei(alsa->pcm_handle, &data[offset], (size - offset) / frame_size); + + if (status < 0) + status = snd_pcm_recover(alsa->pcm_handle, (int)status, 0); + + if (status < 0) + { + WLog_ERR(TAG, "status: %ld\n", status); + rdpsnd_alsa_close(device); + rdpsnd_alsa_open(device, nullptr, alsa->latency); + break; + } + + offset += WINPR_ASSERTING_INT_CAST(size_t, status) * frame_size; + } + + { + snd_pcm_sframes_t available = 0; + snd_pcm_sframes_t delay = 0; + int rc = snd_pcm_avail_delay(alsa->pcm_handle, &available, &delay); + + if ((rc == 0) && (available == 0)) /* Get [ms] from number of samples */ + latency = (UINT32)MIN(UINT32_MAX, delay * 1000 / alsa->actual_rate); + } + + return latency + alsa->latency; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_alsa_parse_addin_args(rdpsndDevicePlugin* device, const ADDIN_ARGV* args) +{ + int status = 0; + DWORD flags = 0; + const COMMAND_LINE_ARGUMENT_A* arg = nullptr; + rdpsndAlsaPlugin* alsa = (rdpsndAlsaPlugin*)device; + COMMAND_LINE_ARGUMENT_A rdpsnd_alsa_args[] = { + { "dev", COMMAND_LINE_VALUE_REQUIRED, "", nullptr, nullptr, -1, nullptr, "device" }, + { nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr } + }; + flags = + COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + status = CommandLineParseArgumentsA(args->argc, args->argv, rdpsnd_alsa_args, flags, alsa, + nullptr, nullptr); + + if (status < 0) + { + WLog_ERR(TAG, "CommandLineParseArgumentsA failed!"); + return CHANNEL_RC_INITIALIZATION_ERROR; + } + + arg = rdpsnd_alsa_args; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev") + { + alsa->device_name = _strdup(arg->Value); + + if (!alsa->device_name) + return CHANNEL_RC_NO_MEMORY; + } + CommandLineSwitchEnd(arg) + } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE alsa_freerdp_rdpsnd_client_subsystem_entry( + PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints)) +{ + const ADDIN_ARGV* args = nullptr; + rdpsndAlsaPlugin* alsa = nullptr; + UINT error = 0; + alsa = (rdpsndAlsaPlugin*)calloc(1, sizeof(rdpsndAlsaPlugin)); + + if (!alsa) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + alsa->device.Open = rdpsnd_alsa_open; + alsa->device.FormatSupported = rdpsnd_alsa_format_supported; + alsa->device.GetVolume = rdpsnd_alsa_get_volume; + alsa->device.SetVolume = rdpsnd_alsa_set_volume; + alsa->device.Play = rdpsnd_alsa_play; + alsa->device.Close = rdpsnd_alsa_close; + alsa->device.Free = rdpsnd_alsa_free; + args = pEntryPoints->args; + + if (args->argc > 1) + { + if ((error = rdpsnd_alsa_parse_addin_args(&alsa->device, args))) + { + WLog_ERR(TAG, "rdpsnd_alsa_parse_addin_args failed with error %" PRIu32 "", error); + goto error_parse_args; + } + } + + if (!alsa->device_name) + { + alsa->device_name = _strdup("default"); + + if (!alsa->device_name) + { + WLog_ERR(TAG, "_strdup failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_strdup; + } + } + + alsa->pcm_handle = nullptr; + alsa->actual_rate = 22050; + alsa->format = SND_PCM_FORMAT_S16_LE; + alsa->actual_channels = 2; + pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)alsa); + return CHANNEL_RC_OK; +error_strdup: + free(alsa->device_name); +error_parse_args: + free(alsa); + return error; +} diff --git a/third_party/FreeRDP/channels/rdpsnd/client/fake/CMakeLists.txt b/third_party/FreeRDP/channels/rdpsnd/client/fake/CMakeLists.txt new file mode 100644 index 0000000..8a30785 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/client/fake/CMakeLists.txt @@ -0,0 +1,27 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2019 Armin Novak +# Copyright 2019 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. + +define_channel_client_subsystem("rdpsnd" "fake" "") + +set(${MODULE_PREFIX}_SRCS rdpsnd_fake.c) + +set(${MODULE_PREFIX}_LIBS winpr freerdp) + +include_directories(..) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") diff --git a/third_party/FreeRDP/channels/rdpsnd/client/fake/rdpsnd_fake.c b/third_party/FreeRDP/channels/rdpsnd/client/fake/rdpsnd_fake.c new file mode 100644 index 0000000..e1f1d6f --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/client/fake/rdpsnd_fake.c @@ -0,0 +1,153 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Output Virtual Channel + * + * Copyright 2019 Armin Novak + * Copyright 2019 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 + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "rdpsnd_main.h" + +typedef struct +{ + rdpsndDevicePlugin device; +} rdpsndFakePlugin; + +static BOOL rdpsnd_fake_open(WINPR_ATTR_UNUSED rdpsndDevicePlugin* device, + WINPR_ATTR_UNUSED const AUDIO_FORMAT* format, + WINPR_ATTR_UNUSED UINT32 latency) +{ + return TRUE; +} + +static void rdpsnd_fake_close(WINPR_ATTR_UNUSED rdpsndDevicePlugin* device) +{ +} + +static BOOL rdpsnd_fake_set_volume(WINPR_ATTR_UNUSED rdpsndDevicePlugin* device, + WINPR_ATTR_UNUSED UINT32 value) +{ + return TRUE; +} + +static void rdpsnd_fake_free(rdpsndDevicePlugin* device) +{ + rdpsndFakePlugin* fake = (rdpsndFakePlugin*)device; + + if (!fake) + return; + + free(fake); +} + +static BOOL rdpsnd_fake_format_supported(WINPR_ATTR_UNUSED rdpsndDevicePlugin* device, + WINPR_ATTR_UNUSED const AUDIO_FORMAT* format) +{ + return TRUE; +} + +static UINT rdpsnd_fake_play(WINPR_ATTR_UNUSED rdpsndDevicePlugin* device, + WINPR_ATTR_UNUSED const BYTE* data, WINPR_ATTR_UNUSED size_t size) +{ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_fake_parse_addin_args(rdpsndFakePlugin* fake, const ADDIN_ARGV* args) +{ + int status = 0; + DWORD flags = 0; + const COMMAND_LINE_ARGUMENT_A* arg = nullptr; + COMMAND_LINE_ARGUMENT_A rdpsnd_fake_args[] = { { nullptr, 0, nullptr, nullptr, nullptr, -1, + nullptr, nullptr } }; + flags = + COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + status = CommandLineParseArgumentsA(args->argc, args->argv, rdpsnd_fake_args, flags, fake, + nullptr, nullptr); + + if (status < 0) + return ERROR_INVALID_DATA; + + arg = rdpsnd_fake_args; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) CommandLineSwitchEnd(arg) + } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE fake_freerdp_rdpsnd_client_subsystem_entry( + PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints)) +{ + const ADDIN_ARGV* args = nullptr; + rdpsndFakePlugin* fake = nullptr; + UINT ret = CHANNEL_RC_OK; + fake = (rdpsndFakePlugin*)calloc(1, sizeof(rdpsndFakePlugin)); + + if (!fake) + return CHANNEL_RC_NO_MEMORY; + + fake->device.Open = rdpsnd_fake_open; + fake->device.FormatSupported = rdpsnd_fake_format_supported; + fake->device.SetVolume = rdpsnd_fake_set_volume; + fake->device.Play = rdpsnd_fake_play; + fake->device.Close = rdpsnd_fake_close; + fake->device.Free = rdpsnd_fake_free; + args = pEntryPoints->args; + + if (args->argc > 1) + { + ret = rdpsnd_fake_parse_addin_args(fake, args); + + if (ret != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "error parsing arguments"); + goto error; + } + } + + pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, &fake->device); + return ret; +error: + rdpsnd_fake_free(&fake->device); + return ret; +} diff --git a/third_party/FreeRDP/channels/rdpsnd/client/ios/CMakeLists.txt b/third_party/FreeRDP/channels/rdpsnd/client/ios/CMakeLists.txt new file mode 100644 index 0000000..16491c2 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/client/ios/CMakeLists.txt @@ -0,0 +1,32 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Laxmikant Rashinkar +# Copyright 2012 Marc-Andre Moreau +# Copyright 2013 Corey Clayton +# +# 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. + +define_channel_client_subsystem("rdpsnd" "ios" "") + +find_library(CORE_AUDIO CoreAudio) +find_library(AUDIO_TOOL AudioToolbox) +find_library(CORE_FOUNDATION CoreFoundation) + +set(${MODULE_PREFIX}_SRCS rdpsnd_ios.c TPCircularBuffer.c) + +set(${MODULE_PREFIX}_LIBS winpr freerdp ${AUDIO_TOOL} ${CORE_AUDIO} ${CORE_FOUNDATION}) + +include_directories(..) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") diff --git a/third_party/FreeRDP/channels/rdpsnd/client/ios/TPCircularBuffer.c b/third_party/FreeRDP/channels/rdpsnd/client/ios/TPCircularBuffer.c new file mode 100644 index 0000000..b29f611 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/client/ios/TPCircularBuffer.c @@ -0,0 +1,153 @@ +// +// TPCircularBuffer.c +// Circular/Ring buffer implementation +// +// https://github.com/michaeltyson/TPCircularBuffer +// +// Created by Michael Tyson on 10/12/2011. +// +// Copyright (C) 2012-2013 A Tasty Pixel +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// + +#include + +#include "TPCircularBuffer.h" +#include "rdpsnd_main.h" + +#include +#include + +#define reportResult(result, operation) (_reportResult((result), (operation), __FILE__, __LINE__)) +static inline bool _reportResult(kern_return_t result, const char* operation, const char* file, + int line) +{ + if (result != ERR_SUCCESS) + { + WLog_DBG(TAG, "%s:%d: %s: %s\n", file, line, operation, mach_error_string(result)); + return false; + } + return true; +} + +bool TPCircularBufferInit(TPCircularBuffer* buffer, int length) +{ + + // Keep trying until we get our buffer, needed to handle race conditions + int retries = 3; + while (true) + { + + buffer->length = round_page(length); // We need whole page sizes + + // Temporarily allocate twice the length, so we have the contiguous address space to + // support a second instance of the buffer directly after + vm_address_t bufferAddress; + kern_return_t result = vm_allocate(mach_task_self(), &bufferAddress, buffer->length * 2, + VM_FLAGS_ANYWHERE); // allocate anywhere it'll fit + if (result != ERR_SUCCESS) + { + if (retries-- == 0) + { + reportResult(result, "Buffer allocation"); + return false; + } + // Try again if we fail + continue; + } + + // Now replace the second half of the allocation with a virtual copy of the first half. + // Deallocate the second half... + result = vm_deallocate(mach_task_self(), bufferAddress + buffer->length, buffer->length); + if (result != ERR_SUCCESS) + { + if (retries-- == 0) + { + reportResult(result, "Buffer deallocation"); + return false; + } + // If this fails somehow, deallocate the whole region and try again + vm_deallocate(mach_task_self(), bufferAddress, buffer->length); + continue; + } + + // Re-map the buffer to the address space immediately after the buffer + vm_address_t virtualAddress = bufferAddress + buffer->length; + vm_prot_t cur_prot, max_prot; + result = vm_remap(mach_task_self(), + &virtualAddress, // mirror target + buffer->length, // size of mirror + 0, // auto alignment + 0, // force remapping to virtualAddress + mach_task_self(), // same task + bufferAddress, // mirror source + 0, // MAP READ-WRITE, NOT COPY + &cur_prot, // unused protection struct + &max_prot, // unused protection struct + VM_INHERIT_DEFAULT); + if (result != ERR_SUCCESS) + { + if (retries-- == 0) + { + reportResult(result, "Remap buffer memory"); + return false; + } + // If this remap failed, we hit a race condition, so deallocate and try again + vm_deallocate(mach_task_self(), bufferAddress, buffer->length); + continue; + } + + if (virtualAddress != bufferAddress + buffer->length) + { + // If the memory is not contiguous, clean up both allocated buffers and try again + if (retries-- == 0) + { + WLog_DBG(TAG, "Couldn't map buffer memory to end of buffer"); + return false; + } + + vm_deallocate(mach_task_self(), virtualAddress, buffer->length); + vm_deallocate(mach_task_self(), bufferAddress, buffer->length); + continue; + } + + buffer->buffer = (void*)bufferAddress; + buffer->fillCount = 0; + buffer->head = buffer->tail = 0; + + return true; + } + return false; +} + +void TPCircularBufferCleanup(TPCircularBuffer* buffer) +{ + vm_deallocate(mach_task_self(), (vm_address_t)buffer->buffer, buffer->length * 2); + memset(buffer, 0, sizeof(TPCircularBuffer)); +} + +void TPCircularBufferClear(TPCircularBuffer* buffer) +{ + int32_t fillCount; + if (TPCircularBufferTail(buffer, &fillCount)) + { + TPCircularBufferConsume(buffer, fillCount); + } +} diff --git a/third_party/FreeRDP/channels/rdpsnd/client/ios/TPCircularBuffer.h b/third_party/FreeRDP/channels/rdpsnd/client/ios/TPCircularBuffer.h new file mode 100644 index 0000000..0e38840 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/client/ios/TPCircularBuffer.h @@ -0,0 +1,217 @@ +// +// TPCircularBuffer.h +// Circular/Ring buffer implementation +// +// https://github.com/michaeltyson/TPCircularBuffer +// +// Created by Michael Tyson on 10/12/2011. +// +// +// This implementation makes use of a virtual memory mapping technique that inserts a virtual copy +// of the buffer memory directly after the buffer's end, negating the need for any buffer +// wrap-around logic. Clients can simply use the returned memory address as if it were contiguous +// space. +// +// The implementation is thread-safe in the case of a single producer and single consumer. +// +// Virtual memory technique originally proposed by Philip Howard (http://vrb.slashusr.org/), and +// adapted to Darwin by Kurt Revis (http://www.snoize.com, +// http://www.snoize.com/Code/PlayBufferedSoundFile.tar.gz) +// +// +// Copyright (C) 2012-2013 A Tasty Pixel +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// + +#ifndef TPCircularBuffer_h +#define TPCircularBuffer_h + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + + typedef struct + { + void* buffer; + int32_t length; + int32_t tail; + int32_t head; + volatile int32_t fillCount; + } TPCircularBuffer; + + /*! + * Initialise buffer + * + * Note that the length is advisory only: Because of the way the + * memory mirroring technique works, the true buffer length will + * be multiples of the device page size (e.g. 4096 bytes) + * + * @param buffer Circular buffer + * @param length Length of buffer + */ + bool TPCircularBufferInit(TPCircularBuffer* buffer, int32_t length); + + /*! + * Cleanup buffer + * + * Releases buffer resources. + */ + void TPCircularBufferCleanup(TPCircularBuffer* buffer); + + /*! + * Clear buffer + * + * Resets buffer to original, empty state. + * + * This is safe for use by consumer while producer is accessing + * buffer. + */ + void TPCircularBufferClear(TPCircularBuffer* buffer); + + // Reading (consuming) + + /*! + * Access end of buffer + * + * This gives you a pointer to the end of the buffer, ready + * for reading, and the number of available bytes to read. + * + * @param buffer Circular buffer + * @param availableBytes On output, the number of bytes ready for reading + * @return Pointer to the first bytes ready for reading, or nullptr if buffer is empty + */ + static inline __attribute__((always_inline)) void* + TPCircularBufferTail(TPCircularBuffer* buffer, int32_t* availableBytes) + { + *availableBytes = buffer->fillCount; + if (*availableBytes == 0) + return nullptr; + return (void*)((char*)buffer->buffer + buffer->tail); + } + + /*! + * Consume bytes in buffer + * + * This frees up the just-read bytes, ready for writing again. + * + * @param buffer Circular buffer + * @param amount Number of bytes to consume + */ + static inline __attribute__((always_inline)) void + TPCircularBufferConsume(TPCircularBuffer* buffer, int32_t amount) + { + buffer->tail = (buffer->tail + amount) % buffer->length; + OSAtomicAdd32Barrier(-amount, &buffer->fillCount); + WINPR_ASSERT(buffer->fillCount >= 0); + } + + /*! + * Version of TPCircularBufferConsume without the memory barrier, for more optimal use in + * single-threaded contexts + */ + static inline __attribute__((always_inline)) void + TPCircularBufferConsumeNoBarrier(TPCircularBuffer* buffer, int32_t amount) + { + buffer->tail = (buffer->tail + amount) % buffer->length; + buffer->fillCount -= amount; + WINPR_ASSERT(buffer->fillCount >= 0); + } + + /*! + * Access front of buffer + * + * This gives you a pointer to the front of the buffer, ready + * for writing, and the number of available bytes to write. + * + * @param buffer Circular buffer + * @param availableBytes On output, the number of bytes ready for writing + * @return Pointer to the first bytes ready for writing, or nullptr if buffer is full + */ + static inline __attribute__((always_inline)) void* + TPCircularBufferHead(TPCircularBuffer* buffer, int32_t* availableBytes) + { + *availableBytes = (buffer->length - buffer->fillCount); + if (*availableBytes == 0) + return nullptr; + return (void*)((char*)buffer->buffer + buffer->head); + } + + // Writing (producing) + + /*! + * Produce bytes in buffer + * + * This marks the given section of the buffer ready for reading. + * + * @param buffer Circular buffer + * @param amount Number of bytes to produce + */ + static inline __attribute__((always_inline)) void + TPCircularBufferProduce(TPCircularBuffer* buffer, int amount) + { + buffer->head = (buffer->head + amount) % buffer->length; + OSAtomicAdd32Barrier(amount, &buffer->fillCount); + WINPR_ASSERT(buffer->fillCount <= buffer->length); + } + + /*! + * Version of TPCircularBufferProduce without the memory barrier, for more optimal use in + * single-threaded contexts + */ + static inline __attribute__((always_inline)) void + TPCircularBufferProduceNoBarrier(TPCircularBuffer* buffer, int amount) + { + buffer->head = (buffer->head + amount) % buffer->length; + buffer->fillCount += amount; + WINPR_ASSERT(buffer->fillCount <= buffer->length); + } + + /*! + * Helper routine to copy bytes to buffer + * + * This copies the given bytes to the buffer, and marks them ready for writing. + * + * @param buffer Circular buffer + * @param src Source buffer + * @param len Number of bytes in source buffer + * @return true if bytes copied, false if there was insufficient space + */ + static inline __attribute__((always_inline)) bool + TPCircularBufferProduceBytes(TPCircularBuffer* buffer, const void* src, int32_t len) + { + int32_t space; + void* ptr = TPCircularBufferHead(buffer, &space); + if (space < len) + return false; + memcpy(ptr, src, len); + TPCircularBufferProduce(buffer, len); + return true; + } + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/third_party/FreeRDP/channels/rdpsnd/client/ios/rdpsnd_ios.c b/third_party/FreeRDP/channels/rdpsnd/client/ios/rdpsnd_ios.c new file mode 100644 index 0000000..7dfca83 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/client/ios/rdpsnd_ios.c @@ -0,0 +1,282 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Output Virtual Channel + * + * Copyright 2013 Dell Software + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include +#include + +#import + +#include "rdpsnd_main.h" +#include "TPCircularBuffer.h" + +#define INPUT_BUFFER_SIZE 32768 +#define CIRCULAR_BUFFER_SIZE (INPUT_BUFFER_SIZE * 4) + +typedef struct +{ + rdpsndDevicePlugin device; + AudioComponentInstance audio_unit; + TPCircularBuffer buffer; + BOOL is_opened; + BOOL is_playing; +} rdpsndIOSPlugin; + +#define THIS(__ptr) ((rdpsndIOSPlugin*)__ptr) + +static OSStatus rdpsnd_ios_render_cb(void* inRefCon, + AudioUnitRenderActionFlags __unused* ioActionFlags, + const AudioTimeStamp __unused* inTimeStamp, UInt32 inBusNumber, + UInt32 __unused inNumberFrames, AudioBufferList* ioData) +{ + if (inBusNumber != 0) + { + return noErr; + } + + rdpsndIOSPlugin* p = THIS(inRefCon); + + for (unsigned int i = 0; i < ioData->mNumberBuffers; i++) + { + AudioBuffer* target_buffer = &ioData->mBuffers[i]; + int32_t available_bytes = 0; + const void* buffer = TPCircularBufferTail(&p->buffer, &available_bytes); + + if (buffer != nullptr && available_bytes > 0) + { + const int bytes_to_copy = MIN((int32_t)target_buffer->mDataByteSize, available_bytes); + memcpy(target_buffer->mData, buffer, bytes_to_copy); + target_buffer->mDataByteSize = bytes_to_copy; + TPCircularBufferConsume(&p->buffer, bytes_to_copy); + } + else + { + target_buffer->mDataByteSize = 0; + AudioOutputUnitStop(p->audio_unit); + p->is_playing = 0; + } + } + + return noErr; +} + +static BOOL rdpsnd_ios_format_supported(rdpsndDevicePlugin* __unused device, + const AUDIO_FORMAT* format) +{ + if (format->wFormatTag == WAVE_FORMAT_PCM) + { + return 1; + } + + return 0; +} + +static BOOL rdpsnd_ios_set_volume(rdpsndDevicePlugin* __unused device, UINT32 __unused value) +{ + return TRUE; +} + +static void rdpsnd_ios_start(rdpsndDevicePlugin* device) +{ + rdpsndIOSPlugin* p = THIS(device); + + /* If this device is not playing... */ + if (!p->is_playing) + { + /* Start the device. */ + int32_t available_bytes = 0; + TPCircularBufferTail(&p->buffer, &available_bytes); + + if (available_bytes > 0) + { + p->is_playing = 1; + AudioOutputUnitStart(p->audio_unit); + } + } +} + +static void rdpsnd_ios_stop(rdpsndDevicePlugin* __unused device) +{ + rdpsndIOSPlugin* p = THIS(device); + + /* If the device is playing... */ + if (p->is_playing) + { + /* Stop the device. */ + AudioOutputUnitStop(p->audio_unit); + p->is_playing = 0; + /* Free all buffers. */ + TPCircularBufferClear(&p->buffer); + } +} + +static UINT rdpsnd_ios_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size) +{ + rdpsndIOSPlugin* p = THIS(device); + const BOOL ok = TPCircularBufferProduceBytes(&p->buffer, data, size); + + if (!ok) + return 0; + + rdpsnd_ios_start(device); + return 10; /* TODO: Get real latencry in [ms] */ +} + +static BOOL rdpsnd_ios_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, + UINT32 __unused latency) +{ + rdpsndIOSPlugin* p = THIS(device); + + if (p->is_opened) + return TRUE; + + /* Find the output audio unit. */ + AudioComponentDescription desc; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = kAudioUnitSubType_RemoteIO; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + AudioComponent audioComponent = AudioComponentFindNext(nullptr, &desc); + + if (audioComponent == nullptr) + return FALSE; + + /* Open the audio unit. */ + OSStatus status = AudioComponentInstanceNew(audioComponent, &p->audio_unit); + + if (status != 0) + return FALSE; + + /* Set the format for the AudioUnit. */ + AudioStreamBasicDescription audioFormat = WINPR_C_ARRAY_INIT; + audioFormat.mSampleRate = format->nSamplesPerSec; + audioFormat.mFormatID = kAudioFormatLinearPCM; + audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; + audioFormat.mFramesPerPacket = 1; /* imminent property of the Linear PCM */ + audioFormat.mChannelsPerFrame = format->nChannels; + audioFormat.mBitsPerChannel = format->wBitsPerSample; + audioFormat.mBytesPerFrame = (format->wBitsPerSample * format->nChannels) / 8; + audioFormat.mBytesPerPacket = audioFormat.mBytesPerFrame * audioFormat.mFramesPerPacket; + status = AudioUnitSetProperty(p->audio_unit, kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, 0, &audioFormat, sizeof(audioFormat)); + + if (status != 0) + { + AudioComponentInstanceDispose(p->audio_unit); + p->audio_unit = nullptr; + return FALSE; + } + + /* Set up the AudioUnit callback. */ + AURenderCallbackStruct callbackStruct = WINPR_C_ARRAY_INIT; + callbackStruct.inputProc = rdpsnd_ios_render_cb; + callbackStruct.inputProcRefCon = p; + status = + AudioUnitSetProperty(p->audio_unit, kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, 0, &callbackStruct, sizeof(callbackStruct)); + + if (status != 0) + { + AudioComponentInstanceDispose(p->audio_unit); + p->audio_unit = nullptr; + return FALSE; + } + + /* Initialize the AudioUnit. */ + status = AudioUnitInitialize(p->audio_unit); + + if (status != 0) + { + AudioComponentInstanceDispose(p->audio_unit); + p->audio_unit = nullptr; + return FALSE; + } + + /* Allocate the circular buffer. */ + const BOOL ok = TPCircularBufferInit(&p->buffer, CIRCULAR_BUFFER_SIZE); + + if (!ok) + { + AudioUnitUninitialize(p->audio_unit); + AudioComponentInstanceDispose(p->audio_unit); + p->audio_unit = nullptr; + return FALSE; + } + + p->is_opened = 1; + return TRUE; +} + +static void rdpsnd_ios_close(rdpsndDevicePlugin* device) +{ + rdpsndIOSPlugin* p = THIS(device); + /* Make sure the device is stopped. */ + rdpsnd_ios_stop(device); + + /* If the device is open... */ + if (p->is_opened) + { + /* Close the device. */ + AudioUnitUninitialize(p->audio_unit); + AudioComponentInstanceDispose(p->audio_unit); + p->audio_unit = nullptr; + p->is_opened = 0; + /* Destroy the circular buffer. */ + TPCircularBufferCleanup(&p->buffer); + } +} + +static void rdpsnd_ios_free(rdpsndDevicePlugin* device) +{ + rdpsndIOSPlugin* p = THIS(device); + /* Ensure the device is closed. */ + rdpsnd_ios_close(device); + /* Free memory associated with the device. */ + free(p); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE ios_freerdp_rdpsnd_client_subsystem_entry( + PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints)) +{ + rdpsndIOSPlugin* p = (rdpsndIOSPlugin*)calloc(1, sizeof(rdpsndIOSPlugin)); + + if (!p) + return CHANNEL_RC_NO_MEMORY; + + p->device.Open = rdpsnd_ios_open; + p->device.FormatSupported = rdpsnd_ios_format_supported; + p->device.SetVolume = rdpsnd_ios_set_volume; + p->device.Play = rdpsnd_ios_play; + p->device.Start = rdpsnd_ios_start; + p->device.Close = rdpsnd_ios_close; + p->device.Free = rdpsnd_ios_free; + pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)p); + return CHANNEL_RC_OK; +} diff --git a/third_party/FreeRDP/channels/rdpsnd/client/mac/CMakeLists.txt b/third_party/FreeRDP/channels/rdpsnd/client/mac/CMakeLists.txt new file mode 100644 index 0000000..57c1558 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/client/mac/CMakeLists.txt @@ -0,0 +1,43 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Laxmikant Rashinkar +# Copyright 2012 Marc-Andre Moreau +# Copyright 2013 Corey Clayton +# +# 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. + +define_channel_client_subsystem("rdpsnd" "mac" "") + +find_library(COCOA_LIBRARY Cocoa REQUIRED) +find_library(CORE_FOUNDATION CoreFoundation) +find_library(CORE_AUDIO CoreAudio REQUIRED) +find_library(AUDIO_TOOL AudioToolbox REQUIRED) +find_library(AV_FOUNDATION AVFoundation REQUIRED) + +set(${MODULE_PREFIX}_SRCS rdpsnd_mac.m) + +set(${MODULE_PREFIX}_LIBS + winpr + freerdp + ${AUDIO_TOOL} + ${AV_FOUNDATION} + ${CORE_AUDIO} + ${COCOA_LIBRARY} + ${CORE_FOUNDATION} +) + +include_directories(..) +include_directories(SYSTEM ${MACAUDIO_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") diff --git a/third_party/FreeRDP/channels/rdpsnd/client/mac/rdpsnd_mac.m b/third_party/FreeRDP/channels/rdpsnd/client/mac/rdpsnd_mac.m new file mode 100644 index 0000000..1c915e1 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/client/mac/rdpsnd_mac.m @@ -0,0 +1,402 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Output Virtual Channel + * + * Copyright 2012 Laxmikant Rashinkar + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 Inuvika Inc. + * Copyright 2016 David PHAM-VAN + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#include "rdpsnd_main.h" + +typedef struct +{ + rdpsndDevicePlugin device; + + BOOL isOpen; + BOOL isPlaying; + + UINT32 latency; + AUDIO_FORMAT format; + + AVAudioEngine *engine; + AVAudioPlayerNode *player; + UINT64 diff; +} rdpsndMacPlugin; + +static BOOL rdpsnd_mac_set_format(rdpsndDevicePlugin *device, const AUDIO_FORMAT *format, + UINT32 latency) +{ + rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device; + if (!mac || !format) + return FALSE; + + mac->latency = latency; + mac->format = *format; + + audio_format_print(WLog_Get(TAG), WLOG_DEBUG, format); + return TRUE; +} + +static char *FormatError(OSStatus st) +{ + switch (st) + { + case kAudioFileUnspecifiedError: + return "kAudioFileUnspecifiedError"; + + case kAudioFileUnsupportedFileTypeError: + return "kAudioFileUnsupportedFileTypeError"; + + case kAudioFileUnsupportedDataFormatError: + return "kAudioFileUnsupportedDataFormatError"; + + case kAudioFileUnsupportedPropertyError: + return "kAudioFileUnsupportedPropertyError"; + + case kAudioFileBadPropertySizeError: + return "kAudioFileBadPropertySizeError"; + + case kAudioFilePermissionsError: + return "kAudioFilePermissionsError"; + + case kAudioFileNotOptimizedError: + return "kAudioFileNotOptimizedError"; + + case kAudioFileInvalidChunkError: + return "kAudioFileInvalidChunkError"; + + case kAudioFileDoesNotAllow64BitDataSizeError: + return "kAudioFileDoesNotAllow64BitDataSizeError"; + + case kAudioFileInvalidPacketOffsetError: + return "kAudioFileInvalidPacketOffsetError"; + + case kAudioFileInvalidFileError: + return "kAudioFileInvalidFileError"; + + case kAudioFileOperationNotSupportedError: + return "kAudioFileOperationNotSupportedError"; + + case kAudioFileNotOpenError: + return "kAudioFileNotOpenError"; + + case kAudioFileEndOfFileError: + return "kAudioFileEndOfFileError"; + + case kAudioFilePositionError: + return "kAudioFilePositionError"; + + case kAudioFileFileNotFoundError: + return "kAudioFileFileNotFoundError"; + + default: + return "unknown error"; + } +} + +static void rdpsnd_mac_release(rdpsndMacPlugin *mac) +{ + if (mac->player) + [mac->player release]; + mac->player = nullptr; + + if (mac->engine) + [mac->engine release]; + mac->engine = nullptr; +} + +static BOOL rdpsnd_mac_open(rdpsndDevicePlugin *device, const AUDIO_FORMAT *format, UINT32 latency) +{ + @autoreleasepool + { + AudioDeviceID outputDeviceID; + UInt32 propertySize; + OSStatus err; + NSError *error; + rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device; + AudioObjectPropertyAddress propertyAddress = { + kAudioHardwarePropertyDefaultOutputDevice, + kAudioObjectPropertyScopeGlobal, +#if defined(MAC_OS_VERSION_12_0) + kAudioObjectPropertyElementMain +#else + kAudioObjectPropertyElementMaster +#endif + }; + + if (mac->isOpen) + return TRUE; + + if (!rdpsnd_mac_set_format(device, format, latency)) + return FALSE; + + propertySize = sizeof(outputDeviceID); + err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, nullptr, + &propertySize, &outputDeviceID); + if (err) + { + WLog_ERR(TAG, "AudioHardwareGetProperty: %s", FormatError(err)); + return FALSE; + } + + mac->engine = [[AVAudioEngine alloc] init]; + if (!mac->engine) + return FALSE; + + err = AudioUnitSetProperty(mac->engine.outputNode.audioUnit, + kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, + 0, &outputDeviceID, sizeof(outputDeviceID)); + if (err) + { + rdpsnd_mac_release(mac); + WLog_ERR(TAG, "AudioUnitSetProperty: %s", FormatError(err)); + return FALSE; + } + + mac->player = [[AVAudioPlayerNode alloc] init]; + if (!mac->player) + { + rdpsnd_mac_release(mac); + WLog_ERR(TAG, "AVAudioPlayerNode::init() failed"); + return FALSE; + } + + [mac->engine attachNode:mac->player]; + + [mac->engine connect:mac->player to:mac->engine.mainMixerNode format:nil]; + + [mac->engine prepare]; + + if (![mac->engine startAndReturnError:&error]) + { + device->Close(device); + WLog_ERR(TAG, "Failed to start audio player %s", + [error.localizedDescription UTF8String]); + return FALSE; + } + + mac->isOpen = TRUE; + return TRUE; + } +} + +static void rdpsnd_mac_close(rdpsndDevicePlugin *device) +{ + @autoreleasepool + { + rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device; + + if (mac->isPlaying) + { + [mac->player stop]; + mac->isPlaying = FALSE; + } + + if (mac->isOpen) + { + [mac->engine stop]; + mac->isOpen = FALSE; + } + + rdpsnd_mac_release(mac); + } +} + +static void rdpsnd_mac_free(rdpsndDevicePlugin *device) +{ + rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device; + device->Close(device); + free(mac); +} + +static BOOL rdpsnd_mac_format_supported(rdpsndDevicePlugin *device, const AUDIO_FORMAT *format) +{ + WINPR_UNUSED(device); + + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + if (format->wBitsPerSample != 16) + return FALSE; + + if (format->nChannels != 2) + return FALSE; + return TRUE; + + default: + return FALSE; + } +} + +static BOOL rdpsnd_mac_set_volume(rdpsndDevicePlugin *device, UINT32 value) +{ + @autoreleasepool + { + Float32 fVolume; + UINT16 volumeLeft; + UINT16 volumeRight; + rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device; + + if (!mac->player) + return FALSE; + + volumeLeft = (value & 0xFFFF); + volumeRight = ((value >> 16) & 0xFFFF); + fVolume = ((float)volumeLeft) / 65535.0f; + + mac->player.volume = fVolume; + + return TRUE; + } +} + +static void rdpsnd_mac_start(rdpsndDevicePlugin *device) +{ + @autoreleasepool + { + rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device; + + if (!mac->isPlaying) + { + if (!mac->engine.isRunning) + { + NSError *error; + + if (![mac->engine startAndReturnError:&error]) + { + device->Close(device); + WLog_ERR(TAG, "Failed to start audio player %s", + [error.localizedDescription UTF8String]); + return; + } + } + + [mac->player play]; + + mac->isPlaying = TRUE; + mac->diff = 100; /* Initial latency, corrected after first sample is played. */ + } + } +} + +static UINT rdpsnd_mac_play(rdpsndDevicePlugin *device, const BYTE *data, size_t size) +{ + @autoreleasepool + { + rdpsndMacPlugin *mac = (rdpsndMacPlugin *)device; + AVAudioPCMBuffer *buffer; + AVAudioFormat *format; + float *const *db; + size_t step; + AVAudioFrameCount count; + UINT64 start = GetTickCount64(); + + if (!mac->isOpen) + return 0; + + step = 2 * mac->format.nChannels; + + count = size / step; + format = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32 + sampleRate:mac->format.nSamplesPerSec + channels:mac->format.nChannels + interleaved:NO]; + + if (!format) + { + WLog_WARN(TAG, "AVAudioFormat::init() failed"); + return 0; + } + + buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:format frameCapacity:count]; + [format release]; + + if (!buffer) + { + WLog_WARN(TAG, "AVAudioPCMBuffer::init() failed"); + return 0; + } + + buffer.frameLength = buffer.frameCapacity; + db = buffer.floatChannelData; + + for (size_t pos = 0; pos < count; pos++) + { + const BYTE *d = &data[pos * step]; + for (size_t x = 0; x < mac->format.nChannels; x++) + { + const float val = (int16_t)((uint16_t)d[0] | ((uint16_t)d[1] << 8)) / 32768.0f; + db[x][pos] = val; + d += sizeof(int16_t); + } + } + + rdpsnd_mac_start(device); + + [mac->player scheduleBuffer:buffer + completionHandler:^{ + UINT64 stop = GetTickCount64(); + if (start > stop) + mac->diff = 0; + else + mac->diff = stop - start; + }]; + + [buffer release]; + + return mac->diff > UINT_MAX ? UINT_MAX : mac->diff; + } +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE mac_freerdp_rdpsnd_client_subsystem_entry( + PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints)) +{ + rdpsndMacPlugin *mac; + mac = (rdpsndMacPlugin *)calloc(1, sizeof(rdpsndMacPlugin)); + + if (!mac) + return CHANNEL_RC_NO_MEMORY; + + mac->device.Open = rdpsnd_mac_open; + mac->device.FormatSupported = rdpsnd_mac_format_supported; + mac->device.SetVolume = rdpsnd_mac_set_volume; + mac->device.Play = rdpsnd_mac_play; + mac->device.Close = rdpsnd_mac_close; + mac->device.Free = rdpsnd_mac_free; + pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin *)mac); + return CHANNEL_RC_OK; +} diff --git a/third_party/FreeRDP/channels/rdpsnd/client/opensles/CMakeLists.txt b/third_party/FreeRDP/channels/rdpsnd/client/opensles/CMakeLists.txt new file mode 100644 index 0000000..5a93720 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/client/opensles/CMakeLists.txt @@ -0,0 +1,29 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2013 Armin Novak +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client_subsystem("rdpsnd" "opensles" "") + +find_package(OpenSLES REQUIRED) + +set(${MODULE_PREFIX}_SRCS opensl_io.c rdpsnd_opensles.c) + +set(${MODULE_PREFIX}_LIBS winpr freerdp ${OpenSLES_LIBRARIES}) + +include_directories(..) +include_directories(SYSTEM ${OpenSLES_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") diff --git a/third_party/FreeRDP/channels/rdpsnd/client/opensles/opensl_io.c b/third_party/FreeRDP/channels/rdpsnd/client/opensles/opensl_io.c new file mode 100644 index 0000000..8dcbb1d --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/client/opensles/opensl_io.c @@ -0,0 +1,422 @@ +/* +opensl_io.c: +Android OpenSL input/output module +Copyright (c) 2012, Victor Lazzarini +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +#include "rdpsnd_main.h" +#include "opensl_io.h" +#define CONV16BIT 32768 +#define CONVMYFLT (1. / 32768.) + +static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void* context); + +// creates the OpenSL ES audio engine +static SLresult openSLCreateEngine(OPENSL_STREAM* p) +{ + SLresult result; + // create engine + result = slCreateEngine(&(p->engineObject), 0, nullptr, 0, nullptr, nullptr); + DEBUG_SND("engineObject=%p", (void*)p->engineObject); + + if (result != SL_RESULT_SUCCESS) + goto engine_end; + + // realize the engine + result = (*p->engineObject)->Realize(p->engineObject, SL_BOOLEAN_FALSE); + DEBUG_SND("Realize=%" PRIu32 "", result); + + if (result != SL_RESULT_SUCCESS) + goto engine_end; + + // get the engine interface, which is needed in order to create other objects + result = (*p->engineObject)->GetInterface(p->engineObject, SL_IID_ENGINE, &(p->engineEngine)); + DEBUG_SND("engineEngine=%p", (void*)p->engineEngine); + + if (result != SL_RESULT_SUCCESS) + goto engine_end; + +engine_end: + return result; +} + +// opens the OpenSL ES device for output +static SLresult openSLPlayOpen(OPENSL_STREAM* p) +{ + SLresult result; + SLuint32 sr = p->sr; + SLuint32 channels = p->outchannels; + WINPR_ASSERT(p->engineObject); + WINPR_ASSERT(p->engineEngine); + + if (channels) + { + // configure audio source + SLDataLocator_AndroidSimpleBufferQueue loc_bufq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, + p->queuesize }; + + switch (sr) + { + case 8000: + sr = SL_SAMPLINGRATE_8; + break; + + case 11025: + sr = SL_SAMPLINGRATE_11_025; + break; + + case 16000: + sr = SL_SAMPLINGRATE_16; + break; + + case 22050: + sr = SL_SAMPLINGRATE_22_05; + break; + + case 24000: + sr = SL_SAMPLINGRATE_24; + break; + + case 32000: + sr = SL_SAMPLINGRATE_32; + break; + + case 44100: + sr = SL_SAMPLINGRATE_44_1; + break; + + case 48000: + sr = SL_SAMPLINGRATE_48; + break; + + case 64000: + sr = SL_SAMPLINGRATE_64; + break; + + case 88200: + sr = SL_SAMPLINGRATE_88_2; + break; + + case 96000: + sr = SL_SAMPLINGRATE_96; + break; + + case 192000: + sr = SL_SAMPLINGRATE_192; + break; + + default: + return -1; + } + + const SLInterfaceID ids[] = { SL_IID_VOLUME }; + const SLboolean req[] = { SL_BOOLEAN_FALSE }; + result = (*p->engineEngine) + ->CreateOutputMix(p->engineEngine, &(p->outputMixObject), 1, ids, req); + DEBUG_SND("engineEngine=%p", (void*)p->engineEngine); + WINPR_ASSERT(!result); + + if (result != SL_RESULT_SUCCESS) + goto end_openaudio; + + // realize the output mix + result = (*p->outputMixObject)->Realize(p->outputMixObject, SL_BOOLEAN_FALSE); + DEBUG_SND("Realize=%" PRIu32 "", result); + WINPR_ASSERT(!result); + + if (result != SL_RESULT_SUCCESS) + goto end_openaudio; + + int speakers; + + if (channels > 1) + speakers = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + else + speakers = SL_SPEAKER_FRONT_CENTER; + + SLDataFormat_PCM format_pcm = { SL_DATAFORMAT_PCM, + channels, + sr, + SL_PCMSAMPLEFORMAT_FIXED_16, + SL_PCMSAMPLEFORMAT_FIXED_16, + speakers, + SL_BYTEORDER_LITTLEENDIAN }; + SLDataSource audioSrc = { &loc_bufq, &format_pcm }; + // configure audio sink + SLDataLocator_OutputMix loc_outmix = { SL_DATALOCATOR_OUTPUTMIX, p->outputMixObject }; + SLDataSink audioSnk = { &loc_outmix, nullptr }; + // create audio player + const SLInterfaceID ids1[] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_VOLUME }; + const SLboolean req1[] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE }; + result = (*p->engineEngine) + ->CreateAudioPlayer(p->engineEngine, &(p->bqPlayerObject), &audioSrc, + &audioSnk, 2, ids1, req1); + DEBUG_SND("bqPlayerObject=%p", (void*)p->bqPlayerObject); + WINPR_ASSERT(!result); + + if (result != SL_RESULT_SUCCESS) + goto end_openaudio; + + // realize the player + result = (*p->bqPlayerObject)->Realize(p->bqPlayerObject, SL_BOOLEAN_FALSE); + DEBUG_SND("Realize=%" PRIu32 "", result); + WINPR_ASSERT(!result); + + if (result != SL_RESULT_SUCCESS) + goto end_openaudio; + + // get the play interface + result = + (*p->bqPlayerObject)->GetInterface(p->bqPlayerObject, SL_IID_PLAY, &(p->bqPlayerPlay)); + DEBUG_SND("bqPlayerPlay=%p", (void*)p->bqPlayerPlay); + WINPR_ASSERT(!result); + + if (result != SL_RESULT_SUCCESS) + goto end_openaudio; + + // get the volume interface + result = (*p->bqPlayerObject) + ->GetInterface(p->bqPlayerObject, SL_IID_VOLUME, &(p->bqPlayerVolume)); + DEBUG_SND("bqPlayerVolume=%p", (void*)p->bqPlayerVolume); + WINPR_ASSERT(!result); + + if (result != SL_RESULT_SUCCESS) + goto end_openaudio; + + // get the buffer queue interface + result = (*p->bqPlayerObject) + ->GetInterface(p->bqPlayerObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + &(p->bqPlayerBufferQueue)); + DEBUG_SND("bqPlayerBufferQueue=%p", (void*)p->bqPlayerBufferQueue); + WINPR_ASSERT(!result); + + if (result != SL_RESULT_SUCCESS) + goto end_openaudio; + + // register callback on the buffer queue + result = (*p->bqPlayerBufferQueue) + ->RegisterCallback(p->bqPlayerBufferQueue, bqPlayerCallback, p); + DEBUG_SND("bqPlayerCallback=%p", (void*)p->bqPlayerCallback); + WINPR_ASSERT(!result); + + if (result != SL_RESULT_SUCCESS) + goto end_openaudio; + + // set the player's state to playing + result = (*p->bqPlayerPlay)->SetPlayState(p->bqPlayerPlay, SL_PLAYSTATE_PLAYING); + DEBUG_SND("SetPlayState=%" PRIu32 "", result); + WINPR_ASSERT(!result); + end_openaudio: + WINPR_ASSERT(!result); + return result; + } + + return SL_RESULT_SUCCESS; +} + +// close the OpenSL IO and destroy the audio engine +static void openSLDestroyEngine(OPENSL_STREAM* p) +{ + // destroy buffer queue audio player object, and invalidate all associated interfaces + if (p->bqPlayerObject != nullptr) + { + (*p->bqPlayerObject)->Destroy(p->bqPlayerObject); + p->bqPlayerObject = nullptr; + p->bqPlayerVolume = nullptr; + p->bqPlayerPlay = nullptr; + p->bqPlayerBufferQueue = nullptr; + p->bqPlayerEffectSend = nullptr; + } + + // destroy output mix object, and invalidate all associated interfaces + if (p->outputMixObject != nullptr) + { + (*p->outputMixObject)->Destroy(p->outputMixObject); + p->outputMixObject = nullptr; + } + + // destroy engine object, and invalidate all associated interfaces + if (p->engineObject != nullptr) + { + (*p->engineObject)->Destroy(p->engineObject); + p->engineObject = nullptr; + p->engineEngine = nullptr; + } +} + +// open the android audio device for and/or output +OPENSL_STREAM* android_OpenAudioDevice(int sr, int outchannels, int bufferframes) +{ + OPENSL_STREAM* p; + p = (OPENSL_STREAM*)calloc(1, sizeof(OPENSL_STREAM)); + + if (!p) + return nullptr; + + p->queuesize = bufferframes; + p->outchannels = outchannels; + p->sr = sr; + + if (openSLCreateEngine(p) != SL_RESULT_SUCCESS) + { + android_CloseAudioDevice(p); + return nullptr; + } + + if (openSLPlayOpen(p) != SL_RESULT_SUCCESS) + { + android_CloseAudioDevice(p); + return nullptr; + } + + p->queue = Queue_New(TRUE, -1, -1); + + if (!p->queue) + { + android_CloseAudioDevice(p); + return nullptr; + } + + return p; +} + +// close the android audio device +void android_CloseAudioDevice(OPENSL_STREAM* p) +{ + if (p == nullptr) + return; + + openSLDestroyEngine(p); + + if (p->queue) + Queue_Free(p->queue); + + free(p); +} + +// this callback handler is called every time a buffer finishes playing +static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void* context) +{ + OPENSL_STREAM* p = (OPENSL_STREAM*)context; + WINPR_ASSERT(p); + WINPR_ASSERT(p->queue); + void* data = Queue_Dequeue(p->queue); + free(data); +} + +// puts a buffer of size samples to the device +int android_AudioOut(OPENSL_STREAM* p, const short* buffer, int size) +{ + HANDLE ev; + WINPR_ASSERT(p); + WINPR_ASSERT(buffer); + WINPR_ASSERT(size > 0); + + ev = Queue_Event(p->queue); + /* Assure, that the queue is not full. */ + if (p->queuesize <= Queue_Count(p->queue) && WaitForSingleObject(ev, INFINITE) == WAIT_FAILED) + { + DEBUG_SND("WaitForSingleObject failed!"); + return -1; + } + + void* data = calloc(size, sizeof(short)); + + if (!data) + { + DEBUG_SND("unable to allocate a buffer"); + return -1; + } + + memcpy(data, buffer, size * sizeof(short)); + Queue_Enqueue(p->queue, data); + (*p->bqPlayerBufferQueue)->Enqueue(p->bqPlayerBufferQueue, data, sizeof(short) * size); + return size; +} + +int android_GetOutputMute(OPENSL_STREAM* p) +{ + SLboolean mute; + WINPR_ASSERT(p); + WINPR_ASSERT(p->bqPlayerVolume); + SLresult rc = (*p->bqPlayerVolume)->GetMute(p->bqPlayerVolume, &mute); + + if (SL_RESULT_SUCCESS != rc) + return SL_BOOLEAN_FALSE; + + return mute; +} + +BOOL android_SetOutputMute(OPENSL_STREAM* p, BOOL _mute) +{ + SLboolean mute = _mute; + WINPR_ASSERT(p); + WINPR_ASSERT(p->bqPlayerVolume); + SLresult rc = (*p->bqPlayerVolume)->SetMute(p->bqPlayerVolume, mute); + + if (SL_RESULT_SUCCESS != rc) + return FALSE; + + return TRUE; +} + +int android_GetOutputVolume(OPENSL_STREAM* p) +{ + SLmillibel level; + WINPR_ASSERT(p); + WINPR_ASSERT(p->bqPlayerVolume); + SLresult rc = (*p->bqPlayerVolume)->GetVolumeLevel(p->bqPlayerVolume, &level); + + if (SL_RESULT_SUCCESS != rc) + return 0; + + return level; +} + +int android_GetOutputVolumeMax(OPENSL_STREAM* p) +{ + SLmillibel level; + WINPR_ASSERT(p); + WINPR_ASSERT(p->bqPlayerVolume); + SLresult rc = (*p->bqPlayerVolume)->GetMaxVolumeLevel(p->bqPlayerVolume, &level); + + if (SL_RESULT_SUCCESS != rc) + return 0; + + return level; +} + +BOOL android_SetOutputVolume(OPENSL_STREAM* p, int level) +{ + SLresult rc = (*p->bqPlayerVolume)->SetVolumeLevel(p->bqPlayerVolume, level); + + if (SL_RESULT_SUCCESS != rc) + return FALSE; + + return TRUE; +} diff --git a/third_party/FreeRDP/channels/rdpsnd/client/opensles/opensl_io.h b/third_party/FreeRDP/channels/rdpsnd/client/opensles/opensl_io.h new file mode 100644 index 0000000..f303d21 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/client/opensles/opensl_io.h @@ -0,0 +1,110 @@ +/* +opensl_io.c: +Android OpenSL input/output module header +Copyright (c) 2012, Victor Lazzarini +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef FREERDP_CHANNEL_RDPSND_CLIENT_OPENSL_IO_H +#define FREERDP_CHANNEL_RDPSND_CLIENT_OPENSL_IO_H + +#include +#include +#include +#include + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + + typedef struct + { + // engine interfaces + SLObjectItf engineObject; + SLEngineItf engineEngine; + + // output mix interfaces + SLObjectItf outputMixObject; + + // buffer queue player interfaces + SLObjectItf bqPlayerObject; + SLPlayItf bqPlayerPlay; + SLVolumeItf bqPlayerVolume; + SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue; + SLEffectSendItf bqPlayerEffectSend; + + unsigned int outchannels; + unsigned int sr; + + unsigned int queuesize; + wQueue* queue; + } OPENSL_STREAM; + + /* + Open the audio device with a given sampling rate (sr), output channels and IO buffer size + in frames. Returns a handle to the OpenSL stream + */ + FREERDP_LOCAL OPENSL_STREAM* android_OpenAudioDevice(int sr, int outchannels, int bufferframes); + /* + Close the audio device + */ + FREERDP_LOCAL void android_CloseAudioDevice(OPENSL_STREAM* p); + /* + Write a buffer to the OpenSL stream *p, of size samples. Returns the number of samples written. + */ + FREERDP_LOCAL int android_AudioOut(OPENSL_STREAM* p, const short* buffer, int size); + /* + * Set the volume input level. + */ + FREERDP_LOCAL void android_SetInputVolume(OPENSL_STREAM* p, int level); + /* + * Get the current output mute setting. + */ + FREERDP_LOCAL int android_GetOutputMute(OPENSL_STREAM* p); + /* + * Change the current output mute setting. + */ + FREERDP_LOCAL BOOL android_SetOutputMute(OPENSL_STREAM* p, BOOL mute); + /* + * Get the current output volume level. + */ + FREERDP_LOCAL int android_GetOutputVolume(OPENSL_STREAM* p); + /* + * Get the maximum output volume level. + */ + FREERDP_LOCAL int android_GetOutputVolumeMax(OPENSL_STREAM* p); + + /* + * Set the volume output level. + */ + FREERDP_LOCAL BOOL android_SetOutputVolume(OPENSL_STREAM* p, int level); +#ifdef __cplusplus +}; +#endif + +#endif /* FREERDP_CHANNEL_RDPSND_CLIENT_OPENSL_IO_H */ diff --git a/third_party/FreeRDP/channels/rdpsnd/client/opensles/rdpsnd_opensles.c b/third_party/FreeRDP/channels/rdpsnd/client/opensles/rdpsnd_opensles.c new file mode 100644 index 0000000..70762ee --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/client/opensles/rdpsnd_opensles.c @@ -0,0 +1,373 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Output Virtual Channel + * + * Copyright 2013 Armin Novak + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "opensl_io.h" +#include "rdpsnd_main.h" + +typedef struct +{ + rdpsndDevicePlugin device; + + UINT32 latency; + int wformat; + int block_size; + char* device_name; + + OPENSL_STREAM* stream; + + UINT32 volume; + + UINT32 rate; + UINT32 channels; + int format; +} rdpsndopenslesPlugin; + +static int rdpsnd_opensles_volume_to_millibel(unsigned short level, int max) +{ + const int min = SL_MILLIBEL_MIN; + const int step = max - min; + const int rc = (level * step / 0xFFFF) + min; + DEBUG_SND("level=%hu, min=%d, max=%d, step=%d, result=%d", level, min, max, step, rc); + return rc; +} + +static unsigned short rdpsnd_opensles_millibel_to_volume(int millibel, int max) +{ + const int min = SL_MILLIBEL_MIN; + const int range = max - min; + const int rc = ((millibel - min) * 0xFFFF + range / 2 + 1) / range; + DEBUG_SND("millibel=%d, min=%d, max=%d, range=%d, result=%d", millibel, min, max, range, rc); + return rc; +} + +static bool rdpsnd_opensles_check_handle(const rdpsndopenslesPlugin* hdl) +{ + bool rc = true; + + if (!hdl) + rc = false; + else + { + if (!hdl->stream) + rc = false; + } + + return rc; +} + +static BOOL rdpsnd_opensles_set_volume(rdpsndDevicePlugin* device, UINT32 volume); + +static int rdpsnd_opensles_set_params(rdpsndopenslesPlugin* opensles) +{ + DEBUG_SND("opensles=%p", (void*)opensles); + + if (!rdpsnd_opensles_check_handle(opensles)) + return 0; + + if (opensles->stream) + android_CloseAudioDevice(opensles->stream); + + opensles->stream = android_OpenAudioDevice(opensles->rate, opensles->channels, 20); + return 0; +} + +static BOOL rdpsnd_opensles_set_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, + UINT32 latency) +{ + rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device; + rdpsnd_opensles_check_handle(opensles); + DEBUG_SND("opensles=%p format=%p, latency=%" PRIu32, (void*)opensles, (void*)format, latency); + + if (format) + { + DEBUG_SND("format=%" PRIu16 ", cbsize=%" PRIu16 ", samples=%" PRIu32 ", bits=%" PRIu16 + ", channels=%" PRIu16 ", align=%" PRIu16 "", + format->wFormatTag, format->cbSize, format->nSamplesPerSec, + format->wBitsPerSample, format->nChannels, format->nBlockAlign); + opensles->rate = format->nSamplesPerSec; + opensles->channels = format->nChannels; + opensles->format = format->wFormatTag; + opensles->wformat = format->wFormatTag; + opensles->block_size = format->nBlockAlign; + } + + opensles->latency = latency; + return (rdpsnd_opensles_set_params(opensles) == 0); +} + +static BOOL rdpsnd_opensles_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, + UINT32 latency) +{ + rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device; + DEBUG_SND("opensles=%p format=%p, latency=%" PRIu32 ", rate=%" PRIu32 "", (void*)opensles, + (void*)format, latency, opensles->rate); + + if (rdpsnd_opensles_check_handle(opensles)) + return TRUE; + + opensles->stream = android_OpenAudioDevice(opensles->rate, opensles->channels, 20); + WINPR_ASSERT(opensles->stream); + + if (!opensles->stream) + WLog_ERR(TAG, "android_OpenAudioDevice failed"); + else + rdpsnd_opensles_set_volume(device, opensles->volume); + + return rdpsnd_opensles_set_format(device, format, latency); +} + +static void rdpsnd_opensles_close(rdpsndDevicePlugin* device) +{ + rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device; + DEBUG_SND("opensles=%p", (void*)opensles); + + if (!rdpsnd_opensles_check_handle(opensles)) + return; + + android_CloseAudioDevice(opensles->stream); + opensles->stream = nullptr; +} + +static void rdpsnd_opensles_free(rdpsndDevicePlugin* device) +{ + rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device; + DEBUG_SND("opensles=%p", (void*)opensles); + WINPR_ASSERT(opensles); + WINPR_ASSERT(opensles->device_name); + free(opensles->device_name); + free(opensles); +} + +static BOOL rdpsnd_opensles_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format) +{ + DEBUG_SND("format=%" PRIu16 ", cbsize=%" PRIu16 ", samples=%" PRIu32 ", bits=%" PRIu16 + ", channels=%" PRIu16 ", align=%" PRIu16 "", + format->wFormatTag, format->cbSize, format->nSamplesPerSec, format->wBitsPerSample, + format->nChannels, format->nBlockAlign); + WINPR_ASSERT(device); + WINPR_ASSERT(format); + + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + if (format->cbSize == 0 && format->nSamplesPerSec <= 48000 && + (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) && + (format->nChannels == 1 || format->nChannels == 2)) + { + return TRUE; + } + + break; + + default: + break; + } + + return FALSE; +} + +static UINT32 rdpsnd_opensles_get_volume(rdpsndDevicePlugin* device) +{ + rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device; + DEBUG_SND("opensles=%p", (void*)opensles); + WINPR_ASSERT(opensles); + + if (opensles->stream) + { + const int max = android_GetOutputVolumeMax(opensles->stream); + const int rc = android_GetOutputVolume(opensles->stream); + + if (android_GetOutputMute(opensles->stream)) + opensles->volume = 0; + else + { + const unsigned short vol = rdpsnd_opensles_millibel_to_volume(rc, max); + opensles->volume = (vol << 16) | (vol & 0xFFFF); + } + } + + return opensles->volume; +} + +static BOOL rdpsnd_opensles_set_volume(rdpsndDevicePlugin* device, UINT32 value) +{ + rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device; + DEBUG_SND("opensles=%p, value=%" PRIu32 "", (void*)opensles, value); + WINPR_ASSERT(opensles); + opensles->volume = value; + + if (opensles->stream) + { + if (0 == opensles->volume) + return android_SetOutputMute(opensles->stream, true); + else + { + const int max = android_GetOutputVolumeMax(opensles->stream); + const int vol = rdpsnd_opensles_volume_to_millibel(value & 0xFFFF, max); + + if (!android_SetOutputMute(opensles->stream, false)) + return FALSE; + + if (!android_SetOutputVolume(opensles->stream, vol)) + return FALSE; + } + } + + return TRUE; +} + +static UINT rdpsnd_opensles_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size) +{ + union + { + const BYTE* b; + const short* s; + } src; + int ret; + rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device; + DEBUG_SND("opensles=%p, data=%p, size=%d", (void*)opensles, (void*)data, size); + + if (!rdpsnd_opensles_check_handle(opensles)) + return 0; + + src.b = data; + DEBUG_SND("size=%d, src=%p", size, (void*)src.b); + WINPR_ASSERT(0 == size % 2); + WINPR_ASSERT(size > 0); + WINPR_ASSERT(src.b); + ret = android_AudioOut(opensles->stream, src.s, size / 2); + + if (ret < 0) + WLog_ERR(TAG, "android_AudioOut failed (%d)", ret); + + return 10; /* TODO: Get real latencry in [ms] */ +} + +static void rdpsnd_opensles_start(rdpsndDevicePlugin* device) +{ + rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device; + rdpsnd_opensles_check_handle(opensles); + DEBUG_SND("opensles=%p", (void*)opensles); +} + +static int rdpsnd_opensles_parse_addin_args(rdpsndDevicePlugin* device, const ADDIN_ARGV* args) +{ + rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)device; + COMMAND_LINE_ARGUMENT_A rdpsnd_opensles_args[] = { + { "dev", COMMAND_LINE_VALUE_REQUIRED, "", nullptr, nullptr, -1, nullptr, "device" }, + { nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr } + }; + + WINPR_ASSERT(opensles); + WINPR_ASSERT(args); + DEBUG_SND("opensles=%p, args=%p", (void*)opensles, (void*)args); + const DWORD flags = + COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + const int status = CommandLineParseArgumentsA(args->argc, args->argv, rdpsnd_opensles_args, + flags, opensles, nullptr, nullptr); + + if (status < 0) + return status; + + const COMMAND_LINE_ARGUMENT_A* arg = rdpsnd_opensles_args; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev") + { + opensles->device_name = _strdup(arg->Value); + + if (!opensles->device_name) + return ERROR_OUTOFMEMORY; + } + CommandLineSwitchEnd(arg) + } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr); + + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE opensles_freerdp_rdpsnd_client_subsystem_entry( + PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints)) +{ + UINT error = ERROR_INTERNAL_ERROR; + DEBUG_SND("pEntryPoints=%p", (void*)pEntryPoints); + rdpsndopenslesPlugin* opensles = (rdpsndopenslesPlugin*)calloc(1, sizeof(rdpsndopenslesPlugin)); + + if (!opensles) + return CHANNEL_RC_NO_MEMORY; + + opensles->device.Open = rdpsnd_opensles_open; + opensles->device.FormatSupported = rdpsnd_opensles_format_supported; + opensles->device.GetVolume = rdpsnd_opensles_get_volume; + opensles->device.SetVolume = rdpsnd_opensles_set_volume; + opensles->device.Start = rdpsnd_opensles_start; + opensles->device.Play = rdpsnd_opensles_play; + opensles->device.Close = rdpsnd_opensles_close; + opensles->device.Free = rdpsnd_opensles_free; + const ADDIN_ARGV* args = pEntryPoints->args; + rdpsnd_opensles_parse_addin_args((rdpsndDevicePlugin*)opensles, args); + + if (!opensles->device_name) + { + opensles->device_name = _strdup("default"); + + if (!opensles->device_name) + { + error = CHANNEL_RC_NO_MEMORY; + goto outstrdup; + } + } + + opensles->rate = 44100; + opensles->channels = 2; + opensles->format = WAVE_FORMAT_ADPCM; + pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)opensles); + DEBUG_SND("success"); + return CHANNEL_RC_OK; +outstrdup: + free(opensles); + return error; +} diff --git a/third_party/FreeRDP/channels/rdpsnd/client/oss/CMakeLists.txt b/third_party/FreeRDP/channels/rdpsnd/client/oss/CMakeLists.txt new file mode 100644 index 0000000..e97d183 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/client/oss/CMakeLists.txt @@ -0,0 +1,31 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright (c) 2015 Rozhuk Ivan +# +# 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. + +define_channel_client_subsystem("rdpsnd" "oss" "") + +find_package(OSS REQUIRED) + +set(${MODULE_PREFIX}_SRCS rdpsnd_oss.c) + +set(${MODULE_PREFIX}_LIBS winpr freerdp ${OSS_LIBRARIES}) + +include_directories(..) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +include_directories(SYSTEM ${OSS_INCLUDE_DIRS}) +cleaning_configure_file(${CMAKE_SOURCE_DIR}/cmake/oss-includes.h.in ${CMAKE_CURRENT_BINARY_DIR}/oss-includes.h @ONLY) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") diff --git a/third_party/FreeRDP/channels/rdpsnd/client/oss/rdpsnd_oss.c b/third_party/FreeRDP/channels/rdpsnd/client/oss/rdpsnd_oss.c new file mode 100644 index 0000000..2c872ad --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/client/oss/rdpsnd_oss.c @@ -0,0 +1,444 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Output Virtual Channel + * + * Copyright (c) 2015 Rozhuk Ivan + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "rdpsnd_main.h" + +typedef struct +{ + rdpsndDevicePlugin device; + + int pcm_handle; + int mixer_handle; + int dev_unit; + + int supported_formats; + + UINT32 latency; + AUDIO_FORMAT format; +} rdpsndOssPlugin; + +#define OSS_LOG_ERR(_text, _error) \ + do \ + { \ + if ((_error) != 0) \ + { \ + char ebuffer[256] = WINPR_C_ARRAY_INIT; \ + WLog_ERR(TAG, "%s: %i - %s", (_text), (_error), \ + winpr_strerror((_error), ebuffer, sizeof(ebuffer))); \ + } \ + } while (0) + +static int rdpsnd_oss_get_format(const AUDIO_FORMAT* format) +{ + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + switch (format->wBitsPerSample) + { + case 8: + return AFMT_S8; + + case 16: + return AFMT_S16_LE; + default: + break; + } + + break; + default: + break; + } + + return 0; +} + +static BOOL rdpsnd_oss_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format) +{ + int req_fmt = 0; + rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device; + + if (device == nullptr || format == nullptr) + return FALSE; + + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + if (format->cbSize != 0 || format->nSamplesPerSec > 48000 || + (format->wBitsPerSample != 8 && format->wBitsPerSample != 16) || + (format->nChannels != 1 && format->nChannels != 2)) + return FALSE; + + break; + + default: + return FALSE; + } + + req_fmt = rdpsnd_oss_get_format(format); + + /* Check really supported formats by dev. */ + if (oss->pcm_handle != -1) + { + if ((req_fmt & oss->supported_formats) == 0) + return FALSE; + } + else + { + if (req_fmt == 0) + return FALSE; + } + + return TRUE; +} + +static BOOL rdpsnd_oss_set_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, + UINT32 latency) +{ + int tmp = 0; + rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device; + + if (device == nullptr || oss->pcm_handle == -1 || format == nullptr) + return FALSE; + + oss->latency = latency; + CopyMemory(&(oss->format), format, sizeof(AUDIO_FORMAT)); + tmp = rdpsnd_oss_get_format(format); + + if (ioctl(oss->pcm_handle, SNDCTL_DSP_SETFMT, &tmp) == -1) + { + OSS_LOG_ERR("SNDCTL_DSP_SETFMT failed", errno); + return FALSE; + } + + tmp = format->nChannels; + + if (ioctl(oss->pcm_handle, SNDCTL_DSP_CHANNELS, &tmp) == -1) + { + OSS_LOG_ERR("SNDCTL_DSP_CHANNELS failed", errno); + return FALSE; + } + + tmp = (int)format->nSamplesPerSec; + + if (ioctl(oss->pcm_handle, SNDCTL_DSP_SPEED, &tmp) == -1) + { + OSS_LOG_ERR("SNDCTL_DSP_SPEED failed", errno); + return FALSE; + } + + tmp = format->nBlockAlign; + + if (ioctl(oss->pcm_handle, SNDCTL_DSP_SETFRAGMENT, &tmp) == -1) + { + OSS_LOG_ERR("SNDCTL_DSP_SETFRAGMENT failed", errno); + return FALSE; + } + + return TRUE; +} + +static void rdpsnd_oss_open_mixer(rdpsndOssPlugin* oss) +{ + int devmask = 0; + char mixer_name[PATH_MAX] = "/dev/mixer"; + + if (oss->mixer_handle != -1) + return; + + if (oss->dev_unit != -1) + (void)sprintf_s(mixer_name, PATH_MAX - 1, "/dev/mixer%i", oss->dev_unit); + + if ((oss->mixer_handle = open(mixer_name, O_RDWR)) < 0) + { + OSS_LOG_ERR("mixer open failed", errno); + oss->mixer_handle = -1; + return; + } + + if (ioctl(oss->mixer_handle, SOUND_MIXER_READ_DEVMASK, &devmask) == -1) + { + OSS_LOG_ERR("SOUND_MIXER_READ_DEVMASK failed", errno); + close(oss->mixer_handle); + oss->mixer_handle = -1; + return; + } +} + +static BOOL rdpsnd_oss_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, UINT32 latency) +{ + char dev_name[PATH_MAX] = "/dev/dsp"; + rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device; + + if (device == nullptr || oss->pcm_handle != -1) + return TRUE; + + if (oss->dev_unit != -1) + (void)sprintf_s(dev_name, PATH_MAX - 1, "/dev/dsp%i", oss->dev_unit); + + WLog_INFO(TAG, "open: %s", dev_name); + + if ((oss->pcm_handle = open(dev_name, O_WRONLY)) < 0) + { + OSS_LOG_ERR("sound dev open failed", errno); + oss->pcm_handle = -1; + return FALSE; + } + + if (ioctl(oss->pcm_handle, SNDCTL_DSP_GETFMTS, &oss->supported_formats) == -1) + { + OSS_LOG_ERR("SNDCTL_DSP_GETFMTS failed", errno); + close(oss->pcm_handle); + oss->pcm_handle = -1; + return FALSE; + } + + rdpsnd_oss_set_format(device, format, latency); + rdpsnd_oss_open_mixer(oss); + return TRUE; +} + +static void rdpsnd_oss_close(rdpsndDevicePlugin* device) +{ + rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device; + + if (device == nullptr) + return; + + if (oss->pcm_handle != -1) + { + WLog_INFO(TAG, "close: dsp"); + close(oss->pcm_handle); + oss->pcm_handle = -1; + } + + if (oss->mixer_handle != -1) + { + WLog_INFO(TAG, "close: mixer"); + close(oss->mixer_handle); + oss->mixer_handle = -1; + } +} + +static void rdpsnd_oss_free(rdpsndDevicePlugin* device) +{ + rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device; + + if (device == nullptr) + return; + + rdpsnd_oss_close(device); + free(oss); +} + +static UINT32 rdpsnd_oss_get_volume(rdpsndDevicePlugin* device) +{ + rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device; + WINPR_ASSERT(oss); + int vol = 0; + + /* On error return 50% volume. */ + UINT32 dwVolumeLeft = ((50 * 0xFFFF) / 100); /* 50% */ + UINT32 dwVolumeRight = ((50 * 0xFFFF) / 100); /* 50% */ + UINT32 dwVolume = ((dwVolumeLeft << 16) | dwVolumeRight); + + if (device == nullptr || oss->mixer_handle == -1) + return dwVolume; + + if (ioctl(oss->mixer_handle, MIXER_READ(SOUND_MIXER_VOLUME), &vol) == -1) + { + OSS_LOG_ERR("MIXER_READ", errno); + return dwVolume; + } + + dwVolumeLeft = (((vol & 0x7f) * 0xFFFF) / 100); + dwVolumeRight = ((((vol >> 8) & 0x7f) * 0xFFFF) / 100); + dwVolume = ((dwVolumeLeft << 16) | dwVolumeRight); + return dwVolume; +} + +static BOOL rdpsnd_oss_set_volume(rdpsndDevicePlugin* device, UINT32 value) +{ + rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device; + WINPR_ASSERT(oss); + + if (device == nullptr || oss->mixer_handle == -1) + return FALSE; + + unsigned left = (((value & 0xFFFF) * 100) / 0xFFFF); + unsigned right = ((((value >> 16) & 0xFFFF) * 100) / 0xFFFF); + + left |= (right << 8); + + if (ioctl(oss->mixer_handle, MIXER_WRITE(SOUND_MIXER_VOLUME), &left) == -1) + { + OSS_LOG_ERR("WRITE_MIXER", errno); + return FALSE; + } + + return TRUE; +} + +static UINT rdpsnd_oss_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size) +{ + rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device; + + if (device == nullptr || oss->mixer_handle == -1) + return 0; + + while (size > 0) + { + ssize_t status = write(oss->pcm_handle, data, size); + + if (status < 0) + { + OSS_LOG_ERR("write fail", errno); + rdpsnd_oss_close(device); + rdpsnd_oss_open(device, nullptr, oss->latency); + break; + } + + data += status; + + if ((size_t)status <= size) + size -= (size_t)status; + else + size = 0; + } + + return 10; /* TODO: Get real latency in [ms] */ +} + +static int rdpsnd_oss_parse_addin_args(rdpsndDevicePlugin* device, const ADDIN_ARGV* args) +{ + int status = 0; + char* str_num = nullptr; + char* eptr = nullptr; + DWORD flags = 0; + const COMMAND_LINE_ARGUMENT_A* arg = nullptr; + rdpsndOssPlugin* oss = (rdpsndOssPlugin*)device; + COMMAND_LINE_ARGUMENT_A rdpsnd_oss_args[] = { + { "dev", COMMAND_LINE_VALUE_REQUIRED, "", nullptr, nullptr, -1, nullptr, "device" }, + { nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr } + }; + flags = + COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + status = CommandLineParseArgumentsA(args->argc, args->argv, rdpsnd_oss_args, flags, oss, + nullptr, nullptr); + + if (status < 0) + return status; + + arg = rdpsnd_oss_args; + errno = 0; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev") + { + str_num = _strdup(arg->Value); + + if (!str_num) + return ERROR_OUTOFMEMORY; + + { + long val = strtol(str_num, &eptr, 10); + + if ((errno != 0) || (val < INT32_MIN) || (val > INT32_MAX)) + { + free(str_num); + return CHANNEL_RC_NULL_DATA; + } + + oss->dev_unit = (int)val; + } + + if (oss->dev_unit < 0 || *eptr != '\0') + oss->dev_unit = -1; + + free(str_num); + } + CommandLineSwitchEnd(arg) + } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr); + + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE oss_freerdp_rdpsnd_client_subsystem_entry( + PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints)) +{ + const ADDIN_ARGV* args = nullptr; + rdpsndOssPlugin* oss = (rdpsndOssPlugin*)calloc(1, sizeof(rdpsndOssPlugin)); + + if (!oss) + return CHANNEL_RC_NO_MEMORY; + + oss->device.Open = rdpsnd_oss_open; + oss->device.FormatSupported = rdpsnd_oss_format_supported; + oss->device.GetVolume = rdpsnd_oss_get_volume; + oss->device.SetVolume = rdpsnd_oss_set_volume; + oss->device.Play = rdpsnd_oss_play; + oss->device.Close = rdpsnd_oss_close; + oss->device.Free = rdpsnd_oss_free; + oss->pcm_handle = -1; + oss->mixer_handle = -1; + oss->dev_unit = -1; + args = pEntryPoints->args; + if (rdpsnd_oss_parse_addin_args(&oss->device, args) < 0) + { + free(oss); + return ERROR_INVALID_PARAMETER; + } + pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)oss); + return CHANNEL_RC_OK; +} diff --git a/third_party/FreeRDP/channels/rdpsnd/client/pulse/CMakeLists.txt b/third_party/FreeRDP/channels/rdpsnd/client/pulse/CMakeLists.txt new file mode 100644 index 0000000..bb8ee9b --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/client/pulse/CMakeLists.txt @@ -0,0 +1,30 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_client_subsystem("rdpsnd" "pulse" "") + +find_package(PulseAudio REQUIRED) +freerdp_client_pc_add_requires_private("libpulse") + +set(${MODULE_PREFIX}_SRCS rdpsnd_pulse.c) + +set(${MODULE_PREFIX}_LIBS winpr freerdp ${PULSEAUDIO_LIBRARY} ${PULSEAUDIO_MAINLOOP_LIBRARY}) + +include_directories(..) +include_directories(SYSTEM ${PULSEAUDIO_INCLUDE_DIR}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") diff --git a/third_party/FreeRDP/channels/rdpsnd/client/pulse/rdpsnd_pulse.c b/third_party/FreeRDP/channels/rdpsnd/client/pulse/rdpsnd_pulse.c new file mode 100644 index 0000000..bbf1536 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/client/pulse/rdpsnd_pulse.c @@ -0,0 +1,789 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Output Virtual Channel + * + * Copyright 2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "rdpsnd_main.h" + +typedef struct +{ + rdpsndDevicePlugin device; + + char* device_name; + char* client_name; + char* stream_name; + pa_threaded_mainloop* mainloop; + pa_context* context; + pa_sample_spec sample_spec; + pa_stream* stream; + UINT32 latency; + UINT32 volume; + time_t reconnect_delay_seconds; + time_t reconnect_time; +} rdpsndPulsePlugin; + +static BOOL rdpsnd_check_pulse(rdpsndPulsePlugin* pulse, BOOL haveStream) +{ + BOOL rc = TRUE; + WINPR_ASSERT(pulse); + + if (!pulse->context) + { + WLog_WARN(TAG, "pulse->context=nullptr"); + rc = FALSE; + } + + if (haveStream) + { + if (!pulse->stream) + { + WLog_WARN(TAG, "pulse->stream=%p", WINPR_CXX_COMPAT_CAST(const void*, pulse->stream)); + rc = FALSE; + } + } + + if (!pulse->mainloop) + { + WLog_WARN(TAG, "pulse->mainloop=%p", WINPR_CXX_COMPAT_CAST(const void*, pulse->mainloop)); + rc = FALSE; + } + + return rc; +} + +static BOOL rdpsnd_pulse_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format); + +static void rdpsnd_pulse_get_sink_info(pa_context* c, const pa_sink_info* i, + WINPR_ATTR_UNUSED int eol, void* userdata) +{ + UINT16 dwVolumeLeft = ((50 * 0xFFFF) / 100); /* 50% */ + UINT16 dwVolumeRight = ((50 * 0xFFFF) / 100); /* 50% */ + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata; + + WINPR_ASSERT(c); + if (!rdpsnd_check_pulse(pulse, FALSE) || !i) + return; + + for (uint8_t x = 0; x < i->volume.channels; x++) + { + pa_volume_t volume = i->volume.values[x]; + + if (volume >= PA_VOLUME_NORM) + volume = PA_VOLUME_NORM - 1; + + switch (x) + { + case 0: + dwVolumeLeft = (UINT16)volume; + break; + + case 1: + dwVolumeRight = (UINT16)volume; + break; + + default: + break; + } + } + + pulse->volume = ((UINT32)dwVolumeLeft << 16U) | dwVolumeRight; +} + +static void rdpsnd_pulse_context_state_callback(pa_context* context, void* userdata) +{ + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata; + + WINPR_ASSERT(context); + WINPR_ASSERT(pulse); + + pa_context_state_t state = pa_context_get_state(context); + + switch (state) + { + case PA_CONTEXT_READY: + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + case PA_CONTEXT_FAILED: + // Destroy context now, create new one for next connection attempt + pa_context_unref(pulse->context); + pulse->context = nullptr; + if (pulse->reconnect_delay_seconds >= 0) + pulse->reconnect_time = time(nullptr) + pulse->reconnect_delay_seconds; + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + case PA_CONTEXT_TERMINATED: + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + default: + break; + } +} + +static BOOL rdpsnd_pulse_connect(rdpsndDevicePlugin* device) +{ + BOOL rc = 0; + pa_operation* o = nullptr; + pa_context_state_t state = PA_CONTEXT_FAILED; + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device; + + if (!rdpsnd_check_pulse(pulse, FALSE)) + return FALSE; + + pa_threaded_mainloop_lock(pulse->mainloop); + + if (pa_context_connect(pulse->context, nullptr, PA_CONTEXT_NOFLAGS, nullptr) < 0) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + return FALSE; + } + + for (;;) + { + state = pa_context_get_state(pulse->context); + + if (state == PA_CONTEXT_READY) + break; + + if (!PA_CONTEXT_IS_GOOD(state)) + { + break; + } + + pa_threaded_mainloop_wait(pulse->mainloop); + } + + o = pa_context_get_sink_info_by_index(pulse->context, 0, rdpsnd_pulse_get_sink_info, pulse); + + if (o) + pa_operation_unref(o); + + if (state == PA_CONTEXT_READY) + { + rc = TRUE; + } + else + { + pa_context_disconnect(pulse->context); + rc = FALSE; + } + + pa_threaded_mainloop_unlock(pulse->mainloop); + return rc; +} + +static void rdpsnd_pulse_stream_success_callback(WINPR_ATTR_UNUSED pa_stream* stream, + WINPR_ATTR_UNUSED int success, void* userdata) +{ + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata; + + if (!rdpsnd_check_pulse(pulse, TRUE)) + return; + + pa_threaded_mainloop_signal(pulse->mainloop, 0); +} + +static void rdpsnd_pulse_wait_for_operation(rdpsndPulsePlugin* pulse, pa_operation* operation) +{ + if (!rdpsnd_check_pulse(pulse, TRUE)) + return; + + if (!operation) + return; + + while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING) + { + pa_threaded_mainloop_wait(pulse->mainloop); + } + + pa_operation_unref(operation); +} + +static void rdpsnd_pulse_stream_state_callback(pa_stream* stream, void* userdata) +{ + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata; + + WINPR_ASSERT(stream); + if (!rdpsnd_check_pulse(pulse, TRUE)) + return; + + pa_stream_state_t state = pa_stream_get_state(stream); + + switch (state) + { + case PA_STREAM_READY: + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + // Stream object is about to be destroyed, clean up our pointer + pulse->stream = nullptr; + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + default: + break; + } +} + +static void rdpsnd_pulse_stream_request_callback(pa_stream* stream, WINPR_ATTR_UNUSED size_t length, + void* userdata) +{ + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)userdata; + + WINPR_ASSERT(stream); + if (!rdpsnd_check_pulse(pulse, TRUE)) + return; + + pa_threaded_mainloop_signal(pulse->mainloop, 0); +} + +static void rdpsnd_pulse_close(rdpsndDevicePlugin* device) +{ + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device; + + WINPR_ASSERT(pulse); + + if (!rdpsnd_check_pulse(pulse, FALSE)) + return; + + pa_threaded_mainloop_lock(pulse->mainloop); + if (pulse->stream) + { + rdpsnd_pulse_wait_for_operation( + pulse, pa_stream_drain(pulse->stream, rdpsnd_pulse_stream_success_callback, pulse)); + pa_stream_disconnect(pulse->stream); + pa_stream_unref(pulse->stream); + pulse->stream = nullptr; + } + pa_threaded_mainloop_unlock(pulse->mainloop); +} + +static BOOL rdpsnd_pulse_set_format_spec(rdpsndPulsePlugin* pulse, const AUDIO_FORMAT* format) +{ + WINPR_ASSERT(format); + + if (!rdpsnd_check_pulse(pulse, FALSE)) + return FALSE; + + if (!rdpsnd_pulse_format_supported(&pulse->device, format)) + return FALSE; + + pa_sample_format_t sformat = PA_SAMPLE_INVALID; + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + switch (format->wBitsPerSample) + { + case 8: + sformat = PA_SAMPLE_U8; + break; + + case 16: + sformat = PA_SAMPLE_S16LE; + break; + + default: + return FALSE; + } + + break; + + default: + return FALSE; + } + + const pa_sample_spec sample_spec = { .format = sformat, + .rate = format->nSamplesPerSec, + .channels = + WINPR_ASSERTING_INT_CAST(uint8_t, format->nChannels) }; + pulse->sample_spec = sample_spec; + return TRUE; +} + +static BOOL rdpsnd_pulse_context_connect(rdpsndDevicePlugin* device) +{ + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device; + WINPR_ASSERT(pulse); + + pulse->context = + pa_context_new(pa_threaded_mainloop_get_api(pulse->mainloop), pulse->client_name); + + if (!pulse->context) + return FALSE; + + pa_context_set_state_callback(pulse->context, rdpsnd_pulse_context_state_callback, pulse); + + return rdpsnd_pulse_connect(&pulse->device); +} + +static BOOL rdpsnd_pulse_open_stream(rdpsndDevicePlugin* device) +{ + pa_stream_state_t state = PA_STREAM_FAILED; + int flags = PA_STREAM_NOFLAGS; + pa_buffer_attr buffer_attr = WINPR_C_ARRAY_INIT; + char ss[PA_SAMPLE_SPEC_SNPRINT_MAX] = WINPR_C_ARRAY_INIT; + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device; + WINPR_ASSERT(pulse); + + if (pa_sample_spec_valid(&pulse->sample_spec) == 0) + { + pa_sample_spec_snprint(ss, sizeof(ss), &pulse->sample_spec); + return FALSE; + } + + pa_threaded_mainloop_lock(pulse->mainloop); + if (!pulse->context) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + if (pulse->reconnect_delay_seconds >= 0 && time(nullptr) - pulse->reconnect_time >= 0) + rdpsnd_pulse_context_connect(device); + pa_threaded_mainloop_lock(pulse->mainloop); + } + + if (!rdpsnd_check_pulse(pulse, FALSE)) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + return FALSE; + } + + pulse->stream = pa_stream_new(pulse->context, pulse->stream_name, &pulse->sample_spec, nullptr); + + if (!pulse->stream) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + return FALSE; + } + + /* register essential callbacks */ + pa_stream_set_state_callback(pulse->stream, rdpsnd_pulse_stream_state_callback, pulse); + pa_stream_set_write_callback(pulse->stream, rdpsnd_pulse_stream_request_callback, pulse); + flags = PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE; + + if (pulse->latency > 0) + { + const size_t val = pa_usec_to_bytes(1000ULL * pulse->latency, &pulse->sample_spec); + buffer_attr.maxlength = UINT32_MAX; + buffer_attr.tlength = (val > UINT32_MAX) ? UINT32_MAX : (UINT32)val; + buffer_attr.prebuf = UINT32_MAX; + buffer_attr.minreq = UINT32_MAX; + buffer_attr.fragsize = UINT32_MAX; + flags |= PA_STREAM_ADJUST_LATENCY; + } + + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) + pa_stream_flags_t eflags = (pa_stream_flags_t)flags; + if (pa_stream_connect_playback(pulse->stream, pulse->device_name, + pulse->latency > 0 ? &buffer_attr : nullptr, eflags, nullptr, + nullptr) < 0) + { + WLog_ERR(TAG, "error connecting playback stream"); + pa_stream_unref(pulse->stream); + pulse->stream = nullptr; + pa_threaded_mainloop_unlock(pulse->mainloop); + return FALSE; + } + + while (pulse->stream) + { + state = pa_stream_get_state(pulse->stream); + + if (state == PA_STREAM_READY) + break; + + if (!PA_STREAM_IS_GOOD(state)) + { + break; + } + + pa_threaded_mainloop_wait(pulse->mainloop); + } + + pa_threaded_mainloop_unlock(pulse->mainloop); + + if (state == PA_STREAM_READY) + return TRUE; + + rdpsnd_pulse_close(device); + return FALSE; +} + +static BOOL rdpsnd_pulse_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, + UINT32 latency) +{ + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device; + + WINPR_ASSERT(format); + + if (!rdpsnd_check_pulse(pulse, FALSE)) + return TRUE; + + if (!rdpsnd_pulse_set_format_spec(pulse, format)) + return FALSE; + + pulse->latency = latency; + + return rdpsnd_pulse_open_stream(device); +} + +static void rdpsnd_pulse_free(rdpsndDevicePlugin* device) +{ + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device; + + if (!pulse) + return; + + rdpsnd_pulse_close(device); + + if (pulse->mainloop) + pa_threaded_mainloop_stop(pulse->mainloop); + + if (pulse->context) + { + pa_context_disconnect(pulse->context); + pa_context_unref(pulse->context); + pulse->context = nullptr; + } + + if (pulse->mainloop) + { + pa_threaded_mainloop_free(pulse->mainloop); + pulse->mainloop = nullptr; + } + + free(pulse->device_name); + free(pulse->client_name); + free(pulse->stream_name); + free(pulse); +} + +static BOOL rdpsnd_pulse_default_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* desired, + AUDIO_FORMAT* defaultFormat) +{ + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device; + + if (!pulse || !defaultFormat) + return FALSE; + + *defaultFormat = *desired; + defaultFormat->data = nullptr; + defaultFormat->cbSize = 0; + defaultFormat->wFormatTag = WAVE_FORMAT_PCM; + if ((defaultFormat->nChannels < 1) || (defaultFormat->nChannels > PA_CHANNELS_MAX)) + defaultFormat->nChannels = 2; + if ((defaultFormat->nSamplesPerSec < 1) || (defaultFormat->nSamplesPerSec > PA_RATE_MAX)) + defaultFormat->nSamplesPerSec = 44100; + if ((defaultFormat->wBitsPerSample != 8) && (defaultFormat->wBitsPerSample != 16)) + defaultFormat->wBitsPerSample = 16; + + defaultFormat->nBlockAlign = defaultFormat->nChannels * defaultFormat->wBitsPerSample / 8; + defaultFormat->nAvgBytesPerSec = defaultFormat->nBlockAlign * defaultFormat->nSamplesPerSec; + return TRUE; +} + +BOOL rdpsnd_pulse_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format) +{ + WINPR_ASSERT(device); + WINPR_ASSERT(format); + + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + if (format->cbSize == 0 && (format->nSamplesPerSec <= PA_RATE_MAX) && + (format->wBitsPerSample == 8 || format->wBitsPerSample == 16) && + (format->nChannels >= 1 && format->nChannels <= PA_CHANNELS_MAX)) + { + return TRUE; + } + + break; + + default: + break; + } + + return FALSE; +} + +static UINT32 rdpsnd_pulse_get_volume(rdpsndDevicePlugin* device) +{ + pa_operation* o = nullptr; + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device; + + if (!rdpsnd_check_pulse(pulse, FALSE)) + return 0; + + pa_threaded_mainloop_lock(pulse->mainloop); + o = pa_context_get_sink_info_by_index(pulse->context, 0, rdpsnd_pulse_get_sink_info, pulse); + if (o) + pa_operation_unref(o); + pa_threaded_mainloop_unlock(pulse->mainloop); + return pulse->volume; +} + +static void rdpsnd_set_volume_success_cb(pa_context* c, int success, void* userdata) +{ + rdpsndPulsePlugin* pulse = userdata; + + if (!rdpsnd_check_pulse(pulse, TRUE)) + return; + WINPR_ASSERT(c); + + WLog_INFO(TAG, "%d", success); +} + +static BOOL rdpsnd_pulse_set_volume(rdpsndDevicePlugin* device, UINT32 value) +{ + pa_cvolume cv = WINPR_C_ARRAY_INIT; + pa_volume_t left = 0; + pa_volume_t right = 0; + pa_operation* operation = nullptr; + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device; + + if (!rdpsnd_check_pulse(pulse, TRUE)) + { + WLog_WARN(TAG, "called before pulse backend was initialized"); + return FALSE; + } + + left = (pa_volume_t)(value & 0xFFFF); + right = (pa_volume_t)((value >> 16) & 0xFFFF); + pa_cvolume_init(&cv); + cv.channels = 2; + cv.values[0] = PA_VOLUME_MUTED + (left * (PA_VOLUME_NORM - PA_VOLUME_MUTED)) / PA_VOLUME_NORM; + cv.values[1] = PA_VOLUME_MUTED + (right * (PA_VOLUME_NORM - PA_VOLUME_MUTED)) / PA_VOLUME_NORM; + pa_threaded_mainloop_lock(pulse->mainloop); + operation = pa_context_set_sink_input_volume(pulse->context, pa_stream_get_index(pulse->stream), + &cv, rdpsnd_set_volume_success_cb, pulse); + + if (operation) + pa_operation_unref(operation); + + pa_threaded_mainloop_unlock(pulse->mainloop); + return TRUE; +} + +static UINT rdpsnd_pulse_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size) +{ + size_t length = 0; + void* pa_data = nullptr; + int status = 0; + pa_usec_t latency = 0; + int negative = 0; + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)device; + + if (!data) + return 0; + + pa_threaded_mainloop_lock(pulse->mainloop); + + if (!rdpsnd_check_pulse(pulse, TRUE)) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + // Discard this playback request and just attempt to reconnect the stream + WLog_DBG(TAG, "reconnecting playback stream"); + rdpsnd_pulse_open_stream(device); + return 0; + } + + while (size > 0) + { + length = size; + + status = pa_stream_begin_write(pulse->stream, &pa_data, &length); + + if (status < 0) + break; + + memcpy(pa_data, data, length); + + status = pa_stream_write(pulse->stream, pa_data, length, nullptr, 0LL, PA_SEEK_RELATIVE); + + if (status < 0) + { + break; + } + + data += length; + size -= length; + } + + if (pa_stream_get_latency(pulse->stream, &latency, &negative) != 0) + latency = 0; + + pa_threaded_mainloop_unlock(pulse->mainloop); + + const pa_usec_t val = latency / 1000; + if (val > UINT32_MAX) + return UINT32_MAX; + return (UINT32)val; +} + +static UINT rdpsnd_pulse_parse_addin_args(rdpsndPulsePlugin* pulse, const ADDIN_ARGV* args) +{ + COMMAND_LINE_ARGUMENT_A rdpsnd_pulse_args[] = { + { "dev", COMMAND_LINE_VALUE_REQUIRED, "", nullptr, nullptr, -1, nullptr, "device" }, + { "reconnect_delay_seconds", COMMAND_LINE_VALUE_REQUIRED, "", + nullptr, nullptr, -1, nullptr, "reconnect_delay_seconds" }, + { "client_name", COMMAND_LINE_VALUE_REQUIRED, "", nullptr, nullptr, -1, + nullptr, "name of pulse client" }, + { "stream_name", COMMAND_LINE_VALUE_REQUIRED, "", nullptr, nullptr, -1, + nullptr, "name of pulse stream" }, + { nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr } + }; + const DWORD flags = + COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + + WINPR_ASSERT(pulse); + WINPR_ASSERT(args); + + const int status = CommandLineParseArgumentsA(args->argc, args->argv, rdpsnd_pulse_args, flags, + pulse, nullptr, nullptr); + + if (status < 0) + return ERROR_INVALID_DATA; + + const COMMAND_LINE_ARGUMENT_A* arg = rdpsnd_pulse_args; + + const char* client_name = nullptr; + const char* stream_name = nullptr; + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dev") + { + pulse->device_name = _strdup(arg->Value); + + if (!pulse->device_name) + return ERROR_OUTOFMEMORY; + } + CommandLineSwitchCase(arg, "reconnect_delay_seconds") + { + unsigned long val = strtoul(arg->Value, nullptr, 0); + + if ((errno != 0) || (val > INT32_MAX)) + return ERROR_INVALID_DATA; + + pulse->reconnect_delay_seconds = (time_t)val; + } + CommandLineSwitchCase(arg, "client_name") + { + client_name = arg->Value; + } + CommandLineSwitchCase(arg, "stream_name") + { + stream_name = arg->Value; + } + CommandLineSwitchEnd(arg) + } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr); + + if (!client_name) + client_name = freerdp_getApplicationDetailsString(); + if (!stream_name) + stream_name = freerdp_getApplicationDetailsString(); + + pulse->client_name = _strdup(client_name); + pulse->stream_name = _strdup(stream_name); + if (!pulse->client_name || !pulse->stream_name) + return ERROR_OUTOFMEMORY; + return CHANNEL_RC_OK; +} + +FREERDP_ENTRY_POINT(UINT VCAPITYPE pulse_freerdp_rdpsnd_client_subsystem_entry( + PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints)) +{ + WINPR_ASSERT(pEntryPoints); + + rdpsndPulsePlugin* pulse = (rdpsndPulsePlugin*)calloc(1, sizeof(rdpsndPulsePlugin)); + + if (!pulse) + return CHANNEL_RC_NO_MEMORY; + + pulse->device.Open = rdpsnd_pulse_open; + pulse->device.FormatSupported = rdpsnd_pulse_format_supported; + pulse->device.GetVolume = rdpsnd_pulse_get_volume; + pulse->device.SetVolume = rdpsnd_pulse_set_volume; + pulse->device.Play = rdpsnd_pulse_play; + pulse->device.Close = rdpsnd_pulse_close; + pulse->device.Free = rdpsnd_pulse_free; + pulse->device.DefaultFormat = rdpsnd_pulse_default_format; + + const ADDIN_ARGV* args = pEntryPoints->args; + UINT ret = rdpsnd_pulse_parse_addin_args(pulse, args); + + if (ret != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "error parsing arguments"); + goto error; + } + + pulse->reconnect_delay_seconds = 5; + pulse->reconnect_time = time(nullptr); + + ret = CHANNEL_RC_NO_MEMORY; + pulse->mainloop = pa_threaded_mainloop_new(); + + if (!pulse->mainloop) + goto error; + + pa_threaded_mainloop_lock(pulse->mainloop); + + if (pa_threaded_mainloop_start(pulse->mainloop) < 0) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + goto error; + } + + pa_threaded_mainloop_unlock(pulse->mainloop); + + if (!rdpsnd_pulse_context_connect((rdpsndDevicePlugin*)pulse)) + goto error; + + pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)pulse); + return CHANNEL_RC_OK; +error: + rdpsnd_pulse_free((rdpsndDevicePlugin*)pulse); + return ret; +} diff --git a/third_party/FreeRDP/channels/rdpsnd/client/rdpsnd_main.c b/third_party/FreeRDP/channels/rdpsnd/client/rdpsnd_main.c new file mode 100644 index 0000000..c140acf --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/client/rdpsnd_main.c @@ -0,0 +1,1882 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Output Virtual Channel + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2012-2013 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 David PHAM-VAN + * + * 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 + +#ifndef _WIN32 +#include +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "rdpsnd_common.h" +#include "rdpsnd_main.h" + +struct rdpsnd_plugin +{ + IWTSPlugin iface; + IWTSListener* listener; + GENERIC_LISTENER_CALLBACK* listener_callback; + + CHANNEL_DEF channelDef; + CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints; + + wStreamPool* pool; + wStream* data_in; + + void* InitHandle; + DWORD OpenHandle; + + wLog* log; + + BYTE cBlockNo; + UINT16 wQualityMode; + UINT16 wCurrentFormatNo; + + AUDIO_FORMAT* ServerFormats; + UINT16 NumberOfServerFormats; + + AUDIO_FORMAT* ClientFormats; + UINT16 NumberOfClientFormats; + + BOOL attached; + BOOL connected; + BOOL dynamic; + + BOOL expectingWave; + BYTE waveData[4]; + UINT16 waveDataSize; + UINT16 wTimeStamp; + UINT64 wArrivalTime; + + UINT32 latency; + BOOL isOpen; + AUDIO_FORMAT* fixed_format; + + UINT32 startPlayTime; + size_t totalPlaySize; + + char* subsystem; + char* device_name; + + /* Device plugin */ + rdpsndDevicePlugin* device; + rdpContext* rdpcontext; + + FREERDP_DSP_CONTEXT* dsp_context; + + HANDLE thread; + wMessageQueue* queue; + BOOL initialized; + + UINT16 wVersion; + UINT32 volume; + BOOL applyVolume; + + size_t references; + BOOL OnOpenCalled; + BOOL async; +}; + +static DWORD WINAPI play_thread(LPVOID arg); + +static const char* rdpsnd_is_dyn_str(BOOL dynamic) +{ + if (dynamic) + return "[dynamic]"; + return "[static]"; +} + +static void rdpsnd_virtual_channel_event_terminated(rdpsndPlugin* rdpsnd); + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_virtual_channel_write(rdpsndPlugin* rdpsnd, wStream* s); + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_send_quality_mode_pdu(rdpsndPlugin* rdpsnd) +{ + wStream* pdu = nullptr; + WINPR_ASSERT(rdpsnd); + pdu = Stream_New(nullptr, 8); + + if (!pdu) + { + WLog_ERR(TAG, "%s Stream_New failed!", rdpsnd_is_dyn_str(rdpsnd->dynamic)); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT8(pdu, SNDC_QUALITYMODE); /* msgType */ + Stream_Write_UINT8(pdu, 0); /* bPad */ + Stream_Write_UINT16(pdu, 4); /* BodySize */ + Stream_Write_UINT16(pdu, rdpsnd->wQualityMode); /* wQualityMode */ + Stream_Write_UINT16(pdu, 0); /* Reserved */ + WLog_Print(rdpsnd->log, WLOG_DEBUG, "%s QualityMode: %" PRIu16 "", + rdpsnd_is_dyn_str(rdpsnd->dynamic), rdpsnd->wQualityMode); + return rdpsnd_virtual_channel_write(rdpsnd, pdu); +} + +static void rdpsnd_select_supported_audio_formats(rdpsndPlugin* rdpsnd) +{ + WINPR_ASSERT(rdpsnd); + audio_formats_free(rdpsnd->ClientFormats, rdpsnd->NumberOfClientFormats); + rdpsnd->NumberOfClientFormats = 0; + rdpsnd->ClientFormats = nullptr; + + if (!rdpsnd->NumberOfServerFormats) + return; + + rdpsnd->ClientFormats = audio_formats_new(rdpsnd->NumberOfServerFormats); + + if (!rdpsnd->ClientFormats || !rdpsnd->device) + return; + + for (UINT16 index = 0; index < rdpsnd->NumberOfServerFormats; index++) + { + const AUDIO_FORMAT* serverFormat = &rdpsnd->ServerFormats[index]; + + if (!audio_format_compatible(rdpsnd->fixed_format, serverFormat)) + continue; + + WINPR_ASSERT(rdpsnd->device->FormatSupported); + if (freerdp_dsp_supports_format(serverFormat, FALSE) || + rdpsnd->device->FormatSupported(rdpsnd->device, serverFormat)) + { + AUDIO_FORMAT* clientFormat = &rdpsnd->ClientFormats[rdpsnd->NumberOfClientFormats++]; + audio_format_copy(serverFormat, clientFormat); + } + } +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_send_client_audio_formats(rdpsndPlugin* rdpsnd) +{ + wStream* pdu = nullptr; + UINT16 length = 0; + UINT32 dwVolume = 0; + UINT16 wNumberOfFormats = 0; + WINPR_ASSERT(rdpsnd); + + if (!rdpsnd->device || (!rdpsnd->dynamic && (rdpsnd->OpenHandle == 0))) + return CHANNEL_RC_INITIALIZATION_ERROR; + + dwVolume = IFCALLRESULT(0, rdpsnd->device->GetVolume, rdpsnd->device); + wNumberOfFormats = rdpsnd->NumberOfClientFormats; + length = 4 + 20; + + for (UINT16 index = 0; index < wNumberOfFormats; index++) + length += (18 + rdpsnd->ClientFormats[index].cbSize); + + pdu = Stream_New(nullptr, length); + + if (!pdu) + { + WLog_ERR(TAG, "%s Stream_New failed!", rdpsnd_is_dyn_str(rdpsnd->dynamic)); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT8(pdu, SNDC_FORMATS); /* msgType */ + Stream_Write_UINT8(pdu, 0); /* bPad */ + Stream_Write_UINT16(pdu, length - 4); /* BodySize */ + Stream_Write_UINT32(pdu, TSSNDCAPS_ALIVE | TSSNDCAPS_VOLUME); /* dwFlags */ + Stream_Write_UINT32(pdu, dwVolume); /* dwVolume */ + Stream_Write_UINT32(pdu, 0); /* dwPitch */ + Stream_Write_UINT16(pdu, 0); /* wDGramPort */ + Stream_Write_UINT16(pdu, wNumberOfFormats); /* wNumberOfFormats */ + Stream_Write_UINT8(pdu, 0); /* cLastBlockConfirmed */ + Stream_Write_UINT16(pdu, CHANNEL_VERSION_WIN_MAX); /* wVersion */ + Stream_Write_UINT8(pdu, 0); /* bPad */ + + for (UINT16 index = 0; index < wNumberOfFormats; index++) + { + const AUDIO_FORMAT* clientFormat = &rdpsnd->ClientFormats[index]; + + if (!audio_format_write(pdu, clientFormat)) + { + Stream_Free(pdu, TRUE); + return ERROR_INTERNAL_ERROR; + } + } + + WLog_Print(rdpsnd->log, WLOG_DEBUG, "%s Client Audio Formats", + rdpsnd_is_dyn_str(rdpsnd->dynamic)); + return rdpsnd_virtual_channel_write(rdpsnd, pdu); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_recv_server_audio_formats_pdu(rdpsndPlugin* rdpsnd, wStream* s) +{ + UINT16 wNumberOfFormats = 0; + UINT ret = ERROR_BAD_LENGTH; + + WINPR_ASSERT(rdpsnd); + audio_formats_free(rdpsnd->ServerFormats, rdpsnd->NumberOfServerFormats); + rdpsnd->NumberOfServerFormats = 0; + rdpsnd->ServerFormats = nullptr; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 30)) + return ERROR_BAD_LENGTH; + + /* http://msdn.microsoft.com/en-us/library/cc240956.aspx */ + Stream_Seek_UINT32(s); /* dwFlags */ + Stream_Seek_UINT32(s); /* dwVolume */ + Stream_Seek_UINT32(s); /* dwPitch */ + Stream_Seek_UINT16(s); /* wDGramPort */ + Stream_Read_UINT16(s, wNumberOfFormats); + Stream_Read_UINT8(s, rdpsnd->cBlockNo); /* cLastBlockConfirmed */ + Stream_Read_UINT16(s, rdpsnd->wVersion); /* wVersion */ + Stream_Seek_UINT8(s); /* bPad */ + rdpsnd->NumberOfServerFormats = wNumberOfFormats; + + if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, wNumberOfFormats, 14ull)) + return ERROR_BAD_LENGTH; + + if (rdpsnd->NumberOfServerFormats > 0) + { + rdpsnd->ServerFormats = audio_formats_new(wNumberOfFormats); + + if (!rdpsnd->ServerFormats) + return CHANNEL_RC_NO_MEMORY; + + for (UINT16 index = 0; index < wNumberOfFormats; index++) + { + AUDIO_FORMAT* format = &rdpsnd->ServerFormats[index]; + + if (!audio_format_read(s, format)) + goto out_fail; + } + } + + WINPR_ASSERT(rdpsnd->device); + ret = IFCALLRESULT(CHANNEL_RC_OK, rdpsnd->device->ServerFormatAnnounce, rdpsnd->device, + rdpsnd->ServerFormats, rdpsnd->NumberOfServerFormats); + if (ret != CHANNEL_RC_OK) + goto out_fail; + + rdpsnd_select_supported_audio_formats(rdpsnd); + WLog_Print(rdpsnd->log, WLOG_DEBUG, "%s Server Audio Formats", + rdpsnd_is_dyn_str(rdpsnd->dynamic)); + ret = rdpsnd_send_client_audio_formats(rdpsnd); + + if (ret == CHANNEL_RC_OK) + { + if (rdpsnd->wVersion >= CHANNEL_VERSION_WIN_7) + ret = rdpsnd_send_quality_mode_pdu(rdpsnd); + } + + return ret; +out_fail: + audio_formats_free(rdpsnd->ServerFormats, rdpsnd->NumberOfServerFormats); + rdpsnd->ServerFormats = nullptr; + rdpsnd->NumberOfServerFormats = 0; + return ret; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_send_training_confirm_pdu(rdpsndPlugin* rdpsnd, UINT16 wTimeStamp, + UINT16 wPackSize) +{ + wStream* pdu = nullptr; + WINPR_ASSERT(rdpsnd); + pdu = Stream_New(nullptr, 8); + + if (!pdu) + { + WLog_ERR(TAG, "%s Stream_New failed!", rdpsnd_is_dyn_str(rdpsnd->dynamic)); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT8(pdu, SNDC_TRAINING); /* msgType */ + Stream_Write_UINT8(pdu, 0); /* bPad */ + Stream_Write_UINT16(pdu, 4); /* BodySize */ + Stream_Write_UINT16(pdu, wTimeStamp); + Stream_Write_UINT16(pdu, wPackSize); + WLog_Print(rdpsnd->log, WLOG_DEBUG, + "%s Training Response: wTimeStamp: %" PRIu16 " wPackSize: %" PRIu16 "", + rdpsnd_is_dyn_str(rdpsnd->dynamic), wTimeStamp, wPackSize); + return rdpsnd_virtual_channel_write(rdpsnd, pdu); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_recv_training_pdu(rdpsndPlugin* rdpsnd, wStream* s) +{ + UINT16 wTimeStamp = 0; + UINT16 wPackSize = 0; + WINPR_ASSERT(rdpsnd); + WINPR_ASSERT(s); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_BAD_LENGTH; + + Stream_Read_UINT16(s, wTimeStamp); + Stream_Read_UINT16(s, wPackSize); + WLog_Print(rdpsnd->log, WLOG_DEBUG, + "%s Training Request: wTimeStamp: %" PRIu16 " wPackSize: %" PRIu16 "", + rdpsnd_is_dyn_str(rdpsnd->dynamic), wTimeStamp, wPackSize); + return rdpsnd_send_training_confirm_pdu(rdpsnd, wTimeStamp, wPackSize); +} + +static BOOL rdpsnd_apply_volume(rdpsndPlugin* rdpsnd) +{ + WINPR_ASSERT(rdpsnd); + + if (rdpsnd->isOpen && rdpsnd->applyVolume && rdpsnd->device) + { + BOOL rc = IFCALLRESULT(TRUE, rdpsnd->device->SetVolume, rdpsnd->device, rdpsnd->volume); + if (!rc) + return FALSE; + rdpsnd->applyVolume = FALSE; + } + return TRUE; +} + +static BOOL rdpsnd_ensure_device_is_open(rdpsndPlugin* rdpsnd, UINT16 wFormatNo, + const AUDIO_FORMAT* format) +{ + if (!rdpsnd) + return FALSE; + WINPR_ASSERT(format); + + if (!rdpsnd->isOpen || (wFormatNo != rdpsnd->wCurrentFormatNo)) + { + BOOL rc = 0; + BOOL supported = 0; + AUDIO_FORMAT deviceFormat = *format; + + IFCALL(rdpsnd->device->Close, rdpsnd->device); + supported = IFCALLRESULT(FALSE, rdpsnd->device->FormatSupported, rdpsnd->device, format); + + if (!supported) + { + if (!IFCALLRESULT(FALSE, rdpsnd->device->DefaultFormat, rdpsnd->device, format, + &deviceFormat)) + { + deviceFormat.wFormatTag = WAVE_FORMAT_PCM; + deviceFormat.wBitsPerSample = 16; + deviceFormat.cbSize = 0; + } + } + + WLog_Print(rdpsnd->log, WLOG_DEBUG, "%s Opening device with format %s [backend %s]", + rdpsnd_is_dyn_str(rdpsnd->dynamic), + audio_format_get_tag_string(format->wFormatTag), + audio_format_get_tag_string(deviceFormat.wFormatTag)); + rc = IFCALLRESULT(FALSE, rdpsnd->device->Open, rdpsnd->device, &deviceFormat, + rdpsnd->latency); + + if (!rc) + return FALSE; + + if (!supported) + { + if (!freerdp_dsp_context_reset(rdpsnd->dsp_context, format, 0u)) + return FALSE; + } + + rdpsnd->isOpen = TRUE; + rdpsnd->wCurrentFormatNo = wFormatNo; + rdpsnd->startPlayTime = 0; + rdpsnd->totalPlaySize = 0; + } + + return rdpsnd_apply_volume(rdpsnd); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_recv_wave_info_pdu(rdpsndPlugin* rdpsnd, wStream* s, UINT16 BodySize) +{ + UINT16 wFormatNo = 0; + const AUDIO_FORMAT* format = nullptr; + WINPR_ASSERT(rdpsnd); + WINPR_ASSERT(s); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 12)) + return ERROR_BAD_LENGTH; + + rdpsnd->wArrivalTime = GetTickCount64(); + Stream_Read_UINT16(s, rdpsnd->wTimeStamp); + Stream_Read_UINT16(s, wFormatNo); + + if (wFormatNo >= rdpsnd->NumberOfClientFormats) + return ERROR_INVALID_DATA; + + Stream_Read_UINT8(s, rdpsnd->cBlockNo); + Stream_Seek(s, 3); /* bPad */ + Stream_Read(s, rdpsnd->waveData, 4); + rdpsnd->waveDataSize = BodySize - 8; + format = &rdpsnd->ClientFormats[wFormatNo]; + WLog_Print(rdpsnd->log, WLOG_DEBUG, + "%s WaveInfo: cBlockNo: %" PRIu8 " wFormatNo: %" PRIu16 " [%s]", + rdpsnd_is_dyn_str(rdpsnd->dynamic), rdpsnd->cBlockNo, wFormatNo, + audio_format_get_tag_string(format->wFormatTag)); + + if (!rdpsnd_ensure_device_is_open(rdpsnd, wFormatNo, format)) + return ERROR_INTERNAL_ERROR; + + rdpsnd->expectingWave = TRUE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_send_wave_confirm_pdu(rdpsndPlugin* rdpsnd, UINT16 wTimeStamp, + BYTE cConfirmedBlockNo) +{ + wStream* pdu = nullptr; + WINPR_ASSERT(rdpsnd); + pdu = Stream_New(nullptr, 8); + + if (!pdu) + { + WLog_ERR(TAG, "%s Stream_New failed!", rdpsnd_is_dyn_str(rdpsnd->dynamic)); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write_UINT8(pdu, SNDC_WAVECONFIRM); + Stream_Write_UINT8(pdu, 0); + Stream_Write_UINT16(pdu, 4); + Stream_Write_UINT16(pdu, wTimeStamp); + Stream_Write_UINT8(pdu, cConfirmedBlockNo); /* cConfirmedBlockNo */ + Stream_Write_UINT8(pdu, 0); /* bPad */ + return rdpsnd_virtual_channel_write(rdpsnd, pdu); +} + +static BOOL rdpsnd_detect_overrun(rdpsndPlugin* rdpsnd, const AUDIO_FORMAT* format, size_t size) +{ + UINT32 bpf = 0; + UINT32 now = 0; + UINT32 duration = 0; + UINT32 totalDuration = 0; + UINT32 remainingDuration = 0; + UINT32 maxDuration = 0; + + if (!rdpsnd || !format) + return FALSE; + + /* Older windows RDP servers do not limit the send buffer, which can + * cause quite a large amount of sound data buffered client side. + * If e.g. sound is paused server side the client will keep playing + * for a long time instead of pausing playback. + * + * To avoid this we check: + * + * 1. Is the sound sample received from a known format these servers + * support + * 2. If it is calculate the size of the client side sound buffer + * 3. If the buffer is too large silently drop the sample which will + * trigger a retransmit later on. + * + * This check must only be applied to these known formats, because + * with newer and other formats the sample size can not be calculated + * without decompressing the sample first. + */ + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + case WAVE_FORMAT_DVI_ADPCM: + case WAVE_FORMAT_ADPCM: + case WAVE_FORMAT_ALAW: + case WAVE_FORMAT_MULAW: + break; + case WAVE_FORMAT_MSG723: + case WAVE_FORMAT_GSM610: + case WAVE_FORMAT_AAC_MS: + default: + return FALSE; + } + + audio_format_print(WLog_Get(TAG), WLOG_DEBUG, format); + bpf = format->nChannels * format->wBitsPerSample * format->nSamplesPerSec / 8; + if (bpf == 0) + return FALSE; + + duration = (UINT32)(1000 * size / bpf); + totalDuration = (UINT32)(1000 * rdpsnd->totalPlaySize / bpf); + now = GetTickCountPrecise(); + if (rdpsnd->startPlayTime == 0) + { + rdpsnd->startPlayTime = now; + rdpsnd->totalPlaySize = size; + return FALSE; + } + else if (now - rdpsnd->startPlayTime > totalDuration + 10) + { + /* Buffer underrun */ + WLog_Print(rdpsnd->log, WLOG_DEBUG, "%s Buffer underrun by %u ms", + rdpsnd_is_dyn_str(rdpsnd->dynamic), + (UINT)(now - rdpsnd->startPlayTime - totalDuration)); + rdpsnd->startPlayTime = now; + rdpsnd->totalPlaySize = size; + return FALSE; + } + else + { + /* Calculate remaining duration to be played */ + remainingDuration = totalDuration - (now - rdpsnd->startPlayTime); + + /* Maximum allow duration calculation */ + maxDuration = duration * 2 + rdpsnd->latency; + + if (remainingDuration + duration > maxDuration) + { + WLog_Print(rdpsnd->log, WLOG_DEBUG, "%s Buffer overrun pending %u ms dropping %u ms", + rdpsnd_is_dyn_str(rdpsnd->dynamic), remainingDuration, duration); + return TRUE; + } + + rdpsnd->totalPlaySize += size; + return FALSE; + } +} + +static UINT rdpsnd_treat_wave(rdpsndPlugin* rdpsnd, wStream* s, size_t size) +{ + AUDIO_FORMAT* format = nullptr; + UINT64 end = 0; + UINT64 diffMS = 0; + UINT64 ts = 0; + UINT latency = 0; + UINT error = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, size)) + return ERROR_BAD_LENGTH; + + if (rdpsnd->wCurrentFormatNo >= rdpsnd->NumberOfClientFormats) + return ERROR_INTERNAL_ERROR; + + /* + * Send the first WaveConfirm PDU. The server side uses this to determine the + * network latency. + * See also [MS-RDPEA] 2.2.3.8 Wave Confirm PDU + */ + error = rdpsnd_send_wave_confirm_pdu(rdpsnd, rdpsnd->wTimeStamp, rdpsnd->cBlockNo); + if (error) + return error; + + const BYTE* data = Stream_ConstPointer(s); + format = &rdpsnd->ClientFormats[rdpsnd->wCurrentFormatNo]; + WLog_Print(rdpsnd->log, WLOG_DEBUG, + "%s Wave: cBlockNo: %" PRIu8 " wTimeStamp: %" PRIu16 ", size: %" PRIuz, + rdpsnd_is_dyn_str(rdpsnd->dynamic), rdpsnd->cBlockNo, rdpsnd->wTimeStamp, size); + + if (rdpsnd->device && rdpsnd->attached && !rdpsnd_detect_overrun(rdpsnd, format, size)) + { + UINT status = CHANNEL_RC_OK; + wStream* pcmData = StreamPool_Take(rdpsnd->pool, 4096); + + if (rdpsnd->device->FormatSupported(rdpsnd->device, format)) + { + if (rdpsnd->device->PlayEx) + latency = rdpsnd->device->PlayEx(rdpsnd->device, format, data, size); + else + latency = IFCALLRESULT(0, rdpsnd->device->Play, rdpsnd->device, data, size); + } + else if (freerdp_dsp_decode(rdpsnd->dsp_context, format, data, size, pcmData)) + { + Stream_SealLength(pcmData); + + if (rdpsnd->device->PlayEx) + latency = rdpsnd->device->PlayEx(rdpsnd->device, format, Stream_Buffer(pcmData), + Stream_Length(pcmData)); + else + latency = IFCALLRESULT(0, rdpsnd->device->Play, rdpsnd->device, + Stream_Buffer(pcmData), Stream_Length(pcmData)); + } + else + status = ERROR_INTERNAL_ERROR; + + Stream_Release(pcmData); + + if (status != CHANNEL_RC_OK) + return status; + } + + end = GetTickCount64(); + diffMS = end - rdpsnd->wArrivalTime + latency; + ts = (rdpsnd->wTimeStamp + diffMS) % UINT16_MAX; + + /* + * Send the second WaveConfirm PDU. With the first WaveConfirm PDU, + * the server side uses this second WaveConfirm PDU to determine the actual + * render latency. + */ + return rdpsnd_send_wave_confirm_pdu(rdpsnd, (UINT16)ts, rdpsnd->cBlockNo); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_recv_wave_pdu(rdpsndPlugin* rdpsnd, wStream* s) +{ + rdpsnd->expectingWave = FALSE; + + /** + * The Wave PDU is a special case: it is always sent after a Wave Info PDU, + * and we do not process its header. Instead, the header is pad that needs + * to be filled with the first four bytes of the audio sample data sent as + * part of the preceding Wave Info PDU. + */ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + CopyMemory(Stream_Buffer(s), rdpsnd->waveData, 4); + return rdpsnd_treat_wave(rdpsnd, s, rdpsnd->waveDataSize); +} + +static UINT rdpsnd_recv_wave2_pdu(rdpsndPlugin* rdpsnd, wStream* s, UINT16 BodySize) +{ + UINT16 wFormatNo = 0; + AUDIO_FORMAT* format = nullptr; + UINT32 dwAudioTimeStamp = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 12)) + return ERROR_BAD_LENGTH; + + Stream_Read_UINT16(s, rdpsnd->wTimeStamp); + Stream_Read_UINT16(s, wFormatNo); + Stream_Read_UINT8(s, rdpsnd->cBlockNo); + Stream_Seek(s, 3); /* bPad */ + Stream_Read_UINT32(s, dwAudioTimeStamp); + if (wFormatNo >= rdpsnd->NumberOfClientFormats) + return ERROR_INVALID_DATA; + format = &rdpsnd->ClientFormats[wFormatNo]; + rdpsnd->waveDataSize = BodySize - 12; + rdpsnd->wArrivalTime = GetTickCount64(); + WLog_Print(rdpsnd->log, WLOG_DEBUG, + "%s Wave2PDU: cBlockNo: %" PRIu8 " wFormatNo: %" PRIu16 + " [%s] , align=%hu wTimeStamp=0x%04" PRIx16 ", dwAudioTimeStamp=0x%08" PRIx32, + rdpsnd_is_dyn_str(rdpsnd->dynamic), rdpsnd->cBlockNo, wFormatNo, + audio_format_get_tag_string(format->wFormatTag), format->nBlockAlign, + rdpsnd->wTimeStamp, dwAudioTimeStamp); + + if (!rdpsnd_ensure_device_is_open(rdpsnd, wFormatNo, format)) + return ERROR_INTERNAL_ERROR; + + return rdpsnd_treat_wave(rdpsnd, s, rdpsnd->waveDataSize); +} + +static void rdpsnd_recv_close_pdu(rdpsndPlugin* rdpsnd) +{ + if (rdpsnd->isOpen) + { + WLog_Print(rdpsnd->log, WLOG_DEBUG, "%s Closing device", + rdpsnd_is_dyn_str(rdpsnd->dynamic)); + } + else + WLog_Print(rdpsnd->log, WLOG_DEBUG, "%s Device already closed", + rdpsnd_is_dyn_str(rdpsnd->dynamic)); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_recv_volume_pdu(rdpsndPlugin* rdpsnd, wStream* s) +{ + BOOL rc = TRUE; + UINT32 dwVolume = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_BAD_LENGTH; + + Stream_Read_UINT32(s, dwVolume); + WLog_Print(rdpsnd->log, WLOG_DEBUG, "%s Volume: 0x%08" PRIX32 "", + rdpsnd_is_dyn_str(rdpsnd->dynamic), dwVolume); + + rdpsnd->volume = dwVolume; + rdpsnd->applyVolume = TRUE; + rc = rdpsnd_apply_volume(rdpsnd); + + if (!rc) + { + WLog_ERR(TAG, "%s error setting volume", rdpsnd_is_dyn_str(rdpsnd->dynamic)); + return CHANNEL_RC_INITIALIZATION_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_recv_pdu(rdpsndPlugin* rdpsnd, wStream* s) +{ + BYTE msgType = 0; + UINT16 BodySize = 0; + UINT status = CHANNEL_RC_OK; + + if (rdpsnd->expectingWave) + { + status = rdpsnd_recv_wave_pdu(rdpsnd, s); + goto out; + } + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + { + status = ERROR_BAD_LENGTH; + goto out; + } + + Stream_Read_UINT8(s, msgType); /* msgType */ + Stream_Seek_UINT8(s); /* bPad */ + Stream_Read_UINT16(s, BodySize); + + switch (msgType) + { + case SNDC_FORMATS: + status = rdpsnd_recv_server_audio_formats_pdu(rdpsnd, s); + break; + + case SNDC_TRAINING: + status = rdpsnd_recv_training_pdu(rdpsnd, s); + break; + + case SNDC_WAVE: + status = rdpsnd_recv_wave_info_pdu(rdpsnd, s, BodySize); + break; + + case SNDC_CLOSE: + rdpsnd_recv_close_pdu(rdpsnd); + break; + + case SNDC_SETVOLUME: + status = rdpsnd_recv_volume_pdu(rdpsnd, s); + break; + + case SNDC_WAVE2: + status = rdpsnd_recv_wave2_pdu(rdpsnd, s, BodySize); + break; + + default: + WLog_ERR(TAG, "%s unknown msgType %" PRIu8 "", rdpsnd_is_dyn_str(rdpsnd->dynamic), + msgType); + break; + } + +out: + Stream_Release(s); + return status; +} + +static void rdpsnd_register_device_plugin(rdpsndPlugin* rdpsnd, rdpsndDevicePlugin* device) +{ + if (rdpsnd->device) + { + WLog_ERR(TAG, "%s existing device, abort.", rdpsnd_is_dyn_str(FALSE)); + return; + } + + rdpsnd->device = device; + device->rdpsnd = rdpsnd; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_load_device_plugin(rdpsndPlugin* rdpsnd, const char* name, + const ADDIN_ARGV* args) +{ + FREERDP_RDPSND_DEVICE_ENTRY_POINTS entryPoints = WINPR_C_ARRAY_INIT; + UINT error = 0; + DWORD flags = FREERDP_ADDIN_CHANNEL_STATIC | FREERDP_ADDIN_CHANNEL_ENTRYEX; + if (rdpsnd->dynamic) + flags = FREERDP_ADDIN_CHANNEL_DYNAMIC; + PVIRTUALCHANNELENTRY pvce = + freerdp_load_channel_addin_entry(RDPSND_CHANNEL_NAME, name, nullptr, flags); + PFREERDP_RDPSND_DEVICE_ENTRY entry = WINPR_FUNC_PTR_CAST(pvce, PFREERDP_RDPSND_DEVICE_ENTRY); + + if (!entry) + return ERROR_INTERNAL_ERROR; + + entryPoints.rdpsnd = rdpsnd; + entryPoints.pRegisterRdpsndDevice = rdpsnd_register_device_plugin; + entryPoints.args = args; + + error = entry(&entryPoints); + if (error) + WLog_ERR(TAG, "%s %s entry returns error %" PRIu32 "", rdpsnd_is_dyn_str(rdpsnd->dynamic), + name, error); + + WLog_INFO(TAG, "%s Loaded %s backend for rdpsnd", rdpsnd_is_dyn_str(rdpsnd->dynamic), name); + return error; +} + +static BOOL rdpsnd_set_subsystem(rdpsndPlugin* rdpsnd, const char* subsystem) +{ + free(rdpsnd->subsystem); + rdpsnd->subsystem = _strdup(subsystem); + return (rdpsnd->subsystem != nullptr); +} + +static BOOL rdpsnd_set_device_name(rdpsndPlugin* rdpsnd, const char* device_name) +{ + free(rdpsnd->device_name); + rdpsnd->device_name = _strdup(device_name); + return (rdpsnd->device_name != nullptr); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_process_addin_args(rdpsndPlugin* rdpsnd, const ADDIN_ARGV* args) +{ + int status = 0; + DWORD flags = 0; + const COMMAND_LINE_ARGUMENT_A* arg = nullptr; + COMMAND_LINE_ARGUMENT_A rdpsnd_args[] = { + { "sys", COMMAND_LINE_VALUE_REQUIRED, "", nullptr, nullptr, -1, nullptr, + "subsystem" }, + { "dev", COMMAND_LINE_VALUE_REQUIRED, "", nullptr, nullptr, -1, nullptr, "device" }, + { "format", COMMAND_LINE_VALUE_REQUIRED, "", nullptr, nullptr, -1, nullptr, + "format" }, + { "rate", COMMAND_LINE_VALUE_REQUIRED, "", nullptr, nullptr, -1, nullptr, "rate" }, + { "channel", COMMAND_LINE_VALUE_REQUIRED, "", nullptr, nullptr, -1, nullptr, + "channel" }, + { "latency", COMMAND_LINE_VALUE_REQUIRED, "", nullptr, nullptr, -1, nullptr, + "latency" }, + { "quality", COMMAND_LINE_VALUE_REQUIRED, "", nullptr, nullptr, -1, nullptr, + "quality mode" }, + { nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr } + }; + rdpsnd->wQualityMode = HIGH_QUALITY; /* default quality mode */ + + if (args->argc > 1) + { + flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON; + status = CommandLineParseArgumentsA(args->argc, args->argv, rdpsnd_args, flags, rdpsnd, + nullptr, nullptr); + + if (status < 0) + return CHANNEL_RC_INITIALIZATION_ERROR; + + arg = rdpsnd_args; + errno = 0; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "sys") + { + if (!rdpsnd_set_subsystem(rdpsnd, arg->Value)) + return CHANNEL_RC_NO_MEMORY; + } + CommandLineSwitchCase(arg, "dev") + { + if (!rdpsnd_set_device_name(rdpsnd, arg->Value)) + return CHANNEL_RC_NO_MEMORY; + } + CommandLineSwitchCase(arg, "format") + { + unsigned long val = strtoul(arg->Value, nullptr, 0); + + if ((errno != 0) || (val > UINT16_MAX)) + return CHANNEL_RC_INITIALIZATION_ERROR; + + rdpsnd->fixed_format->wFormatTag = (UINT16)val; + } + CommandLineSwitchCase(arg, "rate") + { + unsigned long val = strtoul(arg->Value, nullptr, 0); + + if ((errno != 0) || (val > UINT32_MAX)) + return CHANNEL_RC_INITIALIZATION_ERROR; + + rdpsnd->fixed_format->nSamplesPerSec = (UINT32)val; + } + CommandLineSwitchCase(arg, "channel") + { + unsigned long val = strtoul(arg->Value, nullptr, 0); + + if ((errno != 0) || (val > UINT16_MAX)) + return CHANNEL_RC_INITIALIZATION_ERROR; + + rdpsnd->fixed_format->nChannels = (UINT16)val; + } + CommandLineSwitchCase(arg, "latency") + { + unsigned long val = strtoul(arg->Value, nullptr, 0); + + if ((errno != 0) || (val > UINT32_MAX)) + return CHANNEL_RC_INITIALIZATION_ERROR; + + rdpsnd->latency = (UINT32)val; + } + CommandLineSwitchCase(arg, "quality") + { + long wQualityMode = DYNAMIC_QUALITY; + + if (_stricmp(arg->Value, "dynamic") == 0) + wQualityMode = DYNAMIC_QUALITY; + else if (_stricmp(arg->Value, "medium") == 0) + wQualityMode = MEDIUM_QUALITY; + else if (_stricmp(arg->Value, "high") == 0) + wQualityMode = HIGH_QUALITY; + else + { + wQualityMode = strtol(arg->Value, nullptr, 0); + + if (errno != 0) + return CHANNEL_RC_INITIALIZATION_ERROR; + } + + if ((wQualityMode < 0) || (wQualityMode > 2)) + wQualityMode = DYNAMIC_QUALITY; + + rdpsnd->wQualityMode = (UINT16)wQualityMode; + } + CommandLineSwitchDefault(arg) + { + } + CommandLineSwitchEnd(arg) + } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr); + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_process_connect(rdpsndPlugin* rdpsnd) +{ + const struct + { + const char* subsystem; + const char* device; + } backends[] = { +#if defined(WITH_IOSAUDIO) + { "ios", "" }, +#endif +#if defined(WITH_OPENSLES) + { "opensles", "" }, +#endif +#if defined(WITH_PULSE) + { "pulse", "" }, +#endif +#if defined(WITH_ALSA) + { "alsa", "default" }, +#endif +#if defined(WITH_OSS) + { "oss", "" }, +#endif +#if defined(WITH_MACAUDIO) + { "mac", "default" }, +#endif +#if defined(WITH_WINMM) + { "winmm", "" }, +#endif +#if defined(WITH_SNDIO) + { "sndio", "" }, +#endif + { "fake", "" } + }; + const ADDIN_ARGV* args = nullptr; + UINT status = ERROR_INTERNAL_ERROR; + WINPR_ASSERT(rdpsnd); + rdpsnd->latency = 0; + args = (const ADDIN_ARGV*)rdpsnd->channelEntryPoints.pExtendedData; + + if (args) + { + status = rdpsnd_process_addin_args(rdpsnd, args); + + if (status != CHANNEL_RC_OK) + return status; + } + + if (rdpsnd->subsystem) + { + if ((status = rdpsnd_load_device_plugin(rdpsnd, rdpsnd->subsystem, args))) + { + WLog_ERR(TAG, + "%s Unable to load sound playback subsystem %s because of error %" PRIu32 "", + rdpsnd_is_dyn_str(rdpsnd->dynamic), rdpsnd->subsystem, status); + return status; + } + } + else + { + for (size_t x = 0; x < ARRAYSIZE(backends); x++) + { + const char* subsystem_name = backends[x].subsystem; + const char* device_name = backends[x].device; + + if ((status = rdpsnd_load_device_plugin(rdpsnd, subsystem_name, args))) + WLog_ERR(TAG, + "%s Unable to load sound playback subsystem %s because of error %" PRIu32 + "", + rdpsnd_is_dyn_str(rdpsnd->dynamic), subsystem_name, status); + + if (!rdpsnd->device) + continue; + + if (!rdpsnd_set_subsystem(rdpsnd, subsystem_name) || + !rdpsnd_set_device_name(rdpsnd, device_name)) + return CHANNEL_RC_NO_MEMORY; + + break; + } + + if (!rdpsnd->device || status) + return CHANNEL_RC_INITIALIZATION_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpsnd_virtual_channel_write(rdpsndPlugin* rdpsnd, wStream* s) +{ + UINT status = CHANNEL_RC_BAD_INIT_HANDLE; + + if (rdpsnd) + { + if (rdpsnd->dynamic) + { + IWTSVirtualChannel* channel = nullptr; + if (rdpsnd->listener_callback) + { + channel = rdpsnd->listener_callback->channel_callback->channel; + status = + channel->Write(channel, (UINT32)Stream_Length(s), Stream_Buffer(s), nullptr); + } + Stream_Free(s, TRUE); + } + else + { + status = rdpsnd->channelEntryPoints.pVirtualChannelWriteEx( + rdpsnd->InitHandle, rdpsnd->OpenHandle, Stream_Buffer(s), + (UINT32)Stream_GetPosition(s), s); + + if (status != CHANNEL_RC_OK) + { + Stream_Free(s, TRUE); + WLog_ERR(TAG, "%s pVirtualChannelWriteEx failed with %s [%08" PRIX32 "]", + rdpsnd_is_dyn_str(FALSE), WTSErrorToString(status), status); + } + } + } + + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_virtual_channel_event_data_received(rdpsndPlugin* plugin, void* pData, + UINT32 dataLength, UINT32 totalLength, + UINT32 dataFlags) +{ + if ((dataFlags & CHANNEL_FLAG_SUSPEND) || (dataFlags & CHANNEL_FLAG_RESUME)) + return CHANNEL_RC_OK; + + if (dataFlags & CHANNEL_FLAG_FIRST) + { + if (!plugin->data_in) + plugin->data_in = StreamPool_Take(plugin->pool, totalLength); + + Stream_ResetPosition(plugin->data_in); + } + + if (!Stream_EnsureRemainingCapacity(plugin->data_in, dataLength)) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write(plugin->data_in, pData, dataLength); + + if (dataFlags & CHANNEL_FLAG_LAST) + { + Stream_SealLength(plugin->data_in); + Stream_ResetPosition(plugin->data_in); + + if (plugin->async) + { + if (!MessageQueue_Post(plugin->queue, nullptr, 0, plugin->data_in, nullptr)) + return ERROR_INTERNAL_ERROR; + plugin->data_in = nullptr; + } + else + { + UINT error = rdpsnd_recv_pdu(plugin, plugin->data_in); + plugin->data_in = nullptr; + if (error) + return error; + } + } + + return CHANNEL_RC_OK; +} + +static VOID VCAPITYPE rdpsnd_virtual_channel_open_event_ex(LPVOID lpUserParam, DWORD openHandle, + UINT event, LPVOID pData, + UINT32 dataLength, UINT32 totalLength, + UINT32 dataFlags) +{ + UINT error = CHANNEL_RC_OK; + rdpsndPlugin* rdpsnd = (rdpsndPlugin*)lpUserParam; + WINPR_ASSERT(rdpsnd); + WINPR_ASSERT(!rdpsnd->dynamic); + + switch (event) + { + case CHANNEL_EVENT_DATA_RECEIVED: + if (!rdpsnd) + return; + + if (rdpsnd->OpenHandle != openHandle) + { + WLog_ERR(TAG, "%s error no match", rdpsnd_is_dyn_str(rdpsnd->dynamic)); + return; + } + if ((error = rdpsnd_virtual_channel_event_data_received(rdpsnd, pData, dataLength, + totalLength, dataFlags))) + WLog_ERR(TAG, + "%s rdpsnd_virtual_channel_event_data_received failed with error %" PRIu32 + "", + rdpsnd_is_dyn_str(rdpsnd->dynamic), error); + + break; + + case CHANNEL_EVENT_WRITE_CANCELLED: + case CHANNEL_EVENT_WRITE_COMPLETE: + { + wStream* s = (wStream*)pData; + Stream_Free(s, TRUE); + } + break; + + case CHANNEL_EVENT_USER: + break; + default: + break; + } + + if (error && rdpsnd && rdpsnd->rdpcontext) + { + char buffer[8192]; + (void)_snprintf(buffer, sizeof(buffer), + "%s rdpsnd_virtual_channel_open_event_ex reported an error", + rdpsnd_is_dyn_str(rdpsnd->dynamic)); + setChannelError(rdpsnd->rdpcontext, error, buffer); + } +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_virtual_channel_event_connected(rdpsndPlugin* rdpsnd, LPVOID pData, + UINT32 dataLength) +{ + UINT32 status = 0; + DWORD opened = 0; + WINPR_UNUSED(pData); + WINPR_UNUSED(dataLength); + + WINPR_ASSERT(rdpsnd); + WINPR_ASSERT(!rdpsnd->dynamic); + + status = rdpsnd->channelEntryPoints.pVirtualChannelOpenEx( + rdpsnd->InitHandle, &opened, rdpsnd->channelDef.name, rdpsnd_virtual_channel_open_event_ex); + + if (status != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "%s pVirtualChannelOpenEx failed with %s [%08" PRIX32 "]", + rdpsnd_is_dyn_str(rdpsnd->dynamic), WTSErrorToString(status), status); + goto fail; + } + + if (rdpsnd_process_connect(rdpsnd) != CHANNEL_RC_OK) + goto fail; + + rdpsnd->OpenHandle = opened; + return CHANNEL_RC_OK; +fail: + if (opened != 0) + rdpsnd->channelEntryPoints.pVirtualChannelCloseEx(rdpsnd->InitHandle, opened); + return CHANNEL_RC_NO_MEMORY; +} + +static void rdpsnd_terminate_thread(rdpsndPlugin* rdpsnd) +{ + WINPR_ASSERT(rdpsnd); + if (rdpsnd->queue) + MessageQueue_PostQuit(rdpsnd->queue, 0); + + if (rdpsnd->thread) + { + (void)WaitForSingleObject(rdpsnd->thread, INFINITE); + (void)CloseHandle(rdpsnd->thread); + } + + MessageQueue_Free(rdpsnd->queue); + rdpsnd->thread = nullptr; + rdpsnd->queue = nullptr; +} + +static void cleanup_internals(rdpsndPlugin* rdpsnd) +{ + if (!rdpsnd) + return; + + if (rdpsnd->pool) + StreamPool_Return(rdpsnd->pool, rdpsnd->data_in); + + audio_formats_free(rdpsnd->ClientFormats, rdpsnd->NumberOfClientFormats); + audio_formats_free(rdpsnd->ServerFormats, rdpsnd->NumberOfServerFormats); + + rdpsnd->NumberOfClientFormats = 0; + rdpsnd->ClientFormats = nullptr; + rdpsnd->NumberOfServerFormats = 0; + rdpsnd->ServerFormats = nullptr; + + rdpsnd->data_in = nullptr; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_virtual_channel_event_disconnected(rdpsndPlugin* rdpsnd) +{ + UINT error = 0; + + WINPR_ASSERT(rdpsnd); + WINPR_ASSERT(!rdpsnd->dynamic); + if (rdpsnd->OpenHandle != 0) + { + DWORD opened = rdpsnd->OpenHandle; + rdpsnd->OpenHandle = 0; + if (rdpsnd->device) + IFCALL(rdpsnd->device->Close, rdpsnd->device); + + error = rdpsnd->channelEntryPoints.pVirtualChannelCloseEx(rdpsnd->InitHandle, opened); + + if (CHANNEL_RC_OK != error) + { + WLog_ERR(TAG, "%s pVirtualChannelCloseEx failed with %s [%08" PRIX32 "]", + rdpsnd_is_dyn_str(rdpsnd->dynamic), WTSErrorToString(error), error); + return error; + } + } + + cleanup_internals(rdpsnd); + + if (rdpsnd->device) + { + IFCALL(rdpsnd->device->Free, rdpsnd->device); + rdpsnd->device = nullptr; + } + + return CHANNEL_RC_OK; +} + +static void queue_free(void* obj) +{ + wMessage* msg = obj; + if (!msg) + return; + if (msg->id != 0) + return; + wStream* s = msg->wParam; + Stream_Release(s); +} + +static void free_internals(rdpsndPlugin* rdpsnd) +{ + if (!rdpsnd) + return; + + if (rdpsnd->references > 0) + rdpsnd->references--; + + if (rdpsnd->references > 0) + return; + + rdpsnd_terminate_thread(rdpsnd); + freerdp_dsp_context_free(rdpsnd->dsp_context); + StreamPool_Free(rdpsnd->pool); + rdpsnd->pool = nullptr; + rdpsnd->dsp_context = nullptr; +} + +static BOOL allocate_internals(rdpsndPlugin* rdpsnd) +{ + WINPR_ASSERT(rdpsnd); + + if (!rdpsnd->pool) + { + rdpsnd->pool = StreamPool_New(TRUE, 4096); + if (!rdpsnd->pool) + return FALSE; + } + + if (!rdpsnd->dsp_context) + { + rdpsnd->dsp_context = freerdp_dsp_context_new(FALSE); + if (!rdpsnd->dsp_context) + return FALSE; + } + + if (rdpsnd->async) + { + if (!rdpsnd->queue) + { + wObject obj = WINPR_C_ARRAY_INIT; + + obj.fnObjectFree = queue_free; + rdpsnd->queue = MessageQueue_New(&obj); + if (!rdpsnd->queue) + return CHANNEL_RC_NO_MEMORY; + } + + if (!rdpsnd->thread) + { + rdpsnd->thread = CreateThread(nullptr, 0, play_thread, rdpsnd, 0, nullptr); + if (!rdpsnd->thread) + return CHANNEL_RC_INITIALIZATION_ERROR; + } + } + + rdpsnd->references++; + + return TRUE; +} + +static DWORD WINAPI play_thread(LPVOID arg) +{ + UINT error = CHANNEL_RC_OK; + rdpsndPlugin* rdpsnd = arg; + + if (!rdpsnd || !rdpsnd->queue) + return ERROR_INVALID_PARAMETER; + + while (TRUE) + { + int rc = -1; + wMessage message = WINPR_C_ARRAY_INIT; + wStream* s = nullptr; + DWORD status = 0; + DWORD nCount = 0; + HANDLE handles[MAXIMUM_WAIT_OBJECTS] = WINPR_C_ARRAY_INIT; + + handles[nCount++] = MessageQueue_Event(rdpsnd->queue); + handles[nCount++] = freerdp_abort_event(rdpsnd->rdpcontext); + status = WaitForMultipleObjects(nCount, handles, FALSE, INFINITE); + switch (status) + { + case WAIT_OBJECT_0: + break; + default: + return ERROR_TIMEOUT; + } + + rc = MessageQueue_Peek(rdpsnd->queue, &message, TRUE); + if (rc < 1) + continue; + + if (message.id == WMQ_QUIT) + break; + + s = message.wParam; + error = rdpsnd_recv_pdu(rdpsnd, s); + + if (error) + return error; + } + + return CHANNEL_RC_OK; +} + +static UINT rdpsnd_virtual_channel_event_initialized(rdpsndPlugin* rdpsnd) +{ + if (!rdpsnd) + return ERROR_INVALID_PARAMETER; + + if (!allocate_internals(rdpsnd)) + return CHANNEL_RC_NO_MEMORY; + + return CHANNEL_RC_OK; +} + +void rdpsnd_virtual_channel_event_terminated(rdpsndPlugin* rdpsnd) +{ + if (rdpsnd) + { + free_internals(rdpsnd); + audio_formats_free(rdpsnd->fixed_format, 1); + free(rdpsnd->subsystem); + free(rdpsnd->device_name); + rdpsnd->InitHandle = nullptr; + } + + free(rdpsnd); +} + +static VOID VCAPITYPE rdpsnd_virtual_channel_init_event_ex(LPVOID lpUserParam, LPVOID pInitHandle, + UINT event, LPVOID pData, + UINT dataLength) +{ + UINT error = CHANNEL_RC_OK; + rdpsndPlugin* plugin = (rdpsndPlugin*)lpUserParam; + + if (!plugin) + return; + + if (plugin->InitHandle != pInitHandle) + { + WLog_ERR(TAG, "%s error no match", rdpsnd_is_dyn_str(plugin->dynamic)); + return; + } + + switch (event) + { + case CHANNEL_EVENT_INITIALIZED: + error = rdpsnd_virtual_channel_event_initialized(plugin); + break; + + case CHANNEL_EVENT_CONNECTED: + error = rdpsnd_virtual_channel_event_connected(plugin, pData, dataLength); + break; + + case CHANNEL_EVENT_DISCONNECTED: + error = rdpsnd_virtual_channel_event_disconnected(plugin); + break; + + case CHANNEL_EVENT_TERMINATED: + rdpsnd_virtual_channel_event_terminated(plugin); + plugin = nullptr; + break; + + case CHANNEL_EVENT_ATTACHED: + plugin->attached = TRUE; + break; + + case CHANNEL_EVENT_DETACHED: + plugin->attached = FALSE; + break; + + default: + break; + } + + if (error && plugin && plugin->rdpcontext) + { + char buffer[8192]; + (void)_snprintf(buffer, sizeof(buffer), "%s reported an error", + rdpsnd_is_dyn_str(plugin->dynamic)); + setChannelError(plugin->rdpcontext, error, buffer); + } +} + +rdpContext* freerdp_rdpsnd_get_context(rdpsndPlugin* plugin) +{ + if (!plugin) + return nullptr; + + return plugin->rdpcontext; +} + +static rdpsndPlugin* allocatePlugin(void) +{ + rdpsndPlugin* rdpsnd = (rdpsndPlugin*)calloc(1, sizeof(rdpsndPlugin)); + if (!rdpsnd) + goto fail; + + rdpsnd->fixed_format = audio_format_new(); + if (!rdpsnd->fixed_format) + goto fail; + rdpsnd->log = WLog_Get("com.freerdp.channels.rdpsnd.client"); + if (!rdpsnd->log) + goto fail; + + rdpsnd->attached = TRUE; + return rdpsnd; + +fail: + if (rdpsnd) + audio_formats_free(rdpsnd->fixed_format, 1); + free(rdpsnd); + return nullptr; +} +/* rdpsnd is always built-in */ +FREERDP_ENTRY_POINT(BOOL VCAPITYPE rdpsnd_VirtualChannelEntryEx( + PCHANNEL_ENTRY_POINTS_EX pEntryPoints, PVOID pInitHandle)) +{ + UINT rc = 0; + rdpsndPlugin* rdpsnd = nullptr; + CHANNEL_ENTRY_POINTS_FREERDP_EX* pEntryPointsEx = nullptr; + + if (!pEntryPoints) + return FALSE; + + rdpsnd = allocatePlugin(); + + if (!rdpsnd) + return FALSE; + + rdpsnd->channelDef.options = CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP; + (void)sprintf_s(rdpsnd->channelDef.name, ARRAYSIZE(rdpsnd->channelDef.name), + RDPSND_CHANNEL_NAME); + pEntryPointsEx = (CHANNEL_ENTRY_POINTS_FREERDP_EX*)pEntryPoints; + + if ((pEntryPointsEx->cbSize >= sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)) && + (pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER)) + { + rdpsnd->rdpcontext = pEntryPointsEx->context; + if (!freerdp_settings_get_bool(rdpsnd->rdpcontext->settings, + FreeRDP_SynchronousStaticChannels)) + rdpsnd->async = TRUE; + } + + CopyMemory(&(rdpsnd->channelEntryPoints), pEntryPoints, + sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)); + rdpsnd->InitHandle = pInitHandle; + + WINPR_ASSERT(rdpsnd->channelEntryPoints.pVirtualChannelInitEx); + rc = rdpsnd->channelEntryPoints.pVirtualChannelInitEx( + rdpsnd, nullptr, pInitHandle, &rdpsnd->channelDef, 1, VIRTUAL_CHANNEL_VERSION_WIN2000, + rdpsnd_virtual_channel_init_event_ex); + + if (CHANNEL_RC_OK != rc) + { + WLog_ERR(TAG, "%s pVirtualChannelInitEx failed with %s [%08" PRIX32 "]", + rdpsnd_is_dyn_str(FALSE), WTSErrorToString(rc), rc); + rdpsnd_virtual_channel_event_terminated(rdpsnd); + return FALSE; + } + + return TRUE; +} + +static UINT rdpsnd_on_open(IWTSVirtualChannelCallback* pChannelCallback) +{ + GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + rdpsndPlugin* rdpsnd = nullptr; + + WINPR_ASSERT(callback); + + rdpsnd = (rdpsndPlugin*)callback->plugin; + WINPR_ASSERT(rdpsnd); + + if (rdpsnd->OnOpenCalled) + return CHANNEL_RC_OK; + rdpsnd->OnOpenCalled = TRUE; + + if (!allocate_internals(rdpsnd)) + return ERROR_OUTOFMEMORY; + + return rdpsnd_process_connect(rdpsnd); +} + +static UINT rdpsnd_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data) +{ + GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + rdpsndPlugin* plugin = nullptr; + wStream* copy = nullptr; + size_t len = 0; + + len = Stream_GetRemainingLength(data); + + if (!callback || !callback->plugin) + return ERROR_INVALID_PARAMETER; + plugin = (rdpsndPlugin*)callback->plugin; + WINPR_ASSERT(plugin); + + copy = StreamPool_Take(plugin->pool, len); + if (!copy) + return ERROR_OUTOFMEMORY; + Stream_Copy(data, copy, len); + Stream_SealLength(copy); + Stream_ResetPosition(copy); + + if (plugin->async) + { + if (!MessageQueue_Post(plugin->queue, nullptr, 0, copy, nullptr)) + { + Stream_Release(copy); + return ERROR_INTERNAL_ERROR; + } + } + else + { + UINT error = rdpsnd_recv_pdu(plugin, copy); + if (error) + return error; + } + + return CHANNEL_RC_OK; +} + +static UINT rdpsnd_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + rdpsndPlugin* rdpsnd = nullptr; + + WINPR_ASSERT(callback); + + rdpsnd = (rdpsndPlugin*)callback->plugin; + WINPR_ASSERT(rdpsnd); + + rdpsnd->OnOpenCalled = FALSE; + if (rdpsnd->device) + IFCALL(rdpsnd->device->Close, rdpsnd->device); + + cleanup_internals(rdpsnd); + + free_internals(rdpsnd); + if (rdpsnd->device) + { + IFCALL(rdpsnd->device->Free, rdpsnd->device); + rdpsnd->device = nullptr; + } + + free(pChannelCallback); + return CHANNEL_RC_OK; +} + +// NOLINTBEGIN(readability-non-const-parameter) +static UINT rdpsnd_on_new_channel_connection(IWTSListenerCallback* pListenerCallback, + IWTSVirtualChannel* pChannel, BYTE* Data, + BOOL* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +// NOLINTEND(readability-non-const-parameter) +{ + GENERIC_CHANNEL_CALLBACK* callback = nullptr; + GENERIC_LISTENER_CALLBACK* listener_callback = (GENERIC_LISTENER_CALLBACK*)pListenerCallback; + WINPR_ASSERT(listener_callback); + WINPR_ASSERT(pChannel); + WINPR_ASSERT(ppCallback); + callback = (GENERIC_CHANNEL_CALLBACK*)calloc(1, sizeof(GENERIC_CHANNEL_CALLBACK)); + + WINPR_UNUSED(Data); + WINPR_UNUSED(pbAccept); + + if (!callback) + { + WLog_ERR(TAG, "%s calloc failed!", rdpsnd_is_dyn_str(TRUE)); + return CHANNEL_RC_NO_MEMORY; + } + + callback->iface.OnOpen = rdpsnd_on_open; + callback->iface.OnDataReceived = rdpsnd_on_data_received; + callback->iface.OnClose = rdpsnd_on_close; + callback->plugin = listener_callback->plugin; + callback->channel_mgr = listener_callback->channel_mgr; + callback->channel = pChannel; + listener_callback->channel_callback = callback; + *ppCallback = &callback->iface; + return CHANNEL_RC_OK; +} + +static UINT rdpsnd_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr) +{ + UINT status = 0; + rdpsndPlugin* rdpsnd = (rdpsndPlugin*)pPlugin; + WINPR_ASSERT(rdpsnd); + WINPR_ASSERT(pChannelMgr); + if (rdpsnd->initialized) + { + WLog_ERR(TAG, "[%s] channel initialized twice, aborting", RDPSND_DVC_CHANNEL_NAME); + return ERROR_INVALID_DATA; + } + rdpsnd->listener_callback = + (GENERIC_LISTENER_CALLBACK*)calloc(1, sizeof(GENERIC_LISTENER_CALLBACK)); + + if (!rdpsnd->listener_callback) + { + WLog_ERR(TAG, "%s calloc failed!", rdpsnd_is_dyn_str(TRUE)); + return CHANNEL_RC_NO_MEMORY; + } + + rdpsnd->listener_callback->iface.OnNewChannelConnection = rdpsnd_on_new_channel_connection; + rdpsnd->listener_callback->plugin = pPlugin; + rdpsnd->listener_callback->channel_mgr = pChannelMgr; + status = pChannelMgr->CreateListener(pChannelMgr, RDPSND_DVC_CHANNEL_NAME, 0, + &rdpsnd->listener_callback->iface, &(rdpsnd->listener)); + if (status != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "%s CreateListener failed!", rdpsnd_is_dyn_str(TRUE)); + return status; + } + + rdpsnd->listener->pInterface = rdpsnd->iface.pInterface; + status = rdpsnd_virtual_channel_event_initialized(rdpsnd); + + rdpsnd->initialized = status == CHANNEL_RC_OK; + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_plugin_terminated(IWTSPlugin* pPlugin) +{ + rdpsndPlugin* rdpsnd = (rdpsndPlugin*)pPlugin; + if (rdpsnd) + { + if (rdpsnd->listener_callback) + { + IWTSVirtualChannelManager* mgr = rdpsnd->listener_callback->channel_mgr; + if (mgr) + IFCALL(mgr->DestroyListener, mgr, rdpsnd->listener); + } + free(rdpsnd->listener_callback); + free(rdpsnd->iface.pInterface); + } + rdpsnd_virtual_channel_event_terminated(rdpsnd); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE rdpsnd_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)) +{ + UINT error = CHANNEL_RC_OK; + rdpsndPlugin* rdpsnd = nullptr; + + WINPR_ASSERT(pEntryPoints); + WINPR_ASSERT(pEntryPoints->GetPlugin); + + rdpsnd = (rdpsndPlugin*)pEntryPoints->GetPlugin(pEntryPoints, RDPSND_CHANNEL_NAME); + + if (!rdpsnd) + { + IWTSPlugin* iface = nullptr; + union + { + const void* cev; + void* ev; + } cnv; + + rdpsnd = allocatePlugin(); + if (!rdpsnd) + { + WLog_ERR(TAG, "%s calloc failed!", rdpsnd_is_dyn_str(TRUE)); + return CHANNEL_RC_NO_MEMORY; + } + + iface = &rdpsnd->iface; + iface->Initialize = rdpsnd_plugin_initialize; + iface->Connected = nullptr; + iface->Disconnected = nullptr; + iface->Terminated = rdpsnd_plugin_terminated; + + rdpsnd->dynamic = TRUE; + + WINPR_ASSERT(pEntryPoints->GetRdpContext); + rdpsnd->rdpcontext = pEntryPoints->GetRdpContext(pEntryPoints); + + if (!freerdp_settings_get_bool(rdpsnd->rdpcontext->settings, + FreeRDP_SynchronousDynamicChannels)) + rdpsnd->async = TRUE; + + /* user data pointer is not const, cast to avoid warning. */ + cnv.cev = pEntryPoints->GetPluginData(pEntryPoints); + WINPR_ASSERT(pEntryPoints->GetPluginData); + rdpsnd->channelEntryPoints.pExtendedData = cnv.ev; + + error = pEntryPoints->RegisterPlugin(pEntryPoints, RDPSND_CHANNEL_NAME, iface); + } + else + { + WLog_ERR(TAG, "%s could not get rdpsnd Plugin.", rdpsnd_is_dyn_str(TRUE)); + return CHANNEL_RC_BAD_CHANNEL; + } + + return error; +} diff --git a/third_party/FreeRDP/channels/rdpsnd/client/rdpsnd_main.h b/third_party/FreeRDP/channels/rdpsnd/client/rdpsnd_main.h new file mode 100644 index 0000000..33adfcd --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/client/rdpsnd_main.h @@ -0,0 +1,41 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Output Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2012-2013 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPSND_CLIENT_MAIN_H +#define FREERDP_CHANNEL_RDPSND_CLIENT_MAIN_H + +#include +#include +#include +#include +#include + +#define TAG CHANNELS_TAG("rdpsnd.client") + +#if defined(WITH_DEBUG_SND) +#define DEBUG_SND(...) WLog_DBG(TAG, __VA_ARGS__) +#else +#define DEBUG_SND(...) \ + do \ + { \ + } while (0) +#endif + +#endif /* FREERDP_CHANNEL_RDPSND_CLIENT_MAIN_H */ diff --git a/third_party/FreeRDP/channels/rdpsnd/client/sndio/CMakeLists.txt b/third_party/FreeRDP/channels/rdpsnd/client/sndio/CMakeLists.txt new file mode 100644 index 0000000..ba42fa1 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/client/sndio/CMakeLists.txt @@ -0,0 +1,30 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright (c) 2015 Rozhuk Ivan +# Copyright (c) 2020 Ingo Feinerer +# +# 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. + +define_channel_client_subsystem("rdpsnd" "sndio" "") + +find_package(SNDIO REQUIRED) + +set(${MODULE_PREFIX}_SRCS rdpsnd_sndio.c) + +set(${MODULE_PREFIX}_LIBS winpr freerdp ${SNDIO_LIBRARIES}) + +include_directories(..) +include_directories(SYSTEM ${SNDIO_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") diff --git a/third_party/FreeRDP/channels/rdpsnd/client/sndio/rdpsnd_sndio.c b/third_party/FreeRDP/channels/rdpsnd/client/sndio/rdpsnd_sndio.c new file mode 100644 index 0000000..fc30719 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/client/sndio/rdpsnd_sndio.c @@ -0,0 +1,218 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Output Virtual Channel + * + * Copyright 2019 Armin Novak + * Copyright 2019 Thincast Technologies GmbH + * Copyright 2020 Ingo Feinerer + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "rdpsnd_main.h" + +typedef struct +{ + rdpsndDevicePlugin device; + + struct sio_hdl* hdl; + struct sio_par par; +} rdpsndSndioPlugin; + +static BOOL rdpsnd_sndio_open(rdpsndDevicePlugin* device, AUDIO_FORMAT* format, int latency) +{ + rdpsndSndioPlugin* sndio = (rdpsndSndioPlugin*)device; + + if (device == nullptr || format == nullptr) + return FALSE; + + if (sndio->hdl != nullptr) + return TRUE; + + sndio->hdl = sio_open(SIO_DEVANY, SIO_PLAY, 0); + if (sndio->hdl == nullptr) + { + WLog_ERR(TAG, "could not open audio device"); + return FALSE; + } + + sio_initpar(&sndio->par); + sndio->par.bits = format->wBitsPerSample; + sndio->par.pchan = format->nChannels; + sndio->par.rate = format->nSamplesPerSec; + if (!sio_setpar(sndio->hdl, &sndio->par)) + { + WLog_ERR(TAG, "could not set audio parameters"); + return FALSE; + } + if (!sio_getpar(sndio->hdl, &sndio->par)) + { + WLog_ERR(TAG, "could not get audio parameters"); + return FALSE; + } + + if (!sio_start(sndio->hdl)) + { + WLog_ERR(TAG, "could not start audio device"); + return FALSE; + } + + return TRUE; +} + +static void rdpsnd_sndio_close(rdpsndDevicePlugin* device) +{ + rdpsndSndioPlugin* sndio = (rdpsndSndioPlugin*)device; + + if (device == nullptr) + return; + + if (sndio->hdl != nullptr) + { + sio_stop(sndio->hdl); + sio_close(sndio->hdl); + sndio->hdl = nullptr; + } +} + +static BOOL rdpsnd_sndio_set_volume(rdpsndDevicePlugin* device, UINT32 value) +{ + rdpsndSndioPlugin* sndio = (rdpsndSndioPlugin*)device; + + if (device == nullptr || sndio->hdl == nullptr) + return FALSE; + + /* + * Low-order word contains the left-channel volume setting. + * We ignore the right-channel volume setting in the high-order word. + */ + return sio_setvol(sndio->hdl, ((value & 0xFFFF) * SIO_MAXVOL) / 0xFFFF); +} + +static void rdpsnd_sndio_free(rdpsndDevicePlugin* device) +{ + rdpsndSndioPlugin* sndio = (rdpsndSndioPlugin*)device; + + if (device == nullptr) + return; + + rdpsnd_sndio_close(device); + free(sndio); +} + +static BOOL rdpsnd_sndio_format_supported(rdpsndDevicePlugin* device, AUDIO_FORMAT* format) +{ + if (format == nullptr) + return FALSE; + + return (format->wFormatTag == WAVE_FORMAT_PCM); +} + +static void rdpsnd_sndio_play(rdpsndDevicePlugin* device, BYTE* data, int size) +{ + rdpsndSndioPlugin* sndio = (rdpsndSndioPlugin*)device; + + if (device == nullptr || sndio->hdl == nullptr) + return; + + sio_write(sndio->hdl, data, size); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_sndio_parse_addin_args(rdpsndDevicePlugin* device, ADDIN_ARGV* args) +{ + int status; + DWORD flags; + COMMAND_LINE_ARGUMENT_A* arg; + rdpsndSndioPlugin* sndio = (rdpsndSndioPlugin*)device; + COMMAND_LINE_ARGUMENT_A rdpsnd_sndio_args[] = { { nullptr, 0, nullptr, nullptr, nullptr, -1, + nullptr, nullptr } }; + flags = + COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + status = CommandLineParseArgumentsA(args->argc, (const char**)args->argv, rdpsnd_sndio_args, + flags, sndio, nullptr, nullptr); + + if (status < 0) + return ERROR_INVALID_DATA; + + arg = rdpsnd_sndio_args; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) CommandLineSwitchEnd(arg) + } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE sndio_freerdp_rdpsnd_client_subsystem_entry( + PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints)) +{ + ADDIN_ARGV* args; + rdpsndSndioPlugin* sndio; + UINT ret = CHANNEL_RC_OK; + sndio = (rdpsndSndioPlugin*)calloc(1, sizeof(rdpsndSndioPlugin)); + + if (sndio == nullptr) + return CHANNEL_RC_NO_MEMORY; + + sndio->device.Open = rdpsnd_sndio_open; + sndio->device.FormatSupported = rdpsnd_sndio_format_supported; + sndio->device.SetVolume = rdpsnd_sndio_set_volume; + sndio->device.Play = rdpsnd_sndio_play; + sndio->device.Close = rdpsnd_sndio_close; + sndio->device.Free = rdpsnd_sndio_free; + args = pEntryPoints->args; + + if (args->argc > 1) + { + ret = rdpsnd_sndio_parse_addin_args((rdpsndDevicePlugin*)sndio, args); + + if (ret != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "error parsing arguments"); + goto error; + } + } + + pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, &sndio->device); + return ret; +error: + rdpsnd_sndio_free(&sndio->device); + return ret; +} diff --git a/third_party/FreeRDP/channels/rdpsnd/client/winmm/CMakeLists.txt b/third_party/FreeRDP/channels/rdpsnd/client/winmm/CMakeLists.txt new file mode 100644 index 0000000..a58138e --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/client/winmm/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_client_subsystem("rdpsnd" "winmm" "") + +set(${MODULE_PREFIX}_SRCS rdpsnd_winmm.c) + +set(${MODULE_PREFIX}_LIBS winpr freerdp winmm.lib) + +include_directories(..) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") diff --git a/third_party/FreeRDP/channels/rdpsnd/client/winmm/rdpsnd_winmm.c b/third_party/FreeRDP/channels/rdpsnd/client/winmm/rdpsnd_winmm.c new file mode 100644 index 0000000..35a1bf1 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/client/winmm/rdpsnd_winmm.c @@ -0,0 +1,348 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Output Virtual Channel + * + * Copyright 2009-2012 Jay Sorg + * Copyright 2010-2012 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 David PHAM-VAN + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "rdpsnd_main.h" + +typedef struct +{ + rdpsndDevicePlugin device; + + HWAVEOUT hWaveOut; + WAVEFORMATEX format; + UINT32 volume; + wLog* log; + UINT32 latency; + HANDLE hThread; + DWORD threadId; + CRITICAL_SECTION cs; +} rdpsndWinmmPlugin; + +static BOOL rdpsnd_winmm_convert_format(const AUDIO_FORMAT* in, WAVEFORMATEX* out) +{ + if (!in || !out) + return FALSE; + + ZeroMemory(out, sizeof(WAVEFORMATEX)); + out->wFormatTag = WAVE_FORMAT_PCM; + out->nChannels = in->nChannels; + out->nSamplesPerSec = in->nSamplesPerSec; + + switch (in->wFormatTag) + { + case WAVE_FORMAT_PCM: + out->wBitsPerSample = in->wBitsPerSample; + break; + + default: + return FALSE; + } + + out->nBlockAlign = out->nChannels * out->wBitsPerSample / 8; + out->nAvgBytesPerSec = out->nSamplesPerSec * out->nBlockAlign; + return TRUE; +} + +static BOOL rdpsnd_winmm_set_format(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, + UINT32 latency) +{ + rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device; + + winmm->latency = latency; + if (!rdpsnd_winmm_convert_format(format, &winmm->format)) + return FALSE; + + return TRUE; +} + +static DWORD WINAPI waveOutProc(LPVOID lpParameter) +{ + MSG msg; + rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)lpParameter; + while (GetMessage(&msg, nullptr, 0, 0)) + { + if (msg.message == MM_WOM_CLOSE) + { + /* device was closed - exit thread */ + break; + } + else if (msg.message == MM_WOM_DONE) + { + /* free buffer */ + LPWAVEHDR waveHdr = (LPWAVEHDR)msg.lParam; + EnterCriticalSection(&winmm->cs); + waveOutUnprepareHeader((HWAVEOUT)msg.wParam, waveHdr, sizeof(WAVEHDR)); + LeaveCriticalSection(&winmm->cs); + free(waveHdr->lpData); + free(waveHdr); + } + } + + return 0; +} + +static BOOL rdpsnd_winmm_open(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format, + UINT32 latency) +{ + MMRESULT mmResult; + rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device; + + if (winmm->hWaveOut) + return TRUE; + + if (!rdpsnd_winmm_set_format(device, format, latency)) + return FALSE; + + winmm->hThread = CreateThread(nullptr, 0, waveOutProc, winmm, 0, &winmm->threadId); + if (!winmm->hThread) + { + WLog_Print(winmm->log, WLOG_ERROR, "CreateThread failed: %" PRIu32 "", GetLastError()); + return FALSE; + } + + mmResult = waveOutOpen(&winmm->hWaveOut, WAVE_MAPPER, &winmm->format, + (DWORD_PTR)winmm->threadId, 0, CALLBACK_THREAD); + + if (mmResult != MMSYSERR_NOERROR) + { + WLog_Print(winmm->log, WLOG_ERROR, "waveOutOpen failed: %" PRIu32 "", mmResult); + return FALSE; + } + + mmResult = waveOutSetVolume(winmm->hWaveOut, winmm->volume); + + if (mmResult != MMSYSERR_NOERROR) + { + WLog_Print(winmm->log, WLOG_ERROR, "waveOutSetVolume failed: %" PRIu32 "", mmResult); + return FALSE; + } + + return TRUE; +} + +static void rdpsnd_winmm_close(rdpsndDevicePlugin* device) +{ + MMRESULT mmResult; + rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device; + + if (winmm->hWaveOut) + { + EnterCriticalSection(&winmm->cs); + + mmResult = waveOutReset(winmm->hWaveOut); + if (mmResult != MMSYSERR_NOERROR) + WLog_Print(winmm->log, WLOG_ERROR, "waveOutReset failure: %" PRIu32 "", mmResult); + + mmResult = waveOutClose(winmm->hWaveOut); + if (mmResult != MMSYSERR_NOERROR) + WLog_Print(winmm->log, WLOG_ERROR, "waveOutClose failure: %" PRIu32 "", mmResult); + + LeaveCriticalSection(&winmm->cs); + + winmm->hWaveOut = nullptr; + } + + if (winmm->hThread) + { + (void)WaitForSingleObject(winmm->hThread, INFINITE); + (void)CloseHandle(winmm->hThread); + winmm->hThread = nullptr; + } +} + +static void rdpsnd_winmm_free(rdpsndDevicePlugin* device) +{ + rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device; + + if (winmm) + { + rdpsnd_winmm_close(device); + DeleteCriticalSection(&winmm->cs); + free(winmm); + } +} + +static BOOL rdpsnd_winmm_format_supported(rdpsndDevicePlugin* device, const AUDIO_FORMAT* format) +{ + MMRESULT result; + WAVEFORMATEX out; + + WINPR_UNUSED(device); + if (rdpsnd_winmm_convert_format(format, &out)) + { + result = waveOutOpen(nullptr, WAVE_MAPPER, &out, 0, 0, WAVE_FORMAT_QUERY); + + if (result == MMSYSERR_NOERROR) + return TRUE; + } + + return FALSE; +} + +static UINT32 rdpsnd_winmm_get_volume(rdpsndDevicePlugin* device) +{ + MMRESULT mmResult; + DWORD dwVolume = UINT32_MAX; + rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device; + + if (!winmm->hWaveOut) + return dwVolume; + + mmResult = waveOutGetVolume(winmm->hWaveOut, &dwVolume); + if (mmResult != MMSYSERR_NOERROR) + { + WLog_Print(winmm->log, WLOG_ERROR, "waveOutGetVolume failure: %" PRIu32 "", mmResult); + dwVolume = UINT32_MAX; + } + return dwVolume; +} + +static BOOL rdpsnd_winmm_set_volume(rdpsndDevicePlugin* device, UINT32 value) +{ + MMRESULT mmResult; + rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device; + winmm->volume = value; + + if (!winmm->hWaveOut) + return TRUE; + + mmResult = waveOutSetVolume(winmm->hWaveOut, value); + if (mmResult != MMSYSERR_NOERROR) + { + WLog_Print(winmm->log, WLOG_ERROR, "waveOutGetVolume failure: %" PRIu32 "", mmResult); + return FALSE; + } + return TRUE; +} + +static UINT rdpsnd_winmm_play(rdpsndDevicePlugin* device, const BYTE* data, size_t size) +{ + MMRESULT mmResult; + LPWAVEHDR lpWaveHdr; + rdpsndWinmmPlugin* winmm = (rdpsndWinmmPlugin*)device; + + if (!winmm->hWaveOut) + return 0; + + if (size > UINT32_MAX) + return 0; + + lpWaveHdr = (LPWAVEHDR)calloc(1, sizeof(WAVEHDR)); + if (!lpWaveHdr) + return 0; + + lpWaveHdr->dwFlags = 0; + lpWaveHdr->dwLoops = 0; + lpWaveHdr->lpData = malloc(size); + if (!lpWaveHdr->lpData) + goto fail; + memcpy(lpWaveHdr->lpData, data, size); + lpWaveHdr->dwBufferLength = (DWORD)size; + + EnterCriticalSection(&winmm->cs); + + mmResult = waveOutPrepareHeader(winmm->hWaveOut, lpWaveHdr, sizeof(WAVEHDR)); + if (mmResult != MMSYSERR_NOERROR) + { + WLog_Print(winmm->log, WLOG_ERROR, "waveOutPrepareHeader failure: %" PRIu32 "", mmResult); + goto failCS; + } + + mmResult = waveOutWrite(winmm->hWaveOut, lpWaveHdr, sizeof(WAVEHDR)); + if (mmResult != MMSYSERR_NOERROR) + { + WLog_Print(winmm->log, WLOG_ERROR, "waveOutWrite failure: %" PRIu32 "", mmResult); + waveOutUnprepareHeader(winmm->hWaveOut, lpWaveHdr, sizeof(WAVEHDR)); + goto failCS; + } + + LeaveCriticalSection(&winmm->cs); + return winmm->latency; +failCS: + LeaveCriticalSection(&winmm->cs); +fail: + if (lpWaveHdr) + free(lpWaveHdr->lpData); + free(lpWaveHdr); + return 0; +} + +static void rdpsnd_winmm_parse_addin_args(rdpsndDevicePlugin* device, const ADDIN_ARGV* args) +{ + WINPR_UNUSED(device); + WINPR_UNUSED(args); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE winmm_freerdp_rdpsnd_client_subsystem_entry( + PFREERDP_RDPSND_DEVICE_ENTRY_POINTS pEntryPoints)) +{ + const ADDIN_ARGV* args; + rdpsndWinmmPlugin* winmm; + + if (waveOutGetNumDevs() == 0) + { + WLog_Print(WLog_Get(TAG), WLOG_ERROR, "No sound playback device available!"); + return ERROR_DEVICE_NOT_AVAILABLE; + } + + winmm = (rdpsndWinmmPlugin*)calloc(1, sizeof(rdpsndWinmmPlugin)); + if (!winmm) + return CHANNEL_RC_NO_MEMORY; + + winmm->device.Open = rdpsnd_winmm_open; + winmm->device.FormatSupported = rdpsnd_winmm_format_supported; + winmm->device.GetVolume = rdpsnd_winmm_get_volume; + winmm->device.SetVolume = rdpsnd_winmm_set_volume; + winmm->device.Play = rdpsnd_winmm_play; + winmm->device.Close = rdpsnd_winmm_close; + winmm->device.Free = rdpsnd_winmm_free; + winmm->log = WLog_Get(TAG); + InitializeCriticalSection(&winmm->cs); + + args = pEntryPoints->args; + rdpsnd_winmm_parse_addin_args(&winmm->device, args); + winmm->volume = 0xFFFFFFFF; + pEntryPoints->pRegisterRdpsndDevice(pEntryPoints->rdpsnd, (rdpsndDevicePlugin*)winmm); + return CHANNEL_RC_OK; +} diff --git a/third_party/FreeRDP/channels/rdpsnd/common/CMakeLists.txt b/third_party/FreeRDP/channels/rdpsnd/common/CMakeLists.txt new file mode 100644 index 0000000..8dbe29c --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/common/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2018 Armin Novak +# Copyright 2018 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 rdpsnd_common.h rdpsnd_common.c) + +# Library currently header only +add_library(rdpsnd-common STATIC ${SRCS}) +set_property(TARGET rdpsnd-common PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Common") + +freerdp_client_pc_add_library_private(rdpsnd-common) +channel_install(rdpsnd-common ${FREERDP_ADDIN_PATH} "FreeRDPTargets") diff --git a/third_party/FreeRDP/channels/rdpsnd/common/rdpsnd_common.c b/third_party/FreeRDP/channels/rdpsnd/common/rdpsnd_common.c new file mode 100644 index 0000000..a420beb --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/common/rdpsnd_common.c @@ -0,0 +1,21 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Server Audio Virtual Channel + * + * Copyright 2018 Armin Novak + * Copyright 2018 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 "rdpsnd_common.h" diff --git a/third_party/FreeRDP/channels/rdpsnd/common/rdpsnd_common.h b/third_party/FreeRDP/channels/rdpsnd/common/rdpsnd_common.h new file mode 100644 index 0000000..6afcbc7 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/common/rdpsnd_common.h @@ -0,0 +1,43 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Server Audio Virtual Channel + * + * Copyright 2018 Armin Novak + * Copyright 2018 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. + */ + +#ifndef FREERDP_CHANNEL_RDPSND_COMMON_MAIN_H +#define FREERDP_CHANNEL_RDPSND_COMMON_MAIN_H + +#include +#include +#include + +#include +#include +#include +#include + +typedef enum +{ + CHANNEL_VERSION_WIN_XP = 0x02, + CHANNEL_VERSION_WIN_XP_SP1 = 0x05, + CHANNEL_VERSION_WIN_VISTA = 0x05, + CHANNEL_VERSION_WIN_7 = 0x06, + CHANNEL_VERSION_WIN_8 = 0x08, + CHANNEL_VERSION_WIN_MAX = CHANNEL_VERSION_WIN_8 +} RdpSndChannelVersion; + +#endif /* FREERDP_CHANNEL_RDPSND_COMMON_MAIN_H */ diff --git a/third_party/FreeRDP/channels/rdpsnd/server/CMakeLists.txt b/third_party/FreeRDP/channels/rdpsnd/server/CMakeLists.txt new file mode 100644 index 0000000..0282c01 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/server/CMakeLists.txt @@ -0,0 +1,24 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_server("rdpsnd") + +set(${MODULE_PREFIX}_SRCS rdpsnd_main.c rdpsnd_main.h) + +set(${MODULE_PREFIX}_LIBS rdpsnd-common) + +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntry") diff --git a/third_party/FreeRDP/channels/rdpsnd/server/rdpsnd_main.c b/third_party/FreeRDP/channels/rdpsnd/server/rdpsnd_main.c new file mode 100644 index 0000000..79a48cb --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/server/rdpsnd_main.c @@ -0,0 +1,1245 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Server Audio Virtual Channel + * + * Copyright 2012 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "rdpsnd_common.h" +#include "rdpsnd_main.h" + +static wStream* rdpsnd_server_get_buffer(RdpsndServerContext* context) +{ + wStream* s = nullptr; + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + s = context->priv->rdpsnd_pdu; + Stream_ResetPosition(s); + return s; +} + +/** + * Send Server Audio Formats and Version PDU (2.2.2.1) + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_send_formats(RdpsndServerContext* context) +{ + wStream* s = rdpsnd_server_get_buffer(context); + BOOL status = FALSE; + ULONG written = 0; + + if (!Stream_EnsureRemainingCapacity(s, 24)) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT8(s, SNDC_FORMATS); + Stream_Write_UINT8(s, 0); + Stream_Seek_UINT16(s); + Stream_Write_UINT32(s, 0); /* dwFlags */ + Stream_Write_UINT32(s, 0); /* dwVolume */ + Stream_Write_UINT32(s, 0); /* dwPitch */ + Stream_Write_UINT16(s, 0); /* wDGramPort */ + Stream_Write_UINT16( + s, WINPR_ASSERTING_INT_CAST(uint16_t, context->num_server_formats)); /* wNumberOfFormats */ + Stream_Write_UINT8(s, context->block_no); /* cLastBlockConfirmed */ + Stream_Write_UINT16(s, CHANNEL_VERSION_WIN_MAX); /* wVersion */ + Stream_Write_UINT8(s, 0); /* bPad */ + + for (size_t i = 0; i < context->num_server_formats; i++) + { + const AUDIO_FORMAT* format = &context->server_formats[i]; + + if (!audio_format_write(s, format)) + goto fail; + } + + { + const size_t pos = Stream_GetPosition(s); + if (pos > UINT16_MAX) + goto fail; + + WINPR_ASSERT(pos >= 4); + Stream_SetPosition(s, 2); + Stream_Write_UINT16(s, (UINT16)(pos - 4)); + Stream_SetPosition(s, pos); + + WINPR_ASSERT(context->priv); + + status = WTSVirtualChannelWrite(context->priv->ChannelHandle, Stream_BufferAs(s, char), + (UINT32)pos, &written); + Stream_ResetPosition(s); + } +fail: + return status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +/** + * Read Wave Confirm PDU (2.2.3.8) and handle callback + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_recv_waveconfirm(RdpsndServerContext* context, wStream* s) +{ + UINT16 timestamp = 0; + BYTE confirmBlockNum = 0; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, timestamp); + Stream_Read_UINT8(s, confirmBlockNum); + Stream_Seek_UINT8(s); + IFCALLRET(context->ConfirmBlock, error, context, confirmBlockNum, timestamp); + + if (error) + WLog_ERR(TAG, "context->ConfirmBlock failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Read Training Confirm PDU (2.2.3.2) and handle callback + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_recv_trainingconfirm(RdpsndServerContext* context, wStream* s) +{ + UINT16 timestamp = 0; + UINT16 packsize = 0; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT16(s, timestamp); + Stream_Read_UINT16(s, packsize); + + IFCALLRET(context->TrainingConfirm, error, context, timestamp, packsize); + if (error) + WLog_ERR(TAG, "context->TrainingConfirm failed with error %" PRIu32 "", error); + + return error; +} + +/** + * Read Quality Mode PDU (2.2.2.3) + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_recv_quality_mode(RdpsndServerContext* context, wStream* s) +{ + WINPR_ASSERT(context); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + { + WLog_ERR(TAG, "not enough data in stream!"); + return ERROR_INVALID_DATA; + } + + Stream_Read_UINT16(s, context->qualityMode); /* wQualityMode */ + Stream_Seek_UINT16(s); /* Reserved */ + + WLog_DBG(TAG, "Client requested sound quality: 0x%04" PRIX16 "", context->qualityMode); + + return CHANNEL_RC_OK; +} + +/** + * Read Client Audio Formats and Version PDU (2.2.2.2) + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_recv_formats(RdpsndServerContext* context, wStream* s) +{ + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 20)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, context->capsFlags); /* dwFlags */ + Stream_Read_UINT32(s, context->initialVolume); /* dwVolume */ + Stream_Read_UINT32(s, context->initialPitch); /* dwPitch */ + Stream_Read_UINT16(s, context->udpPort); /* wDGramPort */ + Stream_Read_UINT16(s, context->num_client_formats); /* wNumberOfFormats */ + Stream_Read_UINT8(s, context->lastblock); /* cLastBlockConfirmed */ + Stream_Read_UINT16(s, context->clientVersion); /* wVersion */ + Stream_Seek_UINT8(s); /* bPad */ + + /* this check is only a guess as cbSize can influence the size of a format record */ + if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, context->num_client_formats, 18ull)) + return ERROR_INVALID_DATA; + + if (!context->num_client_formats) + { + WLog_ERR(TAG, "client doesn't support any format!"); + return ERROR_INTERNAL_ERROR; + } + + context->client_formats = audio_formats_new(context->num_client_formats); + + if (!context->client_formats) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + for (UINT16 i = 0; i < context->num_client_formats; i++) + { + AUDIO_FORMAT* format = &context->client_formats[i]; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 18)) + { + WLog_ERR(TAG, "not enough data in stream!"); + error = ERROR_INVALID_DATA; + goto out_free; + } + + Stream_Read_UINT16(s, format->wFormatTag); + Stream_Read_UINT16(s, format->nChannels); + Stream_Read_UINT32(s, format->nSamplesPerSec); + Stream_Read_UINT32(s, format->nAvgBytesPerSec); + Stream_Read_UINT16(s, format->nBlockAlign); + Stream_Read_UINT16(s, format->wBitsPerSample); + Stream_Read_UINT16(s, format->cbSize); + + if (format->cbSize > 0) + { + if (!Stream_SafeSeek(s, format->cbSize)) + { + WLog_ERR(TAG, "Stream_SafeSeek failed!"); + error = ERROR_INTERNAL_ERROR; + goto out_free; + } + } + } + + if (!context->num_client_formats) + { + WLog_ERR(TAG, "client doesn't support any known format!"); + goto out_free; + } + + return CHANNEL_RC_OK; +out_free: + free(context->client_formats); + return error; +} + +static DWORD WINAPI rdpsnd_server_thread(LPVOID arg) +{ + DWORD nCount = 0; + DWORD status = 0; + HANDLE events[2] = WINPR_C_ARRAY_INIT; + RdpsndServerContext* context = (RdpsndServerContext*)arg; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + events[nCount++] = context->priv->channelEvent; + events[nCount++] = context->priv->StopEvent; + + WINPR_ASSERT(nCount <= ARRAYSIZE(events)); + + while (TRUE) + { + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "!", error); + break; + } + + status = WaitForSingleObject(context->priv->StopEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error); + break; + } + + if (status == WAIT_OBJECT_0) + break; + + if ((error = rdpsnd_server_handle_messages(context))) + { + WLog_ERR(TAG, "rdpsnd_server_handle_messages failed with error %" PRIu32 "", error); + break; + } + } + + if (error && context->rdpcontext) + setChannelError(context->rdpcontext, error, "rdpsnd_server_thread reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_initialize(RdpsndServerContext* context, BOOL ownThread) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + context->priv->ownThread = ownThread; + return context->Start(context); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_select_format(RdpsndServerContext* context, UINT16 client_format_index) +{ + size_t bs = 0; + size_t out_buffer_size = 0; + AUDIO_FORMAT* format = nullptr; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if ((client_format_index >= context->num_client_formats) || (!context->src_format)) + { + WLog_ERR(TAG, "index %" PRIu16 " is not correct.", client_format_index); + return ERROR_INVALID_DATA; + } + + EnterCriticalSection(&context->priv->lock); + context->priv->src_bytes_per_sample = context->src_format->wBitsPerSample / 8; + context->priv->src_bytes_per_frame = + context->priv->src_bytes_per_sample * context->src_format->nChannels; + context->selected_client_format = client_format_index; + format = &context->client_formats[client_format_index]; + + if (format->nSamplesPerSec == 0) + { + WLog_ERR(TAG, "invalid Client Sound Format!!"); + error = ERROR_INVALID_DATA; + goto out; + } + + if (context->latency <= 0) + context->latency = 50; + + context->priv->out_frames = context->src_format->nSamplesPerSec * context->latency / 1000; + + if (context->priv->out_frames < 1) + context->priv->out_frames = 1; + + switch (format->wFormatTag) + { + case WAVE_FORMAT_DVI_ADPCM: + bs = 4ULL * (format->nBlockAlign - 4ULL * format->nChannels); + context->priv->out_frames -= context->priv->out_frames % bs; + + if (context->priv->out_frames < bs) + context->priv->out_frames = bs; + + break; + + case WAVE_FORMAT_ADPCM: + bs = (format->nBlockAlign - 7 * format->nChannels) * 2 / format->nChannels + 2; + context->priv->out_frames -= context->priv->out_frames % bs; + + if (context->priv->out_frames < bs) + context->priv->out_frames = bs; + + break; + default: + break; + } + + context->priv->out_pending_frames = 0; + out_buffer_size = context->priv->out_frames * context->priv->src_bytes_per_frame; + + if (context->priv->out_buffer_size < out_buffer_size) + { + BYTE* newBuffer = nullptr; + newBuffer = (BYTE*)realloc(context->priv->out_buffer, out_buffer_size); + + if (!newBuffer) + { + WLog_ERR(TAG, "realloc failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + context->priv->out_buffer = newBuffer; + context->priv->out_buffer_size = out_buffer_size; + } + + if (!freerdp_dsp_context_reset(context->priv->dsp_context, format, 0u)) + error = ERROR_INTERNAL_ERROR; +out: + LeaveCriticalSection(&context->priv->lock); + return error; +} + +/** + * Send Training PDU (2.2.3.1) + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_training(RdpsndServerContext* context, UINT16 timestamp, UINT16 packsize, + BYTE* data) +{ + ULONG written = 0; + BOOL status = 0; + wStream* s = rdpsnd_server_get_buffer(context); + + if (!Stream_EnsureRemainingCapacity(s, 8)) + return ERROR_INTERNAL_ERROR; + + Stream_Write_UINT8(s, SNDC_TRAINING); + Stream_Write_UINT8(s, 0); + Stream_Seek_UINT16(s); + Stream_Write_UINT16(s, timestamp); + Stream_Write_UINT16(s, packsize); + + if (packsize > 0) + { + if (!Stream_EnsureRemainingCapacity(s, packsize)) + { + Stream_ResetPosition(s); + return ERROR_INTERNAL_ERROR; + } + + Stream_Write(s, data, packsize); + } + + const size_t end = Stream_GetPosition(s); + if ((end < 4) || (end > UINT16_MAX)) + return ERROR_INTERNAL_ERROR; + + Stream_SetPosition(s, 2); + Stream_Write_UINT16(s, (UINT16)(end - 4)); + + status = WTSVirtualChannelWrite(context->priv->ChannelHandle, Stream_BufferAs(s, char), + (UINT32)end, &written); + + Stream_ResetPosition(s); + + return status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +static BOOL rdpsnd_server_align_wave_pdu(wStream* s, UINT32 alignment) +{ + size_t size = 0; + Stream_SealLength(s); + size = Stream_Length(s); + + if ((size % alignment) != 0) + { + size_t offset = alignment - size % alignment; + + if (!Stream_EnsureRemainingCapacity(s, offset)) + return FALSE; + + Stream_Zero(s, offset); + } + + Stream_SealLength(s); + return TRUE; +} + +/** + * Function description + * context->priv->lock should be obtained before calling this function + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_send_wave_pdu(RdpsndServerContext* context, UINT16 wTimestamp) +{ + AUDIO_FORMAT* format = nullptr; + ULONG written = 0; + UINT error = CHANNEL_RC_OK; + wStream* s = rdpsnd_server_get_buffer(context); + + if (context->selected_client_format > context->num_client_formats) + return ERROR_INTERNAL_ERROR; + + WINPR_ASSERT(context->client_formats); + + format = &context->client_formats[context->selected_client_format]; + /* WaveInfo PDU */ + Stream_ResetPosition(s); + + if (!Stream_EnsureRemainingCapacity(s, 16)) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT8(s, SNDC_WAVE); /* msgType */ + Stream_Write_UINT8(s, 0); /* bPad */ + Stream_Write_UINT16(s, 0); /* BodySize */ + Stream_Write_UINT16(s, wTimestamp); /* wTimeStamp */ + Stream_Write_UINT16(s, context->selected_client_format); /* wFormatNo */ + Stream_Write_UINT8(s, context->block_no); /* cBlockNo */ + Stream_Seek(s, 3); /* bPad */ + const size_t start = Stream_GetPosition(s); + const BYTE* src = context->priv->out_buffer; + const size_t length = + 1ull * context->priv->out_pending_frames * context->priv->src_bytes_per_frame; + + if (!freerdp_dsp_encode(context->priv->dsp_context, context->src_format, src, length, s)) + return ERROR_INTERNAL_ERROR; + + /* Set stream size */ + if (!rdpsnd_server_align_wave_pdu(s, format->nBlockAlign)) + return ERROR_INTERNAL_ERROR; + + const size_t end = Stream_GetPosition(s); + const size_t pos = end - start + 8ULL; + if (pos > UINT16_MAX) + return ERROR_INTERNAL_ERROR; + Stream_SetPosition(s, 2); + Stream_Write_UINT16(s, (UINT16)pos); + Stream_SetPosition(s, end); + + if (!WTSVirtualChannelWrite(context->priv->ChannelHandle, Stream_BufferAs(s, char), + (UINT32)(start + 4), &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + error = ERROR_INTERNAL_ERROR; + } + + if (error != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + error = ERROR_INTERNAL_ERROR; + goto out; + } + + Stream_SetPosition(s, start); + Stream_Write_UINT32(s, 0); /* bPad */ + Stream_SetPosition(s, start); + + WINPR_ASSERT((end - start) <= UINT32_MAX); + if (!WTSVirtualChannelWrite(context->priv->ChannelHandle, Stream_Pointer(s), + (UINT32)(end - start), &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + error = ERROR_INTERNAL_ERROR; + } + + context->block_no = (context->block_no + 1) % 256; + +out: + Stream_ResetPosition(s); + context->priv->out_pending_frames = 0; + return error; +} + +/** + * Function description + * context->priv->lock should be obtained before calling this function + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_send_wave2_pdu(RdpsndServerContext* context, UINT16 formatNo, + const BYTE* data, size_t size, BOOL encoded, + UINT16 timestamp, UINT32 audioTimeStamp) +{ + ULONG written = 0; + UINT error = CHANNEL_RC_OK; + BOOL status = 0; + wStream* s = rdpsnd_server_get_buffer(context); + + if (!Stream_EnsureRemainingCapacity(s, 16)) + { + error = ERROR_INTERNAL_ERROR; + goto out; + } + + /* Wave2 PDU */ + Stream_Write_UINT8(s, SNDC_WAVE2); /* msgType */ + Stream_Write_UINT8(s, 0); /* bPad */ + Stream_Write_UINT16(s, 0); /* BodySize */ + Stream_Write_UINT16(s, timestamp); /* wTimeStamp */ + Stream_Write_UINT16(s, formatNo); /* wFormatNo */ + Stream_Write_UINT8(s, context->block_no); /* cBlockNo */ + Stream_Write_UINT8(s, 0); /* bPad */ + Stream_Write_UINT8(s, 0); /* bPad */ + Stream_Write_UINT8(s, 0); /* bPad */ + Stream_Write_UINT32(s, audioTimeStamp); /* dwAudioTimeStamp */ + + if (encoded) + { + if (!Stream_EnsureRemainingCapacity(s, size)) + { + error = ERROR_INTERNAL_ERROR; + goto out; + } + + Stream_Write(s, data, size); + } + else + { + AUDIO_FORMAT* format = nullptr; + + if (!freerdp_dsp_encode(context->priv->dsp_context, context->src_format, data, size, s)) + { + error = ERROR_INTERNAL_ERROR; + goto out; + } + + format = &context->client_formats[formatNo]; + if (!rdpsnd_server_align_wave_pdu(s, format->nBlockAlign)) + { + error = ERROR_INTERNAL_ERROR; + goto out; + } + } + + { + const size_t end = Stream_GetPosition(s); + if (end > UINT16_MAX + 4) + { + error = ERROR_INTERNAL_ERROR; + goto out; + } + + Stream_SetPosition(s, 2); + Stream_Write_UINT16(s, (UINT16)(end - 4)); + + status = WTSVirtualChannelWrite(context->priv->ChannelHandle, Stream_BufferAs(s, char), + (UINT32)end, &written); + + if (!status || (end != written)) + { + WLog_ERR(TAG, + "WTSVirtualChannelWrite failed! [stream length=%" PRIuz " - written=%" PRIu32, + end, written); + error = ERROR_INTERNAL_ERROR; + } + } + + context->block_no = (context->block_no + 1) % 256; + +out: + Stream_ResetPosition(s); + context->priv->out_pending_frames = 0; + return error; +} + +/* Wrapper function to send WAVE or WAVE2 PDU depending on client connected */ +static UINT rdpsnd_server_send_audio_pdu(RdpsndServerContext* context, UINT16 wTimestamp) +{ + const BYTE* src = nullptr; + size_t length = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (context->selected_client_format >= context->num_client_formats) + return ERROR_INTERNAL_ERROR; + + src = context->priv->out_buffer; + length = context->priv->out_pending_frames * context->priv->src_bytes_per_frame; + + if (context->clientVersion >= CHANNEL_VERSION_WIN_8) + return rdpsnd_server_send_wave2_pdu(context, context->selected_client_format, src, length, + FALSE, wTimestamp, wTimestamp); + else + return rdpsnd_server_send_wave_pdu(context, wTimestamp); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_send_samples(RdpsndServerContext* context, const void* buf, + size_t nframes, UINT16 wTimestamp) +{ + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + EnterCriticalSection(&context->priv->lock); + + if (context->selected_client_format >= context->num_client_formats) + { + /* It's possible while format negotiation has not been done */ + WLog_WARN(TAG, "Drop samples because client format has not been negotiated."); + error = ERROR_NOT_READY; + goto out; + } + + while (nframes > 0) + { + const size_t cframes = + MIN(nframes, context->priv->out_frames - context->priv->out_pending_frames); + size_t cframesize = cframes * context->priv->src_bytes_per_frame; + CopyMemory(context->priv->out_buffer + + (context->priv->out_pending_frames * context->priv->src_bytes_per_frame), + buf, cframesize); + buf = (const BYTE*)buf + cframesize; + nframes -= cframes; + context->priv->out_pending_frames += cframes; + + if (context->priv->out_pending_frames >= context->priv->out_frames) + { + if ((error = rdpsnd_server_send_audio_pdu(context, wTimestamp))) + { + WLog_ERR(TAG, "rdpsnd_server_send_audio_pdu failed with error %" PRIu32 "", error); + break; + } + } + } + +out: + LeaveCriticalSection(&context->priv->lock); + return error; +} + +/** + * Send encoded audio samples using a Wave2 PDU. + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_send_samples2(RdpsndServerContext* context, UINT16 formatNo, + const void* buf, size_t size, UINT16 timestamp, + UINT32 audioTimeStamp) +{ + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (context->clientVersion < CHANNEL_VERSION_WIN_8) + return ERROR_INTERNAL_ERROR; + + EnterCriticalSection(&context->priv->lock); + + error = + rdpsnd_server_send_wave2_pdu(context, formatNo, buf, size, TRUE, timestamp, audioTimeStamp); + + LeaveCriticalSection(&context->priv->lock); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_set_volume(RdpsndServerContext* context, UINT16 left, UINT16 right) +{ + BOOL status = 0; + ULONG written = 0; + wStream* s = rdpsnd_server_get_buffer(context); + + if (!Stream_EnsureRemainingCapacity(s, 8)) + return ERROR_NOT_ENOUGH_MEMORY; + + Stream_Write_UINT8(s, SNDC_SETVOLUME); + Stream_Write_UINT8(s, 0); + Stream_Write_UINT16(s, 4); /* Payload length */ + Stream_Write_UINT16(s, left); + Stream_Write_UINT16(s, right); + + const size_t len = Stream_GetPosition(s); + WINPR_ASSERT(len <= UINT32_MAX); + status = WTSVirtualChannelWrite(context->priv->ChannelHandle, Stream_BufferAs(s, char), + (ULONG)len, &written); + Stream_ResetPosition(s); + return status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_close(RdpsndServerContext* context) +{ + BOOL status = 0; + ULONG written = 0; + UINT error = CHANNEL_RC_OK; + wStream* s = rdpsnd_server_get_buffer(context); + + EnterCriticalSection(&context->priv->lock); + + if (context->priv->out_pending_frames > 0) + { + if (context->selected_client_format >= context->num_client_formats) + { + WLog_ERR(TAG, "Pending audio frame exists while no format selected."); + error = ERROR_INVALID_DATA; + } + else if ((error = rdpsnd_server_send_audio_pdu(context, 0))) + { + WLog_ERR(TAG, "rdpsnd_server_send_audio_pdu failed with error %" PRIu32 "", error); + } + } + + LeaveCriticalSection(&context->priv->lock); + + if (error) + return error; + + context->selected_client_format = 0xFFFF; + + if (!Stream_EnsureRemainingCapacity(s, 4)) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT8(s, SNDC_CLOSE); + Stream_Write_UINT8(s, 0); + Stream_Seek_UINT16(s); + const size_t pos = Stream_GetPosition(s); + WINPR_ASSERT(pos >= 4); + Stream_SetPosition(s, 2); + Stream_Write_UINT16(s, WINPR_ASSERTING_INT_CAST(uint16_t, pos - 4)); + Stream_SetPosition(s, pos); + + const size_t len = Stream_GetPosition(s); + WINPR_ASSERT(len <= UINT32_MAX); + status = WTSVirtualChannelWrite(context->priv->ChannelHandle, Stream_BufferAs(s, char), + (UINT32)len, &written); + Stream_ResetPosition(s); + return status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_start(RdpsndServerContext* context) +{ + void* buffer = nullptr; + DWORD bytesReturned = 0; + RdpsndServerPrivate* priv = nullptr; + UINT error = ERROR_INTERNAL_ERROR; + PULONG pSessionId = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + priv = context->priv; + priv->SessionId = WTS_CURRENT_SESSION; + + if (context->use_dynamic_virtual_channel) + { + UINT32 channelId = 0; + BOOL status = TRUE; + + if (WTSQuerySessionInformationA(context->vcm, WTS_CURRENT_SESSION, WTSSessionId, + (LPSTR*)&pSessionId, &bytesReturned)) + { + priv->SessionId = (DWORD)*pSessionId; + WTSFreeMemory(pSessionId); + priv->ChannelHandle = WTSVirtualChannelOpenEx(priv->SessionId, RDPSND_DVC_CHANNEL_NAME, + WTS_CHANNEL_OPTION_DYNAMIC); + if (!priv->ChannelHandle) + { + WLog_ERR(TAG, "Open audio dynamic virtual channel (%s) failed!", + RDPSND_DVC_CHANNEL_NAME); + return ERROR_INTERNAL_ERROR; + } + + channelId = WTSChannelGetIdByHandle(priv->ChannelHandle); + + IFCALLRET(context->ChannelIdAssigned, status, context, channelId); + if (!status) + { + WLog_ERR(TAG, "context->ChannelIdAssigned failed!"); + goto out_close; + } + } + else + { + WLog_ERR(TAG, "WTSQuerySessionInformationA failed!"); + return ERROR_INTERNAL_ERROR; + } + } + else + { + priv->ChannelHandle = + WTSVirtualChannelOpen(context->vcm, WTS_CURRENT_SESSION, RDPSND_CHANNEL_NAME); + if (!priv->ChannelHandle) + { + WLog_ERR(TAG, "Open audio static virtual channel (rdpsnd) failed!"); + return ERROR_INTERNAL_ERROR; + } + } + + if (!WTSVirtualChannelQuery(priv->ChannelHandle, WTSVirtualEventHandle, &buffer, + &bytesReturned) || + (bytesReturned != sizeof(HANDLE))) + { + WLog_ERR(TAG, + "error during WTSVirtualChannelQuery(WTSVirtualEventHandle) or invalid returned " + "size(%" PRIu32 ")", + bytesReturned); + + if (buffer) + WTSFreeMemory(buffer); + + goto out_close; + } + + priv->channelEvent = *(HANDLE*)buffer; + WTSFreeMemory(buffer); + priv->rdpsnd_pdu = Stream_New(nullptr, 4096); + + if (!priv->rdpsnd_pdu) + { + WLog_ERR(TAG, "Stream_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out_close; + } + + if (!InitializeCriticalSectionEx(&context->priv->lock, 0, 0)) + { + WLog_ERR(TAG, "InitializeCriticalSectionEx failed!"); + goto out_pdu; + } + + if ((error = rdpsnd_server_send_formats(context))) + { + WLog_ERR(TAG, "rdpsnd_server_send_formats failed with error %" PRIu32 "", error); + goto out_lock; + } + + if (priv->ownThread) + { + context->priv->StopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + + if (!context->priv->StopEvent) + { + WLog_ERR(TAG, "CreateEvent failed!"); + goto out_lock; + } + + context->priv->Thread = + CreateThread(nullptr, 0, rdpsnd_server_thread, (void*)context, 0, nullptr); + + if (!context->priv->Thread) + { + WLog_ERR(TAG, "CreateThread failed!"); + goto out_stopEvent; + } + } + + return CHANNEL_RC_OK; +out_stopEvent: + (void)CloseHandle(context->priv->StopEvent); + context->priv->StopEvent = nullptr; +out_lock: + DeleteCriticalSection(&context->priv->lock); +out_pdu: + Stream_Free(context->priv->rdpsnd_pdu, TRUE); + context->priv->rdpsnd_pdu = nullptr; +out_close: + (void)WTSVirtualChannelClose(context->priv->ChannelHandle); + context->priv->ChannelHandle = nullptr; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpsnd_server_stop(RdpsndServerContext* context) +{ + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + if (!context->priv->StopEvent) + return error; + + if (context->priv->ownThread) + { + if (context->priv->StopEvent) + { + (void)SetEvent(context->priv->StopEvent); + + if (WaitForSingleObject(context->priv->Thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error); + return error; + } + + (void)CloseHandle(context->priv->Thread); + (void)CloseHandle(context->priv->StopEvent); + context->priv->Thread = nullptr; + context->priv->StopEvent = nullptr; + } + } + + DeleteCriticalSection(&context->priv->lock); + + if (context->priv->rdpsnd_pdu) + { + Stream_Free(context->priv->rdpsnd_pdu, TRUE); + context->priv->rdpsnd_pdu = nullptr; + } + + if (context->priv->ChannelHandle) + { + (void)WTSVirtualChannelClose(context->priv->ChannelHandle); + context->priv->ChannelHandle = nullptr; + } + + return error; +} + +RdpsndServerContext* rdpsnd_server_context_new(HANDLE vcm) +{ + RdpsndServerPrivate* priv = nullptr; + RdpsndServerContext* context = (RdpsndServerContext*)calloc(1, sizeof(RdpsndServerContext)); + + if (!context) + goto fail; + + context->vcm = vcm; + context->Start = rdpsnd_server_start; + context->Stop = rdpsnd_server_stop; + context->selected_client_format = 0xFFFF; + context->Initialize = rdpsnd_server_initialize; + context->SendFormats = rdpsnd_server_send_formats; + context->SelectFormat = rdpsnd_server_select_format; + context->Training = rdpsnd_server_training; + context->SendSamples = rdpsnd_server_send_samples; + context->SendSamples2 = rdpsnd_server_send_samples2; + context->SetVolume = rdpsnd_server_set_volume; + context->Close = rdpsnd_server_close; + context->priv = priv = (RdpsndServerPrivate*)calloc(1, sizeof(RdpsndServerPrivate)); + + if (!priv) + { + WLog_ERR(TAG, "calloc failed!"); + goto fail; + } + + priv->dsp_context = freerdp_dsp_context_new(TRUE); + + if (!priv->dsp_context) + { + WLog_ERR(TAG, "freerdp_dsp_context_new failed!"); + goto fail; + } + + priv->input_stream = Stream_New(nullptr, 4); + + if (!priv->input_stream) + { + WLog_ERR(TAG, "Stream_New failed!"); + goto fail; + } + + priv->expectedBytes = 4; + priv->waitingHeader = TRUE; + priv->ownThread = TRUE; + return context; +fail: + WINPR_PRAGMA_DIAG_PUSH + WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC + rdpsnd_server_context_free(context); + WINPR_PRAGMA_DIAG_POP + return nullptr; +} + +void rdpsnd_server_context_reset(RdpsndServerContext* context) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + context->priv->expectedBytes = 4; + context->priv->waitingHeader = TRUE; + Stream_SetPosition(context->priv->input_stream, 0); +} + +void rdpsnd_server_context_free(RdpsndServerContext* context) +{ + if (!context) + return; + + if (context->priv) + { + rdpsnd_server_stop(context); + + free(context->priv->out_buffer); + + if (context->priv->dsp_context) + freerdp_dsp_context_free(context->priv->dsp_context); + + if (context->priv->input_stream) + Stream_Free(context->priv->input_stream, TRUE); + } + + free(context->server_formats); + free(context->client_formats); + free(context->priv); + free(context); +} + +HANDLE rdpsnd_server_get_event_handle(RdpsndServerContext* context) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + return context->priv->channelEvent; +} + +/* + * Handle rpdsnd messages - server side + * + * @param Server side context + * + * @return 0 on success + * ERROR_NO_DATA if no data could be read this time + * otherwise error + */ +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpsnd_server_handle_messages(RdpsndServerContext* context) +{ + DWORD bytesReturned = 0; + UINT ret = CHANNEL_RC_OK; + RdpsndServerPrivate* priv = nullptr; + wStream* s = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + + priv = context->priv; + s = priv->input_stream; + + if (!WTSVirtualChannelRead(priv->ChannelHandle, 0, Stream_Pointer(s), priv->expectedBytes, + &bytesReturned)) + { + if (GetLastError() == ERROR_NO_DATA) + return ERROR_NO_DATA; + + WLog_ERR(TAG, "channel connection closed"); + return ERROR_INTERNAL_ERROR; + } + + priv->expectedBytes -= bytesReturned; + Stream_Seek(s, bytesReturned); + + if (priv->expectedBytes) + return CHANNEL_RC_OK; + + Stream_SealLength(s); + Stream_ResetPosition(s); + + if (priv->waitingHeader) + { + /* header case */ + Stream_Read_UINT8(s, priv->msgType); + Stream_Seek_UINT8(s); /* bPad */ + Stream_Read_UINT16(s, priv->expectedBytes); + priv->waitingHeader = FALSE; + Stream_ResetPosition(s); + + if (priv->expectedBytes) + { + if (!Stream_EnsureCapacity(s, priv->expectedBytes)) + { + WLog_ERR(TAG, "Stream_EnsureCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + return CHANNEL_RC_OK; + } + } + + /* when here we have the header + the body */ +#ifdef WITH_DEBUG_SND + WLog_DBG(TAG, "message type %" PRIu8 "", priv->msgType); +#endif + priv->expectedBytes = 4; + priv->waitingHeader = TRUE; + + switch (priv->msgType) + { + case SNDC_WAVECONFIRM: + ret = rdpsnd_server_recv_waveconfirm(context, s); + break; + + case SNDC_TRAINING: + ret = rdpsnd_server_recv_trainingconfirm(context, s); + break; + + case SNDC_FORMATS: + ret = rdpsnd_server_recv_formats(context, s); + + if ((ret == CHANNEL_RC_OK) && (context->clientVersion < CHANNEL_VERSION_WIN_7)) + IFCALL(context->Activated, context); + + break; + + case SNDC_QUALITYMODE: + ret = rdpsnd_server_recv_quality_mode(context, s); + + if ((ret == CHANNEL_RC_OK) && (context->clientVersion >= CHANNEL_VERSION_WIN_7)) + IFCALL(context->Activated, context); + + break; + + default: + WLog_ERR(TAG, "UNKNOWN MESSAGE TYPE!! (0x%02" PRIX8 ")", priv->msgType); + ret = ERROR_INVALID_DATA; + break; + } + + Stream_ResetPosition(s); + return ret; +} diff --git a/third_party/FreeRDP/channels/rdpsnd/server/rdpsnd_main.h b/third_party/FreeRDP/channels/rdpsnd/server/rdpsnd_main.h new file mode 100644 index 0000000..8623dd4 --- /dev/null +++ b/third_party/FreeRDP/channels/rdpsnd/server/rdpsnd_main.h @@ -0,0 +1,59 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Server Audio Virtual Channel + * + * Copyright 2012 Vic Lee + * Copyright 2013 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPSND_SERVER_MAIN_H +#define FREERDP_CHANNEL_RDPSND_SERVER_MAIN_H + +#include +#include +#include + +#include +#include +#include +#include + +#define TAG CHANNELS_TAG("rdpsnd.server") + +struct s_rdpsnd_server_private +{ + BOOL ownThread; + HANDLE Thread; + HANDLE StopEvent; + HANDLE channelEvent; + void* ChannelHandle; + DWORD SessionId; + + BOOL waitingHeader; + DWORD expectedBytes; + BYTE msgType; + wStream* input_stream; + wStream* rdpsnd_pdu; + BYTE* out_buffer; + size_t out_buffer_size; + size_t out_frames; + size_t out_pending_frames; + UINT32 src_bytes_per_sample; + UINT32 src_bytes_per_frame; + FREERDP_DSP_CONTEXT* dsp_context; + CRITICAL_SECTION lock; /* Protect out_buffer and related parameters */ +}; + +#endif /* FREERDP_CHANNEL_RDPSND_SERVER_MAIN_H */ diff --git a/third_party/FreeRDP/channels/remdesk/CMakeLists.txt b/third_party/FreeRDP/channels/remdesk/CMakeLists.txt new file mode 100644 index 0000000..c952041 --- /dev/null +++ b/third_party/FreeRDP/channels/remdesk/CMakeLists.txt @@ -0,0 +1,29 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel("remdesk") + +include_directories(common) +add_subdirectory(common) + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/third_party/FreeRDP/channels/remdesk/ChannelOptions.cmake b/third_party/FreeRDP/channels/remdesk/ChannelOptions.cmake new file mode 100644 index 0000000..652e2b7 --- /dev/null +++ b/third_party/FreeRDP/channels/remdesk/ChannelOptions.cmake @@ -0,0 +1,20 @@ +set(OPTION_DEFAULT ON) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options( + NAME + "remdesk" + TYPE + "static" + DESCRIPTION + "Remote Assistance Virtual Channel Extension" + SPECIFICATIONS + "[MS-RA]" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/remdesk/client/CMakeLists.txt b/third_party/FreeRDP/channels/remdesk/client/CMakeLists.txt new file mode 100644 index 0000000..005b217 --- /dev/null +++ b/third_party/FreeRDP/channels/remdesk/client/CMakeLists.txt @@ -0,0 +1,24 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_client("remdesk") + +set(${MODULE_PREFIX}_SRCS remdesk_main.c remdesk_main.h) + +set(${MODULE_PREFIX}_LIBS winpr freerdp remdesk-common) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntryEx") diff --git a/third_party/FreeRDP/channels/remdesk/client/remdesk_main.c b/third_party/FreeRDP/channels/remdesk/client/remdesk_main.c new file mode 100644 index 0000000..32d881d --- /dev/null +++ b/third_party/FreeRDP/channels/remdesk/client/remdesk_main.c @@ -0,0 +1,1051 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Remote Assistance Virtual Channel + * + * Copyright 2014 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#include "remdesk_main.h" +#include "remdesk_common.h" + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_virtual_channel_write(remdeskPlugin* remdesk, wStream* s) +{ + UINT32 status = 0; + + if (!remdesk) + { + WLog_ERR(TAG, "remdesk was null!"); + Stream_Free(s, TRUE); + return CHANNEL_RC_INVALID_INSTANCE; + } + + WINPR_ASSERT(remdesk->channelEntryPoints.pVirtualChannelWriteEx); + status = remdesk->channelEntryPoints.pVirtualChannelWriteEx( + remdesk->InitHandle, remdesk->OpenHandle, Stream_Buffer(s), (UINT32)Stream_Length(s), s); + + if (status != CHANNEL_RC_OK) + { + Stream_Free(s, TRUE); + WLog_ERR(TAG, "pVirtualChannelWriteEx failed with %s [%08" PRIX32 "]", + WTSErrorToString(status), status); + } + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_generate_expert_blob(remdeskPlugin* remdesk) +{ + const char* name = nullptr; + char* pass = nullptr; + const char* password = nullptr; + rdpSettings* settings = nullptr; + + WINPR_ASSERT(remdesk); + + WINPR_ASSERT(remdesk->rdpcontext); + settings = remdesk->rdpcontext->settings; + WINPR_ASSERT(settings); + + if (remdesk->ExpertBlob) + return CHANNEL_RC_OK; + + password = freerdp_settings_get_string(settings, FreeRDP_RemoteAssistancePassword); + if (!password) + password = freerdp_settings_get_string(settings, FreeRDP_Password); + + if (!password) + { + WLog_ERR(TAG, "password was not set!"); + return ERROR_INTERNAL_ERROR; + } + + name = freerdp_settings_get_string(settings, FreeRDP_Username); + + if (!name) + name = "Expert"; + + const char* stub = freerdp_settings_get_string(settings, FreeRDP_RemoteAssistancePassStub); + remdesk->EncryptedPassStub = + freerdp_assistance_encrypt_pass_stub(password, stub, &(remdesk->EncryptedPassStubSize)); + + if (!remdesk->EncryptedPassStub) + { + WLog_ERR(TAG, "freerdp_assistance_encrypt_pass_stub failed!"); + return ERROR_INTERNAL_ERROR; + } + + pass = freerdp_assistance_bin_to_hex_string(remdesk->EncryptedPassStub, + remdesk->EncryptedPassStubSize); + + if (!pass) + { + WLog_ERR(TAG, "freerdp_assistance_bin_to_hex_string failed!"); + return ERROR_INTERNAL_ERROR; + } + + remdesk->ExpertBlob = freerdp_assistance_construct_expert_blob(name, pass); + free(pass); + + if (!remdesk->ExpertBlob) + { + WLog_ERR(TAG, "freerdp_assistance_construct_expert_blob failed!"); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_recv_ctl_server_announce_pdu(WINPR_ATTR_UNUSED remdeskPlugin* remdesk, + WINPR_ATTR_UNUSED wStream* s, + WINPR_ATTR_UNUSED REMDESK_CHANNEL_HEADER* header) +{ + WINPR_ASSERT(remdesk); + WINPR_ASSERT(s); + WINPR_ASSERT(header); + + WLog_ERR("TODO", "TODO: implement"); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_recv_ctl_version_info_pdu(remdeskPlugin* remdesk, wStream* s, + WINPR_ATTR_UNUSED REMDESK_CHANNEL_HEADER* header) +{ + WINPR_ASSERT(remdesk); + WINPR_ASSERT(s); + WINPR_ASSERT(header); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + const UINT32 versionMajor = Stream_Get_UINT32(s); /* versionMajor (4 bytes) */ + const UINT32 versionMinor = Stream_Get_UINT32(s); /* versionMinor (4 bytes) */ + + if ((versionMajor != 1) || (versionMinor > 2) || (versionMinor == 0)) + { + WLog_ERR(TAG, "Unsupported protocol version %" PRIu32 ".%" PRIu32, versionMajor, + versionMinor); + } + + remdesk->Version = versionMinor; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_send_ctl_version_info_pdu(remdeskPlugin* remdesk) +{ + REMDESK_CTL_VERSION_INFO_PDU pdu = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(remdesk); + + UINT error = remdesk_prepare_ctl_header(&(pdu.ctlHeader), REMDESK_CTL_VERSIONINFO, 8); + if (error) + return error; + + pdu.versionMajor = 1; + pdu.versionMinor = 2; + wStream* s = Stream_New(nullptr, REMDESK_CHANNEL_CTL_SIZE + pdu.ctlHeader.ch.DataLength); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + error = remdesk_write_ctl_header(s, &(pdu.ctlHeader)); + if (error) + { + Stream_Free(s, TRUE); + return error; + } + Stream_Write_UINT32(s, pdu.versionMajor); /* versionMajor (4 bytes) */ + Stream_Write_UINT32(s, pdu.versionMinor); /* versionMinor (4 bytes) */ + Stream_SealLength(s); + + if ((error = remdesk_virtual_channel_write(remdesk, s))) + WLog_ERR(TAG, "remdesk_virtual_channel_write failed with error %" PRIu32 "!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_recv_ctl_result_pdu(WINPR_ATTR_UNUSED remdeskPlugin* remdesk, wStream* s, + WINPR_ATTR_UNUSED REMDESK_CHANNEL_HEADER* header, + UINT32* pResult) +{ + UINT32 result = 0; + + WINPR_ASSERT(remdesk); + WINPR_ASSERT(s); + WINPR_ASSERT(header); + WINPR_ASSERT(pResult); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, result); /* result (4 bytes) */ + *pResult = result; + // WLog_DBG(TAG, "RemdeskRecvResult: 0x%08"PRIX32"", result); + switch (result) + { + case REMDESK_ERROR_HELPEESAIDNO: + WLog_DBG(TAG, "remote assistance connection request was denied"); + return ERROR_CONNECTION_REFUSED; + + default: + break; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_send_ctl_authenticate_pdu(remdeskPlugin* remdesk) +{ + UINT error = ERROR_INTERNAL_ERROR; + size_t cbExpertBlobW = 0; + WCHAR* expertBlobW = nullptr; + size_t cbRaConnectionStringW = 0; + REMDESK_CTL_HEADER ctlHeader = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(remdesk); + + if ((error = remdesk_generate_expert_blob(remdesk))) + { + WLog_ERR(TAG, "remdesk_generate_expert_blob failed with error %" PRIu32 "", error); + return error; + } + + const char* expertBlob = remdesk->ExpertBlob; + WINPR_ASSERT(remdesk->rdpcontext); + rdpSettings* settings = remdesk->rdpcontext->settings; + WINPR_ASSERT(settings); + + const char* raConnectionString = + freerdp_settings_get_string(settings, FreeRDP_RemoteAssistanceRCTicket); + WCHAR* raConnectionStringW = + ConvertUtf8ToWCharAlloc(raConnectionString, &cbRaConnectionStringW); + + if (!raConnectionStringW || (cbRaConnectionStringW > UINT32_MAX / sizeof(WCHAR))) + goto out; + + cbRaConnectionStringW = cbRaConnectionStringW * sizeof(WCHAR); + + expertBlobW = ConvertUtf8ToWCharAlloc(expertBlob, &cbExpertBlobW); + + if (!expertBlobW) + goto out; + + cbExpertBlobW = cbExpertBlobW * sizeof(WCHAR); + error = remdesk_prepare_ctl_header(&(ctlHeader), REMDESK_CTL_AUTHENTICATE, + cbRaConnectionStringW + cbExpertBlobW); + if (error) + goto out; + + { + wStream* s = Stream_New(nullptr, REMDESK_CHANNEL_CTL_SIZE + ctlHeader.ch.DataLength); + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + error = remdesk_write_ctl_header(s, &ctlHeader); + if (error) + { + Stream_Free(s, TRUE); + goto out; + } + Stream_Write(s, raConnectionStringW, cbRaConnectionStringW); + Stream_Write(s, expertBlobW, cbExpertBlobW); + Stream_SealLength(s); + + error = remdesk_virtual_channel_write(remdesk, s); + } + if (error) + WLog_ERR(TAG, "remdesk_virtual_channel_write failed with error %" PRIu32 "!", error); + +out: + free(raConnectionStringW); + free(expertBlobW); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_send_ctl_remote_control_desktop_pdu(remdeskPlugin* remdesk) +{ + UINT error = 0; + size_t length = 0; + + WINPR_ASSERT(remdesk); + WINPR_ASSERT(remdesk->rdpcontext); + rdpSettings* settings = remdesk->rdpcontext->settings; + WINPR_ASSERT(settings); + + const char* raConnectionString = + freerdp_settings_get_string(settings, FreeRDP_RemoteAssistanceRCTicket); + WCHAR* raConnectionStringW = ConvertUtf8ToWCharAlloc(raConnectionString, &length); + size_t cbRaConnectionStringW = length * sizeof(WCHAR); + + if (!raConnectionStringW) + return ERROR_INTERNAL_ERROR; + + REMDESK_CTL_HEADER ctlHeader = WINPR_C_ARRAY_INIT; + error = remdesk_prepare_ctl_header(&ctlHeader, REMDESK_CTL_REMOTE_CONTROL_DESKTOP, + cbRaConnectionStringW); + if (error != CHANNEL_RC_OK) + goto out; + + { + wStream* s = Stream_New(nullptr, REMDESK_CHANNEL_CTL_SIZE + ctlHeader.ch.DataLength); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + error = remdesk_write_ctl_header(s, &ctlHeader); + if (error) + { + Stream_Free(s, TRUE); + goto out; + } + Stream_Write(s, raConnectionStringW, cbRaConnectionStringW); + Stream_SealLength(s); + + if ((error = remdesk_virtual_channel_write(remdesk, s))) + WLog_ERR(TAG, "remdesk_virtual_channel_write failed with error %" PRIu32 "!", error); + } + +out: + free(raConnectionStringW); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_send_ctl_verify_password_pdu(remdeskPlugin* remdesk) +{ + size_t cbExpertBlobW = 0; + REMDESK_CTL_VERIFY_PASSWORD_PDU pdu = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(remdesk); + + UINT error = remdesk_generate_expert_blob(remdesk); + if (error) + { + WLog_ERR(TAG, "remdesk_generate_expert_blob failed with error %" PRIu32 "!", error); + return error; + } + + pdu.expertBlob = remdesk->ExpertBlob; + WCHAR* expertBlobW = ConvertUtf8ToWCharAlloc(pdu.expertBlob, &cbExpertBlobW); + + if (!expertBlobW) + goto out; + + cbExpertBlobW = cbExpertBlobW * sizeof(WCHAR); + error = + remdesk_prepare_ctl_header(&(pdu.ctlHeader), REMDESK_CTL_VERIFY_PASSWORD, cbExpertBlobW); + if (error) + goto out; + + { + wStream* s = + Stream_New(nullptr, 1ULL * REMDESK_CHANNEL_CTL_SIZE + pdu.ctlHeader.ch.DataLength); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + error = remdesk_write_ctl_header(s, &(pdu.ctlHeader)); + if (error) + { + Stream_Free(s, TRUE); + goto out; + } + Stream_Write(s, expertBlobW, cbExpertBlobW); + Stream_SealLength(s); + + error = remdesk_virtual_channel_write(remdesk, s); + } + if (error) + WLog_ERR(TAG, "remdesk_virtual_channel_write failed with error %" PRIu32 "!", error); + +out: + free(expertBlobW); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_send_ctl_expert_on_vista_pdu(remdeskPlugin* remdesk) +{ + REMDESK_CTL_EXPERT_ON_VISTA_PDU pdu = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(remdesk); + + UINT error = remdesk_generate_expert_blob(remdesk); + if (error) + { + WLog_ERR(TAG, "remdesk_generate_expert_blob failed with error %" PRIu32 "!", error); + return error; + } + if (remdesk->EncryptedPassStubSize > UINT32_MAX) + return ERROR_INTERNAL_ERROR; + + pdu.EncryptedPasswordLength = (UINT32)remdesk->EncryptedPassStubSize; + pdu.EncryptedPassword = remdesk->EncryptedPassStub; + error = remdesk_prepare_ctl_header(&(pdu.ctlHeader), REMDESK_CTL_EXPERT_ON_VISTA, + pdu.EncryptedPasswordLength); + if (error) + return error; + + wStream* s = Stream_New(nullptr, REMDESK_CHANNEL_CTL_SIZE + pdu.ctlHeader.ch.DataLength); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + error = remdesk_write_ctl_header(s, &(pdu.ctlHeader)); + if (error) + { + Stream_Free(s, TRUE); + return error; + } + Stream_Write(s, pdu.EncryptedPassword, pdu.EncryptedPasswordLength); + Stream_SealLength(s); + return remdesk_virtual_channel_write(remdesk, s); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_recv_ctl_pdu(remdeskPlugin* remdesk, wStream* s, REMDESK_CHANNEL_HEADER* header) +{ + UINT error = CHANNEL_RC_OK; + UINT32 msgType = 0; + UINT32 result = 0; + + WINPR_ASSERT(remdesk); + WINPR_ASSERT(s); + WINPR_ASSERT(header); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, msgType); /* msgType (4 bytes) */ + + // WLog_DBG(TAG, "msgType: %"PRIu32"", msgType); + + switch (msgType) + { + case REMDESK_CTL_REMOTE_CONTROL_DESKTOP: + break; + + case REMDESK_CTL_RESULT: + if ((error = remdesk_recv_ctl_result_pdu(remdesk, s, header, &result))) + WLog_ERR(TAG, "remdesk_recv_ctl_result_pdu failed with error %" PRIu32 "", error); + + break; + + case REMDESK_CTL_AUTHENTICATE: + break; + + case REMDESK_CTL_SERVER_ANNOUNCE: + if ((error = remdesk_recv_ctl_server_announce_pdu(remdesk, s, header))) + WLog_ERR(TAG, "remdesk_recv_ctl_server_announce_pdu failed with error %" PRIu32 "", + error); + + break; + + case REMDESK_CTL_DISCONNECT: + break; + + case REMDESK_CTL_VERSIONINFO: + if ((error = remdesk_recv_ctl_version_info_pdu(remdesk, s, header))) + { + WLog_ERR(TAG, "remdesk_recv_ctl_version_info_pdu failed with error %" PRIu32 "", + error); + break; + } + + if (remdesk->Version == 1) + { + if ((error = remdesk_send_ctl_version_info_pdu(remdesk))) + { + WLog_ERR(TAG, "remdesk_send_ctl_version_info_pdu failed with error %" PRIu32 "", + error); + break; + } + + if ((error = remdesk_send_ctl_authenticate_pdu(remdesk))) + { + WLog_ERR(TAG, "remdesk_send_ctl_authenticate_pdu failed with error %" PRIu32 "", + error); + break; + } + + if ((error = remdesk_send_ctl_remote_control_desktop_pdu(remdesk))) + { + WLog_ERR( + TAG, + "remdesk_send_ctl_remote_control_desktop_pdu failed with error %" PRIu32 "", + error); + break; + } + } + else if (remdesk->Version == 2) + { + if ((error = remdesk_send_ctl_expert_on_vista_pdu(remdesk))) + { + WLog_ERR(TAG, + "remdesk_send_ctl_expert_on_vista_pdu failed with error %" PRIu32 "", + error); + break; + } + + if ((error = remdesk_send_ctl_verify_password_pdu(remdesk))) + { + WLog_ERR(TAG, + "remdesk_send_ctl_verify_password_pdu failed with error %" PRIu32 "", + error); + break; + } + } + + break; + + case REMDESK_CTL_ISCONNECTED: + break; + + case REMDESK_CTL_VERIFY_PASSWORD: + break; + + case REMDESK_CTL_EXPERT_ON_VISTA: + break; + + case REMDESK_CTL_RANOVICE_NAME: + break; + + case REMDESK_CTL_RAEXPERT_NAME: + break; + + case REMDESK_CTL_TOKEN: + break; + + default: + WLog_ERR(TAG, "unknown msgType: %" PRIu32 "", msgType); + error = ERROR_INVALID_DATA; + break; + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_process_receive(remdeskPlugin* remdesk, wStream* s) +{ + UINT status = 0; + REMDESK_CHANNEL_HEADER header; + + WINPR_ASSERT(remdesk); + WINPR_ASSERT(s); + + if ((status = remdesk_read_channel_header(s, &header))) + { + WLog_ERR(TAG, "remdesk_read_channel_header failed with error %" PRIu32 "", status); + return status; + } + + if (strcmp(header.ChannelName, "RC_CTL") == 0) + { + status = remdesk_recv_ctl_pdu(remdesk, s, &header); + } + else if (strcmp(header.ChannelName, "70") == 0) + { + } + else if (strcmp(header.ChannelName, "71") == 0) + { + } + else if (strcmp(header.ChannelName, ".") == 0) + { + } + else if (strcmp(header.ChannelName, "1000.") == 0) + { + } + else if (strcmp(header.ChannelName, "RA_FX") == 0) + { + } + else + { + } + + return status; +} + +static void remdesk_process_connect(WINPR_ATTR_UNUSED remdeskPlugin* remdesk) +{ + WINPR_ASSERT(remdesk); + WLog_ERR("TODO", "TODO: implement"); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_virtual_channel_event_data_received(remdeskPlugin* remdesk, const void* pData, + UINT32 dataLength, UINT32 totalLength, + UINT32 dataFlags) +{ + wStream* data_in = nullptr; + + WINPR_ASSERT(remdesk); + + if ((dataFlags & CHANNEL_FLAG_SUSPEND) || (dataFlags & CHANNEL_FLAG_RESUME)) + { + return CHANNEL_RC_OK; + } + + if (dataFlags & CHANNEL_FLAG_FIRST) + { + if (remdesk->data_in) + Stream_Free(remdesk->data_in, TRUE); + + remdesk->data_in = Stream_New(nullptr, totalLength); + + if (!remdesk->data_in) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + } + + data_in = remdesk->data_in; + + if (!Stream_EnsureRemainingCapacity(data_in, dataLength)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write(data_in, pData, dataLength); + + if (dataFlags & CHANNEL_FLAG_LAST) + { + if (Stream_Capacity(data_in) != Stream_GetPosition(data_in)) + { + WLog_ERR(TAG, "read error"); + return ERROR_INTERNAL_ERROR; + } + + remdesk->data_in = nullptr; + Stream_SealLength(data_in); + Stream_ResetPosition(data_in); + + if (!MessageQueue_Post(remdesk->queue, nullptr, 0, (void*)data_in, nullptr)) + { + WLog_ERR(TAG, "MessageQueue_Post failed!"); + return ERROR_INTERNAL_ERROR; + } + } + + return CHANNEL_RC_OK; +} + +static VOID VCAPITYPE remdesk_virtual_channel_open_event_ex(LPVOID lpUserParam, DWORD openHandle, + UINT event, LPVOID pData, + UINT32 dataLength, UINT32 totalLength, + UINT32 dataFlags) +{ + UINT error = CHANNEL_RC_OK; + remdeskPlugin* remdesk = (remdeskPlugin*)lpUserParam; + + switch (event) + { + case CHANNEL_EVENT_INITIALIZED: + break; + + case CHANNEL_EVENT_DATA_RECEIVED: + if (!remdesk || (remdesk->OpenHandle != openHandle)) + { + WLog_ERR(TAG, "error no match"); + return; + } + if ((error = remdesk_virtual_channel_event_data_received(remdesk, pData, dataLength, + totalLength, dataFlags))) + WLog_ERR(TAG, + "remdesk_virtual_channel_event_data_received failed with error %" PRIu32 + "!", + error); + + break; + + case CHANNEL_EVENT_WRITE_CANCELLED: + case CHANNEL_EVENT_WRITE_COMPLETE: + { + wStream* s = (wStream*)pData; + Stream_Free(s, TRUE); + } + break; + + case CHANNEL_EVENT_USER: + break; + + default: + WLog_ERR(TAG, "unhandled event %" PRIu32 "!", event); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (error && remdesk && remdesk->rdpcontext) + setChannelError(remdesk->rdpcontext, error, + "remdesk_virtual_channel_open_event_ex reported an error"); +} + +static DWORD WINAPI remdesk_virtual_channel_client_thread(LPVOID arg) +{ + wStream* data = nullptr; + wMessage message = WINPR_C_ARRAY_INIT; + remdeskPlugin* remdesk = (remdeskPlugin*)arg; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(remdesk); + + remdesk_process_connect(remdesk); + + while (1) + { + if (!MessageQueue_Wait(remdesk->queue)) + { + WLog_ERR(TAG, "MessageQueue_Wait failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (!MessageQueue_Peek(remdesk->queue, &message, TRUE)) + { + WLog_ERR(TAG, "MessageQueue_Peek failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (message.id == WMQ_QUIT) + break; + + if (message.id == 0) + { + data = (wStream*)message.wParam; + + if ((error = remdesk_process_receive(remdesk, data))) + { + WLog_ERR(TAG, "remdesk_process_receive failed with error %" PRIu32 "!", error); + Stream_Free(data, TRUE); + break; + } + + Stream_Free(data, TRUE); + } + } + + if (error && remdesk->rdpcontext) + setChannelError(remdesk->rdpcontext, error, + "remdesk_virtual_channel_client_thread reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_virtual_channel_event_connected(remdeskPlugin* remdesk, + WINPR_ATTR_UNUSED LPVOID pData, + WINPR_ATTR_UNUSED UINT32 dataLength) +{ + UINT error = 0; + + WINPR_ASSERT(remdesk); + + remdesk->queue = MessageQueue_New(nullptr); + + if (!remdesk->queue) + { + WLog_ERR(TAG, "MessageQueue_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + + remdesk->thread = + CreateThread(nullptr, 0, remdesk_virtual_channel_client_thread, (void*)remdesk, 0, nullptr); + + if (!remdesk->thread) + { + WLog_ERR(TAG, "CreateThread failed"); + error = ERROR_INTERNAL_ERROR; + goto error_out; + } + + return remdesk->channelEntryPoints.pVirtualChannelOpenEx( + remdesk->InitHandle, &remdesk->OpenHandle, remdesk->channelDef.name, + remdesk_virtual_channel_open_event_ex); +error_out: + MessageQueue_Free(remdesk->queue); + remdesk->queue = nullptr; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_virtual_channel_event_disconnected(remdeskPlugin* remdesk) +{ + UINT rc = CHANNEL_RC_OK; + + WINPR_ASSERT(remdesk); + + if (remdesk->queue && remdesk->thread) + { + if (MessageQueue_PostQuit(remdesk->queue, 0) && + (WaitForSingleObject(remdesk->thread, INFINITE) == WAIT_FAILED)) + { + rc = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", rc); + return rc; + } + } + + if (remdesk->OpenHandle != 0) + { + WINPR_ASSERT(remdesk->channelEntryPoints.pVirtualChannelCloseEx); + rc = remdesk->channelEntryPoints.pVirtualChannelCloseEx(remdesk->InitHandle, + remdesk->OpenHandle); + + if (CHANNEL_RC_OK != rc) + { + WLog_ERR(TAG, "pVirtualChannelCloseEx failed with %s [%08" PRIX32 "]", + WTSErrorToString(rc), rc); + } + + remdesk->OpenHandle = 0; + } + MessageQueue_Free(remdesk->queue); + (void)CloseHandle(remdesk->thread); + Stream_Free(remdesk->data_in, TRUE); + remdesk->data_in = nullptr; + remdesk->queue = nullptr; + remdesk->thread = nullptr; + return rc; +} + +static void remdesk_virtual_channel_event_terminated(remdeskPlugin* remdesk) +{ + WINPR_ASSERT(remdesk); + + remdesk->InitHandle = nullptr; + free(remdesk->context); + free(remdesk); +} + +static VOID VCAPITYPE remdesk_virtual_channel_init_event_ex(LPVOID lpUserParam, LPVOID pInitHandle, + UINT event, LPVOID pData, + UINT dataLength) +{ + UINT error = CHANNEL_RC_OK; + remdeskPlugin* remdesk = (remdeskPlugin*)lpUserParam; + + if (!remdesk || (remdesk->InitHandle != pInitHandle)) + { + WLog_ERR(TAG, "error no match"); + return; + } + + switch (event) + { + case CHANNEL_EVENT_CONNECTED: + if ((error = remdesk_virtual_channel_event_connected(remdesk, pData, dataLength))) + WLog_ERR(TAG, + "remdesk_virtual_channel_event_connected failed with error %" PRIu32 "", + error); + + break; + + case CHANNEL_EVENT_DISCONNECTED: + if ((error = remdesk_virtual_channel_event_disconnected(remdesk))) + WLog_ERR(TAG, + "remdesk_virtual_channel_event_disconnected failed with error %" PRIu32 "", + error); + + break; + + case CHANNEL_EVENT_TERMINATED: + remdesk_virtual_channel_event_terminated(remdesk); + break; + + case CHANNEL_EVENT_ATTACHED: + case CHANNEL_EVENT_DETACHED: + default: + break; + } + + if (error && remdesk->rdpcontext) + setChannelError(remdesk->rdpcontext, error, + "remdesk_virtual_channel_init_event reported an error"); +} + +/* remdesk is always built-in */ +#define VirtualChannelEntryEx remdesk_VirtualChannelEntryEx + +FREERDP_ENTRY_POINT(BOOL VCAPITYPE VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS_EX pEntryPoints, + PVOID pInitHandle)) +{ + UINT rc = 0; + remdeskPlugin* remdesk = nullptr; + RemdeskClientContext* context = nullptr; + CHANNEL_ENTRY_POINTS_FREERDP_EX* pEntryPointsEx = nullptr; + + if (!pEntryPoints) + { + return FALSE; + } + + remdesk = (remdeskPlugin*)calloc(1, sizeof(remdeskPlugin)); + + if (!remdesk) + { + WLog_ERR(TAG, "calloc failed!"); + return FALSE; + } + + remdesk->channelDef.options = CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP | + CHANNEL_OPTION_COMPRESS_RDP | CHANNEL_OPTION_SHOW_PROTOCOL; + (void)sprintf_s(remdesk->channelDef.name, ARRAYSIZE(remdesk->channelDef.name), + REMDESK_SVC_CHANNEL_NAME); + remdesk->Version = 2; + pEntryPointsEx = (CHANNEL_ENTRY_POINTS_FREERDP_EX*)pEntryPoints; + + if ((pEntryPointsEx->cbSize >= sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)) && + (pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER)) + { + context = (RemdeskClientContext*)calloc(1, sizeof(RemdeskClientContext)); + + if (!context) + { + WLog_ERR(TAG, "calloc failed!"); + goto error_out; + } + + context->handle = (void*)remdesk; + remdesk->context = context; + remdesk->rdpcontext = pEntryPointsEx->context; + } + + CopyMemory(&(remdesk->channelEntryPoints), pEntryPoints, + sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)); + remdesk->InitHandle = pInitHandle; + rc = remdesk->channelEntryPoints.pVirtualChannelInitEx( + remdesk, context, pInitHandle, &remdesk->channelDef, 1, VIRTUAL_CHANNEL_VERSION_WIN2000, + remdesk_virtual_channel_init_event_ex); + + if (CHANNEL_RC_OK != rc) + { + WLog_ERR(TAG, "pVirtualChannelInitEx failed with %s [%08" PRIX32 "]", WTSErrorToString(rc), + rc); + goto error_out; + } + + remdesk->channelEntryPoints.pInterface = context; + return TRUE; +error_out: + free(remdesk); + free(context); + return FALSE; +} diff --git a/third_party/FreeRDP/channels/remdesk/client/remdesk_main.h b/third_party/FreeRDP/channels/remdesk/client/remdesk_main.h new file mode 100644 index 0000000..0d9c48d --- /dev/null +++ b/third_party/FreeRDP/channels/remdesk/client/remdesk_main.h @@ -0,0 +1,61 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Remote Assistance Virtual Channel + * + * Copyright 2014 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_REMDESK_CLIENT_MAIN_H +#define FREERDP_CHANNEL_REMDESK_CLIENT_MAIN_H + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#define TAG CHANNELS_TAG("remdesk.client") + +typedef struct +{ + CHANNEL_DEF channelDef; + CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints; + + RemdeskClientContext* context; + + HANDLE thread; + wStream* data_in; + void* InitHandle; + DWORD OpenHandle; + wMessageQueue* queue; + + UINT32 Version; + char* ExpertBlob; + BYTE* EncryptedPassStub; + size_t EncryptedPassStubSize; + rdpContext* rdpcontext; +} remdeskPlugin; + +#endif /* FREERDP_CHANNEL_REMDESK_CLIENT_MAIN_H */ diff --git a/third_party/FreeRDP/channels/remdesk/common/CMakeLists.txt b/third_party/FreeRDP/channels/remdesk/common/CMakeLists.txt new file mode 100644 index 0000000..e7059eb --- /dev/null +++ b/third_party/FreeRDP/channels/remdesk/common/CMakeLists.txt @@ -0,0 +1,25 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2024 Armin Novak +# 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 remdesk_common.h remdesk_common.c) + +add_library(remdesk-common STATIC ${SRCS}) +set_property(TARGET remdesk-common PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Common") + +freerdp_client_pc_add_library_private(remdesk-common) +channel_install(remdesk-common ${FREERDP_ADDIN_PATH} "FreeRDPTargets") diff --git a/third_party/FreeRDP/channels/remdesk/common/remdesk_common.c b/third_party/FreeRDP/channels/remdesk/common/remdesk_common.c new file mode 100644 index 0000000..1d3eb18 --- /dev/null +++ b/third_party/FreeRDP/channels/remdesk/common/remdesk_common.c @@ -0,0 +1,105 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Remote Assistance Virtual Channel - common components + * + * Copyright 2024 Armin Novak + * 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. + */ + +#include "remdesk_common.h" + +#include +#define TAG CHANNELS_TAG("remdesk.common") + +UINT remdesk_write_channel_header(wStream* s, const REMDESK_CHANNEL_HEADER* header) +{ + WCHAR ChannelNameW[32] = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(s); + WINPR_ASSERT(header); + + for (size_t index = 0; index < 32; index++) + { + ChannelNameW[index] = (WCHAR)header->ChannelName[index]; + } + + const size_t ChannelNameLen = + (strnlen(header->ChannelName, sizeof(header->ChannelName)) + 1) * sizeof(WCHAR); + WINPR_ASSERT(ChannelNameLen <= ARRAYSIZE(header->ChannelName)); + + Stream_Write_UINT32(s, (UINT32)ChannelNameLen); /* ChannelNameLen (4 bytes) */ + Stream_Write_UINT32(s, header->DataLength); /* DataLen (4 bytes) */ + Stream_Write(s, ChannelNameW, ChannelNameLen); /* ChannelName (variable) */ + return CHANNEL_RC_OK; +} + +UINT remdesk_write_ctl_header(wStream* s, const REMDESK_CTL_HEADER* ctlHeader) +{ + WINPR_ASSERT(ctlHeader); + const UINT error = remdesk_write_channel_header(s, &ctlHeader->ch); + + if (error != 0) + { + WLog_ERR(TAG, "remdesk_write_channel_header failed with error %" PRIu32 "!", error); + return error; + } + + Stream_Write_UINT32(s, ctlHeader->msgType); /* msgType (4 bytes) */ + return CHANNEL_RC_OK; +} + +UINT remdesk_read_channel_header(wStream* s, REMDESK_CHANNEL_HEADER* header) +{ + UINT32 ChannelNameLen = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return CHANNEL_RC_NO_MEMORY; + + Stream_Read_UINT32(s, ChannelNameLen); /* ChannelNameLen (4 bytes) */ + Stream_Read_UINT32(s, header->DataLength); /* DataLen (4 bytes) */ + + if (ChannelNameLen > 64) + { + WLog_ERR(TAG, "ChannelNameLen > 64!"); + return ERROR_INVALID_DATA; + } + + if ((ChannelNameLen % 2) != 0) + { + WLog_ERR(TAG, "(ChannelNameLen %% 2) != 0!"); + return ERROR_INVALID_DATA; + } + + if (Stream_Read_UTF16_String_As_UTF8_Buffer(s, ChannelNameLen / sizeof(WCHAR), + header->ChannelName, + ARRAYSIZE(header->ChannelName)) < 0) + return ERROR_INVALID_DATA; + + return CHANNEL_RC_OK; +} + +UINT remdesk_prepare_ctl_header(REMDESK_CTL_HEADER* ctlHeader, UINT32 msgType, size_t msgSize) +{ + WINPR_ASSERT(ctlHeader); + + if (msgSize > UINT32_MAX - 4) + return ERROR_INVALID_PARAMETER; + + ctlHeader->msgType = msgType; + (void)sprintf_s(ctlHeader->ch.ChannelName, ARRAYSIZE(ctlHeader->ch.ChannelName), + REMDESK_CHANNEL_CTL_NAME); + ctlHeader->ch.DataLength = (UINT32)(4UL + msgSize); + return CHANNEL_RC_OK; +} diff --git a/third_party/FreeRDP/channels/remdesk/common/remdesk_common.h b/third_party/FreeRDP/channels/remdesk/common/remdesk_common.h new file mode 100644 index 0000000..b7198cb --- /dev/null +++ b/third_party/FreeRDP/channels/remdesk/common/remdesk_common.h @@ -0,0 +1,39 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Remote Assistance Virtual Channel - common components + * + * Copyright 2024 Armin Novak + * 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 +#include + +#include + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT remdesk_write_channel_header(wStream* s, const REMDESK_CHANNEL_HEADER* header); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT remdesk_write_ctl_header(wStream* s, const REMDESK_CTL_HEADER* ctlHeader); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT remdesk_read_channel_header(wStream* s, REMDESK_CHANNEL_HEADER* header); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT remdesk_prepare_ctl_header(REMDESK_CTL_HEADER* ctlHeader, UINT32 msgType, + size_t msgSize); diff --git a/third_party/FreeRDP/channels/remdesk/server/CMakeLists.txt b/third_party/FreeRDP/channels/remdesk/server/CMakeLists.txt new file mode 100644 index 0000000..7725e66 --- /dev/null +++ b/third_party/FreeRDP/channels/remdesk/server/CMakeLists.txt @@ -0,0 +1,24 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_server("remdesk") + +set(${MODULE_PREFIX}_SRCS remdesk_main.c remdesk_main.h) + +set(${MODULE_PREFIX}_LIBS winpr remdesk-common) + +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntry") diff --git a/third_party/FreeRDP/channels/remdesk/server/remdesk_main.c b/third_party/FreeRDP/channels/remdesk/server/remdesk_main.c new file mode 100644 index 0000000..3988474 --- /dev/null +++ b/third_party/FreeRDP/channels/remdesk/server/remdesk_main.c @@ -0,0 +1,642 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Remote Assistance Virtual Channel + * + * Copyright 2014 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include + +#include "remdesk_main.h" +#include "remdesk_common.h" + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_virtual_channel_write(RemdeskServerContext* context, wStream* s) +{ + const size_t len = Stream_Length(s); + WINPR_ASSERT(len <= UINT32_MAX); + ULONG BytesWritten = 0; + BOOL status = WTSVirtualChannelWrite(context->priv->ChannelHandle, Stream_BufferAs(s, char), + (UINT32)len, &BytesWritten); + return (status) ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_send_ctl_result_pdu(RemdeskServerContext* context, UINT32 result) +{ + wStream* s = nullptr; + REMDESK_CTL_RESULT_PDU pdu; + UINT error = 0; + pdu.result = result; + + if ((error = remdesk_prepare_ctl_header(&(pdu.ctlHeader), REMDESK_CTL_RESULT, 4))) + { + WLog_ERR(TAG, "remdesk_prepare_ctl_header failed with error %" PRIu32 "!", error); + return error; + } + + s = Stream_New(nullptr, REMDESK_CHANNEL_CTL_SIZE + pdu.ctlHeader.ch.DataLength); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if ((error = remdesk_write_ctl_header(s, &(pdu.ctlHeader)))) + { + WLog_ERR(TAG, "remdesk_write_ctl_header failed with error %" PRIu32 "!", error); + goto out; + } + + Stream_Write_UINT32(s, pdu.result); /* result (4 bytes) */ + Stream_SealLength(s); + + if ((error = remdesk_virtual_channel_write(context, s))) + WLog_ERR(TAG, "remdesk_virtual_channel_write failed with error %" PRIu32 "!", error); + +out: + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_send_ctl_version_info_pdu(RemdeskServerContext* context) +{ + wStream* s = nullptr; + REMDESK_CTL_VERSION_INFO_PDU pdu; + UINT error = 0; + + if ((error = remdesk_prepare_ctl_header(&(pdu.ctlHeader), REMDESK_CTL_VERSIONINFO, 8))) + { + WLog_ERR(TAG, "remdesk_prepare_ctl_header failed with error %" PRIu32 "!", error); + return error; + } + + pdu.versionMajor = 1; + pdu.versionMinor = 2; + s = Stream_New(nullptr, REMDESK_CHANNEL_CTL_SIZE + pdu.ctlHeader.ch.DataLength); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + if ((error = remdesk_write_ctl_header(s, &(pdu.ctlHeader)))) + { + WLog_ERR(TAG, "remdesk_write_ctl_header failed with error %" PRIu32 "!", error); + goto out; + } + + Stream_Write_UINT32(s, pdu.versionMajor); /* versionMajor (4 bytes) */ + Stream_Write_UINT32(s, pdu.versionMinor); /* versionMinor (4 bytes) */ + Stream_SealLength(s); + + if ((error = remdesk_virtual_channel_write(context, s))) + WLog_ERR(TAG, "remdesk_virtual_channel_write failed with error %" PRIu32 "!", error); + +out: + Stream_Free(s, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_recv_ctl_version_info_pdu(WINPR_ATTR_UNUSED RemdeskServerContext* context, + wStream* s, + WINPR_ATTR_UNUSED REMDESK_CHANNEL_HEADER* header) +{ + UINT32 versionMajor = 0; + UINT32 versionMinor = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, versionMajor); /* versionMajor (4 bytes) */ + Stream_Read_UINT32(s, versionMinor); /* versionMinor (4 bytes) */ + if ((versionMajor != 1) || (versionMinor != 2)) + { + WLog_ERR(TAG, "REMOTEDESKTOP_CTL_VERSIONINFO_PACKET invalid version %" PRIu32 ".%" PRIu32, + versionMajor, versionMinor); + return ERROR_INVALID_DATA; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_recv_ctl_remote_control_desktop_pdu(RemdeskServerContext* context, wStream* s, + REMDESK_CHANNEL_HEADER* header) +{ + size_t cchStringW = 0; + REMDESK_CTL_REMOTE_CONTROL_DESKTOP_PDU pdu = WINPR_C_ARRAY_INIT; + UINT error = 0; + UINT32 msgLength = header->DataLength - 4; + const WCHAR* pStringW = Stream_ConstPointer(s); + const WCHAR* raConnectionStringW = pStringW; + + while ((msgLength > 0) && pStringW[cchStringW]) + { + msgLength -= 2; + cchStringW++; + } + + if (pStringW[cchStringW] || !cchStringW) + return ERROR_INVALID_DATA; + + cchStringW++; + const size_t cbRaConnectionStringW = cchStringW * sizeof(WCHAR); + pdu.raConnectionString = ConvertWCharNToUtf8Alloc( + raConnectionStringW, cbRaConnectionStringW / sizeof(WCHAR), nullptr); + if (!pdu.raConnectionString) + return ERROR_INTERNAL_ERROR; + + WLog_INFO(TAG, "RaConnectionString: %s", pdu.raConnectionString); + free(pdu.raConnectionString); + + if ((error = remdesk_send_ctl_result_pdu(context, 0))) + WLog_ERR(TAG, "remdesk_send_ctl_result_pdu failed with error %" PRIu32 "!", error); + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_recv_ctl_authenticate_pdu(WINPR_ATTR_UNUSED RemdeskServerContext* context, + wStream* s, REMDESK_CHANNEL_HEADER* header) +{ + size_t cchTmpStringW = 0; + const WCHAR* expertBlobW = nullptr; + REMDESK_CTL_AUTHENTICATE_PDU pdu = WINPR_C_ARRAY_INIT; + UINT32 msgLength = header->DataLength - 4; + const WCHAR* pStringW = Stream_ConstPointer(s); + const WCHAR* raConnectionStringW = pStringW; + + while ((msgLength > 0) && pStringW[cchTmpStringW]) + { + msgLength -= 2; + cchTmpStringW++; + } + + if (pStringW[cchTmpStringW] || !cchTmpStringW) + return ERROR_INVALID_DATA; + + cchTmpStringW++; + const size_t cbRaConnectionStringW = cchTmpStringW * sizeof(WCHAR); + pStringW += cchTmpStringW; + expertBlobW = pStringW; + + size_t cchStringW = 0; + while ((msgLength > 0) && pStringW[cchStringW]) + { + msgLength -= 2; + cchStringW++; + } + + if (pStringW[cchStringW] || !cchStringW) + return ERROR_INVALID_DATA; + + cchStringW++; + const size_t cbExpertBlobW = cchStringW * sizeof(WCHAR); + pdu.raConnectionString = ConvertWCharNToUtf8Alloc( + raConnectionStringW, cbRaConnectionStringW / sizeof(WCHAR), nullptr); + if (!pdu.raConnectionString) + return ERROR_INTERNAL_ERROR; + + pdu.expertBlob = ConvertWCharNToUtf8Alloc(expertBlobW, cbExpertBlobW / sizeof(WCHAR), nullptr); + if (!pdu.expertBlob) + { + free(pdu.raConnectionString); + return ERROR_INTERNAL_ERROR; + } + + WLog_INFO(TAG, "RaConnectionString: %s ExpertBlob: %s", pdu.raConnectionString, pdu.expertBlob); + free(pdu.raConnectionString); + free(pdu.expertBlob); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_recv_ctl_verify_password_pdu(RemdeskServerContext* context, wStream* s, + REMDESK_CHANNEL_HEADER* header) +{ + REMDESK_CTL_VERIFY_PASSWORD_PDU pdu = WINPR_C_ARRAY_INIT; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + const WCHAR* expertBlobW = Stream_ConstPointer(s); + if (header->DataLength < 4) + return ERROR_INVALID_PARAMETER; + + const size_t cbExpertBlobW = header->DataLength - 4; + + pdu.expertBlob = ConvertWCharNToUtf8Alloc(expertBlobW, cbExpertBlobW / sizeof(WCHAR), nullptr); + if (!pdu.expertBlob) + return ERROR_INTERNAL_ERROR; + + WLog_INFO(TAG, "ExpertBlob: %s", pdu.expertBlob); + + // TODO: Callback? + + free(pdu.expertBlob); + return remdesk_send_ctl_result_pdu(context, 0); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_recv_ctl_pdu(RemdeskServerContext* context, wStream* s, + REMDESK_CHANNEL_HEADER* header) +{ + UINT error = CHANNEL_RC_OK; + UINT32 msgType = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, msgType); /* msgType (4 bytes) */ + WLog_INFO(TAG, "msgType: %" PRIu32 "", msgType); + + switch (msgType) + { + case REMDESK_CTL_REMOTE_CONTROL_DESKTOP: + if ((error = remdesk_recv_ctl_remote_control_desktop_pdu(context, s, header))) + { + WLog_ERR(TAG, + "remdesk_recv_ctl_remote_control_desktop_pdu failed with error %" PRIu32 + "!", + error); + return error; + } + + break; + + case REMDESK_CTL_AUTHENTICATE: + if ((error = remdesk_recv_ctl_authenticate_pdu(context, s, header))) + { + WLog_ERR(TAG, "remdesk_recv_ctl_authenticate_pdu failed with error %" PRIu32 "!", + error); + return error; + } + + break; + + case REMDESK_CTL_DISCONNECT: + break; + + case REMDESK_CTL_VERSIONINFO: + if ((error = remdesk_recv_ctl_version_info_pdu(context, s, header))) + { + WLog_ERR(TAG, "remdesk_recv_ctl_version_info_pdu failed with error %" PRIu32 "!", + error); + return error; + } + + break; + + case REMDESK_CTL_ISCONNECTED: + break; + + case REMDESK_CTL_VERIFY_PASSWORD: + if ((error = remdesk_recv_ctl_verify_password_pdu(context, s, header))) + { + WLog_ERR(TAG, "remdesk_recv_ctl_verify_password_pdu failed with error %" PRIu32 "!", + error); + return error; + } + + break; + + case REMDESK_CTL_EXPERT_ON_VISTA: + break; + + case REMDESK_CTL_RANOVICE_NAME: + break; + + case REMDESK_CTL_RAEXPERT_NAME: + break; + + case REMDESK_CTL_TOKEN: + break; + + default: + WLog_ERR(TAG, "remdesk_recv_control_pdu: unknown msgType: %" PRIu32 "", msgType); + error = ERROR_INVALID_DATA; + break; + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_server_receive_pdu(RemdeskServerContext* context, wStream* s) +{ + UINT error = CHANNEL_RC_OK; + REMDESK_CHANNEL_HEADER header; + + if ((error = remdesk_read_channel_header(s, &header))) + { + WLog_ERR(TAG, "remdesk_read_channel_header failed with error %" PRIu32 "!", error); + return error; + } + + if (strcmp(header.ChannelName, "RC_CTL") == 0) + { + if ((error = remdesk_recv_ctl_pdu(context, s, &header))) + { + WLog_ERR(TAG, "remdesk_recv_ctl_pdu failed with error %" PRIu32 "!", error); + return error; + } + } + else if (strcmp(header.ChannelName, "70") == 0) + { + } + else if (strcmp(header.ChannelName, "71") == 0) + { + } + else if (strcmp(header.ChannelName, ".") == 0) + { + } + else if (strcmp(header.ChannelName, "1000.") == 0) + { + } + else if (strcmp(header.ChannelName, "RA_FX") == 0) + { + } + else + { + } + + return error; +} + +static DWORD WINAPI remdesk_server_thread(LPVOID arg) +{ + void* buffer = nullptr; + HANDLE events[8] = WINPR_C_ARRAY_INIT; + HANDLE ChannelEvent = nullptr; + DWORD BytesReturned = 0; + UINT error = 0; + RemdeskServerContext* context = (RemdeskServerContext*)arg; + WINPR_ASSERT(context); + wStream* s = Stream_New(nullptr, 4096); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + if (WTSVirtualChannelQuery(context->priv->ChannelHandle, WTSVirtualEventHandle, &buffer, + &BytesReturned) == TRUE) + { + if (BytesReturned == sizeof(HANDLE)) + ChannelEvent = *(HANDLE*)buffer; + + WTSFreeMemory(buffer); + } + else + { + WLog_ERR(TAG, "WTSVirtualChannelQuery failed!"); + error = ERROR_INTERNAL_ERROR; + goto out; + } + + { + DWORD nCount = 0; + events[nCount++] = ChannelEvent; + events[nCount++] = context->priv->StopEvent; + + if ((error = remdesk_send_ctl_version_info_pdu(context))) + { + WLog_ERR(TAG, "remdesk_send_ctl_version_info_pdu failed with error %" PRIu32 "!", + error); + goto out; + } + + while (1) + { + DWORD status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "", error); + break; + } + + status = WaitForSingleObject(context->priv->StopEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + break; + } + + if (status == WAIT_OBJECT_0) + { + break; + } + + const size_t len = Stream_Capacity(s); + if (len > UINT32_MAX) + { + error = ERROR_INTERNAL_ERROR; + break; + } + if (WTSVirtualChannelRead(context->priv->ChannelHandle, 0, Stream_BufferAs(s, char), + (UINT32)len, &BytesReturned)) + { + if (BytesReturned) + Stream_Seek(s, BytesReturned); + } + else + { + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + error = CHANNEL_RC_NO_MEMORY; + break; + } + } + + if (Stream_GetPosition(s) >= 8) + { + const UINT32* pHeader = Stream_BufferAs(s, UINT32); + const UINT32 PduLength = pHeader[0] + pHeader[1] + 8; + + if (PduLength >= Stream_GetPosition(s)) + { + Stream_SealLength(s); + Stream_ResetPosition(s); + + error = remdesk_server_receive_pdu(context, s); + if (error) + { + WLog_ERR(TAG, "remdesk_server_receive_pdu failed with error %" PRIu32 "!", + error); + break; + } + + Stream_ResetPosition(s); + } + } + } + } +out: + Stream_Free(s, TRUE); + + if (error && context->rdpcontext) + setChannelError(context->rdpcontext, error, "remdesk_server_thread reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_server_start(RemdeskServerContext* context) +{ + context->priv->ChannelHandle = + WTSVirtualChannelOpen(context->vcm, WTS_CURRENT_SESSION, REMDESK_SVC_CHANNEL_NAME); + + if (!context->priv->ChannelHandle) + { + WLog_ERR(TAG, "WTSVirtualChannelOpen failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (!(context->priv->StopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr))) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (!(context->priv->Thread = + CreateThread(nullptr, 0, remdesk_server_thread, (void*)context, 0, nullptr))) + { + WLog_ERR(TAG, "CreateThread failed!"); + (void)CloseHandle(context->priv->StopEvent); + context->priv->StopEvent = nullptr; + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT remdesk_server_stop(RemdeskServerContext* context) +{ + UINT error = 0; + (void)SetEvent(context->priv->StopEvent); + + if (WaitForSingleObject(context->priv->Thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error); + return error; + } + + (void)CloseHandle(context->priv->Thread); + (void)CloseHandle(context->priv->StopEvent); + return CHANNEL_RC_OK; +} + +RemdeskServerContext* remdesk_server_context_new(HANDLE vcm) +{ + RemdeskServerContext* context = nullptr; + context = (RemdeskServerContext*)calloc(1, sizeof(RemdeskServerContext)); + + if (context) + { + context->vcm = vcm; + context->Start = remdesk_server_start; + context->Stop = remdesk_server_stop; + context->priv = (RemdeskServerPrivate*)calloc(1, sizeof(RemdeskServerPrivate)); + + if (!context->priv) + { + free(context); + return nullptr; + } + + context->priv->Version = 1; + } + + return context; +} + +void remdesk_server_context_free(RemdeskServerContext* context) +{ + if (context) + { + if (context->priv->ChannelHandle != INVALID_HANDLE_VALUE) + (void)WTSVirtualChannelClose(context->priv->ChannelHandle); + + free(context->priv); + free(context); + } +} diff --git a/third_party/FreeRDP/channels/remdesk/server/remdesk_main.h b/third_party/FreeRDP/channels/remdesk/server/remdesk_main.h new file mode 100644 index 0000000..4268e4e --- /dev/null +++ b/third_party/FreeRDP/channels/remdesk/server/remdesk_main.h @@ -0,0 +1,41 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Remote Assistance Virtual Channel + * + * Copyright 2014 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_REMDESK_SERVER_MAIN_H +#define FREERDP_CHANNEL_REMDESK_SERVER_MAIN_H + +#include +#include +#include + +#include +#include + +#define TAG CHANNELS_TAG("remdesk.server") + +struct s_remdesk_server_private +{ + HANDLE Thread; + HANDLE StopEvent; + void* ChannelHandle; + + UINT32 Version; +}; + +#endif /* FREERDP_CHANNEL_REMDESK_SERVER_MAIN_H */ diff --git a/third_party/FreeRDP/channels/serial/CMakeLists.txt b/third_party/FreeRDP/channels/serial/CMakeLists.txt new file mode 100644 index 0000000..41b312a --- /dev/null +++ b/third_party/FreeRDP/channels/serial/CMakeLists.txt @@ -0,0 +1,22 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel("serial") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/third_party/FreeRDP/channels/serial/ChannelOptions.cmake b/third_party/FreeRDP/channels/serial/ChannelOptions.cmake new file mode 100644 index 0000000..7f47678 --- /dev/null +++ b/third_party/FreeRDP/channels/serial/ChannelOptions.cmake @@ -0,0 +1,38 @@ +set(OPTION_DEFAULT ON) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT OFF) + +if(WIN32) + set(OPTION_CLIENT_DEFAULT OFF) + set(OPTION_SERVER_DEFAULT OFF) + message("Serial redirection not supported on windows") +endif() + +if(ANDROID) + set(OPTION_CLIENT_DEFAULT OFF) + set(OPTION_SERVER_DEFAULT OFF) + message("Serial redirection not supported on android") +endif() + +if(APPLE) + set(OPTION_CLIENT_DEFAULT OFF) + set(OPTION_SERVER_DEFAULT OFF) + message("Serial redirection not supported on apple") +endif() + +define_channel_options( + NAME + "serial" + TYPE + "device" + DESCRIPTION + "Serial Port Virtual Channel Extension" + SPECIFICATIONS + "[MS-RDPESP]" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/serial/client/CMakeLists.txt b/third_party/FreeRDP/channels/serial/client/CMakeLists.txt new file mode 100644 index 0000000..dffd985 --- /dev/null +++ b/third_party/FreeRDP/channels/serial/client/CMakeLists.txt @@ -0,0 +1,27 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_client("serial") + +set(${MODULE_PREFIX}_SRCS serial_main.c) + +set(${MODULE_PREFIX}_LIBS winpr freerdp) + +# Serial implementation is currently linux only. BSD* might also work but untested +if(UNIX AND NOT APPLE AND NOT ANDROID) + add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DeviceServiceEntry") +endif() diff --git a/third_party/FreeRDP/channels/serial/client/serial_main.c b/third_party/FreeRDP/channels/serial/client/serial_main.c new file mode 100644 index 0000000..3ffee29 --- /dev/null +++ b/third_party/FreeRDP/channels/serial/client/serial_main.c @@ -0,0 +1,1022 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Serial Port Device Service Virtual Channel + * + * Copyright 2011 O.S. Systems Software Ltda. + * Copyright 2011 Eduardo Fiss Beloni + * Copyright 2014 Hewlett-Packard Development Company, L.P. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define TAG CHANNELS_TAG("serial.client") + +#define MAX_IRP_THREADS 5 + +typedef struct +{ + DEVICE device; + BOOL permissive; + SERIAL_DRIVER_ID ServerSerialDriverId; + HANDLE hComm; + + wLog* log; + HANDLE MainThread; + wMessageQueue* MainIrpQueue; + + /* one thread per pending IRP and indexed according their CompletionId */ + wListDictionary* IrpThreads; + CRITICAL_SECTION TerminatingIrpThreadsLock; + rdpContext* rdpcontext; +} SERIAL_DEVICE; + +typedef struct +{ + SERIAL_DEVICE* serial; + IRP* irp; +} IRP_THREAD_DATA; + +static void close_terminated_irp_thread_handles(SERIAL_DEVICE* serial, BOOL forceClose); +static NTSTATUS GetLastErrorToIoStatus(SERIAL_DEVICE* serial) +{ + /* http://msdn.microsoft.com/en-us/library/ff547466%28v=vs.85%29.aspx#generic_status_values_for_serial_device_control_requests + */ + switch (GetLastError()) + { + case ERROR_BAD_DEVICE: + return STATUS_INVALID_DEVICE_REQUEST; + + case ERROR_CALL_NOT_IMPLEMENTED: + return STATUS_NOT_IMPLEMENTED; + + case ERROR_CANCELLED: + return STATUS_CANCELLED; + + case ERROR_INSUFFICIENT_BUFFER: + return STATUS_BUFFER_TOO_SMALL; /* NB: STATUS_BUFFER_SIZE_TOO_SMALL not defined */ + + case ERROR_INVALID_DEVICE_OBJECT_PARAMETER: /* eg: SerCx2.sys' _purge() */ + return STATUS_INVALID_DEVICE_STATE; + + case ERROR_INVALID_HANDLE: + return STATUS_INVALID_DEVICE_REQUEST; + + case ERROR_INVALID_PARAMETER: + return STATUS_INVALID_PARAMETER; + + case ERROR_IO_DEVICE: + return STATUS_IO_DEVICE_ERROR; + + case ERROR_IO_PENDING: + return STATUS_PENDING; + + case ERROR_NOT_SUPPORTED: + return STATUS_NOT_SUPPORTED; + + case ERROR_TIMEOUT: + return STATUS_TIMEOUT; + default: + break; + } + + WLog_Print(serial->log, WLOG_DEBUG, "unexpected last-error: 0x%08" PRIX32 "", GetLastError()); + return STATUS_UNSUCCESSFUL; +} + +static UINT serial_process_irp_create(SERIAL_DEVICE* serial, IRP* irp) +{ + DWORD DesiredAccess = 0; + DWORD SharedAccess = 0; + DWORD CreateDisposition = 0; + UINT32 PathLength = 0; + + WINPR_ASSERT(serial); + WINPR_ASSERT(irp); + + if (!Stream_CheckAndLogRequiredLengthWLog(serial->log, irp->input, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, DesiredAccess); /* DesiredAccess (4 bytes) */ + Stream_Seek_UINT64(irp->input); /* AllocationSize (8 bytes) */ + Stream_Seek_UINT32(irp->input); /* FileAttributes (4 bytes) */ + Stream_Read_UINT32(irp->input, SharedAccess); /* SharedAccess (4 bytes) */ + Stream_Read_UINT32(irp->input, CreateDisposition); /* CreateDisposition (4 bytes) */ + Stream_Seek_UINT32(irp->input); /* CreateOptions (4 bytes) */ + Stream_Read_UINT32(irp->input, PathLength); /* PathLength (4 bytes) */ + + if (!Stream_SafeSeek(irp->input, PathLength)) /* Path (variable) */ + return ERROR_INVALID_DATA; + + WINPR_ASSERT(PathLength == 0); /* MS-RDPESP 2.2.2.2 */ +#ifndef _WIN32 + /* Windows 2012 server sends on a first call : + * DesiredAccess = 0x00100080: SYNCHRONIZE | FILE_READ_ATTRIBUTES + * SharedAccess = 0x00000007: FILE_SHARE_DELETE | FILE_SHARE_WRITE | FILE_SHARE_READ + * CreateDisposition = 0x00000001: CREATE_NEW + * + * then Windows 2012 sends : + * DesiredAccess = 0x00120089: SYNCHRONIZE | READ_CONTROL | FILE_READ_ATTRIBUTES | + * FILE_READ_EA | FILE_READ_DATA SharedAccess = 0x00000007: FILE_SHARE_DELETE | + * FILE_SHARE_WRITE | FILE_SHARE_READ CreateDisposition = 0x00000001: CREATE_NEW + * + * WINPR_ASSERT(DesiredAccess == (GENERIC_READ | GENERIC_WRITE)); + * WINPR_ASSERT(SharedAccess == 0); + * WINPR_ASSERT(CreateDisposition == OPEN_EXISTING); + * + */ + WLog_Print(serial->log, WLOG_DEBUG, + "DesiredAccess: 0x%" PRIX32 ", SharedAccess: 0x%" PRIX32 + ", CreateDisposition: 0x%" PRIX32 "", + DesiredAccess, SharedAccess, CreateDisposition); + /* FIXME: As of today only the flags below are supported by CommCreateFileA: */ + DesiredAccess = GENERIC_READ | GENERIC_WRITE; + SharedAccess = 0; + CreateDisposition = OPEN_EXISTING; +#endif + serial->hComm = winpr_CreateFile(serial->device.name, DesiredAccess, SharedAccess, + nullptr, /* SecurityAttributes */ + CreateDisposition, 0, /* FlagsAndAttributes */ + nullptr); /* TemplateFile */ + + if (!serial->hComm || (serial->hComm == INVALID_HANDLE_VALUE)) + { + WLog_Print(serial->log, WLOG_WARN, "CreateFile failure: %s last-error: 0x%08" PRIX32 "", + serial->device.name, GetLastError()); + irp->IoStatus = STATUS_UNSUCCESSFUL; + goto error_handle; + } + + _comm_setServerSerialDriver(serial->hComm, serial->ServerSerialDriverId); + _comm_set_permissive(serial->hComm, serial->permissive); + /* NOTE: binary mode/raw mode required for the redirection. On + * Linux, CommCreateFileA forces this setting. + */ + /* ZeroMemory(&dcb, sizeof(DCB)); */ + /* dcb.DCBlength = sizeof(DCB); */ + /* GetCommState(serial->hComm, &dcb); */ + /* dcb.fBinary = TRUE; */ + /* SetCommState(serial->hComm, &dcb); */ + WINPR_ASSERT(irp->FileId == 0); + irp->FileId = irp->devman->id_sequence++; /* FIXME: why not ((WINPR_COMM*)hComm)->fd? */ + irp->IoStatus = STATUS_SUCCESS; + WLog_Print(serial->log, WLOG_DEBUG, "%s (DeviceId: %" PRIu32 ", FileId: %" PRIu32 ") created.", + serial->device.name, irp->device->id, irp->FileId); + + { + DWORD BytesReturned = 0; + if (!CommDeviceIoControl(serial->hComm, IOCTL_SERIAL_RESET_DEVICE, nullptr, 0, nullptr, 0, + &BytesReturned, nullptr)) + goto error_handle; + } + +error_handle: + Stream_Write_UINT32(irp->output, irp->FileId); /* FileId (4 bytes) */ + Stream_Write_UINT8(irp->output, 0); /* Information (1 byte) */ + return CHANNEL_RC_OK; +} + +static UINT serial_process_irp_close(SERIAL_DEVICE* serial, IRP* irp) +{ + WINPR_ASSERT(serial); + WINPR_ASSERT(irp); + + if (!Stream_CheckAndLogRequiredLengthWLog(serial->log, irp->input, 32)) + return ERROR_INVALID_DATA; + + Stream_Seek(irp->input, 32); /* Padding (32 bytes) */ + + close_terminated_irp_thread_handles(serial, TRUE); + + if (!CloseHandle(serial->hComm)) + { + WLog_Print(serial->log, WLOG_WARN, "CloseHandle failure: %s (%" PRIu32 ") closed.", + serial->device.name, irp->device->id); + irp->IoStatus = STATUS_UNSUCCESSFUL; + goto error_handle; + } + + WLog_Print(serial->log, WLOG_DEBUG, "%s (DeviceId: %" PRIu32 ", FileId: %" PRIu32 ") closed.", + serial->device.name, irp->device->id, irp->FileId); + irp->IoStatus = STATUS_SUCCESS; +error_handle: + serial->hComm = nullptr; + Stream_Zero(irp->output, 5); /* Padding (5 bytes) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT serial_process_irp_read(SERIAL_DEVICE* serial, IRP* irp) +{ + UINT32 Length = 0; + UINT64 Offset = 0; + BYTE* buffer = nullptr; + DWORD nbRead = 0; + + WINPR_ASSERT(serial); + WINPR_ASSERT(irp); + + if (!Stream_CheckAndLogRequiredLengthWLog(serial->log, irp->input, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, Length); /* Length (4 bytes) */ + Stream_Read_UINT64(irp->input, Offset); /* Offset (8 bytes) */ + (void)Offset; /* [MS-RDPESP] 3.2.5.1.4 Processing a Server Read Request Message + * ignored */ + Stream_Seek(irp->input, 20); /* Padding (20 bytes) */ + buffer = (BYTE*)calloc(Length, sizeof(BYTE)); + + if (buffer == nullptr) + { + irp->IoStatus = STATUS_NO_MEMORY; + goto error_handle; + } + + /* MS-RDPESP 3.2.5.1.4: If the Offset field is not set to 0, the value MUST be ignored + * WINPR_ASSERT(Offset == 0); + */ + WLog_Print(serial->log, WLOG_DEBUG, "reading %" PRIu32 " bytes from %s", Length, + serial->device.name); + + /* FIXME: CommReadFile to be replaced by ReadFile */ + if (CommReadFile(serial->hComm, buffer, Length, &nbRead, nullptr)) + { + irp->IoStatus = STATUS_SUCCESS; + } + else + { + WLog_Print(serial->log, WLOG_DEBUG, + "read failure to %s, nbRead=%" PRIu32 ", last-error: 0x%08" PRIX32 "", + serial->device.name, nbRead, GetLastError()); + irp->IoStatus = GetLastErrorToIoStatus(serial); + } + + WLog_Print(serial->log, WLOG_DEBUG, "%" PRIu32 " bytes read from %s", nbRead, + serial->device.name); +error_handle: + Stream_Write_UINT32(irp->output, nbRead); /* Length (4 bytes) */ + + if (nbRead > 0) + { + if (!Stream_EnsureRemainingCapacity(irp->output, nbRead)) + { + WLog_Print(serial->log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!"); + free(buffer); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write(irp->output, buffer, nbRead); /* ReadData */ + } + + free(buffer); + return CHANNEL_RC_OK; +} + +static UINT serial_process_irp_write(SERIAL_DEVICE* serial, IRP* irp) +{ + UINT32 Length = 0; + UINT64 Offset = 0; + DWORD nbWritten = 0; + + WINPR_ASSERT(serial); + WINPR_ASSERT(irp); + + if (!Stream_CheckAndLogRequiredLengthWLog(serial->log, irp->input, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, Length); /* Length (4 bytes) */ + Stream_Read_UINT64(irp->input, Offset); /* Offset (8 bytes) */ + (void)Offset; /* [MS-RDPESP] 3.2.5.1.4 Processing a Server Read Request Message + * ignored */ + if (!Stream_SafeSeek(irp->input, 20)) /* Padding (20 bytes) */ + return ERROR_INVALID_DATA; + + /* MS-RDPESP 3.2.5.1.5: The Offset field is ignored + * WINPR_ASSERT(Offset == 0); + * + * Using a serial printer, noticed though this field could be + * set. + */ + WLog_Print(serial->log, WLOG_DEBUG, "writing %" PRIu32 " bytes to %s", Length, + serial->device.name); + + const void* ptr = Stream_ConstPointer(irp->input); + if (!Stream_SafeSeek(irp->input, Length)) + return ERROR_INVALID_DATA; + /* FIXME: CommWriteFile to be replaced by WriteFile */ + if (CommWriteFile(serial->hComm, ptr, Length, &nbWritten, nullptr)) + { + irp->IoStatus = STATUS_SUCCESS; + } + else + { + WLog_Print(serial->log, WLOG_DEBUG, + "write failure to %s, nbWritten=%" PRIu32 ", last-error: 0x%08" PRIX32 "", + serial->device.name, nbWritten, GetLastError()); + irp->IoStatus = GetLastErrorToIoStatus(serial); + } + + WLog_Print(serial->log, WLOG_DEBUG, "%" PRIu32 " bytes written to %s", nbWritten, + serial->device.name); + Stream_Write_UINT32(irp->output, nbWritten); /* Length (4 bytes) */ + Stream_Write_UINT8(irp->output, 0); /* Padding (1 byte) */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT serial_process_irp_device_control(SERIAL_DEVICE* serial, IRP* irp) +{ + UINT32 IoControlCode = 0; + UINT32 InputBufferLength = 0; + BYTE* InputBuffer = nullptr; + UINT32 OutputBufferLength = 0; + BYTE* OutputBuffer = nullptr; + DWORD BytesReturned = 0; + + WINPR_ASSERT(serial); + WINPR_ASSERT(irp); + + if (!Stream_CheckAndLogRequiredLengthWLog(serial->log, irp->input, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(irp->input, OutputBufferLength); /* OutputBufferLength (4 bytes) */ + Stream_Read_UINT32(irp->input, InputBufferLength); /* InputBufferLength (4 bytes) */ + Stream_Read_UINT32(irp->input, IoControlCode); /* IoControlCode (4 bytes) */ + Stream_Seek(irp->input, 20); /* Padding (20 bytes) */ + + if (!Stream_CheckAndLogRequiredLengthWLog(serial->log, irp->input, InputBufferLength)) + return ERROR_INVALID_DATA; + + OutputBuffer = (BYTE*)calloc(OutputBufferLength, sizeof(BYTE)); + + if (OutputBuffer == nullptr) + { + irp->IoStatus = STATUS_NO_MEMORY; + goto error_handle; + } + + InputBuffer = (BYTE*)calloc(InputBufferLength, sizeof(BYTE)); + + if (InputBuffer == nullptr) + { + irp->IoStatus = STATUS_NO_MEMORY; + goto error_handle; + } + + Stream_Read(irp->input, InputBuffer, InputBufferLength); + WLog_Print(serial->log, WLOG_DEBUG, + "CommDeviceIoControl: CompletionId=%" PRIu32 ", IoControlCode=[0x%" PRIX32 "] %s", + irp->CompletionId, IoControlCode, _comm_serial_ioctl_name(IoControlCode)); + + /* FIXME: CommDeviceIoControl to be replaced by DeviceIoControl() */ + if (CommDeviceIoControl(serial->hComm, IoControlCode, InputBuffer, InputBufferLength, + OutputBuffer, OutputBufferLength, &BytesReturned, nullptr)) + { + /* WLog_Print(serial->log, WLOG_DEBUG, "CommDeviceIoControl: CompletionId=%"PRIu32", + * IoControlCode=[0x%"PRIX32"] %s done", irp->CompletionId, IoControlCode, + * _comm_serial_ioctl_name(IoControlCode)); */ + irp->IoStatus = STATUS_SUCCESS; + } + else + { + WLog_Print(serial->log, WLOG_DEBUG, + "CommDeviceIoControl failure: IoControlCode=[0x%" PRIX32 + "] %s, last-error: 0x%08" PRIX32 "", + IoControlCode, _comm_serial_ioctl_name(IoControlCode), GetLastError()); + irp->IoStatus = GetLastErrorToIoStatus(serial); + } + +error_handle: + /* FIXME: find out whether it's required or not to get + * BytesReturned == OutputBufferLength when + * CommDeviceIoControl returns FALSE */ + WINPR_ASSERT(OutputBufferLength == BytesReturned); + Stream_Write_UINT32(irp->output, BytesReturned); /* OutputBufferLength (4 bytes) */ + + if (BytesReturned > 0) + { + if (!Stream_EnsureRemainingCapacity(irp->output, BytesReturned)) + { + WLog_Print(serial->log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!"); + free(InputBuffer); + free(OutputBuffer); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write(irp->output, OutputBuffer, BytesReturned); /* OutputBuffer */ + } + + /* FIXME: Why at least Windows 2008R2 gets lost with this + * extra byte and likely on a IOCTL_SERIAL_SET_BAUD_RATE? The + * extra byte is well required according MS-RDPEFS + * 2.2.1.5.5 */ + /* else */ + /* { */ + /* Stream_Write_UINT8(irp->output, 0); /\* Padding (1 byte) *\/ */ + /* } */ + free(InputBuffer); + free(OutputBuffer); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT serial_process_irp(SERIAL_DEVICE* serial, IRP* irp) +{ + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(serial); + WINPR_ASSERT(irp); + + WLog_Print(serial->log, WLOG_DEBUG, "IRP MajorFunction: %s, MinorFunction: 0x%08" PRIX32 "\n", + rdpdr_irp_string(irp->MajorFunction), irp->MinorFunction); + + switch (irp->MajorFunction) + { + case IRP_MJ_CREATE: + error = serial_process_irp_create(serial, irp); + break; + + case IRP_MJ_CLOSE: + error = serial_process_irp_close(serial, irp); + break; + + case IRP_MJ_READ: + error = serial_process_irp_read(serial, irp); + break; + + case IRP_MJ_WRITE: + error = serial_process_irp_write(serial, irp); + break; + + case IRP_MJ_DEVICE_CONTROL: + error = serial_process_irp_device_control(serial, irp); + break; + + default: + irp->IoStatus = STATUS_NOT_SUPPORTED; + break; + } + + DWORD level = WLOG_TRACE; + if (error) + level = WLOG_WARN; + + WLog_Print(serial->log, level, + "[%s|0x%08" PRIx32 "] completed with %s [0x%08" PRIx32 "] (IoStatus %s [0x%08" PRIx32 + "])", + rdpdr_irp_string(irp->MajorFunction), irp->MajorFunction, WTSErrorToString(error), + error, NtStatus2Tag(irp->IoStatus), WINPR_CXX_COMPAT_CAST(UINT32, irp->IoStatus)); + + return error; +} + +static DWORD WINAPI irp_thread_func(LPVOID arg) +{ + IRP_THREAD_DATA* data = (IRP_THREAD_DATA*)arg; + + WINPR_ASSERT(data); + WINPR_ASSERT(data->serial); + WINPR_ASSERT(data->irp); + + /* blocks until the end of the request */ + UINT error = serial_process_irp(data->serial, data->irp); + if (error) + { + WLog_Print(data->serial->log, WLOG_ERROR, + "serial_process_irp failed with error %" PRIu32 "", error); + data->irp->Discard(data->irp); + goto error_out; + } + + EnterCriticalSection(&data->serial->TerminatingIrpThreadsLock); + WINPR_ASSERT(data->irp->Complete); + error = data->irp->Complete(data->irp); + LeaveCriticalSection(&data->serial->TerminatingIrpThreadsLock); +error_out: + + if (error && data->serial->rdpcontext) + setChannelError(data->serial->rdpcontext, error, "irp_thread_func reported an error"); + + /* NB: At this point, the server might already being reusing + * the CompletionId whereas the thread is not yet + * terminated */ + free(data); + ExitThread(error); + return error; +} + +static void close_unterminated_irp_thread(wListDictionary* list, wLog* log, ULONG_PTR id) +{ + WINPR_ASSERT(list); + HANDLE self = _GetCurrentThread(); + HANDLE cirpThread = ListDictionary_GetItemValue(list, (void*)id); + if (self == cirpThread) + WLog_Print(log, WLOG_DEBUG, "Skipping termination of own IRP thread"); + else + ListDictionary_Remove(list, (void*)id); +} + +static void close_terminated_irp_thread(wListDictionary* list, wLog* log, ULONG_PTR id) +{ + WINPR_ASSERT(list); + + HANDLE cirpThread = ListDictionary_GetItemValue(list, (void*)id); + /* FIXME: not quite sure a zero timeout is a good thing to check whether a thread is + * still alive or not */ + const DWORD waitResult = WaitForSingleObject(cirpThread, 0); + + if (waitResult == WAIT_OBJECT_0) + ListDictionary_Remove(list, (void*)id); + else if (waitResult != WAIT_TIMEOUT) + { + /* unexpected thread state */ + WLog_Print(log, WLOG_WARN, "WaitForSingleObject, got an unexpected result=0x%" PRIX32 "\n", + waitResult); + } +} + +void close_terminated_irp_thread_handles(SERIAL_DEVICE* serial, BOOL forceClose) +{ + WINPR_ASSERT(serial); + + EnterCriticalSection(&serial->TerminatingIrpThreadsLock); + + ListDictionary_Lock(serial->IrpThreads); + ULONG_PTR* ids = nullptr; + const size_t nbIds = ListDictionary_GetKeys(serial->IrpThreads, &ids); + + for (size_t i = 0; i < nbIds; i++) + { + ULONG_PTR id = ids[i]; + if (forceClose) + close_unterminated_irp_thread(serial->IrpThreads, serial->log, id); + else + close_terminated_irp_thread(serial->IrpThreads, serial->log, id); + } + + free(ids); + ListDictionary_Unlock(serial->IrpThreads); + + LeaveCriticalSection(&serial->TerminatingIrpThreadsLock); +} + +static void create_irp_thread(SERIAL_DEVICE* serial, IRP* irp) +{ + IRP_THREAD_DATA* data = nullptr; + HANDLE irpThread = nullptr; + HANDLE previousIrpThread = nullptr; + uintptr_t key = 0; + + WINPR_ASSERT(serial); + WINPR_ASSERT(irp); + + close_terminated_irp_thread_handles(serial, FALSE); + + /* NB: At this point and thanks to the synchronization we're + * sure that the incoming IRP uses well a recycled + * CompletionId or the server sent again an IRP already posted + * which didn't get yet a response (this later server behavior + * at least observed with IOCTL_SERIAL_WAIT_ON_MASK and + * mstsc.exe). + * + * FIXME: behavior documented somewhere? behavior not yet + * observed with FreeRDP). + */ + key = irp->CompletionId + 1ull; + + ListDictionary_Lock(serial->IrpThreads); + previousIrpThread = ListDictionary_GetItemValue(serial->IrpThreads, (void*)key); + ListDictionary_Unlock(serial->IrpThreads); + + if (previousIrpThread) + { + /* Thread still alived <=> Request still pending */ + WLog_Print(serial->log, WLOG_DEBUG, + "IRP recall: IRP with the CompletionId=%" PRIu32 " not yet completed!", + irp->CompletionId); + WINPR_ASSERT(FALSE); /* unimplemented */ + /* TODO: WINPR_ASSERTs that previousIrpThread handles well + * the same request by checking more details. Need an + * access to the IRP object used by previousIrpThread + */ + /* TODO: taking over the pending IRP or sending a kind + * of wake up signal to accelerate the pending + * request + * + * To be considered: + * if (IoControlCode == IOCTL_SERIAL_WAIT_ON_MASK) { + * pComm->PendingEvents |= SERIAL_EV_FREERDP_*; + * } + */ + irp->Discard(irp); + return; + } + + /* error_handle to be used ... */ + data = (IRP_THREAD_DATA*)calloc(1, sizeof(IRP_THREAD_DATA)); + + if (data == nullptr) + { + WLog_Print(serial->log, WLOG_WARN, "Could not allocate a new IRP_THREAD_DATA."); + goto error_handle; + } + + data->serial = serial; + data->irp = irp; + /* data freed by irp_thread_func */ + irpThread = CreateThread(nullptr, 0, irp_thread_func, (void*)data, CREATE_SUSPENDED, nullptr); + + if (irpThread == INVALID_HANDLE_VALUE) + { + WLog_Print(serial->log, WLOG_WARN, "Could not allocate a new IRP thread."); + goto error_handle; + } + + key = irp->CompletionId + 1ull; + + ListDictionary_Lock(serial->IrpThreads); + if (ListDictionary_Count(serial->IrpThreads) >= MAX_IRP_THREADS) + { + WLog_Print(serial->log, WLOG_WARN, + "Number of IRP threads threshold reached: %" PRIuz ", keep on anyway", + ListDictionary_Count(serial->IrpThreads)); + WINPR_ASSERT(FALSE); /* unimplemented */ + /* TODO: MAX_IRP_THREADS has been thought to avoid a + * flooding of pending requests. Use + * WaitForMultipleObjects() when available in winpr + * for threads. + */ + } + + { + const BOOL added = ListDictionary_Add(serial->IrpThreads, (void*)key, irpThread); + ListDictionary_Unlock(serial->IrpThreads); + + if (!added) + { + WLog_Print(serial->log, WLOG_ERROR, "ListDictionary_Add failed!"); + goto error_handle; + } + } + + ResumeThread(irpThread); + + return; +error_handle: + if (irpThread) + (void)CloseHandle(irpThread); + irp->IoStatus = STATUS_NO_MEMORY; + WINPR_ASSERT(irp->Complete); + const UINT rc = irp->Complete(irp); + if (rc != CHANNEL_RC_OK) + WLog_Print(serial->log, WLOG_WARN, "irp->Complete failed with %" PRIu32, rc); + free(data); +} + +static DWORD WINAPI serial_thread_func(LPVOID arg) +{ + IRP* irp = nullptr; + wMessage message = WINPR_C_ARRAY_INIT; + SERIAL_DEVICE* serial = (SERIAL_DEVICE*)arg; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(serial); + + while (1) + { + if (!MessageQueue_Wait(serial->MainIrpQueue)) + { + WLog_Print(serial->log, WLOG_ERROR, "MessageQueue_Wait failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (!MessageQueue_Peek(serial->MainIrpQueue, &message, TRUE)) + { + WLog_Print(serial->log, WLOG_ERROR, "MessageQueue_Peek failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (message.id == WMQ_QUIT) + break; + + irp = (IRP*)message.wParam; + + if (irp) + create_irp_thread(serial, irp); + } + + ListDictionary_Lock(serial->IrpThreads); + ListDictionary_Clear(serial->IrpThreads); + ListDictionary_Unlock(serial->IrpThreads); + + if (error && serial->rdpcontext) + setChannelError(serial->rdpcontext, error, "serial_thread_func reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT serial_irp_request(DEVICE* device, IRP* irp) +{ + SERIAL_DEVICE* serial = (SERIAL_DEVICE*)device; + WINPR_ASSERT(irp != nullptr); + WINPR_ASSERT(serial); + + /* NB: ENABLE_ASYNCIO is set, (MS-RDPEFS 2.2.2.7.2) this + * allows the server to send multiple simultaneous read or + * write requests. + */ + + if (!MessageQueue_Post(serial->MainIrpQueue, nullptr, 0, (void*)irp, nullptr)) + { + WLog_Print(serial->log, WLOG_ERROR, "MessageQueue_Post failed!"); + irp->Discard(irp); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT serial_free(DEVICE* device) +{ + UINT error = 0; + SERIAL_DEVICE* serial = (SERIAL_DEVICE*)device; + if (!serial) + return CHANNEL_RC_OK; + + WLog_Print(serial->log, WLOG_DEBUG, "freeing"); + if (serial->MainIrpQueue) + MessageQueue_PostQuit(serial->MainIrpQueue, 0); + + if (serial->MainThread) + { + if (WaitForSingleObject(serial->MainThread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_Print(serial->log, WLOG_ERROR, + "WaitForSingleObject failed with error %" PRIu32 "!", error); + } + (void)CloseHandle(serial->MainThread); + } + + if (serial->hComm) + (void)CloseHandle(serial->hComm); + + /* Clean up resources */ + Stream_Free(serial->device.data, TRUE); + MessageQueue_Free(serial->MainIrpQueue); + ListDictionary_Free(serial->IrpThreads); + DeleteCriticalSection(&serial->TerminatingIrpThreadsLock); + free(serial); + return CHANNEL_RC_OK; +} + +static void serial_message_free(void* obj) +{ + wMessage* msg = obj; + if (!msg) + return; + if (msg->id != 0) + return; + + IRP* irp = (IRP*)msg->wParam; + if (!irp) + return; + WINPR_ASSERT(irp->Discard); + irp->Discard(irp); +} + +static void irp_thread_close(void* arg) +{ + HANDLE hdl = arg; + if (hdl) + { + HANDLE thz = _GetCurrentThread(); + if (thz == hdl) + WLog_WARN(TAG, "closing self, ignoring..."); + else + { + (void)TerminateThread(hdl, 0); + (void)WaitForSingleObject(hdl, INFINITE); + (void)CloseHandle(hdl); + } + } +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT( + UINT VCAPITYPE serial_DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints)) +{ + size_t len = 0; + SERIAL_DEVICE* serial = nullptr; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(pEntryPoints); + + RDPDR_SERIAL* device = (RDPDR_SERIAL*)pEntryPoints->device; + WINPR_ASSERT(device); + + wLog* log = WLog_Get(TAG); + const char* name = device->device.Name; + const char* path = device->Path; + const char* driver = device->Driver; + + if (!name || (name[0] == '*')) + { + /* TODO: implement auto detection of serial ports */ + WLog_Print(log, WLOG_WARN, + "Serial port autodetection not implemented, nothing will be redirected!"); + return CHANNEL_RC_OK; + } + + if ((name && name[0]) && (path && path[0])) + { + WLog_Print(log, WLOG_DEBUG, "Defining %s as %s", name, path); + + if (!DefineCommDevice(name /* eg: COM1 */, path /* eg: /dev/ttyS0 */)) + { + DWORD status = GetLastError(); + WLog_Print(log, WLOG_ERROR, "DefineCommDevice failed with %08" PRIx32, status); + return ERROR_INTERNAL_ERROR; + } + + serial = (SERIAL_DEVICE*)calloc(1, sizeof(SERIAL_DEVICE)); + + if (!serial) + { + WLog_Print(log, WLOG_ERROR, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + serial->log = log; + serial->device.type = RDPDR_DTYP_SERIAL; + serial->device.name = name; + serial->device.IRPRequest = serial_irp_request; + serial->device.Free = serial_free; + serial->rdpcontext = pEntryPoints->rdpcontext; + len = strlen(name); + serial->device.data = Stream_New(nullptr, len + 1); + + if (!serial->device.data) + { + WLog_Print(serial->log, WLOG_ERROR, "calloc failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + + for (size_t i = 0; i <= len; i++) + Stream_Write_INT8(serial->device.data, name[i] < 0 ? '_' : name[i]); + + if (driver != nullptr) + { + if (_stricmp(driver, "Serial") == 0) + serial->ServerSerialDriverId = SerialDriverSerialSys; + else if (_stricmp(driver, "SerCx") == 0) + serial->ServerSerialDriverId = SerialDriverSerCxSys; + else if (_stricmp(driver, "SerCx2") == 0) + serial->ServerSerialDriverId = SerialDriverSerCx2Sys; + else + { + WLog_Print(serial->log, WLOG_WARN, "Unknown server's serial driver: %s.", driver); + WLog_Print(serial->log, WLOG_WARN, + "Valid options are: 'Serial' (default), 'SerCx' and 'SerCx2'"); + goto error_out; + } + } + else + { + /* default driver */ + serial->ServerSerialDriverId = SerialDriverSerialSys; + } + + if (device->Permissive != nullptr) + { + if (_stricmp(device->Permissive, "permissive") == 0) + { + serial->permissive = TRUE; + } + else + { + WLog_Print(serial->log, WLOG_WARN, "Unknown flag: %s", device->Permissive); + goto error_out; + } + } + + WLog_Print(serial->log, WLOG_DEBUG, "Server's serial driver: %s (id: %u)", driver, + serial->ServerSerialDriverId); + + serial->MainIrpQueue = MessageQueue_New(nullptr); + + if (!serial->MainIrpQueue) + { + WLog_Print(serial->log, WLOG_ERROR, "MessageQueue_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + + { + wObject* obj = MessageQueue_Object(serial->MainIrpQueue); + WINPR_ASSERT(obj); + obj->fnObjectFree = serial_message_free; + } + + /* IrpThreads content only modified by create_irp_thread() */ + serial->IrpThreads = ListDictionary_New(FALSE); + + if (!serial->IrpThreads) + { + WLog_Print(serial->log, WLOG_ERROR, "ListDictionary_New failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto error_out; + } + + { + wObject* obj = ListDictionary_ValueObject(serial->IrpThreads); + WINPR_ASSERT(obj); + obj->fnObjectFree = irp_thread_close; + } + + InitializeCriticalSection(&serial->TerminatingIrpThreadsLock); + + error = pEntryPoints->RegisterDevice(pEntryPoints->devman, &serial->device); + if (error != CHANNEL_RC_OK) + { + WLog_Print(serial->log, WLOG_ERROR, + "EntryPoints->RegisterDevice failed with error %" PRIu32 "!", error); + goto error_out; + } + + serial->MainThread = CreateThread(nullptr, 0, serial_thread_func, serial, 0, nullptr); + if (!serial->MainThread) + { + WLog_Print(serial->log, WLOG_ERROR, "CreateThread failed!"); + error = ERROR_INTERNAL_ERROR; + goto error_out; + } + } + + return error; +error_out: + if (serial) + serial_free(&serial->device); + return error; +} diff --git a/third_party/FreeRDP/channels/server/CMakeLists.txt b/third_party/FreeRDP/channels/server/CMakeLists.txt new file mode 100644 index 0000000..5b55d14 --- /dev/null +++ b/third_party/FreeRDP/channels/server/CMakeLists.txt @@ -0,0 +1,36 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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 "freerdp-channels-server") +set(MODULE_PREFIX "FREERDP_CHANNELS_SERVER") + +set(${MODULE_PREFIX}_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/channels.c ${CMAKE_CURRENT_SOURCE_DIR}/channels.h) + +foreach(STATIC_MODULE ${CHANNEL_STATIC_SERVER_MODULES}) + set(STATIC_MODULE_NAME ${${STATIC_MODULE}_SERVER_NAME}) + set(STATIC_MODULE_CHANNEL ${${STATIC_MODULE}_SERVER_CHANNEL}) + set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${STATIC_MODULE_NAME}) +endforeach() + +add_library(${MODULE_NAME} STATIC ${${MODULE_PREFIX}_SRCS}) + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} winpr freerdp) + +set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} PARENT_SCOPE) +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} PARENT_SCOPE) + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Server/Common") diff --git a/third_party/FreeRDP/channels/server/channels.c b/third_party/FreeRDP/channels/server/channels.c new file mode 100644 index 0000000..635ee85 --- /dev/null +++ b/third_party/FreeRDP/channels/server/channels.c @@ -0,0 +1,243 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Server Channels + * + * Copyright 2011-2012 Vic Lee + * Copyright 2012 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "channels.h" + +/** + * this is a workaround to force importing symbols + * will need to fix that later on cleanly + */ + +#if defined(CHANNEL_AUDIN_SERVER) +#include +#endif +#if defined(CHANNEL_RDPSND_SERVER) +#include +#endif +#if defined(CHANNEL_CLIPRDR_SERVER) +#include +#endif +#if defined(CHANNEL_ECHO_SERVER) +#include +#endif +#if defined(CHANNEL_RDPDR_SERVER) +#include +#endif +#if defined(CHANNEL_RDPEI_SERVER) +#include +#endif +#if defined(CHANNEL_DRDYNVC_SERVER) +#include +#endif +#if defined(CHANNEL_REMDESK_SERVER) +#include +#endif +#if defined(CHANNEL_ENCOMSP_SERVER) +#include +#endif +#if defined(CHANNEL_RAIL_SERVER) +#include +#endif +#if defined(CHANNEL_TELEMETRY_SERVER) +#include +#endif +#if defined(CHANNEL_RDPGFX_SERVER) +#include +#endif +#if defined(CHANNEL_DISP_SERVER) +#include +#endif + +#if defined(CHANNEL_RDPEMSC_SERVER) +#include +#endif /* CHANNEL_RDPEMSC_SERVER */ + +#if defined(CHANNEL_RDPECAM_SERVER) +#include +#include +#endif + +#if defined(CHANNEL_LOCATION_SERVER) +#include +#endif /* CHANNEL_LOCATION_SERVER */ + +#ifdef WITH_CHANNEL_GFXREDIR +#include +#endif /* WITH_CHANNEL_GFXREDIR */ + +#if defined(CHANNEL_AINPUT_SERVER) +#include +#endif + +extern void freerdp_channels_dummy(void); + +void freerdp_channels_dummy(void) +{ +#if defined(CHANNEL_AUDIN_SERVER) + audin_server_context* audin = nullptr; +#endif +#if defined(CHANNEL_RDPSND_SERVER) + RdpsndServerContext* rdpsnd = nullptr; +#endif +#if defined(CHANNEL_CLIPRDR_SERVER) + CliprdrServerContext* cliprdr = nullptr; +#endif +#if defined(CHANNEL_ECHO_SERVER) + echo_server_context* echo = nullptr; +#endif +#if defined(CHANNEL_RDPDR_SERVER) + RdpdrServerContext* rdpdr = nullptr; +#endif +#if defined(CHANNEL_DRDYNVC_SERVER) + DrdynvcServerContext* drdynvc = nullptr; +#endif +#if defined(CHANNEL_RDPEI_SERVER) + RdpeiServerContext* rdpei = nullptr; +#endif +#if defined(CHANNEL_REMDESK_SERVER) + RemdeskServerContext* remdesk = nullptr; +#endif +#if defined(CHANNEL_ENCOMSP_SERVER) + EncomspServerContext* encomsp = nullptr; +#endif +#if defined(CHANNEL_RAIL_SERVER) + RailServerContext* rail = nullptr; +#endif +#if defined(CHANNEL_TELEMETRY_SERVER) + TelemetryServerContext* telemetry = nullptr; +#endif +#if defined(CHANNEL_RDPGFX_SERVER) + RdpgfxServerContext* rdpgfx = nullptr; +#endif +#if defined(CHANNEL_DISP_SERVER) + DispServerContext* disp = nullptr; +#endif +#if defined(CHANNEL_RDPEMSC_SERVER) + MouseCursorServerContext* mouse_cursor = nullptr; +#endif /* CHANNEL_RDPEMSC_SERVER */ +#if defined(CHANNEL_RDPECAM_SERVER) + CamDevEnumServerContext* camera_enumerator = nullptr; + CameraDeviceServerContext* camera_device = nullptr; +#endif +#if defined(CHANNEL_LOCATION_SERVER) + LocationServerContext* location = nullptr; +#endif /* CHANNEL_LOCATION_SERVER */ +#ifdef WITH_CHANNEL_GFXREDIR + GfxRedirServerContext* gfxredir; +#endif // WITH_CHANNEL_GFXREDIR +#if defined(CHANNEL_AUDIN_SERVER) + audin = audin_server_context_new(nullptr); +#endif +#if defined(CHANNEL_AUDIN_SERVER) + audin_server_context_free(audin); +#endif +#if defined(CHANNEL_RDPSND_SERVER) + rdpsnd = rdpsnd_server_context_new(nullptr); + rdpsnd_server_context_free(rdpsnd); +#endif +#if defined(CHANNEL_CLIPRDR_SERVER) + cliprdr = cliprdr_server_context_new(nullptr); + cliprdr_server_context_free(cliprdr); +#endif +#if defined(CHANNEL_ECHO_SERVER) + echo = echo_server_context_new(nullptr); + echo_server_context_free(echo); +#endif +#if defined(CHANNEL_RDPDR_SERVER) + rdpdr = rdpdr_server_context_new(nullptr); + rdpdr_server_context_free(rdpdr); +#endif +#if defined(CHANNEL_DRDYNVC_SERVER) + drdynvc = drdynvc_server_context_new(nullptr); + drdynvc_server_context_free(drdynvc); +#endif +#if defined(CHANNEL_RDPEI_SERVER) + rdpei = rdpei_server_context_new(nullptr); + rdpei_server_context_free(rdpei); +#endif +#if defined(CHANNEL_REMDESK_SERVER) + remdesk = remdesk_server_context_new(nullptr); + remdesk_server_context_free(remdesk); +#endif +#if defined(CHANNEL_ENCOMSP_SERVER) + encomsp = encomsp_server_context_new(nullptr); + encomsp_server_context_free(encomsp); +#endif +#if defined(CHANNEL_RAIL_SERVER) + rail = rail_server_context_new(nullptr); + rail_server_context_free(rail); +#endif +#if defined(CHANNEL_TELEMETRY_SERVER) + telemetry = telemetry_server_context_new(nullptr); + telemetry_server_context_free(telemetry); +#endif +#if defined(CHANNEL_RDPGFX_SERVER) + rdpgfx = rdpgfx_server_context_new(nullptr); + rdpgfx_server_context_free(rdpgfx); +#endif +#if defined(CHANNEL_DISP_SERVER) + disp = disp_server_context_new(nullptr); + disp_server_context_free(disp); +#endif +#if defined(CHANNEL_RDPEMSC_SERVER) + mouse_cursor = mouse_cursor_server_context_new(nullptr); + mouse_cursor_server_context_free(mouse_cursor); +#endif /* CHANNEL_RDPEMSC_SERVER */ + +#if defined(CHANNEL_RDPECAM_SERVER) + camera_enumerator = cam_dev_enum_server_context_new(nullptr); + cam_dev_enum_server_context_free(camera_enumerator); + camera_device = camera_device_server_context_new(nullptr); + camera_device_server_context_free(camera_device); +#endif + +#if defined(CHANNEL_LOCATION_SERVER) + location = location_server_context_new(nullptr); + location_server_context_free(location); +#endif /* CHANNEL_LOCATION_SERVER */ + +#ifdef WITH_CHANNEL_GFXREDIR + gfxredir = gfxredir_server_context_new(nullptr); + gfxredir_server_context_free(gfxredir); +#endif // WITH_CHANNEL_GFXREDIR +#if defined(CHANNEL_AINPUT_SERVER) + { + ainput_server_context* ainput = ainput_server_context_new(nullptr); + ainput_server_context_free(ainput); + } +#endif +} + +/** + * end of ugly symbols import workaround + */ diff --git a/third_party/FreeRDP/channels/server/channels.h b/third_party/FreeRDP/channels/server/channels.h new file mode 100644 index 0000000..a6c4791 --- /dev/null +++ b/third_party/FreeRDP/channels/server/channels.h @@ -0,0 +1,24 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Server Channels + * + * Copyright 2011-2012 Vic Lee + * Copyright 2012 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_SERVER_CHANNELS_H +#define FREERDP_CHANNEL_SERVER_CHANNELS_H + +#endif /* FREERDP_CHANNEL_SERVER_CHANNELS_H */ diff --git a/third_party/FreeRDP/channels/smartcard/CMakeLists.txt b/third_party/FreeRDP/channels/smartcard/CMakeLists.txt new file mode 100644 index 0000000..00ad99e --- /dev/null +++ b/third_party/FreeRDP/channels/smartcard/CMakeLists.txt @@ -0,0 +1,22 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel("smartcard") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/third_party/FreeRDP/channels/smartcard/ChannelOptions.cmake b/third_party/FreeRDP/channels/smartcard/ChannelOptions.cmake new file mode 100644 index 0000000..14966b0 --- /dev/null +++ b/third_party/FreeRDP/channels/smartcard/ChannelOptions.cmake @@ -0,0 +1,20 @@ +set(OPTION_DEFAULT ON) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT OFF) + +define_channel_options( + NAME + "smartcard" + TYPE + "device" + DESCRIPTION + "Smart Card Virtual Channel Extension" + SPECIFICATIONS + "[MS-RDPESC]" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/smartcard/client/CMakeLists.txt b/third_party/FreeRDP/channels/smartcard/client/CMakeLists.txt new file mode 100644 index 0000000..473f3d9 --- /dev/null +++ b/third_party/FreeRDP/channels/smartcard/client/CMakeLists.txt @@ -0,0 +1,26 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_client("smartcard") + +set(${MODULE_PREFIX}_SRCS smartcard_main.c smartcard_main.h) + +set(${MODULE_PREFIX}_LIBS winpr freerdp ${OPENSSL_LIBRARIES}) +if(WITH_SMARTCARD_EMULATE) + list(APPEND ${MODULE_PREFIX}_LIBS ZLIB::ZLIB) +endif() +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DeviceServiceEntry") diff --git a/third_party/FreeRDP/channels/smartcard/client/smartcard_main.c b/third_party/FreeRDP/channels/smartcard/client/smartcard_main.c new file mode 100644 index 0000000..826a34f --- /dev/null +++ b/third_party/FreeRDP/channels/smartcard/client/smartcard_main.c @@ -0,0 +1,717 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Smartcard Device Service Virtual Channel + * + * Copyright 2011 O.S. Systems Software Ltda. + * Copyright 2011 Eduardo Fiss Beloni + * Copyright 2011 Anthony Tong + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2016 David PHAM-VAN + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "smartcard_main.h" + +#define CAST_FROM_DEVICE(device) cast_device_from(device, __func__, __FILE__, __LINE__) + +typedef struct +{ + SMARTCARD_OPERATION operation; + IRP* irp; +} scard_irp_queue_element; + +static void smartcard_context_free(void* pCtx); + +static UINT smartcard_complete_irp(SMARTCARD_DEVICE* smartcard, IRP* irp, BOOL* handled); + +static SMARTCARD_DEVICE* cast_device_from(DEVICE* device, const char* fkt, const char* file, + size_t line) +{ + if (!device) + { + WLog_ERR(TAG, "%s [%s:%" PRIuz "] Called smartcard channel with nullptr device", fkt, file, + line); + return nullptr; + } + + if (device->type != RDPDR_DTYP_SMARTCARD) + { + WLog_ERR(TAG, + "%s [%s:%" PRIuz "] Called smartcard channel with invalid device of type %" PRIx32, + fkt, file, line, device->type); + return nullptr; + } + + return (SMARTCARD_DEVICE*)device; +} + +static DWORD WINAPI smartcard_context_thread(LPVOID arg) +{ + SMARTCARD_CONTEXT* pContext = (SMARTCARD_CONTEXT*)arg; + DWORD nCount = 0; + DWORD waitStatus = 0; + HANDLE hEvents[2] = WINPR_C_ARRAY_INIT; + wMessage message = WINPR_C_ARRAY_INIT; + SMARTCARD_DEVICE* smartcard = nullptr; + UINT error = CHANNEL_RC_OK; + smartcard = pContext->smartcard; + + hEvents[nCount++] = MessageQueue_Event(pContext->IrpQueue); + + while (1) + { + waitStatus = WaitForMultipleObjects(nCount, hEvents, FALSE, INFINITE); + + if (waitStatus == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "!", error); + break; + } + + waitStatus = WaitForSingleObject(MessageQueue_Event(pContext->IrpQueue), 0); + + if (waitStatus == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error); + break; + } + + if (waitStatus == WAIT_OBJECT_0) + { + scard_irp_queue_element* element = nullptr; + + if (!MessageQueue_Peek(pContext->IrpQueue, &message, TRUE)) + { + WLog_ERR(TAG, "MessageQueue_Peek failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (message.id == WMQ_QUIT) + break; + + element = (scard_irp_queue_element*)message.wParam; + + if (element) + { + BOOL handled = FALSE; + WINPR_ASSERT(smartcard); + + const LONG status = + smartcard_irp_device_control_call(smartcard->callctx, element->irp->output, + &element->irp->IoStatus, &element->operation); + if (status) + { + element->irp->Discard(element->irp); + smartcard_operation_free(&element->operation, TRUE); + WLog_ERR(TAG, + "smartcard_irp_device_control_call failed with error %s [%" PRId32 "]", + NtStatus2Tag(status), status); + error = (UINT)status; + break; + } + + error = smartcard_complete_irp(smartcard, element->irp, &handled); + if (!handled) + element->irp->Discard(element->irp); + smartcard_operation_free(&element->operation, TRUE); + + if (error) + { + WLog_ERR(TAG, "smartcard_complete_irp failed with %s [%" PRIu32 "]", + WTSErrorToString(error), error); + break; + } + } + } + } + + if (error && smartcard->rdpcontext) + setChannelError(smartcard->rdpcontext, error, "smartcard_context_thread reported an error"); + + ExitThread(error); + return error; +} + +static void smartcard_operation_queue_free(void* obj) +{ + wMessage* msg = obj; + if (!msg) + return; + if (msg->id != 0) + return; + + scard_irp_queue_element* element = (scard_irp_queue_element*)msg->wParam; + if (!element) + return; + WINPR_ASSERT(element->irp); + WINPR_ASSERT(element->irp->Discard); + element->irp->Discard(element->irp); + smartcard_operation_free(&element->operation, TRUE); +} + +static void* smartcard_context_new(void* smartcard, SCARDCONTEXT hContext) +{ + SMARTCARD_CONTEXT* pContext = (SMARTCARD_CONTEXT*)calloc(1, sizeof(SMARTCARD_CONTEXT)); + + WLog_VRB(TAG, "smartcard context create %p", (const void*)pContext); + if (!pContext) + { + WLog_ERR(TAG, "calloc failed!"); + return pContext; + } + + pContext->smartcard = smartcard; + pContext->hContext = hContext; + pContext->IrpQueue = MessageQueue_New(nullptr); + + if (!pContext->IrpQueue) + { + WLog_ERR(TAG, "MessageQueue_New failed!"); + goto fail; + } + + { + wObject* obj = MessageQueue_Object(pContext->IrpQueue); + WINPR_ASSERT(obj); + obj->fnObjectFree = smartcard_operation_queue_free; + } + + pContext->thread = CreateThread(nullptr, 0, smartcard_context_thread, pContext, 0, nullptr); + + if (!pContext->thread) + { + WLog_ERR(TAG, "CreateThread failed!"); + goto fail; + } + + return pContext; +fail: + smartcard_context_free(pContext); + return nullptr; +} + +void smartcard_context_free(void* pCtx) +{ + SMARTCARD_CONTEXT* pContext = pCtx; + + WLog_VRB(TAG, "smartcard context destroy %p", pCtx); + if (!pContext) + return; + + /* cancel blocking calls like SCardGetStatusChange */ + WINPR_ASSERT(pContext->smartcard); + smartcard_call_cancel_context(pContext->smartcard->callctx, pContext->hContext); + + if (pContext->IrpQueue) + { + if (MessageQueue_PostQuit(pContext->IrpQueue, 0)) + { + if (WaitForSingleObject(pContext->thread, INFINITE) == WAIT_FAILED) + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", GetLastError()); + + (void)CloseHandle(pContext->thread); + } + MessageQueue_Free(pContext->IrpQueue); + } + smartcard_call_release_context(pContext->smartcard->callctx, pContext->hContext); + free(pContext); +} + +static UINT smartcard_free_(SMARTCARD_DEVICE* smartcard) +{ + if (!smartcard) + return CHANNEL_RC_OK; + + if (smartcard->IrpQueue) + { + MessageQueue_Free(smartcard->IrpQueue); + (void)CloseHandle(smartcard->thread); + } + + Stream_Free(smartcard->device.data, TRUE); + ListDictionary_Free(smartcard->rgOutstandingMessages); + + smartcard_call_context_free(smartcard->callctx); + + free(smartcard); + return CHANNEL_RC_OK; +} +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT smartcard_free(DEVICE* device) +{ + SMARTCARD_DEVICE* smartcard = CAST_FROM_DEVICE(device); + + WLog_VRB(TAG, "smartcard device destroy: %p", (const void*)smartcard); + if (!smartcard) + return ERROR_INVALID_PARAMETER; + + /** + * Calling smartcard_release_all_contexts to unblock all operations waiting for transactions + * to unlock. + */ + smartcard_call_cancel_all_context(smartcard->callctx); + + /* Stopping all threads and cancelling all IRPs */ + + if (smartcard->IrpQueue) + { + if (MessageQueue_PostQuit(smartcard->IrpQueue, 0) && + (WaitForSingleObject(smartcard->thread, INFINITE) == WAIT_FAILED)) + { + DWORD error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error); + return error; + } + } + + return smartcard_free_(smartcard); +} + +/** + * Initialization occurs when the protocol server sends a device announce message. + * At that time, we need to cancel all outstanding IRPs. + */ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT smartcard_init(DEVICE* device) +{ + SMARTCARD_DEVICE* smartcard = CAST_FROM_DEVICE(device); + + WLog_VRB(TAG, "smartcard device init %p", (const void*)smartcard); + if (!smartcard) + return ERROR_INVALID_PARAMETER; + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT smartcard_complete_irp(SMARTCARD_DEVICE* smartcard, IRP* irp, BOOL* handled) +{ + WINPR_ASSERT(smartcard); + WINPR_ASSERT(irp); + WINPR_ASSERT(handled); + + uintptr_t key = (uintptr_t)irp->CompletionId + 1; + ListDictionary_Remove(smartcard->rgOutstandingMessages, (void*)key); + + WINPR_ASSERT(irp->Complete); + *handled = TRUE; + return irp->Complete(irp); +} + +/** + * Multiple threads and SCardGetStatusChange: + * http://musclecard.996296.n3.nabble.com/Multiple-threads-and-SCardGetStatusChange-td4430.html + */ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT smartcard_process_irp(SMARTCARD_DEVICE* smartcard, IRP* irp, BOOL* handled) +{ + LONG status = 0; + BOOL asyncIrp = FALSE; + SMARTCARD_CONTEXT* pContext = nullptr; + + WINPR_ASSERT(smartcard); + WINPR_ASSERT(handled); + WINPR_ASSERT(irp); + WINPR_ASSERT(irp->Complete); + + uintptr_t key = (uintptr_t)irp->CompletionId + 1; + + if (!ListDictionary_Add(smartcard->rgOutstandingMessages, (void*)key, irp)) + { + WLog_ERR(TAG, "ListDictionary_Add failed!"); + return ERROR_INTERNAL_ERROR; + } + + if (irp->MajorFunction == IRP_MJ_DEVICE_CONTROL) + { + scard_irp_queue_element* element = calloc(1, sizeof(scard_irp_queue_element)); + if (!element) + return ERROR_OUTOFMEMORY; + + element->irp = irp; + element->operation.completionID = irp->CompletionId; + + status = smartcard_irp_device_control_decode(irp->input, irp->CompletionId, irp->FileId, + &element->operation); + + if (status != SCARD_S_SUCCESS) + { + UINT error = 0; + + smartcard_operation_free(&element->operation, TRUE); + irp->IoStatus = STATUS_UNSUCCESSFUL; + + if ((error = smartcard_complete_irp(smartcard, irp, handled))) + { + WLog_ERR(TAG, "Queue_Enqueue failed!"); + return error; + } + + return CHANNEL_RC_OK; + } + + asyncIrp = TRUE; + + switch (element->operation.ioControlCode) + { + case SCARD_IOCTL_ESTABLISHCONTEXT: + case SCARD_IOCTL_RELEASECONTEXT: + case SCARD_IOCTL_ISVALIDCONTEXT: + case SCARD_IOCTL_CANCEL: + case SCARD_IOCTL_ACCESSSTARTEDEVENT: + case SCARD_IOCTL_RELEASETARTEDEVENT: + asyncIrp = FALSE; + break; + + case SCARD_IOCTL_LISTREADERGROUPSA: + case SCARD_IOCTL_LISTREADERGROUPSW: + case SCARD_IOCTL_LISTREADERSA: + case SCARD_IOCTL_LISTREADERSW: + case SCARD_IOCTL_INTRODUCEREADERGROUPA: + case SCARD_IOCTL_INTRODUCEREADERGROUPW: + case SCARD_IOCTL_FORGETREADERGROUPA: + case SCARD_IOCTL_FORGETREADERGROUPW: + case SCARD_IOCTL_INTRODUCEREADERA: + case SCARD_IOCTL_INTRODUCEREADERW: + case SCARD_IOCTL_FORGETREADERA: + case SCARD_IOCTL_FORGETREADERW: + case SCARD_IOCTL_ADDREADERTOGROUPA: + case SCARD_IOCTL_ADDREADERTOGROUPW: + case SCARD_IOCTL_REMOVEREADERFROMGROUPA: + case SCARD_IOCTL_REMOVEREADERFROMGROUPW: + case SCARD_IOCTL_LOCATECARDSA: + case SCARD_IOCTL_LOCATECARDSW: + case SCARD_IOCTL_LOCATECARDSBYATRA: + case SCARD_IOCTL_LOCATECARDSBYATRW: + case SCARD_IOCTL_READCACHEA: + case SCARD_IOCTL_READCACHEW: + case SCARD_IOCTL_WRITECACHEA: + case SCARD_IOCTL_WRITECACHEW: + case SCARD_IOCTL_GETREADERICON: + case SCARD_IOCTL_GETDEVICETYPEID: + case SCARD_IOCTL_GETSTATUSCHANGEA: + case SCARD_IOCTL_GETSTATUSCHANGEW: + case SCARD_IOCTL_CONNECTA: + case SCARD_IOCTL_CONNECTW: + case SCARD_IOCTL_RECONNECT: + case SCARD_IOCTL_DISCONNECT: + case SCARD_IOCTL_BEGINTRANSACTION: + case SCARD_IOCTL_ENDTRANSACTION: + case SCARD_IOCTL_STATE: + case SCARD_IOCTL_STATUSA: + case SCARD_IOCTL_STATUSW: + case SCARD_IOCTL_TRANSMIT: + case SCARD_IOCTL_CONTROL: + case SCARD_IOCTL_GETATTRIB: + case SCARD_IOCTL_SETATTRIB: + case SCARD_IOCTL_GETTRANSMITCOUNT: + asyncIrp = TRUE; + break; + default: + break; + } + + pContext = smartcard_call_get_context(smartcard->callctx, element->operation.hContext); + + if (!pContext) + asyncIrp = FALSE; + + if (!asyncIrp) + { + UINT error = 0; + + status = + smartcard_irp_device_control_call(smartcard->callctx, element->irp->output, + &element->irp->IoStatus, &element->operation); + smartcard_operation_free(&element->operation, TRUE); + + if (status) + { + WLog_ERR(TAG, "smartcard_irp_device_control_call failed with error %" PRId32 "!", + status); + return (UINT32)status; + } + + if ((error = smartcard_complete_irp(smartcard, irp, handled))) + { + WLog_ERR(TAG, "Queue_Enqueue failed!"); + return error; + } + } + else + { + if (pContext) + { + if (!MessageQueue_Post(pContext->IrpQueue, nullptr, 0, (void*)element, nullptr)) + { + smartcard_operation_free(&element->operation, TRUE); + WLog_ERR(TAG, "MessageQueue_Post failed!"); + return ERROR_INTERNAL_ERROR; + } + *handled = TRUE; + } + } + } + else + { + UINT ustatus = 0; + WLog_ERR(TAG, "Unexpected SmartCard IRP: MajorFunction %s, MinorFunction: 0x%08" PRIX32 "", + rdpdr_irp_string(irp->MajorFunction), irp->MinorFunction); + irp->IoStatus = STATUS_NOT_SUPPORTED; + + if ((ustatus = smartcard_complete_irp(smartcard, irp, handled))) + { + WLog_ERR(TAG, "Queue_Enqueue failed!"); + return ustatus; + } + } + + return CHANNEL_RC_OK; +} + +static DWORD WINAPI smartcard_thread_func(LPVOID arg) +{ + IRP* irp = nullptr; + DWORD nCount = 0; + DWORD status = 0; + HANDLE hEvents[1] = WINPR_C_ARRAY_INIT; + wMessage message = WINPR_C_ARRAY_INIT; + UINT error = CHANNEL_RC_OK; + SMARTCARD_DEVICE* smartcard = CAST_FROM_DEVICE(arg); + + if (!smartcard) + return ERROR_INVALID_PARAMETER; + + hEvents[nCount++] = MessageQueue_Event(smartcard->IrpQueue); + + while (1) + { + status = WaitForMultipleObjects(nCount, hEvents, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "!", error); + break; + } + + if (status == WAIT_OBJECT_0) + { + if (!MessageQueue_Peek(smartcard->IrpQueue, &message, TRUE)) + { + WLog_ERR(TAG, "MessageQueue_Peek failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (message.id == WMQ_QUIT) + break; + + irp = (IRP*)message.wParam; + + if (irp) + { + BOOL handled = FALSE; + if ((error = smartcard_process_irp(smartcard, irp, &handled))) + { + WLog_ERR(TAG, "smartcard_process_irp failed with error %" PRIu32 "!", error); + goto out; + } + if (!handled) + { + WINPR_ASSERT(irp->Discard); + irp->Discard(irp); + } + } + } + } + +out: + + if (error && smartcard->rdpcontext) + setChannelError(smartcard->rdpcontext, error, "smartcard_thread_func reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT smartcard_irp_request(DEVICE* device, IRP* irp) +{ + SMARTCARD_DEVICE* smartcard = CAST_FROM_DEVICE(device); + + if (!smartcard) + { + irp->Discard(irp); + return ERROR_INVALID_PARAMETER; + } + + if (!MessageQueue_Post(smartcard->IrpQueue, nullptr, 0, (void*)irp, nullptr)) + { + WLog_ERR(TAG, "MessageQueue_Post failed!"); + irp->Discard(irp); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +static void smartcard_free_irp(void* obj) +{ + wMessage* msg = obj; + if (!msg) + return; + if (msg->id != 0) + return; + + IRP* irp = (IRP*)msg->wParam; + if (!irp) + return; + WINPR_ASSERT(irp->Discard); + irp->Discard(irp); +} + +/* smartcard is always built-in */ +#define DeviceServiceEntry smartcard_DeviceServiceEntry + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints)) +{ + size_t length = 0; + UINT error = CHANNEL_RC_NO_MEMORY; + + SMARTCARD_DEVICE* smartcard = (SMARTCARD_DEVICE*)calloc(1, sizeof(SMARTCARD_DEVICE)); + WLog_VRB(TAG, "smartcard device create: %p", (const void*)smartcard); + if (!smartcard) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + smartcard->device.type = RDPDR_DTYP_SMARTCARD; + smartcard->device.name = "SCARD"; + smartcard->device.IRPRequest = smartcard_irp_request; + smartcard->device.Init = smartcard_init; + smartcard->device.Free = smartcard_free; + smartcard->rdpcontext = pEntryPoints->rdpcontext; + length = strlen(smartcard->device.name); + smartcard->device.data = Stream_New(nullptr, length + 1); + + if (!smartcard->device.data) + { + WLog_ERR(TAG, "Stream_New failed!"); + goto fail; + } + + Stream_Write(smartcard->device.data, "SCARD", 6); + smartcard->IrpQueue = MessageQueue_New(nullptr); + + if (!smartcard->IrpQueue) + { + WLog_ERR(TAG, "MessageQueue_New failed!"); + goto fail; + } + + wObject* obj = MessageQueue_Object(smartcard->IrpQueue); + WINPR_ASSERT(obj); + obj->fnObjectFree = smartcard_free_irp; + + smartcard->rgOutstandingMessages = ListDictionary_New(TRUE); + + if (!smartcard->rgOutstandingMessages) + { + WLog_ERR(TAG, "ListDictionary_New failed!"); + goto fail; + } + + smartcard->callctx = smartcard_call_context_new(smartcard->rdpcontext->settings); + if (!smartcard->callctx) + goto fail; + + if (!smarcard_call_set_callbacks(smartcard->callctx, smartcard, smartcard_context_new, + smartcard_context_free)) + goto fail; + + if ((error = pEntryPoints->RegisterDevice(pEntryPoints->devman, &smartcard->device))) + { + WLog_ERR(TAG, "RegisterDevice failed!"); + goto fail; + } + + smartcard->thread = + CreateThread(nullptr, 0, smartcard_thread_func, smartcard, CREATE_SUSPENDED, nullptr); + + if (!smartcard->thread) + { + WLog_ERR(TAG, "CreateThread failed!"); + error = ERROR_INTERNAL_ERROR; + goto fail; + } + + ResumeThread(smartcard->thread); + + if (pEntryPoints->device->Name) + { + if (!smartcard_call_context_add(smartcard->callctx, pEntryPoints->device->Name)) + goto fail; + } + + return CHANNEL_RC_OK; +fail: + smartcard_free_(smartcard); + return error; +} diff --git a/third_party/FreeRDP/channels/smartcard/client/smartcard_main.h b/third_party/FreeRDP/channels/smartcard/client/smartcard_main.h new file mode 100644 index 0000000..c588588 --- /dev/null +++ b/third_party/FreeRDP/channels/smartcard/client/smartcard_main.h @@ -0,0 +1,59 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Smartcard Device Service Virtual Channel + * + * Copyright 2011 O.S. Systems Software Ltda. + * Copyright 2011 Eduardo Fiss Beloni + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_SMARTCARD_CLIENT_MAIN_H +#define FREERDP_CHANNEL_SMARTCARD_CLIENT_MAIN_H + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#define TAG CHANNELS_TAG("smartcard.client") + +typedef struct +{ + DEVICE device; + + HANDLE thread; + scard_call_context* callctx; + wMessageQueue* IrpQueue; + wListDictionary* rgOutstandingMessages; + rdpContext* rdpcontext; +} SMARTCARD_DEVICE; + +typedef struct +{ + HANDLE thread; + SCARDCONTEXT hContext; + wMessageQueue* IrpQueue; + SMARTCARD_DEVICE* smartcard; +} SMARTCARD_CONTEXT; + +#endif /* FREERDP_CHANNEL_SMARTCARD_CLIENT_MAIN_H */ diff --git a/third_party/FreeRDP/channels/sshagent/CMakeLists.txt b/third_party/FreeRDP/channels/sshagent/CMakeLists.txt new file mode 100644 index 0000000..d0e7c12 --- /dev/null +++ b/third_party/FreeRDP/channels/sshagent/CMakeLists.txt @@ -0,0 +1,23 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# Copyright 2017 Ben Cohen +# +# 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. + +define_channel("sshagent") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/third_party/FreeRDP/channels/sshagent/ChannelOptions.cmake b/third_party/FreeRDP/channels/sshagent/ChannelOptions.cmake new file mode 100644 index 0000000..6672256 --- /dev/null +++ b/third_party/FreeRDP/channels/sshagent/ChannelOptions.cmake @@ -0,0 +1,20 @@ +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options( + NAME + "sshagent" + TYPE + "dynamic" + DESCRIPTION + "SSH Agent Forwarding (experimental)" + SPECIFICATIONS + "" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/sshagent/client/CMakeLists.txt b/third_party/FreeRDP/channels/sshagent/client/CMakeLists.txt new file mode 100644 index 0000000..b09c4f6 --- /dev/null +++ b/third_party/FreeRDP/channels/sshagent/client/CMakeLists.txt @@ -0,0 +1,27 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# Copyright 2017 Ben Cohen +# +# 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. + +define_channel_client("sshagent") + +set(${MODULE_PREFIX}_SRCS sshagent_main.c sshagent_main.h) + +set(${MODULE_PREFIX}_LIBS winpr) + +include_directories(..) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") diff --git a/third_party/FreeRDP/channels/sshagent/client/sshagent_main.c b/third_party/FreeRDP/channels/sshagent/client/sshagent_main.c new file mode 100644 index 0000000..614bde8 --- /dev/null +++ b/third_party/FreeRDP/channels/sshagent/client/sshagent_main.c @@ -0,0 +1,398 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SSH Agent Virtual Channel Extension + * + * Copyright 2013 Christian Hofstaedtler + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2017 Ben Cohen + * + * 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. + */ + +/* + * sshagent_main.c: DVC plugin to forward queries from RDP to the ssh-agent + * + * This relays data to and from an ssh-agent program equivalent running on the + * RDP server to an ssh-agent running locally. Unlike the normal ssh-agent, + * which sends data over an SSH channel, the data is send over an RDP dynamic + * virtual channel. + * + * protocol specification: + * Forward data verbatim over RDP dynamic virtual channel named "sshagent" + * between a ssh client on the xrdp server and the real ssh-agent where + * the RDP client is running. Each connection by a separate client to + * xrdp-ssh-agent gets a separate DVC invocation. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "sshagent_main.h" + +#include +#include +#include + +#define TAG CHANNELS_TAG("sshagent.client") + +typedef struct +{ + IWTSListenerCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + + rdpContext* rdpcontext; + const char* agent_uds_path; +} SSHAGENT_LISTENER_CALLBACK; + +typedef struct +{ + GENERIC_CHANNEL_CALLBACK generic; + + rdpContext* rdpcontext; + int agent_fd; + HANDLE thread; + CRITICAL_SECTION lock; +} SSHAGENT_CHANNEL_CALLBACK; + +typedef struct +{ + IWTSPlugin iface; + + SSHAGENT_LISTENER_CALLBACK* listener_callback; + + rdpContext* rdpcontext; +} SSHAGENT_PLUGIN; + +/** + * Function to open the connection to the sshagent + * + * @return The fd on success, otherwise -1 + */ +static int connect_to_sshagent(const char* udspath) +{ + WINPR_ASSERT(udspath); + + int agent_fd = socket(AF_UNIX, SOCK_STREAM, 0); + + if (agent_fd == -1) + { + WLog_ERR(TAG, "Can't open Unix domain socket!"); + return -1; + } + + struct sockaddr_un addr = WINPR_C_ARRAY_INIT; + + addr.sun_family = AF_UNIX; + + strncpy(addr.sun_path, udspath, sizeof(addr.sun_path) - 1); + + int rc = connect(agent_fd, (struct sockaddr*)&addr, sizeof(addr)); + + if (rc != 0) + { + WLog_ERR(TAG, "Can't connect to Unix domain socket \"%s\"!", udspath); + close(agent_fd); + return -1; + } + + return agent_fd; +} + +/** + * Entry point for thread to read from the ssh-agent socket and forward + * the data to RDP + * + * @return 0 + */ +static DWORD WINAPI sshagent_read_thread(LPVOID data) +{ + SSHAGENT_CHANNEL_CALLBACK* callback = (SSHAGENT_CHANNEL_CALLBACK*)data; + WINPR_ASSERT(callback); + + BYTE buffer[4096] = WINPR_C_ARRAY_INIT; + int going = 1; + UINT status = CHANNEL_RC_OK; + + while (going) + { + const ssize_t bytes_read = read(callback->agent_fd, buffer, sizeof(buffer)); + + if (bytes_read == 0) + { + /* Socket closed cleanly at other end */ + going = 0; + } + else if (bytes_read < 0) + { + if (errno != EINTR) + { + WLog_ERR(TAG, "Error reading from sshagent, errno=%d", errno); + status = ERROR_READ_FAULT; + going = 0; + } + } + else if ((size_t)bytes_read > ULONG_MAX) + { + status = ERROR_READ_FAULT; + going = 0; + } + else + { + /* Something read: forward to virtual channel */ + IWTSVirtualChannel* channel = callback->generic.channel; + status = channel->Write(channel, (ULONG)bytes_read, buffer, nullptr); + + if (status != CHANNEL_RC_OK) + { + going = 0; + } + } + } + + close(callback->agent_fd); + + if (status != CHANNEL_RC_OK) + setChannelError(callback->rdpcontext, status, "sshagent_read_thread reported an error"); + + ExitThread(status); + return status; +} + +/** + * Callback for data received from the RDP server; forward this to ssh-agent + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT sshagent_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data) +{ + SSHAGENT_CHANNEL_CALLBACK* callback = (SSHAGENT_CHANNEL_CALLBACK*)pChannelCallback; + WINPR_ASSERT(callback); + + BYTE* pBuffer = Stream_Pointer(data); + size_t cbSize = Stream_GetRemainingLength(data); + BYTE* pos = pBuffer; + /* Forward what we have received to the ssh agent */ + size_t bytes_to_write = cbSize; + errno = 0; + + while (bytes_to_write > 0) + { + const ssize_t bytes_written = write(callback->agent_fd, pos, bytes_to_write); + + if (bytes_written < 0) + { + if (errno != EINTR) + { + WLog_ERR(TAG, "Error writing to sshagent, errno=%d", errno); + return ERROR_WRITE_FAULT; + } + } + else + { + bytes_to_write -= WINPR_ASSERTING_INT_CAST(size_t, bytes_written); + pos += bytes_written; + } + } + + /* Consume stream */ + Stream_Seek(data, cbSize); + return CHANNEL_RC_OK; +} + +/** + * Callback for when the virtual channel is closed + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT sshagent_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + SSHAGENT_CHANNEL_CALLBACK* callback = (SSHAGENT_CHANNEL_CALLBACK*)pChannelCallback; + WINPR_ASSERT(callback); + + /* Call shutdown() to wake up the read() in sshagent_read_thread(). */ + shutdown(callback->agent_fd, SHUT_RDWR); + EnterCriticalSection(&callback->lock); + + if (WaitForSingleObject(callback->thread, INFINITE) == WAIT_FAILED) + { + UINT error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error); + return error; + } + + (void)CloseHandle(callback->thread); + LeaveCriticalSection(&callback->lock); + DeleteCriticalSection(&callback->lock); + free(callback); + return CHANNEL_RC_OK; +} + +/** + * Callback for when a new virtual channel is opened + * + * @return 0 on success, otherwise a Win32 error code + */ +// NOLINTBEGIN(readability-non-const-parameter) +static UINT sshagent_on_new_channel_connection(IWTSListenerCallback* pListenerCallback, + IWTSVirtualChannel* pChannel, BYTE* Data, + BOOL* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +// NOLINTEND(readability-non-const-parameter) +{ + SSHAGENT_LISTENER_CALLBACK* listener_callback = (SSHAGENT_LISTENER_CALLBACK*)pListenerCallback; + WINPR_UNUSED(Data); + WINPR_UNUSED(pbAccept); + + SSHAGENT_CHANNEL_CALLBACK* callback = + (SSHAGENT_CHANNEL_CALLBACK*)calloc(1, sizeof(SSHAGENT_CHANNEL_CALLBACK)); + + if (!callback) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + /* Now open a connection to the local ssh-agent. Do this for each + * connection to the plugin in case we mess up the agent session. */ + callback->agent_fd = connect_to_sshagent(listener_callback->agent_uds_path); + + if (callback->agent_fd == -1) + { + free(callback); + return CHANNEL_RC_INITIALIZATION_ERROR; + } + + InitializeCriticalSection(&callback->lock); + + GENERIC_CHANNEL_CALLBACK* generic = &callback->generic; + generic->iface.OnDataReceived = sshagent_on_data_received; + generic->iface.OnClose = sshagent_on_close; + generic->plugin = listener_callback->plugin; + generic->channel_mgr = listener_callback->channel_mgr; + generic->channel = pChannel; + callback->rdpcontext = listener_callback->rdpcontext; + callback->thread = CreateThread(nullptr, 0, sshagent_read_thread, (void*)callback, 0, nullptr); + + if (!callback->thread) + { + WLog_ERR(TAG, "CreateThread failed!"); + DeleteCriticalSection(&callback->lock); + free(callback); + return CHANNEL_RC_INITIALIZATION_ERROR; + } + + *ppCallback = (IWTSVirtualChannelCallback*)callback; + return CHANNEL_RC_OK; +} + +/** + * Callback for when the plugin is initialised + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT sshagent_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr) +{ + SSHAGENT_PLUGIN* sshagent = (SSHAGENT_PLUGIN*)pPlugin; + WINPR_ASSERT(sshagent); + WINPR_ASSERT(pChannelMgr); + + sshagent->listener_callback = + (SSHAGENT_LISTENER_CALLBACK*)calloc(1, sizeof(SSHAGENT_LISTENER_CALLBACK)); + + if (!sshagent->listener_callback) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + sshagent->listener_callback->rdpcontext = sshagent->rdpcontext; + sshagent->listener_callback->iface.OnNewChannelConnection = sshagent_on_new_channel_connection; + sshagent->listener_callback->plugin = pPlugin; + sshagent->listener_callback->channel_mgr = pChannelMgr; + // NOLINTNEXTLINE(concurrency-mt-unsafe) + sshagent->listener_callback->agent_uds_path = getenv("SSH_AUTH_SOCK"); + + if (sshagent->listener_callback->agent_uds_path == nullptr) + { + WLog_ERR(TAG, "Environment variable $SSH_AUTH_SOCK undefined!"); + free(sshagent->listener_callback); + sshagent->listener_callback = nullptr; + return CHANNEL_RC_INITIALIZATION_ERROR; + } + + return pChannelMgr->CreateListener(pChannelMgr, "SSHAGENT", 0, + (IWTSListenerCallback*)sshagent->listener_callback, nullptr); +} + +/** + * Callback for when the plugin is terminated + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT sshagent_plugin_terminated(IWTSPlugin* pPlugin) +{ + SSHAGENT_PLUGIN* sshagent = (SSHAGENT_PLUGIN*)pPlugin; + free(sshagent); + return CHANNEL_RC_OK; +} + +/** + * Main entry point for sshagent DVC plugin + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE sshagent_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)) +{ + UINT status = CHANNEL_RC_OK; + + WINPR_ASSERT(pEntryPoints); + + SSHAGENT_PLUGIN* sshagent = (SSHAGENT_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints, "sshagent"); + + if (!sshagent) + { + sshagent = (SSHAGENT_PLUGIN*)calloc(1, sizeof(SSHAGENT_PLUGIN)); + + if (!sshagent) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + sshagent->iface.Initialize = sshagent_plugin_initialize; + sshagent->iface.Connected = nullptr; + sshagent->iface.Disconnected = nullptr; + sshagent->iface.Terminated = sshagent_plugin_terminated; + sshagent->rdpcontext = pEntryPoints->GetRdpContext(pEntryPoints); + status = pEntryPoints->RegisterPlugin(pEntryPoints, "sshagent", &sshagent->iface); + } + + return status; +} diff --git a/third_party/FreeRDP/channels/sshagent/client/sshagent_main.h b/third_party/FreeRDP/channels/sshagent/client/sshagent_main.h new file mode 100644 index 0000000..1fcc214 --- /dev/null +++ b/third_party/FreeRDP/channels/sshagent/client/sshagent_main.h @@ -0,0 +1,42 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SSH Agent Virtual Channel Extension + * + * Copyright 2013 Christian Hofstaedtler + * Copyright 2017 Ben Cohen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SSHAGENT_MAIN_H +#define SSHAGENT_MAIN_H + +#include + +#include + +#include +#include +#include + +#define DVC_TAG CHANNELS_TAG("sshagent.client") +#ifdef WITH_DEBUG_SSHAGENT +#define DEBUG_SSHAGENT(...) WLog_DBG(DVC_TAG, __VA_ARGS__) +#else +#define DEBUG_SSHAGENT(...) \ + do \ + { \ + } while (0) +#endif + +#endif /* SSHAGENT_MAIN_H */ diff --git a/third_party/FreeRDP/channels/telemetry/CMakeLists.txt b/third_party/FreeRDP/channels/telemetry/CMakeLists.txt new file mode 100644 index 0000000..ddef6f7 --- /dev/null +++ b/third_party/FreeRDP/channels/telemetry/CMakeLists.txt @@ -0,0 +1,22 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2022 Pascal Nowack +# +# 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. + +define_channel("telemetry") + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/third_party/FreeRDP/channels/telemetry/ChannelOptions.cmake b/third_party/FreeRDP/channels/telemetry/ChannelOptions.cmake new file mode 100644 index 0000000..153fd57 --- /dev/null +++ b/third_party/FreeRDP/channels/telemetry/ChannelOptions.cmake @@ -0,0 +1,20 @@ +set(OPTION_DEFAULT ON) +set(OPTION_CLIENT_DEFAULT OFF) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options( + NAME + "telemetry" + TYPE + "dynamic" + DESCRIPTION + "Telemetry Virtual Channel Extension" + SPECIFICATIONS + "[MS-RDPET]" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/telemetry/server/CMakeLists.txt b/third_party/FreeRDP/channels/telemetry/server/CMakeLists.txt new file mode 100644 index 0000000..ea023cb --- /dev/null +++ b/third_party/FreeRDP/channels/telemetry/server/CMakeLists.txt @@ -0,0 +1,24 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2022 Pascal Nowack +# +# 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. + +define_channel_server("telemetry") + +set(${MODULE_PREFIX}_SRCS telemetry_main.c) + +set(${MODULE_PREFIX}_LIBS freerdp) + +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry") diff --git a/third_party/FreeRDP/channels/telemetry/server/telemetry_main.c b/third_party/FreeRDP/channels/telemetry/server/telemetry_main.c new file mode 100644 index 0000000..59c6f86 --- /dev/null +++ b/third_party/FreeRDP/channels/telemetry/server/telemetry_main.c @@ -0,0 +1,449 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Telemetry Virtual Channel Extension + * + * Copyright 2022 Pascal Nowack + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#define TAG CHANNELS_TAG("telemetry.server") + +typedef enum +{ + TELEMETRY_INITIAL, + TELEMETRY_OPENED, +} eTelemetryChannelState; + +typedef struct +{ + TelemetryServerContext context; + + HANDLE stopEvent; + + HANDLE thread; + void* telemetry_channel; + + DWORD SessionId; + + BOOL isOpened; + BOOL externalThread; + + /* Channel state */ + eTelemetryChannelState state; + + wStream* buffer; +} telemetry_server; + +static UINT telemetry_server_initialize(TelemetryServerContext* context, BOOL externalThread) +{ + UINT error = CHANNEL_RC_OK; + telemetry_server* telemetry = (telemetry_server*)context; + + WINPR_ASSERT(telemetry); + + if (telemetry->isOpened) + { + WLog_WARN(TAG, "Application error: TELEMETRY channel already initialized, " + "calling in this state is not possible!"); + return ERROR_INVALID_STATE; + } + + telemetry->externalThread = externalThread; + + return error; +} + +static UINT telemetry_server_open_channel(telemetry_server* telemetry) +{ + TelemetryServerContext* context = &telemetry->context; + DWORD Error = ERROR_SUCCESS; + HANDLE hEvent = nullptr; + DWORD BytesReturned = 0; + PULONG pSessionId = nullptr; + UINT32 channelId = 0; + BOOL status = TRUE; + + WINPR_ASSERT(telemetry); + + if (WTSQuerySessionInformationA(telemetry->context.vcm, WTS_CURRENT_SESSION, WTSSessionId, + (LPSTR*)&pSessionId, &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSQuerySessionInformationA failed!"); + return ERROR_INTERNAL_ERROR; + } + + telemetry->SessionId = (DWORD)*pSessionId; + WTSFreeMemory(pSessionId); + hEvent = WTSVirtualChannelManagerGetEventHandle(telemetry->context.vcm); + + if (WaitForSingleObject(hEvent, 1000) == WAIT_FAILED) + { + Error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", Error); + return Error; + } + + telemetry->telemetry_channel = WTSVirtualChannelOpenEx( + telemetry->SessionId, TELEMETRY_DVC_CHANNEL_NAME, WTS_CHANNEL_OPTION_DYNAMIC); + if (!telemetry->telemetry_channel) + { + Error = GetLastError(); + WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed with error %" PRIu32 "!", Error); + return Error; + } + + channelId = WTSChannelGetIdByHandle(telemetry->telemetry_channel); + + IFCALLRET(context->ChannelIdAssigned, status, context, channelId); + if (!status) + { + WLog_ERR(TAG, "context->ChannelIdAssigned failed!"); + return ERROR_INTERNAL_ERROR; + } + + return Error; +} + +static UINT telemetry_server_recv_rdp_telemetry_pdu(TelemetryServerContext* context, wStream* s) +{ + TELEMETRY_RDP_TELEMETRY_PDU pdu; + UINT error = CHANNEL_RC_OK; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 16)) + return ERROR_NO_DATA; + + Stream_Read_UINT32(s, pdu.PromptForCredentialsMillis); + Stream_Read_UINT32(s, pdu.PromptForCredentialsDoneMillis); + Stream_Read_UINT32(s, pdu.GraphicsChannelOpenedMillis); + Stream_Read_UINT32(s, pdu.FirstGraphicsReceivedMillis); + + IFCALLRET(context->RdpTelemetry, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->RdpTelemetry failed with error %" PRIu32 "", error); + + return error; +} + +static UINT telemetry_process_message(telemetry_server* telemetry) +{ + BOOL rc = 0; + UINT error = ERROR_INTERNAL_ERROR; + ULONG BytesReturned = 0; + BYTE MessageId = 0; + BYTE Length = 0; + wStream* s = nullptr; + + WINPR_ASSERT(telemetry); + WINPR_ASSERT(telemetry->telemetry_channel); + + s = telemetry->buffer; + WINPR_ASSERT(s); + + Stream_ResetPosition(s); + rc = WTSVirtualChannelRead(telemetry->telemetry_channel, 0, nullptr, 0, &BytesReturned); + if (!rc) + goto out; + + if (BytesReturned < 1) + { + error = CHANNEL_RC_OK; + goto out; + } + + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + if (WTSVirtualChannelRead(telemetry->telemetry_channel, 0, Stream_BufferAs(s, char), + (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + goto out; + } + + Stream_SetLength(s, BytesReturned); + if (!Stream_CheckAndLogRequiredLength(TAG, s, 2)) + return ERROR_NO_DATA; + + Stream_Read_UINT8(s, MessageId); + Stream_Read_UINT8(s, Length); + if (!Stream_CheckAndLogRequiredLength(TAG, s, Length)) + return ERROR_NO_DATA; + + switch (MessageId) + { + case 0x01: + error = telemetry_server_recv_rdp_telemetry_pdu(&telemetry->context, s); + break; + default: + WLog_ERR(TAG, "telemetry_process_message: unknown MessageId %" PRIu8 "", MessageId); + break; + } + +out: + if (error) + WLog_ERR(TAG, "Response failed with error %" PRIu32 "!", error); + + return error; +} + +static UINT telemetry_server_context_poll_int(TelemetryServerContext* context) +{ + telemetry_server* telemetry = (telemetry_server*)context; + UINT error = ERROR_INTERNAL_ERROR; + + WINPR_ASSERT(telemetry); + + switch (telemetry->state) + { + case TELEMETRY_INITIAL: + error = telemetry_server_open_channel(telemetry); + if (error) + WLog_ERR(TAG, "telemetry_server_open_channel failed with error %" PRIu32 "!", + error); + else + telemetry->state = TELEMETRY_OPENED; + break; + case TELEMETRY_OPENED: + error = telemetry_process_message(telemetry); + break; + default: + break; + } + + return error; +} + +static HANDLE telemetry_server_get_channel_handle(telemetry_server* telemetry) +{ + void* buffer = nullptr; + DWORD BytesReturned = 0; + HANDLE ChannelEvent = nullptr; + + WINPR_ASSERT(telemetry); + + if (WTSVirtualChannelQuery(telemetry->telemetry_channel, WTSVirtualEventHandle, &buffer, + &BytesReturned) == TRUE) + { + if (BytesReturned == sizeof(HANDLE)) + ChannelEvent = *(HANDLE*)buffer; + + WTSFreeMemory(buffer); + } + + return ChannelEvent; +} + +static DWORD WINAPI telemetry_server_thread_func(LPVOID arg) +{ + DWORD nCount = 0; + HANDLE events[2] = WINPR_C_ARRAY_INIT; + telemetry_server* telemetry = (telemetry_server*)arg; + UINT error = CHANNEL_RC_OK; + DWORD status = 0; + + WINPR_ASSERT(telemetry); + + nCount = 0; + events[nCount++] = telemetry->stopEvent; + + while ((error == CHANNEL_RC_OK) && (WaitForSingleObject(events[0], 0) != WAIT_OBJECT_0)) + { + switch (telemetry->state) + { + case TELEMETRY_INITIAL: + error = telemetry_server_context_poll_int(&telemetry->context); + if (error == CHANNEL_RC_OK) + { + events[1] = telemetry_server_get_channel_handle(telemetry); + nCount = 2; + } + break; + case TELEMETRY_OPENED: + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + switch (status) + { + case WAIT_OBJECT_0: + break; + case WAIT_OBJECT_0 + 1: + case WAIT_TIMEOUT: + error = telemetry_server_context_poll_int(&telemetry->context); + break; + + case WAIT_FAILED: + default: + error = ERROR_INTERNAL_ERROR; + break; + } + break; + default: + break; + } + } + + (void)WTSVirtualChannelClose(telemetry->telemetry_channel); + telemetry->telemetry_channel = nullptr; + + if (error && telemetry->context.rdpcontext) + setChannelError(telemetry->context.rdpcontext, error, + "telemetry_server_thread_func reported an error"); + + ExitThread(error); + return error; +} + +static UINT telemetry_server_open(TelemetryServerContext* context) +{ + telemetry_server* telemetry = (telemetry_server*)context; + + WINPR_ASSERT(telemetry); + + if (!telemetry->externalThread && (telemetry->thread == nullptr)) + { + telemetry->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + if (!telemetry->stopEvent) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + telemetry->thread = + CreateThread(nullptr, 0, telemetry_server_thread_func, telemetry, 0, nullptr); + if (!telemetry->thread) + { + WLog_ERR(TAG, "CreateThread failed!"); + (void)CloseHandle(telemetry->stopEvent); + telemetry->stopEvent = nullptr; + return ERROR_INTERNAL_ERROR; + } + } + telemetry->isOpened = TRUE; + + return CHANNEL_RC_OK; +} + +static UINT telemetry_server_close(TelemetryServerContext* context) +{ + UINT error = CHANNEL_RC_OK; + telemetry_server* telemetry = (telemetry_server*)context; + + WINPR_ASSERT(telemetry); + + if (!telemetry->externalThread && telemetry->thread) + { + (void)SetEvent(telemetry->stopEvent); + + if (WaitForSingleObject(telemetry->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + return error; + } + + (void)CloseHandle(telemetry->thread); + (void)CloseHandle(telemetry->stopEvent); + telemetry->thread = nullptr; + telemetry->stopEvent = nullptr; + } + if (telemetry->externalThread) + { + if (telemetry->state != TELEMETRY_INITIAL) + { + (void)WTSVirtualChannelClose(telemetry->telemetry_channel); + telemetry->telemetry_channel = nullptr; + telemetry->state = TELEMETRY_INITIAL; + } + } + telemetry->isOpened = FALSE; + + return error; +} + +static UINT telemetry_server_context_poll(TelemetryServerContext* context) +{ + telemetry_server* telemetry = (telemetry_server*)context; + + WINPR_ASSERT(telemetry); + + if (!telemetry->externalThread) + return ERROR_INTERNAL_ERROR; + + return telemetry_server_context_poll_int(context); +} + +static BOOL telemetry_server_context_handle(TelemetryServerContext* context, HANDLE* handle) +{ + telemetry_server* telemetry = (telemetry_server*)context; + + WINPR_ASSERT(telemetry); + WINPR_ASSERT(handle); + + if (!telemetry->externalThread) + return FALSE; + if (telemetry->state == TELEMETRY_INITIAL) + return FALSE; + + *handle = telemetry_server_get_channel_handle(telemetry); + + return TRUE; +} + +TelemetryServerContext* telemetry_server_context_new(HANDLE vcm) +{ + telemetry_server* telemetry = (telemetry_server*)calloc(1, sizeof(telemetry_server)); + + if (!telemetry) + return nullptr; + + telemetry->context.vcm = vcm; + telemetry->context.Initialize = telemetry_server_initialize; + telemetry->context.Open = telemetry_server_open; + telemetry->context.Close = telemetry_server_close; + telemetry->context.Poll = telemetry_server_context_poll; + telemetry->context.ChannelHandle = telemetry_server_context_handle; + + telemetry->buffer = Stream_New(nullptr, 4096); + if (!telemetry->buffer) + goto fail; + + return &telemetry->context; +fail: + WINPR_PRAGMA_DIAG_PUSH + WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC + telemetry_server_context_free(&telemetry->context); + WINPR_PRAGMA_DIAG_POP + return nullptr; +} + +void telemetry_server_context_free(TelemetryServerContext* context) +{ + telemetry_server* telemetry = (telemetry_server*)context; + + if (telemetry) + { + telemetry_server_close(context); + Stream_Free(telemetry->buffer, TRUE); + } + + free(telemetry); +} diff --git a/third_party/FreeRDP/channels/tsmf/CMakeLists.txt b/third_party/FreeRDP/channels/tsmf/CMakeLists.txt new file mode 100644 index 0000000..762a747 --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/CMakeLists.txt @@ -0,0 +1,22 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel("tsmf") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/third_party/FreeRDP/channels/tsmf/ChannelOptions.cmake b/third_party/FreeRDP/channels/tsmf/ChannelOptions.cmake new file mode 100644 index 0000000..1a17bea --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/ChannelOptions.cmake @@ -0,0 +1,20 @@ +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT OFF) +set(OPTION_SERVER_DEFAULT OFF) + +define_channel_options( + NAME + "tsmf" + TYPE + "dynamic" + DESCRIPTION + "[DEPRECATED] Video Redirection Virtual Channel Extension" + SPECIFICATIONS + "[MS-RDPEV]" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/tsmf/client/CMakeLists.txt b/third_party/FreeRDP/channels/tsmf/client/CMakeLists.txt new file mode 100644 index 0000000..5c71cbb --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/client/CMakeLists.txt @@ -0,0 +1,82 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# Copyright 2012 Hewlett-Packard Development Company, L.P. +# +# 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. + +define_channel_client("tsmf") + +message(DEPRECATION "TSMF channel is no longer maintained. Use [MS-RDPEVOR] (/video) instead.") + +find_package(PkgConfig) +if(PkgConfig_FOUND) + pkg_check_modules(gstreamer gstreamer-1.0) + freerdp_client_pc_add_requires_private("gstreamer-1.0") +endif() + +if(WITH_GSTREAMER_1_0) + if(gstreamer_FOUND) + add_compile_definitions(WITH_GSTREAMER_1_0) + else() + message(WARNING "gstreamer not detected, disabling support") + endif() +endif() + +set(${MODULE_PREFIX}_SRCS + tsmf_audio.c + tsmf_audio.h + tsmf_codec.c + tsmf_codec.h + tsmf_constants.h + tsmf_decoder.c + tsmf_decoder.h + tsmf_ifman.c + tsmf_ifman.h + tsmf_main.c + tsmf_main.h + tsmf_media.c + tsmf_media.h + tsmf_types.h +) + +set(${MODULE_PREFIX}_LIBS winpr freerdp) +include_directories(..) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") + +if(WITH_VIDEO_FFMPEG) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "ffmpeg" "decoder") +endif() + +if(WITH_GSTREAMER_1_0) + find_package(X11) + if(X11_Xrandr_FOUND) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "gstreamer" "decoder") + else() + message(WARNING "Disabling tsmf gstreamer because XRandR wasn't found") + endif() +endif() + +if(WITH_OSS) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "oss" "audio") +endif() + +if(WITH_ALSA) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "alsa" "audio") +endif() + +if(WITH_PULSE) + add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "pulse" "audio") +endif() diff --git a/third_party/FreeRDP/channels/tsmf/client/alsa/CMakeLists.txt b/third_party/FreeRDP/channels/tsmf/client/alsa/CMakeLists.txt new file mode 100644 index 0000000..bae0442 --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/client/alsa/CMakeLists.txt @@ -0,0 +1,30 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_client_subsystem("tsmf" "alsa" "audio") + +find_package(ALSA REQUIRED) +freerdp_client_pc_add_requires_private("alsa") + +set(${MODULE_PREFIX}_SRCS tsmf_alsa.c) + +set(${MODULE_PREFIX}_LIBS winpr freerdp ${ALSA_LIBRARIES}) + +include_directories(..) +include_directories(SYSTEM ${ALSA_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") diff --git a/third_party/FreeRDP/channels/tsmf/client/alsa/tsmf_alsa.c b/third_party/FreeRDP/channels/tsmf/client/alsa/tsmf_alsa.c new file mode 100644 index 0000000..d24e296 --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/client/alsa/tsmf_alsa.c @@ -0,0 +1,238 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - ALSA Audio Device + * + * Copyright 2010-2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#include "tsmf_audio.h" + +typedef struct +{ + ITSMFAudioDevice iface; + + char device[32]; + snd_pcm_t* out_handle; + UINT32 source_rate; + UINT32 actual_rate; + UINT32 source_channels; + UINT32 actual_channels; + UINT32 bytes_per_sample; +} TSMFAlsaAudioDevice; + +static BOOL tsmf_alsa_open_device(TSMFAlsaAudioDevice* alsa) +{ + int error = 0; + error = snd_pcm_open(&alsa->out_handle, alsa->device, SND_PCM_STREAM_PLAYBACK, 0); + + if (error < 0) + { + WLog_ERR(TAG, "failed to open device %s", alsa->device); + return FALSE; + } + + DEBUG_TSMF("open device %s", alsa->device); + return TRUE; +} + +static BOOL tsmf_alsa_open(ITSMFAudioDevice* audio, const char* device) +{ + TSMFAlsaAudioDevice* alsa = (TSMFAlsaAudioDevice*)audio; + + if (!device) + { + strncpy(alsa->device, "default", sizeof(alsa->device)); + } + else + { + strncpy(alsa->device, device, sizeof(alsa->device) - 1); + } + + return tsmf_alsa_open_device(alsa); +} + +static BOOL tsmf_alsa_set_format(ITSMFAudioDevice* audio, UINT32 sample_rate, UINT32 channels, + UINT32 bits_per_sample) +{ + int error = 0; + snd_pcm_uframes_t frames = 0; + snd_pcm_hw_params_t* hw_params = nullptr; + snd_pcm_sw_params_t* sw_params = nullptr; + TSMFAlsaAudioDevice* alsa = (TSMFAlsaAudioDevice*)audio; + + if (!alsa->out_handle) + return FALSE; + + snd_pcm_drop(alsa->out_handle); + alsa->actual_rate = alsa->source_rate = sample_rate; + alsa->actual_channels = alsa->source_channels = channels; + alsa->bytes_per_sample = bits_per_sample / 8; + error = snd_pcm_hw_params_malloc(&hw_params); + + if (error < 0) + { + WLog_ERR(TAG, "snd_pcm_hw_params_malloc failed"); + return FALSE; + } + + snd_pcm_hw_params_any(alsa->out_handle, hw_params); + snd_pcm_hw_params_set_access(alsa->out_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); + snd_pcm_hw_params_set_format(alsa->out_handle, hw_params, SND_PCM_FORMAT_S16_LE); + snd_pcm_hw_params_set_rate_near(alsa->out_handle, hw_params, &alsa->actual_rate, nullptr); + snd_pcm_hw_params_set_channels_near(alsa->out_handle, hw_params, &alsa->actual_channels); + frames = sample_rate; + snd_pcm_hw_params_set_buffer_size_near(alsa->out_handle, hw_params, &frames); + snd_pcm_hw_params(alsa->out_handle, hw_params); + snd_pcm_hw_params_free(hw_params); + error = snd_pcm_sw_params_malloc(&sw_params); + + if (error < 0) + { + WLog_ERR(TAG, "snd_pcm_sw_params_malloc"); + return FALSE; + } + + snd_pcm_sw_params_current(alsa->out_handle, sw_params); + snd_pcm_sw_params_set_start_threshold(alsa->out_handle, sw_params, frames / 2); + snd_pcm_sw_params(alsa->out_handle, sw_params); + snd_pcm_sw_params_free(sw_params); + snd_pcm_prepare(alsa->out_handle); + DEBUG_TSMF("sample_rate %" PRIu32 " channels %" PRIu32 " bits_per_sample %" PRIu32 "", + sample_rate, channels, bits_per_sample); + DEBUG_TSMF("hardware buffer %lu frames", frames); + + if ((alsa->actual_rate != alsa->source_rate) || + (alsa->actual_channels != alsa->source_channels)) + { + DEBUG_TSMF("actual rate %" PRIu32 " / channel %" PRIu32 " is different " + "from source rate %" PRIu32 " / channel %" PRIu32 ", resampling required.", + alsa->actual_rate, alsa->actual_channels, alsa->source_rate, + alsa->source_channels); + } + + return TRUE; +} + +static BOOL tsmf_alsa_play(ITSMFAudioDevice* audio, const BYTE* src, UINT32 data_size) +{ + const BYTE* pindex = nullptr; + TSMFAlsaAudioDevice* alsa = (TSMFAlsaAudioDevice*)audio; + DEBUG_TSMF("data_size %" PRIu32 "", data_size); + + if (alsa->out_handle) + { + const size_t rbytes_per_frame = 1ULL * alsa->actual_channels * alsa->bytes_per_sample; + pindex = src; + const BYTE* end = pindex + data_size; + + while (pindex < end) + { + const size_t len = (size_t)(end - pindex); + const size_t frames = len / rbytes_per_frame; + snd_pcm_sframes_t error = snd_pcm_writei(alsa->out_handle, pindex, frames); + + if (error == -EPIPE) + { + snd_pcm_recover(alsa->out_handle, -EPIPE, 0); + error = 0; + } + else if (error < 0) + { + DEBUG_TSMF("error len %ld", error); + snd_pcm_close(alsa->out_handle); + alsa->out_handle = 0; + tsmf_alsa_open_device(alsa); + break; + } + + DEBUG_TSMF("%d frames played.", error); + + if (error == 0) + break; + + pindex += (size_t)error * rbytes_per_frame; + } + } + + return TRUE; +} + +static UINT64 tsmf_alsa_get_latency(ITSMFAudioDevice* audio) +{ + UINT64 latency = 0; + snd_pcm_sframes_t frames = 0; + TSMFAlsaAudioDevice* alsa = (TSMFAlsaAudioDevice*)audio; + + if (alsa->out_handle && alsa->actual_rate > 0 && + snd_pcm_delay(alsa->out_handle, &frames) == 0 && frames > 0) + { + latency = ((UINT64)frames) * 10000000LL / (UINT64)alsa->actual_rate; + } + + return latency; +} + +static BOOL tsmf_alsa_flush(WINPR_ATTR_UNUSED ITSMFAudioDevice* audio) +{ + return TRUE; +} + +static void tsmf_alsa_free(ITSMFAudioDevice* audio) +{ + TSMFAlsaAudioDevice* alsa = (TSMFAlsaAudioDevice*)audio; + DEBUG_TSMF(""); + + if (alsa->out_handle) + { + snd_pcm_drain(alsa->out_handle); + snd_pcm_close(alsa->out_handle); + } + + free(alsa); +} + +FREERDP_ENTRY_POINT(UINT VCAPITYPE alsa_freerdp_tsmf_client_audio_subsystem_entry(void* ptr)) +{ + ITSMFAudioDevice** sptr = (ITSMFAudioDevice**)ptr; + WINPR_ASSERT(sptr); + *sptr = nullptr; + + TSMFAlsaAudioDevice* alsa = calloc(1, sizeof(TSMFAlsaAudioDevice)); + if (!alsa) + return ERROR_OUTOFMEMORY; + + alsa->iface.Open = tsmf_alsa_open; + alsa->iface.SetFormat = tsmf_alsa_set_format; + alsa->iface.Play = tsmf_alsa_play; + alsa->iface.GetLatency = tsmf_alsa_get_latency; + alsa->iface.Flush = tsmf_alsa_flush; + alsa->iface.Free = tsmf_alsa_free; + *sptr = &alsa->iface; + return CHANNEL_RC_OK; +} diff --git a/third_party/FreeRDP/channels/tsmf/client/ffmpeg/CMakeLists.txt b/third_party/FreeRDP/channels/tsmf/client/ffmpeg/CMakeLists.txt new file mode 100644 index 0000000..717cdde --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/client/ffmpeg/CMakeLists.txt @@ -0,0 +1,35 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_client_subsystem("tsmf" "ffmpeg" "decoder") + +set(${MODULE_PREFIX}_SRCS tsmf_ffmpeg.c) + +set(${MODULE_PREFIX}_LIBS freerdp ${FFMPEG_LIBRARIES}) +if(APPLE) + # For this to work on apple, we need to add some frameworks + find_library(COREFOUNDATION_LIBRARY CoreFoundation) + find_library(COREVIDEO_LIBRARY CoreVideo) + find_library(COREVIDEODECODE_LIBRARY VideoDecodeAcceleration) + + list(APPEND ${MODULE_PREFIX}_LIBS ${COREFOUNDATION_LIBRARY} ${COREVIDEO_LIBRARY} ${COREVIDEODECODE_LIBRARY}) +endif() + +include_directories(..) +include_directories(SYSTEM ${FFMPEG_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") diff --git a/third_party/FreeRDP/channels/tsmf/client/ffmpeg/tsmf_ffmpeg.c b/third_party/FreeRDP/channels/tsmf/client/ffmpeg/tsmf_ffmpeg.c new file mode 100644 index 0000000..896623e --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/client/ffmpeg/tsmf_ffmpeg.c @@ -0,0 +1,723 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - FFmpeg Decoder + * + * Copyright 2010-2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + +#include "tsmf_constants.h" +#include "tsmf_decoder.h" +#include "tsmf_audio.h" + +/* Compatibility with older FFmpeg */ +#if LIBAVUTIL_VERSION_MAJOR < 50 +#define AVMEDIA_TYPE_VIDEO 0 +#define AVMEDIA_TYPE_AUDIO 1 +#endif + +#if LIBAVCODEC_VERSION_MAJOR < 54 +#define MAX_AUDIO_FRAME_SIZE AVCODEC_MAX_AUDIO_FRAME_SIZE +#else +#define MAX_AUDIO_FRAME_SIZE 192000 +#endif + +#if LIBAVCODEC_VERSION_MAJOR < 55 +#define AV_CODEC_ID_VC1 CODEC_ID_VC1 +#define AV_CODEC_ID_WMAV2 CODEC_ID_WMAV2 +#define AV_CODEC_ID_WMAPRO CODEC_ID_WMAPRO +#define AV_CODEC_ID_MP3 CODEC_ID_MP3 +#define AV_CODEC_ID_MP2 CODEC_ID_MP2 +#define AV_CODEC_ID_MPEG2VIDEO CODEC_ID_MPEG2VIDEO +#define AV_CODEC_ID_WMV3 CODEC_ID_WMV3 +#define AV_CODEC_ID_AAC CODEC_ID_AAC +#define AV_CODEC_ID_H264 CODEC_ID_H264 +#define AV_CODEC_ID_AC3 CODEC_ID_AC3 +#endif + +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(56, 34, 2) +#define AV_CODEC_CAP_TRUNCATED CODEC_CAP_TRUNCATED +#define AV_CODEC_FLAG_TRUNCATED CODEC_FLAG_TRUNCATED +#endif + +#if LIBAVUTIL_VERSION_MAJOR < 52 +#define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P +#endif + +typedef struct +{ + ITSMFDecoder iface; + + int media_type; +#if LIBAVCODEC_VERSION_MAJOR < 55 + enum CodecID codec_id; +#else + enum AVCodecID codec_id; +#endif + AVCodecContext* codec_context; + const AVCodec* codec; + AVFrame* frame; + int prepared; + + BYTE* decoded_data; + UINT32 decoded_size; + UINT32 decoded_size_max; +} TSMFFFmpegDecoder; + +static BOOL tsmf_ffmpeg_init_context(ITSMFDecoder* decoder) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder; + mdecoder->codec_context = avcodec_alloc_context3(nullptr); + + if (!mdecoder->codec_context) + { + WLog_ERR(TAG, "avcodec_alloc_context failed."); + return FALSE; + } + + return TRUE; +} + +static BOOL tsmf_ffmpeg_init_video_stream(ITSMFDecoder* decoder, const TS_AM_MEDIA_TYPE* media_type) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder; + mdecoder->codec_context->width = WINPR_ASSERTING_INT_CAST(int, media_type->Width); + mdecoder->codec_context->height = WINPR_ASSERTING_INT_CAST(int, media_type->Height); + mdecoder->codec_context->bit_rate = WINPR_ASSERTING_INT_CAST(int, media_type->BitRate); + mdecoder->codec_context->time_base.den = + WINPR_ASSERTING_INT_CAST(int, media_type->SamplesPerSecond.Numerator); + mdecoder->codec_context->time_base.num = + WINPR_ASSERTING_INT_CAST(int, media_type->SamplesPerSecond.Denominator); +#if LIBAVCODEC_VERSION_MAJOR < 55 + mdecoder->frame = avcodec_alloc_frame(); +#else + mdecoder->frame = av_frame_alloc(); +#endif + return TRUE; +} + +static BOOL tsmf_ffmpeg_init_audio_stream(ITSMFDecoder* decoder, const TS_AM_MEDIA_TYPE* media_type) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder; + mdecoder->codec_context->sample_rate = + WINPR_ASSERTING_INT_CAST(int, media_type->SamplesPerSecond.Numerator); + mdecoder->codec_context->bit_rate = WINPR_ASSERTING_INT_CAST(int, media_type->BitRate); +#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 28, 100) + mdecoder->codec_context->ch_layout.nb_channels = + WINPR_ASSERTING_INT_CAST(int, media_type->Channels); +#else + mdecoder->codec_context->channels = WINPR_ASSERTING_INT_CAST(int, media_type->Channels); +#endif + mdecoder->codec_context->block_align = WINPR_ASSERTING_INT_CAST(int, media_type->BlockAlign); +#if LIBAVCODEC_VERSION_MAJOR < 55 +#ifdef AV_CPU_FLAG_SSE2 + mdecoder->codec_context->dsp_mask = AV_CPU_FLAG_SSE2 | AV_CPU_FLAG_MMX2; +#else +#if LIBAVCODEC_VERSION_MAJOR < 53 + mdecoder->codec_context->dsp_mask = FF_MM_SSE2 | FF_MM_MMXEXT; +#else + mdecoder->codec_context->dsp_mask = FF_MM_SSE2 | FF_MM_MMX2; +#endif +#endif +#else /* LIBAVCODEC_VERSION_MAJOR < 55 */ +#ifdef AV_CPU_FLAG_SSE2 +#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 17, 100) + av_set_cpu_flags_mask(AV_CPU_FLAG_SSE2 | AV_CPU_FLAG_MMXEXT); +#else + av_force_cpu_flags(AV_CPU_FLAG_SSE2 | AV_CPU_FLAG_MMXEXT); +#endif +#else + av_set_cpu_flags_mask(FF_MM_SSE2 | FF_MM_MMX2); +#endif +#endif /* LIBAVCODEC_VERSION_MAJOR < 55 */ + return TRUE; +} + +static BOOL tsmf_ffmpeg_init_stream(ITSMFDecoder* decoder, const TS_AM_MEDIA_TYPE* media_type) +{ + BYTE* p = nullptr; + UINT32 size = 0; + const BYTE* s = nullptr; + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder; + + WINPR_PRAGMA_DIAG_PUSH + WINPR_PRAGMA_DIAG_IGNORED_QUALIFIERS + mdecoder->codec = avcodec_find_decoder(mdecoder->codec_id); + WINPR_PRAGMA_DIAG_POP + + if (!mdecoder->codec) + { + WLog_ERR(TAG, "avcodec_find_decoder failed."); + return FALSE; + } + + mdecoder->codec_context->codec_id = mdecoder->codec_id; + mdecoder->codec_context->codec_type = mdecoder->media_type; + + switch (mdecoder->media_type) + { + case AVMEDIA_TYPE_VIDEO: + if (!tsmf_ffmpeg_init_video_stream(decoder, media_type)) + return FALSE; + + break; + + case AVMEDIA_TYPE_AUDIO: + if (!tsmf_ffmpeg_init_audio_stream(decoder, media_type)) + return FALSE; + + break; + + default: + WLog_ERR(TAG, "unknown media_type %d", mdecoder->media_type); + break; + } + + if (media_type->ExtraData) + { + /* Add a padding to avoid invalid memory read in some codec */ + mdecoder->codec_context->extradata_size = + WINPR_ASSERTING_INT_CAST(int, media_type->ExtraDataSize + 8); + if (mdecoder->codec_context->extradata_size == 0) + return FALSE; + mdecoder->codec_context->extradata = calloc(1, mdecoder->codec_context->extradata_size); + + if (!mdecoder->codec_context->extradata) + return FALSE; + + if (media_type->SubType == TSMF_SUB_TYPE_AVC1 && + media_type->FormatType == TSMF_FORMAT_TYPE_MPEG2VIDEOINFO) + { + size_t required = 6; + /* The extradata format that FFmpeg uses is following CodecPrivate in Matroska. + See http://haali.su/mkv/codecs.pdf */ + p = mdecoder->codec_context->extradata; + if ((mdecoder->codec_context->extradata_size < 0) || + ((size_t)mdecoder->codec_context->extradata_size < required)) + return FALSE; + *p++ = 1; /* Reserved? */ + *p++ = media_type->ExtraData[8]; /* Profile */ + *p++ = 0; /* Profile */ + *p++ = media_type->ExtraData[12]; /* Level */ + *p++ = 0xff; /* Flag? */ + *p++ = 0xe0 | 0x01; /* Reserved | #sps */ + s = media_type->ExtraData + 20; + size = ((UINT32)(*s)) * 256 + ((UINT32)(*(s + 1))); + required += size + 2; + if ((mdecoder->codec_context->extradata_size < 0) || + ((size_t)mdecoder->codec_context->extradata_size < required)) + return FALSE; + memcpy(p, s, size + 2); + s += size + 2; + p += size + 2; + required++; + if ((mdecoder->codec_context->extradata_size < 0) || + ((size_t)mdecoder->codec_context->extradata_size < required)) + return FALSE; + *p++ = 1; /* #pps */ + size = ((UINT32)(*s)) * 256 + ((UINT32)(*(s + 1))); + required += size + 2; + if ((mdecoder->codec_context->extradata_size < 0) || + ((size_t)mdecoder->codec_context->extradata_size < required)) + return FALSE; + memcpy(p, s, size + 2); + } + else + { + memcpy(mdecoder->codec_context->extradata, media_type->ExtraData, + media_type->ExtraDataSize); + if ((mdecoder->codec_context->extradata_size < 0) || + ((size_t)mdecoder->codec_context->extradata_size < + media_type->ExtraDataSize + 8ull)) + return FALSE; + memset(mdecoder->codec_context->extradata + media_type->ExtraDataSize, 0, 8); + } + } + +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(59, 18, 100) + if (mdecoder->codec->capabilities & AV_CODEC_CAP_TRUNCATED) + mdecoder->codec_context->flags |= AV_CODEC_FLAG_TRUNCATED; +#endif + + return TRUE; +} + +static BOOL tsmf_ffmpeg_prepare(ITSMFDecoder* decoder) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder; + + if (avcodec_open2(mdecoder->codec_context, mdecoder->codec, nullptr) < 0) + { + WLog_ERR(TAG, "avcodec_open2 failed."); + return FALSE; + } + + mdecoder->prepared = 1; + return TRUE; +} + +static BOOL tsmf_ffmpeg_set_format(ITSMFDecoder* decoder, TS_AM_MEDIA_TYPE* media_type) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder; + + WINPR_ASSERT(mdecoder); + WINPR_ASSERT(media_type); + + switch (media_type->MajorType) + { + case TSMF_MAJOR_TYPE_VIDEO: + mdecoder->media_type = AVMEDIA_TYPE_VIDEO; + break; + + case TSMF_MAJOR_TYPE_AUDIO: + mdecoder->media_type = AVMEDIA_TYPE_AUDIO; + break; + + default: + return FALSE; + } + + switch (media_type->SubType) + { + case TSMF_SUB_TYPE_WVC1: + mdecoder->codec_id = AV_CODEC_ID_VC1; + break; + + case TSMF_SUB_TYPE_WMA2: + mdecoder->codec_id = AV_CODEC_ID_WMAV2; + break; + + case TSMF_SUB_TYPE_WMA9: + mdecoder->codec_id = AV_CODEC_ID_WMAPRO; + break; + + case TSMF_SUB_TYPE_MP3: + mdecoder->codec_id = AV_CODEC_ID_MP3; + break; + + case TSMF_SUB_TYPE_MP2A: + mdecoder->codec_id = AV_CODEC_ID_MP2; + break; + + case TSMF_SUB_TYPE_MP2V: + mdecoder->codec_id = AV_CODEC_ID_MPEG2VIDEO; + break; + + case TSMF_SUB_TYPE_WMV3: + mdecoder->codec_id = AV_CODEC_ID_WMV3; + break; + + case TSMF_SUB_TYPE_AAC: + mdecoder->codec_id = AV_CODEC_ID_AAC; + + /* For AAC the pFormat is a HEAACWAVEINFO struct, and the codec data + is at the end of it. See + http://msdn.microsoft.com/en-us/library/dd757806.aspx */ + if (media_type->ExtraData) + { + if (media_type->ExtraDataSize < 12) + return FALSE; + + media_type->ExtraData += 12; + media_type->ExtraDataSize -= 12; + } + + break; + + case TSMF_SUB_TYPE_H264: + case TSMF_SUB_TYPE_AVC1: + mdecoder->codec_id = AV_CODEC_ID_H264; + break; + + case TSMF_SUB_TYPE_AC3: + mdecoder->codec_id = AV_CODEC_ID_AC3; + break; + + default: + return FALSE; + } + + if (!tsmf_ffmpeg_init_context(decoder)) + return FALSE; + + if (!tsmf_ffmpeg_init_stream(decoder, media_type)) + return FALSE; + + if (!tsmf_ffmpeg_prepare(decoder)) + return FALSE; + + return TRUE; +} + +static BOOL tsmf_ffmpeg_decode_video(ITSMFDecoder* decoder, const BYTE* data, UINT32 data_size, + UINT32 extensions) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder; + int decoded = 0; + int len = 0; + AVFrame* frame = nullptr; + BOOL ret = TRUE; +#if LIBAVCODEC_VERSION_MAJOR < 52 || \ + (LIBAVCODEC_VERSION_MAJOR == 52 && LIBAVCODEC_VERSION_MINOR <= 20) + len = avcodec_decode_video(mdecoder->codec_context, mdecoder->frame, &decoded, data, data_size); +#else + { + AVPacket pkt = WINPR_C_ARRAY_INIT; +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 133, 100) + av_init_packet(&pkt); +#endif + pkt.data = WINPR_CAST_CONST_PTR_AWAY(data, BYTE*); + pkt.size = WINPR_ASSERTING_INT_CAST(int, data_size); + + if (extensions & TSMM_SAMPLE_EXT_CLEANPOINT) + pkt.flags |= AV_PKT_FLAG_KEY; + +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 48, 101) + len = avcodec_decode_video2(mdecoder->codec_context, mdecoder->frame, &decoded, &pkt); +#else + len = avcodec_send_packet(mdecoder->codec_context, &pkt); + if (len > 0) + { + len = avcodec_receive_frame(mdecoder->codec_context, mdecoder->frame); + if (len == AVERROR(EAGAIN)) + return TRUE; + } +#endif + } +#endif + + if (len < 0) + { + WLog_ERR(TAG, "data_size %" PRIu32 ", avcodec_decode_video failed (%d)", data_size, len); + ret = FALSE; + } + else if (!decoded) + { + WLog_ERR(TAG, "data_size %" PRIu32 ", no frame is decoded.", data_size); + ret = FALSE; + } + else + { + DEBUG_TSMF("linesize[0] %d linesize[1] %d linesize[2] %d linesize[3] %d " + "pix_fmt %d width %d height %d", + mdecoder->frame->linesize[0], mdecoder->frame->linesize[1], + mdecoder->frame->linesize[2], mdecoder->frame->linesize[3], + mdecoder->codec_context->pix_fmt, mdecoder->codec_context->width, + mdecoder->codec_context->height); + mdecoder->decoded_size = av_image_get_buffer_size(mdecoder->codec_context->pix_fmt, + mdecoder->codec_context->width, + mdecoder->codec_context->height, 1); + mdecoder->decoded_data = calloc(1, mdecoder->decoded_size); + + if (!mdecoder->decoded_data) + return FALSE; + +#if LIBAVCODEC_VERSION_MAJOR < 55 + frame = avcodec_alloc_frame(); +#else + frame = av_frame_alloc(); +#endif + av_image_fill_arrays(frame->data, frame->linesize, mdecoder->decoded_data, + mdecoder->codec_context->pix_fmt, mdecoder->codec_context->width, + mdecoder->codec_context->height, 1); + + const uint8_t* ptr[AV_NUM_DATA_POINTERS] = WINPR_C_ARRAY_INIT; + for (size_t x = 0; x < AV_NUM_DATA_POINTERS; x++) + ptr[x] = mdecoder->frame->data[x]; + + av_image_copy(frame->data, frame->linesize, ptr, mdecoder->frame->linesize, + mdecoder->codec_context->pix_fmt, mdecoder->codec_context->width, + mdecoder->codec_context->height); + av_free(frame); + } + + return ret; +} + +static BOOL tsmf_ffmpeg_decode_audio(ITSMFDecoder* decoder, const BYTE* data, UINT32 data_size, + WINPR_ATTR_UNUSED UINT32 extensions) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder; + int len = 0; + int frame_size = 0; + + if (mdecoder->decoded_size_max == 0) + mdecoder->decoded_size_max = MAX_AUDIO_FRAME_SIZE + 16; + + mdecoder->decoded_data = calloc(1, mdecoder->decoded_size_max); + + if (!mdecoder->decoded_data) + return FALSE; + + /* align the memory for SSE2 needs */ + BYTE* dst = (BYTE*)(((uintptr_t)mdecoder->decoded_data + 15) & ~0x0F); + size_t dst_offset = (size_t)(dst - mdecoder->decoded_data); + const BYTE* src = data; + UINT32 src_size = data_size; + + while (src_size > 0) + { + /* Ensure enough space for decoding */ + if (mdecoder->decoded_size_max - mdecoder->decoded_size < MAX_AUDIO_FRAME_SIZE) + { + BYTE* tmp_data = nullptr; + tmp_data = realloc(mdecoder->decoded_data, mdecoder->decoded_size_max * 2ull + 16ull); + + if (!tmp_data) + return FALSE; + + mdecoder->decoded_size_max = mdecoder->decoded_size_max * 2ull + 16ull; + mdecoder->decoded_data = tmp_data; + dst = (BYTE*)(((uintptr_t)mdecoder->decoded_data + 15) & ~0x0F); + + const size_t diff = (size_t)(dst - mdecoder->decoded_data); + if (diff != dst_offset) + { + /* re-align the memory if the alignment has changed after realloc */ + memmove(dst, mdecoder->decoded_data + dst_offset, mdecoder->decoded_size); + dst_offset = diff; + } + + dst += mdecoder->decoded_size; + } + +#if LIBAVCODEC_VERSION_MAJOR < 52 || \ + (LIBAVCODEC_VERSION_MAJOR == 52 && LIBAVCODEC_VERSION_MINOR <= 20) + frame_size = mdecoder->decoded_size_max - mdecoder->decoded_size; + len = avcodec_decode_audio2(mdecoder->codec_context, (int16_t*)dst, &frame_size, src, + src_size); +#else + { +#if LIBAVCODEC_VERSION_MAJOR < 55 + AVFrame* decoded_frame = avcodec_alloc_frame(); +#else + AVFrame* decoded_frame = av_frame_alloc(); +#endif + int got_frame = 0; + AVPacket pkt = WINPR_C_ARRAY_INIT; +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 133, 100) + av_init_packet(&pkt); +#endif + + pkt.data = WINPR_CAST_CONST_PTR_AWAY(src, BYTE*); + pkt.size = WINPR_ASSERTING_INT_CAST(int, src_size); +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 48, 101) + len = avcodec_decode_audio4(mdecoder->codec_context, decoded_frame, &got_frame, &pkt); +#else + len = avcodec_send_packet(mdecoder->codec_context, &pkt); + if (len > 0) + { + len = avcodec_receive_frame(mdecoder->codec_context, decoded_frame); + if (len == AVERROR(EAGAIN)) + return TRUE; + } +#endif + + if (len >= 0 && got_frame) + { +#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 28, 100) + const int channels = mdecoder->codec_context->ch_layout.nb_channels; +#else + const int channels = mdecoder->codec_context->channels; +#endif + frame_size = + av_samples_get_buffer_size(nullptr, channels, decoded_frame->nb_samples, + mdecoder->codec_context->sample_fmt, 1); + memcpy(dst, decoded_frame->data[0], frame_size); + } + else + { + frame_size = 0; + } + + av_free(decoded_frame); + } +#endif + + if (len > 0) + { + src += len; + src_size -= len; + } + + if (frame_size > 0) + { + mdecoder->decoded_size += frame_size; + dst += frame_size; + } + } + + if (mdecoder->decoded_size == 0) + { + free(mdecoder->decoded_data); + mdecoder->decoded_data = nullptr; + } + else if (dst_offset) + { + /* move the aligned decoded data to original place */ + memmove(mdecoder->decoded_data, mdecoder->decoded_data + dst_offset, + mdecoder->decoded_size); + } + + DEBUG_TSMF("data_size %" PRIu32 " decoded_size %" PRIu32 "", data_size, mdecoder->decoded_size); + return TRUE; +} + +static BOOL tsmf_ffmpeg_decode(ITSMFDecoder* decoder, const BYTE* data, UINT32 data_size, + UINT32 extensions) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder; + + if (mdecoder->decoded_data) + { + free(mdecoder->decoded_data); + mdecoder->decoded_data = nullptr; + } + + mdecoder->decoded_size = 0; + + switch (mdecoder->media_type) + { + case AVMEDIA_TYPE_VIDEO: + return tsmf_ffmpeg_decode_video(decoder, data, data_size, extensions); + + case AVMEDIA_TYPE_AUDIO: + return tsmf_ffmpeg_decode_audio(decoder, data, data_size, extensions); + + default: + WLog_ERR(TAG, "unknown media type."); + return FALSE; + } +} + +static BYTE* tsmf_ffmpeg_get_decoded_data(ITSMFDecoder* decoder, UINT32* size) +{ + BYTE* buf = nullptr; + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder; + *size = mdecoder->decoded_size; + buf = mdecoder->decoded_data; + mdecoder->decoded_data = nullptr; + mdecoder->decoded_size = 0; + return buf; +} + +static UINT32 tsmf_ffmpeg_get_decoded_format(ITSMFDecoder* decoder) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder; + + switch (mdecoder->codec_context->pix_fmt) + { + case AV_PIX_FMT_YUV420P: + return RDP_PIXFMT_I420; + + default: + WLog_ERR(TAG, "unsupported pixel format %d", mdecoder->codec_context->pix_fmt); + return (UINT32)-1; + } +} + +static BOOL tsmf_ffmpeg_get_decoded_dimension(ITSMFDecoder* decoder, UINT32* width, UINT32* height) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder; + + if (mdecoder->codec_context->width > 0 && mdecoder->codec_context->height > 0) + { + *width = mdecoder->codec_context->width; + *height = mdecoder->codec_context->height; + return TRUE; + } + else + { + return FALSE; + } +} + +static void tsmf_ffmpeg_free(ITSMFDecoder* decoder) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*)decoder; + + if (mdecoder->frame) + av_free(mdecoder->frame); + + free(mdecoder->decoded_data); + + if (mdecoder->codec_context) + { + free(mdecoder->codec_context->extradata); + mdecoder->codec_context->extradata = nullptr; + +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 69, 100) + avcodec_free_context(&mdecoder->codec_context); +#else + if (mdecoder->prepared) + avcodec_close(mdecoder->codec_context); + + av_free(mdecoder->codec_context); +#endif + } + + free(decoder); +} + +static INIT_ONCE g_Initialized = INIT_ONCE_STATIC_INIT; +static BOOL CALLBACK InitializeAvCodecs(WINPR_ATTR_UNUSED PINIT_ONCE once, + WINPR_ATTR_UNUSED PVOID param, + WINPR_ATTR_UNUSED PVOID* context) +{ +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100) + avcodec_register_all(); +#endif + return TRUE; +} + +FREERDP_ENTRY_POINT(UINT VCAPITYPE ffmpeg_freerdp_tsmf_client_decoder_subsystem_entry(void* ptr)) +{ + ITSMFDecoder** sptr = (ITSMFDecoder**)ptr; + WINPR_ASSERT(sptr); + *sptr = nullptr; + + TSMFFFmpegDecoder* decoder = nullptr; + if (!InitOnceExecuteOnce(&g_Initialized, InitializeAvCodecs, nullptr, nullptr)) + return ERROR_INTERNAL_ERROR; + WLog_DBG(TAG, "TSMFDecoderEntry FFMPEG"); + decoder = (TSMFFFmpegDecoder*)calloc(1, sizeof(TSMFFFmpegDecoder)); + + if (!decoder) + return ERROR_OUTOFMEMORY; + + decoder->iface.SetFormat = tsmf_ffmpeg_set_format; + decoder->iface.Decode = tsmf_ffmpeg_decode; + decoder->iface.GetDecodedData = tsmf_ffmpeg_get_decoded_data; + decoder->iface.GetDecodedFormat = tsmf_ffmpeg_get_decoded_format; + decoder->iface.GetDecodedDimension = tsmf_ffmpeg_get_decoded_dimension; + decoder->iface.Free = tsmf_ffmpeg_free; + *sptr = &decoder->iface; + return CHANNEL_RC_OK; +} diff --git a/third_party/FreeRDP/channels/tsmf/client/gstreamer/CMakeLists.txt b/third_party/FreeRDP/channels/tsmf/client/gstreamer/CMakeLists.txt new file mode 100644 index 0000000..ce1b1ae --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/client/gstreamer/CMakeLists.txt @@ -0,0 +1,61 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script for gstreamer subsystem +# +# (C) Copyright 2012 Hewlett-Packard Development Company, L.P. +# +# 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. + +define_channel_client_subsystem("tsmf" "gstreamer" "decoder") + +if(NOT gstreamer_FOUND) + message(FATAL_ERROR "GStreamer library not found, but required for TSMF module.") +endif() + +set(SRC "tsmf_gstreamer.c") + +pkg_check_modules(gstreamerbase gstreamer-base-1.0 REQUIRED) +pkg_check_modules(gstreamervideo gstreamer-video-1.0 REQUIRED) +pkg_check_modules(gstreamerapp gstreamer-app-1.0 REQUIRED) +freerdp_client_pc_add_requires_private("gstreamer-base-1.0;gstreamer-video-1.0;gstreamer-app-1.0") + +set(LIBS ${gstreamer_LIBRARIES} ${gstreamerbase_LIBRARIES} ${gstreamervideo_LIBRARIES} ${gstreamerapp_LIBRARIES}) +include_directories( + SYSTEM ${gstreamer_INCLUDE_DIRS} ${gstreamerbase_INCLUDE_DIRS} ${gstreamervideo_INCLUDE_DIRS} + ${gstreamerapp_INCLUDE_DIRS} +) + +if(ANDROID) + set(SRC ${SRC} tsmf_android.c) +else() + find_package(X11 REQUIRED) + freerdp_client_pc_add_requires_private("x11") + + list(APPEND SRC tsmf_X11.c) + list(APPEND LIBS ${X11_LIBRARIES} ${X11_Xext_LIB}) + if(NOT APPLE) + list(APPEND LIBS rt) + endif() + + if(X11_Xext_FOUND) + add_compile_definitions(WITH_XEXT=1) + endif() + +endif() + +set(${MODULE_PREFIX}_SRCS "${SRC}") + +set(${MODULE_PREFIX}_LIBS ${LIBS} winpr) + +include_directories(..) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") diff --git a/third_party/FreeRDP/channels/tsmf/client/gstreamer/tsmf_X11.c b/third_party/FreeRDP/channels/tsmf/client/gstreamer/tsmf_X11.c new file mode 100644 index 0000000..d553512 --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/client/gstreamer/tsmf_X11.c @@ -0,0 +1,501 @@ +/* + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - GStreamer Decoder X11 specifics + * + * (C) Copyright 2014 Thincast Technologies GmbH + * (C) Copyright 2014 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#ifndef __CYGWIN__ +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if GST_VERSION_MAJOR > 0 +#include +#else +#include +#endif + +#include +#include +#include + +#include + +#include "tsmf_platform.h" +#include "tsmf_constants.h" +#include "tsmf_decoder.h" + +#if !defined(WITH_XEXT) +#warning "Building TSMF without shape extension support" +#endif + +struct X11Handle +{ + int shmid; + int* xfwin; +#if defined(WITH_XEXT) + BOOL has_shape; +#endif + Display* disp; + Window subwin; + BOOL subwinMapped; +#if GST_VERSION_MAJOR > 0 + GstVideoOverlay* overlay; +#else + GstXOverlay* overlay; +#endif + int subwinWidth; + int subwinHeight; + int subwinX; + int subwinY; +}; + +static const char* get_shm_id() +{ + static char shm_id[128]; + sprintf_s(shm_id, sizeof(shm_id), "/com.freerdp.xfreerdp.tsmf_%016X", GetCurrentProcessId()); + return shm_id; +} + +static GstBusSyncReply tsmf_platform_bus_sync_handler(GstBus* bus, GstMessage* message, + gpointer user_data) +{ + struct X11Handle* hdl; + + TSMFGstreamerDecoder* decoder = user_data; + + if (GST_MESSAGE_TYPE(message) != GST_MESSAGE_ELEMENT) + return GST_BUS_PASS; + +#if GST_VERSION_MAJOR > 0 + if (!gst_is_video_overlay_prepare_window_handle_message(message)) + return GST_BUS_PASS; +#else + if (!gst_structure_has_name(message->structure, "prepare-xwindow-id")) + return GST_BUS_PASS; +#endif + + hdl = (struct X11Handle*)decoder->platform; + + if (hdl->subwin) + { +#if GST_VERSION_MAJOR > 0 + hdl->overlay = GST_VIDEO_OVERLAY(GST_MESSAGE_SRC(message)); + gst_video_overlay_set_window_handle(hdl->overlay, hdl->subwin); + gst_video_overlay_handle_events(hdl->overlay, FALSE); +#else + hdl->overlay = GST_X_OVERLAY(GST_MESSAGE_SRC(message)); +#if GST_CHECK_VERSION(0, 10, 31) + gst_x_overlay_set_window_handle(hdl->overlay, hdl->subwin); +#else + gst_x_overlay_set_xwindow_id(hdl->overlay, hdl->subwin); +#endif + gst_x_overlay_handle_events(hdl->overlay, TRUE); +#endif + + if (hdl->subwinWidth != -1 && hdl->subwinHeight != -1 && hdl->subwinX != -1 && + hdl->subwinY != -1) + { +#if GST_VERSION_MAJOR > 0 + if (!gst_video_overlay_set_render_rectangle(hdl->overlay, 0, 0, hdl->subwinWidth, + hdl->subwinHeight)) + { + WLog_ERR(TAG, "Could not resize overlay!"); + } + + gst_video_overlay_expose(hdl->overlay); +#else + if (!gst_x_overlay_set_render_rectangle(hdl->overlay, 0, 0, hdl->subwinWidth, + hdl->subwinHeight)) + { + WLog_ERR(TAG, "Could not resize overlay!"); + } + + gst_x_overlay_expose(hdl->overlay); +#endif + XLockDisplay(hdl->disp); + XMoveResizeWindow(hdl->disp, hdl->subwin, hdl->subwinX, hdl->subwinY, hdl->subwinWidth, + hdl->subwinHeight); + XSync(hdl->disp, FALSE); + XUnlockDisplay(hdl->disp); + } + } + else + { + g_warning("Window was not available before retrieving the overlay!"); + } + + gst_message_unref(message); + + return GST_BUS_DROP; +} + +const char* tsmf_platform_get_video_sink(void) +{ + return "autovideosink"; +} + +const char* tsmf_platform_get_audio_sink(void) +{ + return "autoaudiosink"; +} + +int tsmf_platform_create(TSMFGstreamerDecoder* decoder) +{ + struct X11Handle* hdl; + + if (!decoder) + return -1; + + if (decoder->platform) + return -1; + + hdl = calloc(1, sizeof(struct X11Handle)); + if (!hdl) + { + WLog_ERR(TAG, "Could not allocate handle."); + return -1; + } + + decoder->platform = hdl; + hdl->shmid = shm_open(get_shm_id(), (O_RDWR | O_CREAT), (PROT_READ | PROT_WRITE)); + if (hdl->shmid == -1) + { + char ebuffer[256] = WINPR_C_ARRAY_INIT; + WLog_ERR(TAG, "failed to get access to shared memory - shmget(%s): %i - %s", get_shm_id(), + errno, winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + return -2; + } + + hdl->xfwin = mmap(0, sizeof(void*), PROT_READ | PROT_WRITE, MAP_SHARED, hdl->shmid, 0); + if (hdl->xfwin == MAP_FAILED) + { + WLog_ERR(TAG, "shmat failed!"); + return -3; + } + + hdl->disp = XOpenDisplay(nullptr); + if (!hdl->disp) + { + WLog_ERR(TAG, "Failed to open display"); + return -4; + } + + hdl->subwinMapped = FALSE; + hdl->subwinX = -1; + hdl->subwinY = -1; + hdl->subwinWidth = -1; + hdl->subwinHeight = -1; + + return 0; +} + +int tsmf_platform_set_format(TSMFGstreamerDecoder* decoder) +{ + if (!decoder) + return -1; + + if (decoder->media_type == TSMF_MAJOR_TYPE_VIDEO) + { + } + + return 0; +} + +int tsmf_platform_register_handler(TSMFGstreamerDecoder* decoder) +{ + GstBus* bus; + + if (!decoder) + return -1; + + if (!decoder->pipe) + return -1; + + bus = gst_pipeline_get_bus(GST_PIPELINE(decoder->pipe)); + +#if GST_VERSION_MAJOR > 0 + gst_bus_set_sync_handler(bus, (GstBusSyncHandler)tsmf_platform_bus_sync_handler, decoder, + nullptr); +#else + gst_bus_set_sync_handler(bus, (GstBusSyncHandler)tsmf_platform_bus_sync_handler, decoder); +#endif + + if (!bus) + { + WLog_ERR(TAG, "gst_pipeline_get_bus failed!"); + return 1; + } + + gst_object_unref(bus); + + return 0; +} + +int tsmf_platform_free(TSMFGstreamerDecoder* decoder) +{ + struct X11Handle* hdl = decoder->platform; + + if (!hdl) + return -1; + + if (hdl->disp) + XCloseDisplay(hdl->disp); + + if (hdl->xfwin) + munmap(0, sizeof(void*)); + + if (hdl->shmid >= 0) + close(hdl->shmid); + + free(hdl); + decoder->platform = nullptr; + + return 0; +} + +int tsmf_window_create(TSMFGstreamerDecoder* decoder) +{ + struct X11Handle* hdl; + + if (decoder->media_type != TSMF_MAJOR_TYPE_VIDEO) + { + decoder->ready = TRUE; + return -3; + } + else + { + if (!decoder) + return -1; + + if (!decoder->platform) + return -1; + + hdl = (struct X11Handle*)decoder->platform; + + if (!hdl->subwin) + { + XLockDisplay(hdl->disp); + hdl->subwin = XCreateSimpleWindow(hdl->disp, *(int*)hdl->xfwin, 0, 0, 1, 1, 0, 0, 0); + XUnlockDisplay(hdl->disp); + + if (!hdl->subwin) + { + WLog_ERR(TAG, "Could not create subwindow!"); + } + } + + tsmf_window_map(decoder); + + decoder->ready = TRUE; +#if defined(WITH_XEXT) + int event, error; + XLockDisplay(hdl->disp); + hdl->has_shape = XShapeQueryExtension(hdl->disp, &event, &error); + XUnlockDisplay(hdl->disp); +#endif + } + + return 0; +} + +int tsmf_window_resize(TSMFGstreamerDecoder* decoder, int x, int y, int width, int height, + int nr_rects, RDP_RECT* rects) +{ + struct X11Handle* hdl; + + if (!decoder) + return -1; + + if (decoder->media_type != TSMF_MAJOR_TYPE_VIDEO) + { + return -3; + } + + if (!decoder->platform) + return -1; + + hdl = (struct X11Handle*)decoder->platform; + DEBUG_TSMF("resize: x=%d, y=%d, w=%d, h=%d", x, y, width, height); + + if (hdl->overlay) + { +#if GST_VERSION_MAJOR > 0 + + if (!gst_video_overlay_set_render_rectangle(hdl->overlay, 0, 0, width, height)) + { + WLog_ERR(TAG, "Could not resize overlay!"); + } + + gst_video_overlay_expose(hdl->overlay); +#else + if (!gst_x_overlay_set_render_rectangle(hdl->overlay, 0, 0, width, height)) + { + WLog_ERR(TAG, "Could not resize overlay!"); + } + + gst_x_overlay_expose(hdl->overlay); +#endif + } + + if (hdl->subwin) + { + hdl->subwinX = x; + hdl->subwinY = y; + hdl->subwinWidth = width; + hdl->subwinHeight = height; + + XLockDisplay(hdl->disp); + XMoveResizeWindow(hdl->disp, hdl->subwin, hdl->subwinX, hdl->subwinY, hdl->subwinWidth, + hdl->subwinHeight); + + /* Unmap the window if there are no visibility rects */ + if (nr_rects == 0) + tsmf_window_unmap(decoder); + else + tsmf_window_map(decoder); + +#if defined(WITH_XEXT) + if (hdl->has_shape) + { + XRectangle* xrects = nullptr; + + if (nr_rects == 0) + { + xrects = calloc(1, sizeof(XRectangle)); + xrects->x = x; + xrects->y = y; + xrects->width = width; + xrects->height = height; + } + else + { + xrects = calloc(nr_rects, sizeof(XRectangle)); + } + + if (xrects) + { + for (int i = 0; i < nr_rects; i++) + { + xrects[i].x = rects[i].x - x; + xrects[i].y = rects[i].y - y; + xrects[i].width = rects[i].width; + xrects[i].height = rects[i].height; + } + + XShapeCombineRectangles(hdl->disp, hdl->subwin, ShapeBounding, x, y, xrects, + nr_rects, ShapeSet, 0); + free(xrects); + } + } +#endif + XSync(hdl->disp, FALSE); + XUnlockDisplay(hdl->disp); + } + + return 0; +} + +int tsmf_window_map(TSMFGstreamerDecoder* decoder) +{ + struct X11Handle* hdl; + if (!decoder) + return -1; + + hdl = (struct X11Handle*)decoder->platform; + + /* Only need to map the window if it is not currently mapped */ + if ((hdl->subwin) && (!hdl->subwinMapped)) + { + XLockDisplay(hdl->disp); + XMapWindow(hdl->disp, hdl->subwin); + hdl->subwinMapped = TRUE; + XSync(hdl->disp, FALSE); + XUnlockDisplay(hdl->disp); + } + + return 0; +} + +int tsmf_window_unmap(TSMFGstreamerDecoder* decoder) +{ + struct X11Handle* hdl; + if (!decoder) + return -1; + + hdl = (struct X11Handle*)decoder->platform; + + /* only need to unmap window if it is currently mapped */ + if ((hdl->subwin) && (hdl->subwinMapped)) + { + XLockDisplay(hdl->disp); + XUnmapWindow(hdl->disp, hdl->subwin); + hdl->subwinMapped = FALSE; + XSync(hdl->disp, FALSE); + XUnlockDisplay(hdl->disp); + } + + return 0; +} + +int tsmf_window_destroy(TSMFGstreamerDecoder* decoder) +{ + struct X11Handle* hdl; + + if (!decoder) + return -1; + + decoder->ready = FALSE; + + if (decoder->media_type != TSMF_MAJOR_TYPE_VIDEO) + return -3; + + if (!decoder->platform) + return -1; + + hdl = (struct X11Handle*)decoder->platform; + + if (hdl->subwin) + { + XLockDisplay(hdl->disp); + XDestroyWindow(hdl->disp, hdl->subwin); + XSync(hdl->disp, FALSE); + XUnlockDisplay(hdl->disp); + } + + hdl->overlay = nullptr; + hdl->subwin = 0; + hdl->subwinMapped = FALSE; + hdl->subwinX = -1; + hdl->subwinY = -1; + hdl->subwinWidth = -1; + hdl->subwinHeight = -1; + return 0; +} diff --git a/third_party/FreeRDP/channels/tsmf/client/gstreamer/tsmf_gstreamer.c b/third_party/FreeRDP/channels/tsmf/client/gstreamer/tsmf_gstreamer.c new file mode 100644 index 0000000..54e746e --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/client/gstreamer/tsmf_gstreamer.c @@ -0,0 +1,1064 @@ +/* + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - GStreamer Decoder + * + * (C) Copyright 2012 HP Development Company, LLC + * (C) Copyright 2014 Thincast Technologies GmbH + * (C) Copyright 2014 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#include "tsmf_constants.h" +#include "tsmf_decoder.h" +#include "tsmf_platform.h" + +/* 1 second = 10,000,000 100ns units*/ +#define SEEK_TOLERANCE 10 * 1000 * 1000 + +static BOOL tsmf_gstreamer_pipeline_build(TSMFGstreamerDecoder* mdecoder); +static void tsmf_gstreamer_clean_up(TSMFGstreamerDecoder* mdecoder); +static int tsmf_gstreamer_pipeline_set_state(TSMFGstreamerDecoder* mdecoder, + GstState desired_state); +static BOOL tsmf_gstreamer_buffer_level(ITSMFDecoder* decoder); + +static const char* get_type(TSMFGstreamerDecoder* mdecoder) +{ + if (!mdecoder) + return nullptr; + + switch (mdecoder->media_type) + { + case TSMF_MAJOR_TYPE_VIDEO: + return "VIDEO"; + case TSMF_MAJOR_TYPE_AUDIO: + return "AUDIO"; + default: + return "UNKNOWN"; + } +} + +static void cb_child_added(GstChildProxy* child_proxy, GObject* object, + TSMFGstreamerDecoder* mdecoder) +{ + DEBUG_TSMF("NAME: %s", G_OBJECT_TYPE_NAME(object)); + + if (!g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstXvImageSink") || + !g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstXImageSink") || + !g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstFluVAAutoSink")) + { + gst_base_sink_set_max_lateness((GstBaseSink*)object, 10000000); /* nanoseconds */ + g_object_set(G_OBJECT(object), "sync", TRUE, nullptr); /* synchronize on the clock */ + g_object_set(G_OBJECT(object), "async", TRUE, nullptr); /* no async state changes */ + } + + else if (!g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstAlsaSink") || + !g_strcmp0(G_OBJECT_TYPE_NAME(object), "GstPulseSink")) + { + gst_base_sink_set_max_lateness((GstBaseSink*)object, 10000000); /* nanoseconds */ + g_object_set(G_OBJECT(object), "slave-method", 1, nullptr); + g_object_set(G_OBJECT(object), "buffer-time", (gint64)20000, nullptr); /* microseconds */ + g_object_set(G_OBJECT(object), "drift-tolerance", (gint64)20000, + nullptr); /* microseconds */ + g_object_set(G_OBJECT(object), "latency-time", (gint64)10000, nullptr); /* microseconds */ + g_object_set(G_OBJECT(object), "sync", TRUE, nullptr); /* synchronize on the clock */ + g_object_set(G_OBJECT(object), "async", TRUE, nullptr); /* no async state changes */ + } +} + +static void tsmf_gstreamer_enough_data(GstAppSrc* src, gpointer user_data) +{ + TSMFGstreamerDecoder* mdecoder = user_data; + (void)mdecoder; + DEBUG_TSMF("%s", get_type(mdecoder)); +} + +static void tsmf_gstreamer_need_data(GstAppSrc* src, guint length, gpointer user_data) +{ + TSMFGstreamerDecoder* mdecoder = user_data; + (void)mdecoder; + DEBUG_TSMF("%s length=%u", get_type(mdecoder), length); +} + +static gboolean tsmf_gstreamer_seek_data(GstAppSrc* src, guint64 offset, gpointer user_data) +{ + TSMFGstreamerDecoder* mdecoder = user_data; + (void)mdecoder; + DEBUG_TSMF("%s offset=%" PRIu64 "", get_type(mdecoder), offset); + + return TRUE; +} + +static BOOL tsmf_gstreamer_change_volume(ITSMFDecoder* decoder, UINT32 newVolume, UINT32 muted) +{ + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder; + + if (!mdecoder || !mdecoder->pipe) + return TRUE; + + if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO) + return TRUE; + + mdecoder->gstMuted = (BOOL)muted; + DEBUG_TSMF("mute=[%" PRId32 "]", mdecoder->gstMuted); + mdecoder->gstVolume = (double)newVolume / (double)10000; + DEBUG_TSMF("gst_new_vol=[%f]", mdecoder->gstVolume); + + if (!mdecoder->volume) + return TRUE; + + if (!G_IS_OBJECT(mdecoder->volume)) + return TRUE; + + g_object_set(mdecoder->volume, "mute", mdecoder->gstMuted, nullptr); + g_object_set(mdecoder->volume, "volume", mdecoder->gstVolume, nullptr); + + return TRUE; +} + +static inline GstClockTime tsmf_gstreamer_timestamp_ms_to_gst(UINT64 ms_timestamp) +{ + /* + * Convert Microsoft 100ns timestamps to Gstreamer 1ns units. + */ + return (GstClockTime)(ms_timestamp * 100); +} + +int tsmf_gstreamer_pipeline_set_state(TSMFGstreamerDecoder* mdecoder, GstState desired_state) +{ + GstStateChangeReturn state_change; + const char* name; + const char* sname = get_type(mdecoder); + + if (!mdecoder) + return 0; + + if (!mdecoder->pipe) + return 0; /* Just in case this is called during startup or shutdown when we don't expect it + */ + + if (desired_state == mdecoder->state) + return 0; /* Redundant request - Nothing to do */ + + name = gst_element_state_get_name(desired_state); /* For debug */ + DEBUG_TSMF("%s to %s", sname, name); + state_change = gst_element_set_state(mdecoder->pipe, desired_state); + + if (state_change == GST_STATE_CHANGE_FAILURE) + { + WLog_ERR(TAG, "%s: (%s) GST_STATE_CHANGE_FAILURE.", sname, name); + } + else if (state_change == GST_STATE_CHANGE_ASYNC) + { + WLog_ERR(TAG, "%s: (%s) GST_STATE_CHANGE_ASYNC.", sname, name); + mdecoder->state = desired_state; + } + else + { + mdecoder->state = desired_state; + } + + return 0; +} + +static GstBuffer* tsmf_get_buffer_from_data(const void* raw_data, gsize size) +{ + GstBuffer* buffer; + gpointer data; + + if (!raw_data) + return nullptr; + + if (size < 1) + return nullptr; + + data = g_malloc(size); + + if (!data) + { + WLog_ERR(TAG, "Could not allocate %" G_GSIZE_FORMAT " bytes of data.", size); + return nullptr; + } + + CopyMemory(data, raw_data, size); + +#if GST_VERSION_MAJOR > 0 + buffer = gst_buffer_new_wrapped(data, size); +#else + buffer = gst_buffer_new(); + + if (!buffer) + { + WLog_ERR(TAG, "Could not create GstBuffer"); + free(data); + return nullptr; + } + + GST_BUFFER_MALLOCDATA(buffer) = data; + GST_BUFFER_SIZE(buffer) = size; + GST_BUFFER_DATA(buffer) = GST_BUFFER_MALLOCDATA(buffer); +#endif + + return buffer; +} + +static BOOL tsmf_gstreamer_set_format(ITSMFDecoder* decoder, TS_AM_MEDIA_TYPE* media_type) +{ + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder; + + if (!mdecoder) + return FALSE; + + DEBUG_TSMF(""); + + switch (media_type->MajorType) + { + case TSMF_MAJOR_TYPE_VIDEO: + mdecoder->media_type = TSMF_MAJOR_TYPE_VIDEO; + break; + case TSMF_MAJOR_TYPE_AUDIO: + mdecoder->media_type = TSMF_MAJOR_TYPE_AUDIO; + break; + default: + return FALSE; + } + + switch (media_type->SubType) + { + case TSMF_SUB_TYPE_WVC1: + mdecoder->gst_caps = gst_caps_new_simple( + "video/x-wmv", "bitrate", G_TYPE_UINT, media_type->BitRate, "width", G_TYPE_INT, + media_type->Width, "height", G_TYPE_INT, media_type->Height, "wmvversion", + G_TYPE_INT, 3, +#if GST_VERSION_MAJOR > 0 + "format", G_TYPE_STRING, "WVC1", +#else + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('W', 'V', 'C', '1'), +#endif + "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator, + media_type->SamplesPerSecond.Denominator, "pixel-aspect-ratio", GST_TYPE_FRACTION, + 1, 1, nullptr); + break; + case TSMF_SUB_TYPE_MP4S: + mdecoder->gst_caps = gst_caps_new_simple( + "video/x-divx", "divxversion", G_TYPE_INT, 5, "bitrate", G_TYPE_UINT, + media_type->BitRate, "width", G_TYPE_INT, media_type->Width, "height", G_TYPE_INT, + media_type->Height, +#if GST_VERSION_MAJOR > 0 + "format", G_TYPE_STRING, "MP42", +#else + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('M', 'P', '4', '2'), +#endif + "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator, + media_type->SamplesPerSecond.Denominator, nullptr); + break; + case TSMF_SUB_TYPE_MP42: + mdecoder->gst_caps = gst_caps_new_simple( + "video/x-msmpeg", "msmpegversion", G_TYPE_INT, 42, "bitrate", G_TYPE_UINT, + media_type->BitRate, "width", G_TYPE_INT, media_type->Width, "height", G_TYPE_INT, + media_type->Height, +#if GST_VERSION_MAJOR > 0 + "format", G_TYPE_STRING, "MP42", +#else + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('M', 'P', '4', '2'), +#endif + "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator, + media_type->SamplesPerSecond.Denominator, nullptr); + break; + case TSMF_SUB_TYPE_MP43: + mdecoder->gst_caps = gst_caps_new_simple( + "video/x-msmpeg", "msmpegversion", G_TYPE_INT, 43, "bitrate", G_TYPE_UINT, + media_type->BitRate, "width", G_TYPE_INT, media_type->Width, "height", G_TYPE_INT, + media_type->Height, +#if GST_VERSION_MAJOR > 0 + "format", G_TYPE_STRING, "MP43", +#else + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('M', 'P', '4', '3'), +#endif + "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator, + media_type->SamplesPerSecond.Denominator, nullptr); + break; + case TSMF_SUB_TYPE_M4S2: + mdecoder->gst_caps = gst_caps_new_simple( + "video/mpeg", "mpegversion", G_TYPE_INT, 4, "width", G_TYPE_INT, media_type->Width, + "height", G_TYPE_INT, media_type->Height, +#if GST_VERSION_MAJOR > 0 + "format", G_TYPE_STRING, "M4S2", +#else + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('M', '4', 'S', '2'), +#endif + "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator, + media_type->SamplesPerSecond.Denominator, nullptr); + break; + case TSMF_SUB_TYPE_WMA9: + mdecoder->gst_caps = gst_caps_new_simple( + "audio/x-wma", "wmaversion", G_TYPE_INT, 3, "rate", G_TYPE_INT, + media_type->SamplesPerSecond.Numerator, "channels", G_TYPE_INT, + media_type->Channels, "bitrate", G_TYPE_INT, media_type->BitRate, "depth", + G_TYPE_INT, media_type->BitsPerSample, "width", G_TYPE_INT, + media_type->BitsPerSample, "block_align", G_TYPE_INT, media_type->BlockAlign, + nullptr); + break; + case TSMF_SUB_TYPE_WMA1: + mdecoder->gst_caps = gst_caps_new_simple( + "audio/x-wma", "wmaversion", G_TYPE_INT, 1, "rate", G_TYPE_INT, + media_type->SamplesPerSecond.Numerator, "channels", G_TYPE_INT, + media_type->Channels, "bitrate", G_TYPE_INT, media_type->BitRate, "depth", + G_TYPE_INT, media_type->BitsPerSample, "width", G_TYPE_INT, + media_type->BitsPerSample, "block_align", G_TYPE_INT, media_type->BlockAlign, + nullptr); + break; + case TSMF_SUB_TYPE_WMA2: + mdecoder->gst_caps = gst_caps_new_simple( + "audio/x-wma", "wmaversion", G_TYPE_INT, 2, "rate", G_TYPE_INT, + media_type->SamplesPerSecond.Numerator, "channels", G_TYPE_INT, + media_type->Channels, "bitrate", G_TYPE_INT, media_type->BitRate, "depth", + G_TYPE_INT, media_type->BitsPerSample, "width", G_TYPE_INT, + media_type->BitsPerSample, "block_align", G_TYPE_INT, media_type->BlockAlign, + nullptr); + break; + case TSMF_SUB_TYPE_MP3: + mdecoder->gst_caps = + gst_caps_new_simple("audio/mpeg", "mpegversion", G_TYPE_INT, 1, "layer", G_TYPE_INT, + 3, "rate", G_TYPE_INT, media_type->SamplesPerSecond.Numerator, + "channels", G_TYPE_INT, media_type->Channels, nullptr); + break; + case TSMF_SUB_TYPE_WMV1: + mdecoder->gst_caps = gst_caps_new_simple( + "video/x-wmv", "bitrate", G_TYPE_UINT, media_type->BitRate, "width", G_TYPE_INT, + media_type->Width, "height", G_TYPE_INT, media_type->Height, "wmvversion", + G_TYPE_INT, 1, +#if GST_VERSION_MAJOR > 0 + "format", G_TYPE_STRING, "WMV1", +#else + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('W', 'M', 'V', '1'), +#endif + "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator, + media_type->SamplesPerSecond.Denominator, nullptr); + break; + case TSMF_SUB_TYPE_WMV2: + mdecoder->gst_caps = gst_caps_new_simple( + "video/x-wmv", "width", G_TYPE_INT, media_type->Width, "height", G_TYPE_INT, + media_type->Height, "wmvversion", G_TYPE_INT, 2, +#if GST_VERSION_MAJOR > 0 + "format", G_TYPE_STRING, "WMV2", +#else + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('W', 'M', 'V', '2'), +#endif + "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator, + media_type->SamplesPerSecond.Denominator, "pixel-aspect-ratio", GST_TYPE_FRACTION, + 1, 1, nullptr); + break; + case TSMF_SUB_TYPE_WMV3: + mdecoder->gst_caps = gst_caps_new_simple( + "video/x-wmv", "bitrate", G_TYPE_UINT, media_type->BitRate, "width", G_TYPE_INT, + media_type->Width, "height", G_TYPE_INT, media_type->Height, "wmvversion", + G_TYPE_INT, 3, +#if GST_VERSION_MAJOR > 0 + "format", G_TYPE_STRING, "WMV3", +#else + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC('W', 'M', 'V', '3'), +#endif + "framerate", GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator, + media_type->SamplesPerSecond.Denominator, "pixel-aspect-ratio", GST_TYPE_FRACTION, + 1, 1, nullptr); + break; + case TSMF_SUB_TYPE_AVC1: + case TSMF_SUB_TYPE_H264: + mdecoder->gst_caps = gst_caps_new_simple( + "video/x-h264", "width", G_TYPE_INT, media_type->Width, "height", G_TYPE_INT, + media_type->Height, "framerate", GST_TYPE_FRACTION, + media_type->SamplesPerSecond.Numerator, media_type->SamplesPerSecond.Denominator, + "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, "stream-format", G_TYPE_STRING, + "byte-stream", "alignment", G_TYPE_STRING, "nal", nullptr); + break; + case TSMF_SUB_TYPE_AC3: + mdecoder->gst_caps = gst_caps_new_simple( + "audio/x-ac3", "rate", G_TYPE_INT, media_type->SamplesPerSecond.Numerator, + "channels", G_TYPE_INT, media_type->Channels, nullptr); + break; + case TSMF_SUB_TYPE_AAC: + + /* For AAC the pFormat is a HEAACWAVEINFO struct, and the codec data + is at the end of it. See + http://msdn.microsoft.com/en-us/library/dd757806.aspx */ + if (media_type->ExtraData) + { + if (media_type->ExtraDataSize < 12) + return FALSE; + media_type->ExtraData += 12; + media_type->ExtraDataSize -= 12; + } + + mdecoder->gst_caps = gst_caps_new_simple( + "audio/mpeg", "rate", G_TYPE_INT, media_type->SamplesPerSecond.Numerator, + "channels", G_TYPE_INT, media_type->Channels, "mpegversion", G_TYPE_INT, 4, + "framed", G_TYPE_BOOLEAN, TRUE, "stream-format", G_TYPE_STRING, "raw", nullptr); + break; + case TSMF_SUB_TYPE_MP1A: + mdecoder->gst_caps = + gst_caps_new_simple("audio/mpeg", "mpegversion", G_TYPE_INT, 1, "channels", + G_TYPE_INT, media_type->Channels, nullptr); + break; + case TSMF_SUB_TYPE_MP1V: + mdecoder->gst_caps = + gst_caps_new_simple("video/mpeg", "mpegversion", G_TYPE_INT, 1, "width", G_TYPE_INT, + media_type->Width, "height", G_TYPE_INT, media_type->Height, + "systemstream", G_TYPE_BOOLEAN, FALSE, nullptr); + break; + case TSMF_SUB_TYPE_YUY2: +#if GST_VERSION_MAJOR > 0 + mdecoder->gst_caps = gst_caps_new_simple( + "video/x-raw", "format", G_TYPE_STRING, "YUY2", "width", G_TYPE_INT, + media_type->Width, "height", G_TYPE_INT, media_type->Height, nullptr); +#else + mdecoder->gst_caps = gst_caps_new_simple( + "video/x-raw-yuv", "format", G_TYPE_STRING, "YUY2", "width", G_TYPE_INT, + media_type->Width, "height", G_TYPE_INT, media_type->Height, "framerate", + GST_TYPE_FRACTION, media_type->SamplesPerSecond.Numerator, + media_type->SamplesPerSecond.Denominator, nullptr); +#endif + break; + case TSMF_SUB_TYPE_MP2V: + mdecoder->gst_caps = + gst_caps_new_simple("video/mpeg", "mpegversion", G_TYPE_INT, 2, "systemstream", + G_TYPE_BOOLEAN, FALSE, nullptr); + break; + case TSMF_SUB_TYPE_MP2A: + mdecoder->gst_caps = + gst_caps_new_simple("audio/mpeg", "mpegversion", G_TYPE_INT, 1, "rate", G_TYPE_INT, + media_type->SamplesPerSecond.Numerator, "channels", G_TYPE_INT, + media_type->Channels, nullptr); + break; + case TSMF_SUB_TYPE_FLAC: + mdecoder->gst_caps = gst_caps_new_simple("audio/x-flac", "", nullptr); + break; + default: + WLog_ERR(TAG, "unknown format:(%d).", media_type->SubType); + return FALSE; + } + + if (media_type->ExtraDataSize > 0) + { + GstBuffer* buffer; + DEBUG_TSMF("Extra data available (%" PRIu32 ")", media_type->ExtraDataSize); + buffer = tsmf_get_buffer_from_data(media_type->ExtraData, media_type->ExtraDataSize); + + if (!buffer) + { + WLog_ERR(TAG, "could not allocate GstBuffer!"); + return FALSE; + } + + gst_caps_set_simple(mdecoder->gst_caps, "codec_data", GST_TYPE_BUFFER, buffer, nullptr); + } + + DEBUG_TSMF("%p format '%s'", (void*)mdecoder, gst_caps_to_string(mdecoder->gst_caps)); + tsmf_platform_set_format(mdecoder); + + /* Create the pipeline... */ + if (!tsmf_gstreamer_pipeline_build(mdecoder)) + return FALSE; + + return TRUE; +} + +void tsmf_gstreamer_clean_up(TSMFGstreamerDecoder* mdecoder) +{ + if (!mdecoder || !mdecoder->pipe) + return; + + if (mdecoder->pipe && GST_OBJECT_REFCOUNT_VALUE(mdecoder->pipe) > 0) + { + tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_NULL); + gst_object_unref(mdecoder->pipe); + } + + mdecoder->ready = FALSE; + mdecoder->paused = FALSE; + + mdecoder->pipe = nullptr; + mdecoder->src = nullptr; + mdecoder->queue = nullptr; +} + +BOOL tsmf_gstreamer_pipeline_build(TSMFGstreamerDecoder* mdecoder) +{ +#if GST_VERSION_MAJOR > 0 + const char* video = + "appsrc name=videosource ! queue2 name=videoqueue ! decodebin name=videodecoder !"; + const char* audio = + "appsrc name=audiosource ! queue2 name=audioqueue ! decodebin name=audiodecoder ! " + "audioconvert ! audiorate ! audioresample ! volume name=audiovolume !"; +#else + const char* video = + "appsrc name=videosource ! queue2 name=videoqueue ! decodebin2 name=videodecoder !"; + const char* audio = + "appsrc name=audiosource ! queue2 name=audioqueue ! decodebin2 name=audiodecoder ! " + "audioconvert ! audiorate ! audioresample ! volume name=audiovolume !"; +#endif + char pipeline[1024]; + + if (!mdecoder) + return FALSE; + + /* TODO: Construction of the pipeline from a string allows easy overwrite with arguments. + * The only fixed elements necessary are appsrc and the volume element for audio streams. + * The rest could easily be provided in gstreamer pipeline notation from command line. */ + if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO) + sprintf_s(pipeline, sizeof(pipeline), "%s %s name=videosink", video, + tsmf_platform_get_video_sink()); + else + sprintf_s(pipeline, sizeof(pipeline), "%s %s name=audiosink", audio, + tsmf_platform_get_audio_sink()); + + DEBUG_TSMF("pipeline=%s", pipeline); + mdecoder->pipe = gst_parse_launch(pipeline, nullptr); + + if (!mdecoder->pipe) + { + WLog_ERR(TAG, "Failed to create new pipe"); + return FALSE; + } + + if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO) + mdecoder->src = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "videosource"); + else + mdecoder->src = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "audiosource"); + + if (!mdecoder->src) + { + WLog_ERR(TAG, "Failed to get appsrc"); + return FALSE; + } + + if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO) + mdecoder->queue = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "videoqueue"); + else + mdecoder->queue = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "audioqueue"); + + if (!mdecoder->queue) + { + WLog_ERR(TAG, "Failed to get queue"); + return FALSE; + } + + if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO) + mdecoder->outsink = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "videosink"); + else + mdecoder->outsink = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "audiosink"); + + if (!mdecoder->outsink) + { + WLog_ERR(TAG, "Failed to get sink"); + return FALSE; + } + + g_signal_connect(mdecoder->outsink, "child-added", G_CALLBACK(cb_child_added), mdecoder); + + if (mdecoder->media_type == TSMF_MAJOR_TYPE_AUDIO) + { + mdecoder->volume = gst_bin_get_by_name(GST_BIN(mdecoder->pipe), "audiovolume"); + + if (!mdecoder->volume) + { + WLog_ERR(TAG, "Failed to get volume"); + return FALSE; + } + + tsmf_gstreamer_change_volume((ITSMFDecoder*)mdecoder, mdecoder->gstVolume * ((double)10000), + mdecoder->gstMuted); + } + + tsmf_platform_register_handler(mdecoder); + /* AppSrc settings */ + GstAppSrcCallbacks callbacks = { + tsmf_gstreamer_need_data, tsmf_gstreamer_enough_data, tsmf_gstreamer_seek_data, { nullptr } + }; + g_object_set(mdecoder->src, "format", GST_FORMAT_TIME, nullptr); + g_object_set(mdecoder->src, "is-live", FALSE, nullptr); + g_object_set(mdecoder->src, "block", FALSE, nullptr); + g_object_set(mdecoder->src, "blocksize", 1024, nullptr); + gst_app_src_set_caps((GstAppSrc*)mdecoder->src, mdecoder->gst_caps); + gst_app_src_set_callbacks((GstAppSrc*)mdecoder->src, &callbacks, mdecoder, nullptr); + gst_app_src_set_stream_type((GstAppSrc*)mdecoder->src, GST_APP_STREAM_TYPE_SEEKABLE); + gst_app_src_set_latency((GstAppSrc*)mdecoder->src, 0, -1); + gst_app_src_set_max_bytes((GstAppSrc*)mdecoder->src, (guint64)0); // unlimited + g_object_set(G_OBJECT(mdecoder->queue), "use-buffering", FALSE, nullptr); + g_object_set(G_OBJECT(mdecoder->queue), "use-rate-estimate", FALSE, nullptr); + g_object_set(G_OBJECT(mdecoder->queue), "max-size-buffers", 0, nullptr); + g_object_set(G_OBJECT(mdecoder->queue), "max-size-bytes", 0, nullptr); + g_object_set(G_OBJECT(mdecoder->queue), "max-size-time", (guint64)0, nullptr); + + /* Only set these properties if not an autosink, otherwise we will set properties when real + * sinks are added */ + if (!g_strcmp0(G_OBJECT_TYPE_NAME(mdecoder->outsink), "GstAutoVideoSink") && + !g_strcmp0(G_OBJECT_TYPE_NAME(mdecoder->outsink), "GstAutoAudioSink")) + { + if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO) + { + gst_base_sink_set_max_lateness((GstBaseSink*)mdecoder->outsink, + 10000000); /* nanoseconds */ + } + else + { + gst_base_sink_set_max_lateness((GstBaseSink*)mdecoder->outsink, + 10000000); /* nanoseconds */ + g_object_set(G_OBJECT(mdecoder->outsink), "buffer-time", (gint64)20000, + nullptr); /* microseconds */ + g_object_set(G_OBJECT(mdecoder->outsink), "drift-tolerance", (gint64)20000, + nullptr); /* microseconds */ + g_object_set(G_OBJECT(mdecoder->outsink), "latency-time", (gint64)10000, + nullptr); /* microseconds */ + g_object_set(G_OBJECT(mdecoder->outsink), "slave-method", 1, nullptr); + } + g_object_set(G_OBJECT(mdecoder->outsink), "sync", TRUE, + nullptr); /* synchronize on the clock */ + g_object_set(G_OBJECT(mdecoder->outsink), "async", TRUE, + nullptr); /* no async state changes */ + } + + tsmf_window_create(mdecoder); + tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_READY); + tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PLAYING); + mdecoder->pipeline_start_time_valid = 0; + mdecoder->shutdown = 0; + mdecoder->paused = FALSE; + + GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(mdecoder->pipe), GST_DEBUG_GRAPH_SHOW_ALL, + get_type(mdecoder)); + + return TRUE; +} + +static BOOL tsmf_gstreamer_decodeEx(ITSMFDecoder* decoder, const BYTE* data, UINT32 data_size, + UINT32 extensions, UINT64 start_time, UINT64 end_time, + UINT64 duration) +{ + GstBuffer* gst_buf; + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder; + UINT64 sample_time = tsmf_gstreamer_timestamp_ms_to_gst(start_time); + BOOL useTimestamps = TRUE; + + if (!mdecoder) + { + WLog_ERR(TAG, "Decoder not initialized!"); + return FALSE; + } + + /* + * This function is always called from a stream-specific thread. + * It should be alright to block here if necessary. + * We don't expect to block here often, since the pipeline should + * have more than enough buffering. + */ + DEBUG_TSMF( + "%s. Start:(%" PRIu64 ") End:(%" PRIu64 ") Duration:(%" PRIu64 ") Last Start:(%" PRIu64 ")", + get_type(mdecoder), start_time, end_time, duration, mdecoder->last_sample_start_time); + + if (mdecoder->shutdown) + { + WLog_ERR(TAG, "decodeEx called on shutdown decoder"); + return TRUE; + } + + if (mdecoder->gst_caps == nullptr) + { + WLog_ERR(TAG, "tsmf_gstreamer_set_format not called or invalid format."); + return FALSE; + } + + if (!mdecoder->pipe) + tsmf_gstreamer_pipeline_build(mdecoder); + + if (!mdecoder->src) + { + WLog_ERR( + TAG, + "failed to construct pipeline correctly. Unable to push buffer to source element."); + return FALSE; + } + + gst_buf = tsmf_get_buffer_from_data(data, data_size); + + if (gst_buf == nullptr) + { + WLog_ERR(TAG, "tsmf_get_buffer_from_data(%p, %" PRIu32 ") failed.", (void*)data, data_size); + return FALSE; + } + + /* Relative timestamping will sometimes be set to 0 + * so we ignore these timestamps just to be safe(bit 8) + */ + if (extensions & 0x00000080) + { + DEBUG_TSMF("Ignoring the timestamps - relative - bit 8"); + useTimestamps = FALSE; + } + + /* If no timestamps exist then we don't want to look at the timestamp values (bit 7) */ + if (extensions & 0x00000040) + { + DEBUG_TSMF("Ignoring the timestamps - none - bit 7"); + useTimestamps = FALSE; + } + + /* If performing a seek */ + if (mdecoder->seeking) + { + mdecoder->seeking = FALSE; + tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PAUSED); + mdecoder->pipeline_start_time_valid = 0; + } + + if (mdecoder->pipeline_start_time_valid) + { + DEBUG_TSMF("%s start time %" PRIu64 "", get_type(mdecoder), start_time); + + /* Adjusted the condition for a seek to be based on start time only + * WMV1 and WMV2 files in particular have bad end time and duration values + * there seems to be no real side effects of just using the start time instead + */ + UINT64 minTime = mdecoder->last_sample_start_time - (UINT64)SEEK_TOLERANCE; + UINT64 maxTime = mdecoder->last_sample_start_time + (UINT64)SEEK_TOLERANCE; + + /* Make sure the minTime stops at 0 , should we be at the beginning of the stream */ + if (mdecoder->last_sample_start_time < (UINT64)SEEK_TOLERANCE) + minTime = 0; + + /* If the start_time is valid and different from the previous start time by more than the + * seek tolerance, then we have a seek condition */ + if (((start_time > maxTime) || (start_time < minTime)) && useTimestamps) + { + DEBUG_TSMF("tsmf_gstreamer_decodeEx: start_time=[%" PRIu64 + "] > last_sample_start_time=[%" PRIu64 "] OR ", + start_time, mdecoder->last_sample_start_time); + DEBUG_TSMF("tsmf_gstreamer_decodeEx: start_time=[%" PRIu64 + "] < last_sample_start_time=[%" PRIu64 "] with", + start_time, mdecoder->last_sample_start_time); + DEBUG_TSMF( + "tsmf_gstreamer_decodeEX: a tolerance of more than [%lu] from the last sample", + SEEK_TOLERANCE); + DEBUG_TSMF("tsmf_gstreamer_decodeEX: minTime=[%" PRIu64 "] maxTime=[%" PRIu64 "]", + minTime, maxTime); + + mdecoder->seeking = TRUE; + + /* since we can't make the gstreamer pipeline jump to the new start time after a seek - + * we just maintain an offset between realtime and gstreamer time + */ + mdecoder->seek_offset = start_time; + } + } + else + { + DEBUG_TSMF("%s start time %" PRIu64 "", get_type(mdecoder), start_time); + /* Always set base/start time to 0. Will use seek offset to translate real buffer times + * back to 0. This allows the video to be started from anywhere and the ability to handle + * seeks without rebuilding the pipeline, etc. since that is costly + */ + gst_element_set_base_time(mdecoder->pipe, tsmf_gstreamer_timestamp_ms_to_gst(0)); + gst_element_set_start_time(mdecoder->pipe, tsmf_gstreamer_timestamp_ms_to_gst(0)); + mdecoder->pipeline_start_time_valid = 1; + + /* Set the seek offset if buffer has valid timestamps. */ + if (useTimestamps) + mdecoder->seek_offset = start_time; + + if (!gst_element_seek(mdecoder->pipe, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) + { + WLog_ERR(TAG, "seek failed"); + } + } + +#if GST_VERSION_MAJOR > 0 + if (useTimestamps) + GST_BUFFER_PTS(gst_buf) = + sample_time - tsmf_gstreamer_timestamp_ms_to_gst(mdecoder->seek_offset); + else + GST_BUFFER_PTS(gst_buf) = GST_CLOCK_TIME_NONE; +#else + if (useTimestamps) + GST_BUFFER_TIMESTAMP(gst_buf) = + sample_time - tsmf_gstreamer_timestamp_ms_to_gst(mdecoder->seek_offset); + else + GST_BUFFER_TIMESTAMP(gst_buf) = GST_CLOCK_TIME_NONE; +#endif + GST_BUFFER_DURATION(gst_buf) = GST_CLOCK_TIME_NONE; + GST_BUFFER_OFFSET(gst_buf) = GST_BUFFER_OFFSET_NONE; +#if GST_VERSION_MAJOR > 0 +#else + gst_buffer_set_caps(gst_buf, mdecoder->gst_caps); +#endif + gst_app_src_push_buffer(GST_APP_SRC(mdecoder->src), gst_buf); + + /* Should only update the last timestamps if the current ones are valid */ + if (useTimestamps) + { + mdecoder->last_sample_start_time = start_time; + mdecoder->last_sample_end_time = end_time; + } + + if (mdecoder->pipe && (GST_STATE(mdecoder->pipe) != GST_STATE_PLAYING)) + { + DEBUG_TSMF("%s: state=%s", get_type(mdecoder), + gst_element_state_get_name(GST_STATE(mdecoder->pipe))); + + DEBUG_TSMF("%s Paused: %" PRIi32 " Shutdown: %i Ready: %" PRIi32 "", get_type(mdecoder), + mdecoder->paused, mdecoder->shutdown, mdecoder->ready); + if (!mdecoder->paused && !mdecoder->shutdown && mdecoder->ready) + tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PLAYING); + } + + return TRUE; +} + +static BOOL tsmf_gstreamer_control(ITSMFDecoder* decoder, ITSMFControlMsg control_msg, UINT32* arg) +{ + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder; + + if (!mdecoder) + { + WLog_ERR(TAG, "Control called with no decoder!"); + return TRUE; + } + + if (control_msg == Control_Pause) + { + DEBUG_TSMF("Control_Pause %s", get_type(mdecoder)); + + if (mdecoder->paused) + { + WLog_ERR(TAG, "%s: Ignoring Control_Pause, already received!", get_type(mdecoder)); + return TRUE; + } + + tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PAUSED); + mdecoder->shutdown = 0; + mdecoder->paused = TRUE; + } + else if (control_msg == Control_Resume) + { + DEBUG_TSMF("Control_Resume %s", get_type(mdecoder)); + + if (!mdecoder->paused && !mdecoder->shutdown) + { + WLog_ERR(TAG, "%s: Ignoring Control_Resume, already received!", get_type(mdecoder)); + return TRUE; + } + + mdecoder->shutdown = 0; + mdecoder->paused = FALSE; + } + else if (control_msg == Control_Stop) + { + DEBUG_TSMF("Control_Stop %s", get_type(mdecoder)); + + if (mdecoder->shutdown) + { + WLog_ERR(TAG, "%s: Ignoring Control_Stop, already received!", get_type(mdecoder)); + return TRUE; + } + + /* Reset stamps, flush buffers, etc */ + if (mdecoder->pipe) + { + tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_NULL); + tsmf_window_destroy(mdecoder); + tsmf_gstreamer_clean_up(mdecoder); + } + mdecoder->seek_offset = 0; + mdecoder->pipeline_start_time_valid = 0; + mdecoder->shutdown = 1; + } + else if (control_msg == Control_Restart) + { + DEBUG_TSMF("Control_Restart %s", get_type(mdecoder)); + mdecoder->shutdown = 0; + mdecoder->paused = FALSE; + + if (mdecoder->pipeline_start_time_valid) + tsmf_gstreamer_pipeline_set_state(mdecoder, GST_STATE_PLAYING); + } + else + WLog_ERR(TAG, "Unknown control message %08x", control_msg); + + return TRUE; +} + +static BOOL tsmf_gstreamer_buffer_level(ITSMFDecoder* decoder) +{ + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder; + DEBUG_TSMF(""); + + if (!mdecoder) + return FALSE; + + guint clbuff = 0; + + if (G_IS_OBJECT(mdecoder->queue)) + g_object_get(mdecoder->queue, "current-level-buffers", &clbuff, nullptr); + + DEBUG_TSMF("%s buffer level %u", get_type(mdecoder), clbuff); + return clbuff; +} + +static void tsmf_gstreamer_free(ITSMFDecoder* decoder) +{ + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder; + DEBUG_TSMF("%s", get_type(mdecoder)); + + if (mdecoder) + { + tsmf_window_destroy(mdecoder); + tsmf_gstreamer_clean_up(mdecoder); + + if (mdecoder->gst_caps) + gst_caps_unref(mdecoder->gst_caps); + + tsmf_platform_free(mdecoder); + ZeroMemory(mdecoder, sizeof(TSMFGstreamerDecoder)); + free(mdecoder); + mdecoder = nullptr; + } +} + +static UINT64 tsmf_gstreamer_get_running_time(ITSMFDecoder* decoder) +{ + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder; + + if (!mdecoder) + return 0; + + if (!mdecoder->outsink) + return mdecoder->last_sample_start_time; + + if (!mdecoder->pipe) + return 0; + + GstFormat fmt = GST_FORMAT_TIME; + gint64 pos = 0; +#if GST_VERSION_MAJOR > 0 + gst_element_query_position(mdecoder->pipe, fmt, &pos); +#else + gst_element_query_position(mdecoder->pipe, &fmt, &pos); +#endif + return (UINT64)(pos / 100 + mdecoder->seek_offset); +} + +static BOOL tsmf_gstreamer_update_rendering_area(ITSMFDecoder* decoder, int newX, int newY, + int newWidth, int newHeight, int numRectangles, + RDP_RECT* rectangles) +{ + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder; + DEBUG_TSMF("x=%d, y=%d, w=%d, h=%d, rect=%d", newX, newY, newWidth, newHeight, numRectangles); + + if (mdecoder->media_type == TSMF_MAJOR_TYPE_VIDEO) + { + return tsmf_window_resize(mdecoder, newX, newY, newWidth, newHeight, numRectangles, + rectangles) == 0; + } + + return TRUE; +} + +static BOOL tsmf_gstreamer_ack(ITSMFDecoder* decoder, BOOL (*cb)(void*, BOOL), void* stream) +{ + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder; + DEBUG_TSMF(""); + mdecoder->ack_cb = nullptr; + mdecoder->stream = stream; + return TRUE; +} + +static BOOL tsmf_gstreamer_sync(ITSMFDecoder* decoder, void (*cb)(void*), void* stream) +{ + TSMFGstreamerDecoder* mdecoder = (TSMFGstreamerDecoder*)decoder; + DEBUG_TSMF(""); + mdecoder->sync_cb = nullptr; + mdecoder->stream = stream; + return TRUE; +} + +FREERDP_ENTRY_POINT(UINT VCAPITYPE gstreamer_freerdp_tsmf_client_decoder_subsystem_entry(void* ptr)) +{ + ITSMFDecoder** sptr = (ITSMFDecoder**)ptr; + WINPR_ASSERT(sptr); + *sptr = nullptr; + +#if GST_CHECK_VERSION(0, 10, 31) + if (!gst_is_initialized()) + { + gst_init(nullptr, nullptr); + } +#else + gst_init(nullptr, nullptr); +#endif + + TSMFGstreamerDecoder* decoder; + decoder = calloc(1, sizeof(TSMFGstreamerDecoder)); + + if (!decoder) + return ERROR_OUTOFMEMORY; + + decoder->iface.SetFormat = tsmf_gstreamer_set_format; + decoder->iface.Decode = nullptr; + decoder->iface.GetDecodedData = nullptr; + decoder->iface.GetDecodedFormat = nullptr; + decoder->iface.GetDecodedDimension = nullptr; + decoder->iface.GetRunningTime = tsmf_gstreamer_get_running_time; + decoder->iface.UpdateRenderingArea = tsmf_gstreamer_update_rendering_area; + decoder->iface.Free = tsmf_gstreamer_free; + decoder->iface.Control = tsmf_gstreamer_control; + decoder->iface.DecodeEx = tsmf_gstreamer_decodeEx; + decoder->iface.ChangeVolume = tsmf_gstreamer_change_volume; + decoder->iface.BufferLevel = tsmf_gstreamer_buffer_level; + decoder->iface.SetAckFunc = tsmf_gstreamer_ack; + decoder->iface.SetSyncFunc = tsmf_gstreamer_sync; + decoder->paused = FALSE; + decoder->gstVolume = 0.5; + decoder->gstMuted = FALSE; + decoder->state = GST_STATE_VOID_PENDING; /* No real state yet */ + decoder->last_sample_start_time = 0; + decoder->last_sample_end_time = 0; + decoder->seek_offset = 0; + decoder->seeking = FALSE; + + if (tsmf_platform_create(decoder) < 0) + { + free(decoder); + return ERROR_INTERNAL_ERROR; + } + + *sptr = &decoder->iface; + return CHANNEL_RC_OK; +} diff --git a/third_party/FreeRDP/channels/tsmf/client/gstreamer/tsmf_platform.h b/third_party/FreeRDP/channels/tsmf/client/gstreamer/tsmf_platform.h new file mode 100644 index 0000000..3a79b0c --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/client/gstreamer/tsmf_platform.h @@ -0,0 +1,105 @@ +/* + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - GStreamer Decoder + * platform specific functions + * + * (C) Copyright 2014 Thincast Technologies GmbH + * (C) Copyright 2014 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_TSMF_CLIENT_GST_PLATFORM_H +#define FREERDP_CHANNEL_TSMF_CLIENT_GST_PLATFORM_H + +#include +#include + +typedef struct +{ + ITSMFDecoder iface; + + int media_type; /* TSMF_MAJOR_TYPE_AUDIO or TSMF_MAJOR_TYPE_VIDEO */ + + gint64 duration; + + GstState state; + GstCaps* gst_caps; + + GstElement* pipe; + GstElement* src; + GstElement* queue; + GstElement* outsink; + GstElement* volume; + + BOOL ready; + BOOL paused; + UINT64 last_sample_start_time; + UINT64 last_sample_end_time; + BOOL seeking; + UINT64 seek_offset; + + double gstVolume; + BOOL gstMuted; + + int pipeline_start_time_valid; /* We've set the start time and have not reset the pipeline */ + int shutdown; /* The decoder stream is shutting down */ + + void* platform; + + BOOL (*ack_cb)(void*, BOOL); + void (*sync_cb)(void*); + void* stream; + +} TSMFGstreamerDecoder; + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL const char* tsmf_platform_get_video_sink(void); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL const char* tsmf_platform_get_audio_sink(void); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL int tsmf_platform_create(TSMFGstreamerDecoder* decoder); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL int tsmf_platform_set_format(TSMFGstreamerDecoder* decoder); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL int tsmf_platform_register_handler(TSMFGstreamerDecoder* decoder); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL int tsmf_platform_free(TSMFGstreamerDecoder* decoder); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL int tsmf_window_create(TSMFGstreamerDecoder* decoder); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL int tsmf_window_resize(TSMFGstreamerDecoder* decoder, int x, int y, int width, + int height, int nr_rect, RDP_RECT* visible); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL int tsmf_window_destroy(TSMFGstreamerDecoder* decoder); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL int tsmf_window_map(TSMFGstreamerDecoder* decoder); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL int tsmf_window_unmap(TSMFGstreamerDecoder* decoder); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL BOOL tsmf_gstreamer_add_pad(TSMFGstreamerDecoder* mdecoder); + +FREERDP_LOCAL void tsmf_gstreamer_remove_pad(TSMFGstreamerDecoder* mdecoder); + +#endif /* FREERDP_CHANNEL_TSMF_CLIENT_GST_PLATFORM_H */ diff --git a/third_party/FreeRDP/channels/tsmf/client/oss/CMakeLists.txt b/third_party/FreeRDP/channels/tsmf/client/oss/CMakeLists.txt new file mode 100644 index 0000000..20c384e --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/client/oss/CMakeLists.txt @@ -0,0 +1,29 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright (c) 2015 Rozhuk Ivan +# +# 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. + +define_channel_client_subsystem("tsmf" "oss" "audio") + +find_package(OSS REQUIRED) + +set(${MODULE_PREFIX}_SRCS tsmf_oss.c) + +set(${MODULE_PREFIX}_LIBS winpr ${OSS_LIBRARIES}) + +include_directories(..) +include_directories(SYSTEM ${OSS_INCLUDE_DIRS}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") diff --git a/third_party/FreeRDP/channels/tsmf/client/oss/tsmf_oss.c b/third_party/FreeRDP/channels/tsmf/client/oss/tsmf_oss.c new file mode 100644 index 0000000..50027d3 --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/client/oss/tsmf_oss.c @@ -0,0 +1,237 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - OSS Audio Device + * + * Copyright (c) 2015 Rozhuk Ivan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#if defined(__OpenBSD__) +#include +#else +#include +#endif +#include + +#include +#include + +#include "tsmf_audio.h" + +typedef struct +{ + ITSMFAudioDevice iface; + + char dev_name[PATH_MAX]; + int pcm_handle; + + UINT32 sample_rate; + UINT32 channels; + UINT32 bits_per_sample; + + UINT32 data_size_last; +} TSMFOssAudioDevice; + +#define OSS_LOG_ERR(_text, _error) \ + do \ + { \ + if ((_error) != 0) \ + { \ + char ebuffer[256] = WINPR_C_ARRAY_INIT; \ + WLog_ERR(TAG, "%s: %i - %s", (_text), (_error), \ + winpr_strerror((_error), ebuffer, sizeof(ebuffer))); \ + } \ + } while (0) + +static BOOL tsmf_oss_open(ITSMFAudioDevice* audio, const char* device) +{ + int tmp = 0; + TSMFOssAudioDevice* oss = (TSMFOssAudioDevice*)audio; + + if (oss == nullptr || oss->pcm_handle != -1) + return FALSE; + + if (device == nullptr) /* Default device. */ + { + strncpy(oss->dev_name, "/dev/dsp", sizeof(oss->dev_name)); + } + else + { + strncpy(oss->dev_name, device, sizeof(oss->dev_name) - 1); + } + + if ((oss->pcm_handle = open(oss->dev_name, O_WRONLY)) < 0) + { + OSS_LOG_ERR("sound dev open failed", errno); + oss->pcm_handle = -1; + return FALSE; + } + + const int rc = ioctl(oss->pcm_handle, SNDCTL_DSP_GETFMTS, &tmp); + if (rc == -1) + { + OSS_LOG_ERR("SNDCTL_DSP_GETFMTS failed", errno); + close(oss->pcm_handle); + oss->pcm_handle = -1; + return FALSE; + } + + if ((AFMT_S16_LE & tmp) == 0) + { + OSS_LOG_ERR("SNDCTL_DSP_GETFMTS - AFMT_S16_LE", EOPNOTSUPP); + close(oss->pcm_handle); + oss->pcm_handle = -1; + return FALSE; + } + + WLog_INFO(TAG, "open: %s", oss->dev_name); + return TRUE; +} + +static BOOL tsmf_oss_set_format(ITSMFAudioDevice* audio, UINT32 sample_rate, UINT32 channels, + UINT32 bits_per_sample) +{ + int tmp = 0; + TSMFOssAudioDevice* oss = (TSMFOssAudioDevice*)audio; + + if (oss == nullptr || oss->pcm_handle == -1) + return FALSE; + + oss->sample_rate = sample_rate; + oss->channels = channels; + oss->bits_per_sample = bits_per_sample; + tmp = AFMT_S16_LE; + + if (ioctl(oss->pcm_handle, SNDCTL_DSP_SETFMT, &tmp) == -1) + OSS_LOG_ERR("SNDCTL_DSP_SETFMT failed", errno); + + tmp = WINPR_ASSERTING_INT_CAST(int, channels); + + if (ioctl(oss->pcm_handle, SNDCTL_DSP_CHANNELS, &tmp) == -1) + OSS_LOG_ERR("SNDCTL_DSP_CHANNELS failed", errno); + + tmp = WINPR_ASSERTING_INT_CAST(int, sample_rate); + + if (ioctl(oss->pcm_handle, SNDCTL_DSP_SPEED, &tmp) == -1) + OSS_LOG_ERR("SNDCTL_DSP_SPEED failed", errno); + + tmp = WINPR_ASSERTING_INT_CAST(int, ((bits_per_sample / 8) * channels * sample_rate)); + + if (ioctl(oss->pcm_handle, SNDCTL_DSP_SETFRAGMENT, &tmp) == -1) + OSS_LOG_ERR("SNDCTL_DSP_SETFRAGMENT failed", errno); + + DEBUG_TSMF("sample_rate %" PRIu32 " channels %" PRIu32 " bits_per_sample %" PRIu32 "", + sample_rate, channels, bits_per_sample); + return TRUE; +} + +static BOOL tsmf_oss_play(ITSMFAudioDevice* audio, const BYTE* data, UINT32 data_size) +{ + UINT32 offset = 0; + TSMFOssAudioDevice* oss = (TSMFOssAudioDevice*)audio; + DEBUG_TSMF("tsmf_oss_play: data_size %" PRIu32 "", data_size); + + if (oss == nullptr || oss->pcm_handle == -1) + return FALSE; + + if (data == nullptr || data_size == 0) + return TRUE; + + offset = 0; + oss->data_size_last = data_size; + + while (offset < data_size) + { + const ssize_t status = write(oss->pcm_handle, &data[offset], (data_size - offset)); + + if (status < 0) + { + OSS_LOG_ERR("write fail", errno); + return FALSE; + } + + offset += status; + } + + return TRUE; +} + +static UINT64 tsmf_oss_get_latency(ITSMFAudioDevice* audio) +{ + UINT64 latency = 0; + TSMFOssAudioDevice* oss = (TSMFOssAudioDevice*)audio; + + if (oss == nullptr) + return 0; + + // latency = ((oss->data_size_last / (oss->bits_per_sample / 8)) * oss->sample_rate); + // WLog_INFO(TAG, "latency: %zu", latency); + return latency; +} + +static BOOL tsmf_oss_flush(WINPR_ATTR_UNUSED ITSMFAudioDevice* audio) +{ + return TRUE; +} + +static void tsmf_oss_free(ITSMFAudioDevice* audio) +{ + TSMFOssAudioDevice* oss = (TSMFOssAudioDevice*)audio; + + if (oss == nullptr) + return; + + if (oss->pcm_handle != -1) + { + WLog_INFO(TAG, "close: %s", oss->dev_name); + close(oss->pcm_handle); + } + + free(oss); +} + +FREERDP_ENTRY_POINT(UINT VCAPITYPE oss_freerdp_tsmf_client_audio_subsystem_entry(void* ptr)) +{ + ITSMFAudioDevice** sptr = (ITSMFAudioDevice**)ptr; + WINPR_ASSERT(sptr); + *sptr = nullptr; + + TSMFOssAudioDevice* oss = calloc(1, sizeof(TSMFOssAudioDevice)); + if (!oss) + return ERROR_OUTOFMEMORY; + + oss->iface.Open = tsmf_oss_open; + oss->iface.SetFormat = tsmf_oss_set_format; + oss->iface.Play = tsmf_oss_play; + oss->iface.GetLatency = tsmf_oss_get_latency; + oss->iface.Flush = tsmf_oss_flush; + oss->iface.Free = tsmf_oss_free; + oss->pcm_handle = -1; + *sptr = &oss->iface; + return CHANNEL_RC_OK; +} diff --git a/third_party/FreeRDP/channels/tsmf/client/pulse/CMakeLists.txt b/third_party/FreeRDP/channels/tsmf/client/pulse/CMakeLists.txt new file mode 100644 index 0000000..2a1b880 --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/client/pulse/CMakeLists.txt @@ -0,0 +1,30 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel_client_subsystem("tsmf" "pulse" "audio") + +find_package(PulseAudio REQUIRED) +freerdp_client_pc_add_requires_private("libpulse") + +set(${MODULE_PREFIX}_SRCS tsmf_pulse.c) + +set(${MODULE_PREFIX}_LIBS winpr ${PULSEAUDIO_LIBRARY} ${PULSEAUDIO_MAINLOOP_LIBRARY}) + +include_directories(..) +include_directories(SYSTEM ${PULSEAUDIO_INCLUDE_DIR}) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") diff --git a/third_party/FreeRDP/channels/tsmf/client/pulse/tsmf_pulse.c b/third_party/FreeRDP/channels/tsmf/client/pulse/tsmf_pulse.c new file mode 100644 index 0000000..5e9c72c --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/client/pulse/tsmf_pulse.c @@ -0,0 +1,427 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - PulseAudio Device + * + * Copyright 2010-2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#include "tsmf_audio.h" + +typedef struct +{ + ITSMFAudioDevice iface; + + char device[32]; + pa_threaded_mainloop* mainloop; + pa_context* context; + pa_sample_spec sample_spec; + pa_stream* stream; +} TSMFPulseAudioDevice; + +static void tsmf_pulse_context_state_callback(pa_context* context, void* userdata) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)userdata; + pa_context_state_t state = pa_context_get_state(context); + + switch (state) + { + case PA_CONTEXT_READY: + DEBUG_TSMF("PA_CONTEXT_READY"); + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + DEBUG_TSMF("state %d", state); + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + default: + DEBUG_TSMF("state %d", state); + break; + } +} + +static BOOL tsmf_pulse_connect(TSMFPulseAudioDevice* pulse) +{ + pa_context_state_t state = PA_CONTEXT_FAILED; + + if (!pulse->context) + return FALSE; + + if (pa_context_connect(pulse->context, nullptr, 0, nullptr)) + { + WLog_ERR(TAG, "pa_context_connect failed (%d)", pa_context_errno(pulse->context)); + return FALSE; + } + + pa_threaded_mainloop_lock(pulse->mainloop); + + if (pa_threaded_mainloop_start(pulse->mainloop) < 0) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + WLog_ERR(TAG, "pa_threaded_mainloop_start failed (%d)", pa_context_errno(pulse->context)); + return FALSE; + } + + for (;;) + { + state = pa_context_get_state(pulse->context); + + if (state == PA_CONTEXT_READY) + break; + + if (!PA_CONTEXT_IS_GOOD(state)) + { + DEBUG_TSMF("bad context state (%d)", pa_context_errno(pulse->context)); + break; + } + + pa_threaded_mainloop_wait(pulse->mainloop); + } + + pa_threaded_mainloop_unlock(pulse->mainloop); + + if (state == PA_CONTEXT_READY) + { + DEBUG_TSMF("connected"); + return TRUE; + } + else + { + pa_context_disconnect(pulse->context); + return FALSE; + } +} + +static BOOL tsmf_pulse_open(ITSMFAudioDevice* audio, const char* device) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)audio; + + if (device) + { + strncpy(pulse->device, device, sizeof(pulse->device) - 1); + } + + pulse->mainloop = pa_threaded_mainloop_new(); + + if (!pulse->mainloop) + { + WLog_ERR(TAG, "pa_threaded_mainloop_new failed"); + return FALSE; + } + + pulse->context = pa_context_new(pa_threaded_mainloop_get_api(pulse->mainloop), + freerdp_getApplicationDetailsString()); + + if (!pulse->context) + { + WLog_ERR(TAG, "pa_context_new failed"); + return FALSE; + } + + pa_context_set_state_callback(pulse->context, tsmf_pulse_context_state_callback, pulse); + + if (!tsmf_pulse_connect(pulse)) + { + WLog_ERR(TAG, "tsmf_pulse_connect failed"); + return FALSE; + } + + DEBUG_TSMF("open device %s", pulse->device); + return TRUE; +} + +static void tsmf_pulse_stream_success_callback(WINPR_ATTR_UNUSED pa_stream* stream, + WINPR_ATTR_UNUSED int success, void* userdata) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)userdata; + pa_threaded_mainloop_signal(pulse->mainloop, 0); +} + +static void tsmf_pulse_wait_for_operation(TSMFPulseAudioDevice* pulse, pa_operation* operation) +{ + if (operation == nullptr) + return; + + while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING) + { + pa_threaded_mainloop_wait(pulse->mainloop); + } + + pa_operation_unref(operation); +} + +static void tsmf_pulse_stream_state_callback(pa_stream* stream, void* userdata) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)userdata; + WINPR_ASSERT(pulse); + + pa_stream_state_t state = pa_stream_get_state(stream); + + switch (state) + { + case PA_STREAM_READY: + DEBUG_TSMF("PA_STREAM_READY"); + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + DEBUG_TSMF("state %d", state); + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + default: + DEBUG_TSMF("state %d", state); + break; + } +} + +static void tsmf_pulse_stream_request_callback(WINPR_ATTR_UNUSED pa_stream* stream, + WINPR_ATTR_UNUSED size_t length, void* userdata) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)userdata; + DEBUG_TSMF("%" PRIdz "", length); + pa_threaded_mainloop_signal(pulse->mainloop, 0); +} + +static BOOL tsmf_pulse_close_stream(TSMFPulseAudioDevice* pulse) +{ + if (!pulse->context || !pulse->stream) + return FALSE; + + DEBUG_TSMF(""); + pa_threaded_mainloop_lock(pulse->mainloop); + pa_stream_set_write_callback(pulse->stream, nullptr, nullptr); + tsmf_pulse_wait_for_operation( + pulse, pa_stream_drain(pulse->stream, tsmf_pulse_stream_success_callback, pulse)); + pa_stream_disconnect(pulse->stream); + pa_stream_unref(pulse->stream); + pulse->stream = nullptr; + pa_threaded_mainloop_unlock(pulse->mainloop); + return TRUE; +} + +static BOOL tsmf_pulse_open_stream(TSMFPulseAudioDevice* pulse) +{ + pa_stream_state_t state = PA_STREAM_FAILED; + pa_buffer_attr buffer_attr = WINPR_C_ARRAY_INIT; + + if (!pulse->context) + return FALSE; + + DEBUG_TSMF(""); + pa_threaded_mainloop_lock(pulse->mainloop); + pulse->stream = pa_stream_new(pulse->context, freerdp_getApplicationDetailsString(), + &pulse->sample_spec, nullptr); + + if (!pulse->stream) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + WLog_ERR(TAG, "pa_stream_new failed (%d)", pa_context_errno(pulse->context)); + return FALSE; + } + + pa_stream_set_state_callback(pulse->stream, tsmf_pulse_stream_state_callback, pulse); + pa_stream_set_write_callback(pulse->stream, tsmf_pulse_stream_request_callback, pulse); + buffer_attr.maxlength = (uint32_t)pa_usec_to_bytes(500000, &pulse->sample_spec); + buffer_attr.tlength = (uint32_t)pa_usec_to_bytes(250000, &pulse->sample_spec); + buffer_attr.prebuf = (UINT32)-1; + buffer_attr.minreq = (UINT32)-1; + buffer_attr.fragsize = (UINT32)-1; + + if (pa_stream_connect_playback( + pulse->stream, pulse->device[0] ? pulse->device : nullptr, &buffer_attr, + PA_STREAM_ADJUST_LATENCY | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE, + nullptr, nullptr) < 0) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + WLog_ERR(TAG, "pa_stream_connect_playback failed (%d)", pa_context_errno(pulse->context)); + return FALSE; + } + + for (;;) + { + state = pa_stream_get_state(pulse->stream); + + if (state == PA_STREAM_READY) + break; + + if (!PA_STREAM_IS_GOOD(state)) + { + WLog_ERR(TAG, "bad stream state (%d)", pa_context_errno(pulse->context)); + break; + } + + pa_threaded_mainloop_wait(pulse->mainloop); + } + + pa_threaded_mainloop_unlock(pulse->mainloop); + + if (state == PA_STREAM_READY) + { + DEBUG_TSMF("connected"); + return TRUE; + } + else + { + tsmf_pulse_close_stream(pulse); + return FALSE; + } +} + +static BOOL tsmf_pulse_set_format(ITSMFAudioDevice* audio, UINT32 sample_rate, UINT32 channels, + WINPR_ATTR_UNUSED UINT32 bits_per_sample) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)audio; + DEBUG_TSMF("sample_rate %" PRIu32 " channels %" PRIu32 " bits_per_sample %" PRIu32 "", + sample_rate, channels, bits_per_sample); + pulse->sample_spec.rate = sample_rate; + + WINPR_ASSERT(channels <= UINT8_MAX); + pulse->sample_spec.channels = (uint8_t)channels; + pulse->sample_spec.format = PA_SAMPLE_S16LE; + return tsmf_pulse_open_stream(pulse); +} + +static BOOL tsmf_pulse_play(ITSMFAudioDevice* audio, const BYTE* data, UINT32 data_size) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)audio; + const BYTE* src = nullptr; + size_t len = 0; + int ret = 0; + DEBUG_TSMF("data_size %" PRIu32 "", data_size); + + if (pulse->stream) + { + pa_threaded_mainloop_lock(pulse->mainloop); + src = data; + + while (data_size > 0) + { + while ((len = pa_stream_writable_size(pulse->stream)) == 0) + { + DEBUG_TSMF("waiting"); + pa_threaded_mainloop_wait(pulse->mainloop); + } + + if (len == (size_t)-1) + break; + + if (len > data_size) + len = data_size; + + ret = pa_stream_write(pulse->stream, src, len, nullptr, 0LL, PA_SEEK_RELATIVE); + + if (ret < 0) + { + DEBUG_TSMF("pa_stream_write failed (%d)", pa_context_errno(pulse->context)); + break; + } + + src += len; + data_size -= len; + } + + pa_threaded_mainloop_unlock(pulse->mainloop); + } + + return TRUE; +} + +static UINT64 tsmf_pulse_get_latency(ITSMFAudioDevice* audio) +{ + pa_usec_t usec = 0; + UINT64 latency = 0; + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)audio; + + if (pulse->stream && pa_stream_get_latency(pulse->stream, &usec, nullptr) == 0) + { + latency = ((UINT64)usec) * 10LL; + } + + return latency; +} + +static BOOL tsmf_pulse_flush(ITSMFAudioDevice* audio) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)audio; + pa_threaded_mainloop_lock(pulse->mainloop); + tsmf_pulse_wait_for_operation( + pulse, pa_stream_flush(pulse->stream, tsmf_pulse_stream_success_callback, pulse)); + pa_threaded_mainloop_unlock(pulse->mainloop); + return TRUE; +} + +static void tsmf_pulse_free(ITSMFAudioDevice* audio) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)audio; + DEBUG_TSMF(""); + tsmf_pulse_close_stream(pulse); + + if (pulse->mainloop) + { + pa_threaded_mainloop_stop(pulse->mainloop); + } + + if (pulse->context) + { + pa_context_disconnect(pulse->context); + pa_context_unref(pulse->context); + pulse->context = nullptr; + } + + if (pulse->mainloop) + { + pa_threaded_mainloop_free(pulse->mainloop); + pulse->mainloop = nullptr; + } + + free(pulse); +} + +FREERDP_ENTRY_POINT(UINT VCAPITYPE pulse_freerdp_tsmf_client_audio_subsystem_entry(void* ptr)) +{ + ITSMFAudioDevice** sptr = (ITSMFAudioDevice**)ptr; + WINPR_ASSERT(sptr); + *sptr = nullptr; + + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*)calloc(1, sizeof(TSMFPulseAudioDevice)); + + if (!pulse) + return ERROR_OUTOFMEMORY; + + pulse->iface.Open = tsmf_pulse_open; + pulse->iface.SetFormat = tsmf_pulse_set_format; + pulse->iface.Play = tsmf_pulse_play; + pulse->iface.GetLatency = tsmf_pulse_get_latency; + pulse->iface.Flush = tsmf_pulse_flush; + pulse->iface.Free = tsmf_pulse_free; + *sptr = &pulse->iface; + return CHANNEL_RC_OK; +} diff --git a/third_party/FreeRDP/channels/tsmf/client/tsmf_audio.c b/third_party/FreeRDP/channels/tsmf/client/tsmf_audio.c new file mode 100644 index 0000000..5058ed6 --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/client/tsmf_audio.c @@ -0,0 +1,99 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Audio Device Manager + * + * Copyright 2010-2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include "tsmf_audio.h" + +static ITSMFAudioDevice* tsmf_load_audio_device_by_name(const char* name, const char* device) +{ + ITSMFAudioDevice* audio = nullptr; + union + { + PVIRTUALCHANNELENTRY pvce; + TSMF_AUDIO_DEVICE_ENTRY entry; + } cnv; + cnv.pvce = freerdp_load_channel_addin_entry("tsmf", name, "audio", 0); + + if (!cnv.entry) + return nullptr; + + const UINT rc = cnv.entry(&audio); + + if ((rc != CHANNEL_RC_OK) || !audio) + { + WLog_ERR(TAG, "failed to call export function in %s", name); + return nullptr; + } + + if (!audio->Open(audio, device)) + { + audio->Free(audio); + audio = nullptr; + WLog_ERR(TAG, "failed to open, name: %s, device: %s", name, device); + } + else + { + WLog_DBG(TAG, "name: %s, device: %s", name, device); + } + + return audio; +} + +ITSMFAudioDevice* tsmf_load_audio_device(const char* name, const char* device) +{ + ITSMFAudioDevice* audio = nullptr; + + if (name) + { + audio = tsmf_load_audio_device_by_name(name, device); + } + else + { +#if defined(WITH_PULSE) + if (!audio) + audio = tsmf_load_audio_device_by_name("pulse", device); +#endif + +#if defined(WITH_OSS) + if (!audio) + audio = tsmf_load_audio_device_by_name("oss", device); +#endif + +#if defined(WITH_ALSA) + if (!audio) + audio = tsmf_load_audio_device_by_name("alsa", device); +#endif + } + + if (audio == nullptr) + { + WLog_ERR(TAG, "no sound device."); + } + else + { + WLog_DBG(TAG, "name: %s, device: %s", name, device); + } + + return audio; +} diff --git a/third_party/FreeRDP/channels/tsmf/client/tsmf_audio.h b/third_party/FreeRDP/channels/tsmf/client/tsmf_audio.h new file mode 100644 index 0000000..9df4ffb --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/client/tsmf_audio.h @@ -0,0 +1,53 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Audio Device Manager + * + * Copyright 2010-2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_TSMF_CLIENT_AUDIO_H +#define FREERDP_CHANNEL_TSMF_CLIENT_AUDIO_H + +#include "tsmf_types.h" + +typedef struct s_ITSMFAudioDevice ITSMFAudioDevice; + +struct s_ITSMFAudioDevice +{ + /* Open the audio device. */ + WINPR_ATTR_NODISCARD BOOL (*Open)(ITSMFAudioDevice* audio, const char* device); + /* Set the audio data format. */ + WINPR_ATTR_NODISCARD BOOL (*SetFormat)(ITSMFAudioDevice* audio, UINT32 sample_rate, + UINT32 channels, UINT32 bits_per_sample); + /* Play audio data. */ + WINPR_ATTR_NODISCARD BOOL (*Play)(ITSMFAudioDevice* audio, const BYTE* data, UINT32 data_size); + /* Get the latency of the last written sample, in 100ns */ + WINPR_ATTR_NODISCARD UINT64 (*GetLatency)(ITSMFAudioDevice* audio); + /* Change the playback volume level */ + WINPR_ATTR_NODISCARD BOOL (*ChangeVolume)(ITSMFAudioDevice* audio, UINT32 newVolume, + UINT32 muted); + /* Flush queued audio data */ + WINPR_ATTR_NODISCARD BOOL (*Flush)(ITSMFAudioDevice* audio); + /* Free the audio device */ + void (*Free)(ITSMFAudioDevice* audio); +}; + +#define TSMF_AUDIO_DEVICE_EXPORT_FUNC_NAME "TSMFAudioDeviceEntry" +typedef UINT(VCAPITYPE* TSMF_AUDIO_DEVICE_ENTRY)(ITSMFAudioDevice** dev); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL ITSMFAudioDevice* tsmf_load_audio_device(const char* name, + const char* device); + +#endif /* FREERDP_CHANNEL_TSMF_CLIENT_AUDIO_H */ diff --git a/third_party/FreeRDP/channels/tsmf/client/tsmf_codec.c b/third_party/FreeRDP/channels/tsmf/client/tsmf_codec.c new file mode 100644 index 0000000..49b56e5 --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/client/tsmf_codec.c @@ -0,0 +1,615 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Codec + * + * Copyright 2010-2011 Vic Lee + * Copyright 2012 Hewlett-Packard Development Company, L.P. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include "tsmf_decoder.h" +#include "tsmf_constants.h" +#include "tsmf_types.h" + +#include "tsmf_codec.h" + +#include + +#define TAG CHANNELS_TAG("tsmf.client") + +typedef struct +{ + BYTE guid[16]; + const char* name; + int type; +} TSMFMediaTypeMap; + +static const TSMFMediaTypeMap tsmf_major_type_map[] = { + /* 73646976-0000-0010-8000-00AA00389B71 */ + { { 0x76, 0x69, 0x64, 0x73, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIATYPE_Video", + TSMF_MAJOR_TYPE_VIDEO }, + + /* 73647561-0000-0010-8000-00AA00389B71 */ + { { 0x61, 0x75, 0x64, 0x73, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIATYPE_Audio", + TSMF_MAJOR_TYPE_AUDIO }, + + { { 0 }, "Unknown", TSMF_MAJOR_TYPE_UNKNOWN } +}; + +static const TSMFMediaTypeMap tsmf_sub_type_map[] = { + /* 31435657-0000-0010-8000-00AA00389B71 */ + { { 0x57, 0x56, 0x43, 0x31, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_WVC1", + TSMF_SUB_TYPE_WVC1 }, + + /* 00000160-0000-0010-8000-00AA00389B71 */ + { { 0x60, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_WMAudioV1", /* V7, V8 has the same GUID */ + TSMF_SUB_TYPE_WMA1 }, + + /* 00000161-0000-0010-8000-00AA00389B71 */ + { { 0x61, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_WMAudioV2", /* V7, V8 has the same GUID */ + TSMF_SUB_TYPE_WMA2 }, + + /* 00000162-0000-0010-8000-00AA00389B71 */ + { { 0x62, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_WMAudioV9", + TSMF_SUB_TYPE_WMA9 }, + + /* 00000055-0000-0010-8000-00AA00389B71 */ + { { 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_MP3", + TSMF_SUB_TYPE_MP3 }, + + /* E06D802B-DB46-11CF-B4D1-00805F6CBBEA */ + { { 0x2B, 0x80, 0x6D, 0xE0, 0x46, 0xDB, 0xCF, 0x11, 0xB4, 0xD1, 0x00, 0x80, 0x5F, 0x6C, 0xBB, + 0xEA }, + "MEDIASUBTYPE_MPEG2_AUDIO", + TSMF_SUB_TYPE_MP2A }, + + /* E06D8026-DB46-11CF-B4D1-00805F6CBBEA */ + { { 0x26, 0x80, 0x6D, 0xE0, 0x46, 0xDB, 0xCF, 0x11, 0xB4, 0xD1, 0x00, 0x80, 0x5F, 0x6C, 0xBB, + 0xEA }, + "MEDIASUBTYPE_MPEG2_VIDEO", + TSMF_SUB_TYPE_MP2V }, + + /* 31564D57-0000-0010-8000-00AA00389B71 */ + { { 0x57, 0x4D, 0x56, 0x31, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_WMV1", + TSMF_SUB_TYPE_WMV1 }, + + /* 32564D57-0000-0010-8000-00AA00389B71 */ + { { 0x57, 0x4D, 0x56, 0x32, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_WMV2", + TSMF_SUB_TYPE_WMV2 }, + + /* 33564D57-0000-0010-8000-00AA00389B71 */ + { { 0x57, 0x4D, 0x56, 0x33, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_WMV3", + TSMF_SUB_TYPE_WMV3 }, + + /* 00001610-0000-0010-8000-00AA00389B71 */ + { { 0x10, 0x16, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_MPEG_HEAAC", + TSMF_SUB_TYPE_AAC }, + + /* 34363248-0000-0010-8000-00AA00389B71 */ + { { 0x48, 0x32, 0x36, 0x34, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_H264", + TSMF_SUB_TYPE_H264 }, + + /* 31435641-0000-0010-8000-00AA00389B71 */ + { { 0x41, 0x56, 0x43, 0x31, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_AVC1", + TSMF_SUB_TYPE_AVC1 }, + + /* 3334504D-0000-0010-8000-00AA00389B71 */ + { { 0x4D, 0x50, 0x34, 0x33, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_MP43", + TSMF_SUB_TYPE_MP43 }, + + /* 5634504D-0000-0010-8000-00AA00389B71 */ + { { 0x4D, 0x50, 0x34, 0x56, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_MP4S", + TSMF_SUB_TYPE_MP4S }, + + /* 3234504D-0000-0010-8000-00AA00389B71 */ + { { 0x4D, 0x50, 0x34, 0x32, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_MP42", + TSMF_SUB_TYPE_MP42 }, + + /* 3253344D-0000-0010-8000-00AA00389B71 */ + { { 0x4D, 0x34, 0x53, 0x32, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_MP42", + TSMF_SUB_TYPE_M4S2 }, + + /* E436EB81-524F-11CE-9F53-0020AF0BA770 */ + { { 0x81, 0xEB, 0x36, 0xE4, 0x4F, 0x52, 0xCE, 0x11, 0x9F, 0x53, 0x00, 0x20, 0xAF, 0x0B, 0xA7, + 0x70 }, + "MEDIASUBTYPE_MP1V", + TSMF_SUB_TYPE_MP1V }, + + /* 00000050-0000-0010-8000-00AA00389B71 */ + { { 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_MP1A", + TSMF_SUB_TYPE_MP1A }, + + /* E06D802C-DB46-11CF-B4D1-00805F6CBBEA */ + { { 0x2C, 0x80, 0x6D, 0xE0, 0x46, 0xDB, 0xCF, 0x11, 0xB4, 0xD1, 0x00, 0x80, 0x5F, 0x6C, 0xBB, + 0xEA }, + "MEDIASUBTYPE_DOLBY_AC3", + TSMF_SUB_TYPE_AC3 }, + + /* 32595559-0000-0010-8000-00AA00389B71 */ + { { 0x59, 0x55, 0x59, 0x32, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_YUY2", + TSMF_SUB_TYPE_YUY2 }, + + /* Opencodec IDS */ + { { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_FLAC", + TSMF_SUB_TYPE_FLAC }, + + { { 0x61, 0x34, 0x70, 0x6D, 0x7A, 0x76, 0x4D, 0x49, 0xB4, 0x78, 0xF2, 0x9D, 0x25, 0xDC, 0x90, + 0x37 }, + "MEDIASUBTYPE_OGG", + TSMF_SUB_TYPE_OGG }, + + { { 0x4D, 0x34, 0x53, 0x32, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_H263", + TSMF_SUB_TYPE_H263 }, + + /* WebMMF codec IDS */ + { { 0x56, 0x50, 0x38, 0x30, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, + 0x71 }, + "MEDIASUBTYPE_VP8", + TSMF_SUB_TYPE_VP8 }, + + { { 0x0B, 0xD1, 0x2F, 0x8D, 0x41, 0x58, 0x6B, 0x4A, 0x89, 0x05, 0x58, 0x8F, 0xEC, 0x1A, 0xDE, + 0xD9 }, + "MEDIASUBTYPE_OGG", + TSMF_SUB_TYPE_OGG }, + + { { 0 }, "Unknown", TSMF_SUB_TYPE_UNKNOWN } + +}; + +static const TSMFMediaTypeMap tsmf_format_type_map[] = { + /* AED4AB2D-7326-43CB-9464-C879CAB9C43D */ + { { 0x2D, 0xAB, 0xD4, 0xAE, 0x26, 0x73, 0xCB, 0x43, 0x94, 0x64, 0xC8, 0x79, 0xCA, 0xB9, 0xC4, + 0x3D }, + "FORMAT_MFVideoFormat", + TSMF_FORMAT_TYPE_MFVIDEOFORMAT }, + + /* 05589F81-C356-11CE-BF01-00AA0055595A */ + { { 0x81, 0x9F, 0x58, 0x05, 0x56, 0xC3, 0xCE, 0x11, 0xBF, 0x01, 0x00, 0xAA, 0x00, 0x55, 0x59, + 0x5A }, + "FORMAT_WaveFormatEx", + TSMF_FORMAT_TYPE_WAVEFORMATEX }, + + /* E06D80E3-DB46-11CF-B4D1-00805F6CBBEA */ + { { 0xE3, 0x80, 0x6D, 0xE0, 0x46, 0xDB, 0xCF, 0x11, 0xB4, 0xD1, 0x00, 0x80, 0x5F, 0x6C, 0xBB, + 0xEA }, + "FORMAT_MPEG2_VIDEO", + TSMF_FORMAT_TYPE_MPEG2VIDEOINFO }, + + /* F72A76A0-EB0A-11D0-ACE4-0000C0CC16BA */ + { { 0xA0, 0x76, 0x2A, 0xF7, 0x0A, 0xEB, 0xD0, 0x11, 0xAC, 0xE4, 0x00, 0x00, 0xC0, 0xCC, 0x16, + 0xBA }, + "FORMAT_VideoInfo2", + TSMF_FORMAT_TYPE_VIDEOINFO2 }, + + /* 05589F82-C356-11CE-BF01-00AA0055595A */ + { { 0x82, 0x9F, 0x58, 0x05, 0x56, 0xC3, 0xCE, 0x11, 0xBF, 0x01, 0x00, 0xAA, 0x00, 0x55, 0x59, + 0x5A }, + "FORMAT_MPEG1_VIDEO", + TSMF_FORMAT_TYPE_MPEG1VIDEOINFO }, + + { { 0 }, "Unknown", TSMF_FORMAT_TYPE_UNKNOWN } +}; + +static void tsmf_print_guid(const BYTE* guid) +{ + WINPR_UNUSED(guid); + +#ifdef WITH_DEBUG_TSMF + char guidString[37]; + + (void)snprintf(guidString, sizeof(guidString), + "%02" PRIX8 "%02" PRIX8 "%02" PRIX8 "%02" PRIX8 "-%02" PRIX8 "%02" PRIX8 + "-%02" PRIX8 "%02" PRIX8 "-%02" PRIX8 "%02" PRIX8 "-%02" PRIX8 "%02" PRIX8 + "%02" PRIX8 "%02" PRIX8 "%02" PRIX8 "%02" PRIX8 "", + guid[3], guid[2], guid[1], guid[0], guid[5], guid[4], guid[7], guid[6], guid[8], + guid[9], guid[10], guid[11], guid[12], guid[13], guid[14], guid[15]); + + WLog_INFO(TAG, "%s", guidString); +#endif +} + +/* http://msdn.microsoft.com/en-us/library/dd318229.aspx */ +static UINT32 tsmf_codec_parse_BITMAPINFOHEADER(TS_AM_MEDIA_TYPE* mediatype, wStream* s, + BOOL bypass) +{ + UINT32 biSize = 0; + UINT32 biWidth = 0; + UINT32 biHeight = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 40)) + return 0; + Stream_Read_UINT32(s, biSize); + Stream_Read_UINT32(s, biWidth); + Stream_Read_UINT32(s, biHeight); + Stream_Seek(s, 28); + + if (mediatype->Width == 0) + mediatype->Width = biWidth; + + if (mediatype->Height == 0) + mediatype->Height = biHeight; + + /* Assume there will be no color table for video? */ + if (biSize < 40) + return 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, (biSize - 40))) + return 0; + + if (bypass && biSize > 40) + Stream_Seek(s, biSize - 40); + + return (bypass ? biSize : 40); +} + +/* http://msdn.microsoft.com/en-us/library/dd407326.aspx */ +static UINT32 tsmf_codec_parse_VIDEOINFOHEADER2(TS_AM_MEDIA_TYPE* mediatype, wStream* s) +{ + UINT64 AvgTimePerFrame = 0; + + /* VIDEOINFOHEADER2.rcSource, RECT(LONG left, LONG top, LONG right, LONG bottom) */ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 72)) + return 0; + + Stream_Seek_UINT32(s); + Stream_Seek_UINT32(s); + Stream_Read_UINT32(s, mediatype->Width); + Stream_Read_UINT32(s, mediatype->Height); + /* VIDEOINFOHEADER2.rcTarget */ + Stream_Seek(s, 16); + /* VIDEOINFOHEADER2.dwBitRate */ + Stream_Read_UINT32(s, mediatype->BitRate); + /* VIDEOINFOHEADER2.dwBitErrorRate */ + Stream_Seek_UINT32(s); + /* VIDEOINFOHEADER2.AvgTimePerFrame */ + Stream_Read_UINT64(s, AvgTimePerFrame); + mediatype->SamplesPerSecond.Numerator = 1000000; + mediatype->SamplesPerSecond.Denominator = (UINT32)(AvgTimePerFrame / 10ULL); + /* Remaining fields before bmiHeader */ + Stream_Seek(s, 24); + return 72; +} + +/* http://msdn.microsoft.com/en-us/library/dd390700.aspx */ +static UINT32 tsmf_codec_parse_VIDEOINFOHEADER(TS_AM_MEDIA_TYPE* mediatype, wStream* s) +{ + /* + typedef struct { + RECT rcSource; //16 + RECT rcTarget; //16 32 + DWORD dwBitRate; //4 36 + DWORD dwBitErrorRate; //4 40 + REFERENCE_TIME AvgTimePerFrame; //8 48 + BITMAPINFOHEADER bmiHeader; + } VIDEOINFOHEADER; + */ + UINT64 AvgTimePerFrame = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 48)) + return 0; + + /* VIDEOINFOHEADER.rcSource, RECT(LONG left, LONG top, LONG right, LONG bottom) */ + Stream_Seek_UINT32(s); + Stream_Seek_UINT32(s); + Stream_Read_UINT32(s, mediatype->Width); + Stream_Read_UINT32(s, mediatype->Height); + /* VIDEOINFOHEADER.rcTarget */ + Stream_Seek(s, 16); + /* VIDEOINFOHEADER.dwBitRate */ + Stream_Read_UINT32(s, mediatype->BitRate); + /* VIDEOINFOHEADER.dwBitErrorRate */ + Stream_Seek_UINT32(s); + /* VIDEOINFOHEADER.AvgTimePerFrame */ + Stream_Read_UINT64(s, AvgTimePerFrame); + mediatype->SamplesPerSecond.Numerator = 1000000; + mediatype->SamplesPerSecond.Denominator = (UINT32)(AvgTimePerFrame / 10ULL); + return 48; +} + +static BOOL tsmf_read_format_type(TS_AM_MEDIA_TYPE* mediatype, wStream* s, UINT32 cbFormat) +{ + UINT32 i = 0; + UINT32 j = 0; + + switch (mediatype->FormatType) + { + case TSMF_FORMAT_TYPE_MFVIDEOFORMAT: + /* http://msdn.microsoft.com/en-us/library/aa473808.aspx */ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 176)) + return FALSE; + + Stream_Seek(s, 8); /* dwSize and ? */ + Stream_Read_UINT32(s, mediatype->Width); /* videoInfo.dwWidth */ + Stream_Read_UINT32(s, mediatype->Height); /* videoInfo.dwHeight */ + Stream_Seek(s, 32); + /* videoInfo.FramesPerSecond */ + Stream_Read_UINT32(s, mediatype->SamplesPerSecond.Numerator); + Stream_Read_UINT32(s, mediatype->SamplesPerSecond.Denominator); + Stream_Seek(s, 80); + Stream_Read_UINT32(s, mediatype->BitRate); /* compressedInfo.AvgBitrate */ + Stream_Seek(s, 36); + + if (cbFormat > 176) + { + const size_t nsize = cbFormat - 176; + if (mediatype->ExtraDataSize < nsize) + return FALSE; + if (!Stream_CheckAndLogRequiredLength(TAG, s, nsize)) + return FALSE; + mediatype->ExtraDataSize = (UINT32)nsize; + mediatype->ExtraData = Stream_Pointer(s); + } + break; + + case TSMF_FORMAT_TYPE_WAVEFORMATEX: + /* http://msdn.microsoft.com/en-us/library/dd757720.aspx */ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 18)) + return FALSE; + + Stream_Seek_UINT16(s); + Stream_Read_UINT16(s, mediatype->Channels); + Stream_Read_UINT32(s, mediatype->SamplesPerSecond.Numerator); + mediatype->SamplesPerSecond.Denominator = 1; + Stream_Read_UINT32(s, mediatype->BitRate); + mediatype->BitRate *= 8; + Stream_Read_UINT16(s, mediatype->BlockAlign); + Stream_Read_UINT16(s, mediatype->BitsPerSample); + Stream_Read_UINT16(s, mediatype->ExtraDataSize); + + if (mediatype->ExtraDataSize > 0) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, mediatype->ExtraDataSize)) + return FALSE; + mediatype->ExtraData = Stream_Pointer(s); + } + break; + + case TSMF_FORMAT_TYPE_MPEG1VIDEOINFO: + /* http://msdn.microsoft.com/en-us/library/dd390700.aspx */ + i = tsmf_codec_parse_VIDEOINFOHEADER(mediatype, s); + if (!i) + return FALSE; + j = tsmf_codec_parse_BITMAPINFOHEADER(mediatype, s, TRUE); + if (!j) + return FALSE; + i += j; + + if (cbFormat > i) + { + mediatype->ExtraDataSize = cbFormat - i; + if (!Stream_CheckAndLogRequiredLength(TAG, s, mediatype->ExtraDataSize)) + return FALSE; + mediatype->ExtraData = Stream_Pointer(s); + } + break; + + case TSMF_FORMAT_TYPE_MPEG2VIDEOINFO: + /* http://msdn.microsoft.com/en-us/library/dd390707.aspx */ + i = tsmf_codec_parse_VIDEOINFOHEADER2(mediatype, s); + if (!i) + return FALSE; + j = tsmf_codec_parse_BITMAPINFOHEADER(mediatype, s, TRUE); + if (!j) + return FALSE; + i += j; + + if (cbFormat > i) + { + mediatype->ExtraDataSize = cbFormat - i; + if (!Stream_CheckAndLogRequiredLength(TAG, s, mediatype->ExtraDataSize)) + return FALSE; + mediatype->ExtraData = Stream_Pointer(s); + } + break; + + case TSMF_FORMAT_TYPE_VIDEOINFO2: + i = tsmf_codec_parse_VIDEOINFOHEADER2(mediatype, s); + if (!i) + return FALSE; + j = tsmf_codec_parse_BITMAPINFOHEADER(mediatype, s, FALSE); + if (!j) + return FALSE; + i += j; + + if (cbFormat > i) + { + mediatype->ExtraDataSize = cbFormat - i; + if (!Stream_CheckAndLogRequiredLength(TAG, s, mediatype->ExtraDataSize)) + return FALSE; + mediatype->ExtraData = Stream_Pointer(s); + } + break; + + default: + WLog_INFO(TAG, "unhandled format type 0x%08x", (unsigned)mediatype->FormatType); + break; + } + return TRUE; +} + +BOOL tsmf_codec_parse_media_type(TS_AM_MEDIA_TYPE* mediatype, wStream* s) +{ + UINT32 cbFormat = 0; + + ZeroMemory(mediatype, sizeof(TS_AM_MEDIA_TYPE)); + + /* MajorType */ + DEBUG_TSMF("MediaMajorType:"); + if (!Stream_CheckAndLogRequiredLength(TAG, s, 16)) + return FALSE; + tsmf_print_guid(Stream_Pointer(s)); + + size_t i = 0; + for (; tsmf_major_type_map[i].type != TSMF_MAJOR_TYPE_UNKNOWN; i++) + { + if (memcmp(tsmf_major_type_map[i].guid, Stream_Pointer(s), 16) == 0) + break; + } + + mediatype->MajorType = tsmf_major_type_map[i].type; + if (mediatype->MajorType == TSMF_MAJOR_TYPE_UNKNOWN) + return FALSE; + + DEBUG_TSMF("MediaMajorType %s", tsmf_major_type_map[i].name); + Stream_Seek(s, 16); + + /* SubType */ + DEBUG_TSMF("MediaSubType:"); + if (!Stream_CheckAndLogRequiredLength(TAG, s, 16)) + return FALSE; + tsmf_print_guid(Stream_Pointer(s)); + + for (i = 0; tsmf_sub_type_map[i].type != TSMF_SUB_TYPE_UNKNOWN; i++) + { + if (memcmp(tsmf_sub_type_map[i].guid, Stream_Pointer(s), 16) == 0) + break; + } + + mediatype->SubType = tsmf_sub_type_map[i].type; + if (mediatype->SubType == TSMF_SUB_TYPE_UNKNOWN) + return FALSE; + + DEBUG_TSMF("MediaSubType %s", tsmf_sub_type_map[i].name); + Stream_Seek(s, 16); + + /* bFixedSizeSamples, bTemporalCompression, SampleSize */ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 12)) + return FALSE; + Stream_Seek(s, 12); + + /* FormatType */ + DEBUG_TSMF("FormatType:"); + if (!Stream_CheckAndLogRequiredLength(TAG, s, 16)) + return FALSE; + tsmf_print_guid(Stream_Pointer(s)); + + for (i = 0; tsmf_format_type_map[i].type != TSMF_FORMAT_TYPE_UNKNOWN; i++) + { + if (memcmp(tsmf_format_type_map[i].guid, Stream_Pointer(s), 16) == 0) + break; + } + + mediatype->FormatType = tsmf_format_type_map[i].type; + if (mediatype->FormatType == TSMF_FORMAT_TYPE_UNKNOWN) + return FALSE; + + DEBUG_TSMF("FormatType %s", tsmf_format_type_map[i].name); + Stream_Seek(s, 16); + + /* cbFormat */ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return FALSE; + Stream_Read_UINT32(s, cbFormat); + DEBUG_TSMF("cbFormat %" PRIu32 "", cbFormat); +#ifdef WITH_DEBUG_TSMF + winpr_HexDump(TAG, WLOG_DEBUG, Stream_Pointer(s), cbFormat); +#endif + + const BOOL ret = tsmf_read_format_type(mediatype, s, cbFormat); + + if (mediatype->SamplesPerSecond.Numerator == 0) + mediatype->SamplesPerSecond.Numerator = 1; + + if (mediatype->SamplesPerSecond.Denominator == 0) + mediatype->SamplesPerSecond.Denominator = 1; + + return ret; +} + +BOOL tsmf_codec_check_media_type(const char* decoder_name, wStream* s) +{ + size_t pos = 0; + BOOL ret = FALSE; + TS_AM_MEDIA_TYPE mediatype; + + static BOOL decoderAvailable = FALSE; + static BOOL firstRun = TRUE; + + if (firstRun) + { + firstRun = FALSE; + if (tsmf_check_decoder_available(decoder_name)) + decoderAvailable = TRUE; + } + + pos = Stream_GetPosition(s); + if (decoderAvailable) + ret = tsmf_codec_parse_media_type(&mediatype, s); + Stream_SetPosition(s, pos); + + if (ret) + { + ITSMFDecoder* decoder = tsmf_load_decoder(decoder_name, &mediatype); + + if (!decoder) + { + WLog_WARN(TAG, "Format not supported by decoder %s", decoder_name); + ret = FALSE; + } + else + { + decoder->Free(decoder); + } + } + + return ret; +} diff --git a/third_party/FreeRDP/channels/tsmf/client/tsmf_codec.h b/third_party/FreeRDP/channels/tsmf/client/tsmf_codec.h new file mode 100644 index 0000000..128c628 --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/client/tsmf_codec.h @@ -0,0 +1,31 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Codec + * + * Copyright 2010-2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_TSMF_CLIENT_CODEC_H +#define FREERDP_CHANNEL_TSMF_CLIENT_CODEC_H + +#include "tsmf_types.h" + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL BOOL tsmf_codec_parse_media_type(TS_AM_MEDIA_TYPE* mediatype, wStream* s); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL BOOL tsmf_codec_check_media_type(const char* decoder_name, wStream* s); + +#endif /* FREERDP_CHANNEL_TSMF_CLIENT_CODEC_H */ diff --git a/third_party/FreeRDP/channels/tsmf/client/tsmf_constants.h b/third_party/FreeRDP/channels/tsmf/client/tsmf_constants.h new file mode 100644 index 0000000..abfa4e4 --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/client/tsmf_constants.h @@ -0,0 +1,139 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Constants + * + * Copyright 2010-2011 Vic Lee + * Copyright 2012 Hewlett-Packard Development Company, L.P. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_TSMF_CLIENT_CONSTANTS_H +#define FREERDP_CHANNEL_TSMF_CLIENT_CONSTANTS_H + +#define GUID_SIZE 16 +#define TSMF_BUFFER_PADDING_SIZE 8 + +/* Interface IDs defined in [MS-RDPEV]. There's no constant names in the MS + documentation, so we create them on our own. */ +#define TSMF_INTERFACE_DEFAULT 0x00000000 +#define TSMF_INTERFACE_CLIENT_NOTIFICATIONS 0x00000001 +#define TSMF_INTERFACE_CAPABILITIES 0x00000002 + +/* Interface ID Mask */ +#define STREAM_ID_STUB 0x80000000 +#define STREAM_ID_PROXY 0x40000000 +#define STREAM_ID_NONE 0x00000000 + +/* Function ID */ +/* Common IDs for all interfaces are as follows. */ +#define RIMCALL_RELEASE 0x00000001 +#define RIMCALL_QUERYINTERFACE 0x00000002 +/* Capabilities Negotiator Interface IDs are as follows. */ +#define RIM_EXCHANGE_CAPABILITY_REQUEST 0x00000100 +/* The Client Notifications Interface ID is as follows. */ +#define PLAYBACK_ACK 0x00000100 +#define CLIENT_EVENT_NOTIFICATION 0x00000101 +/* Server Data Interface IDs are as follows. */ +#define EXCHANGE_CAPABILITIES_REQ 0x00000100 +#define SET_CHANNEL_PARAMS 0x00000101 +#define ADD_STREAM 0x00000102 +#define ON_SAMPLE 0x00000103 +#define SET_VIDEO_WINDOW 0x00000104 +#define ON_NEW_PRESENTATION 0x00000105 +#define SHUTDOWN_PRESENTATION_REQ 0x00000106 +#define SET_TOPOLOGY_REQ 0x00000107 +#define CHECK_FORMAT_SUPPORT_REQ 0x00000108 +#define ON_PLAYBACK_STARTED 0x00000109 +#define ON_PLAYBACK_PAUSED 0x0000010a +#define ON_PLAYBACK_STOPPED 0x0000010b +#define ON_PLAYBACK_RESTARTED 0x0000010c +#define ON_PLAYBACK_RATE_CHANGED 0x0000010d +#define ON_FLUSH 0x0000010e +#define ON_STREAM_VOLUME 0x0000010f +#define ON_CHANNEL_VOLUME 0x00000110 +#define ON_END_OF_STREAM 0x00000111 +#define SET_ALLOCATOR 0x00000112 +#define NOTIFY_PREROLL 0x00000113 +#define UPDATE_GEOMETRY_INFO 0x00000114 +#define REMOVE_STREAM 0x00000115 +#define SET_SOURCE_VIDEO_RECT 0x00000116 + +/* Supported platform */ +#define MMREDIR_CAPABILITY_PLATFORM_MF 0x00000001 +#define MMREDIR_CAPABILITY_PLATFORM_DSHOW 0x00000002 +#define MMREDIR_CAPABILITY_PLATFORM_OTHER 0x00000004 + +/* TSMM_CLIENT_EVENT Constants */ +#define TSMM_CLIENT_EVENT_ENDOFSTREAM 0x0064 +#define TSMM_CLIENT_EVENT_STOP_COMPLETED 0x00C8 +#define TSMM_CLIENT_EVENT_START_COMPLETED 0x00C9 +#define TSMM_CLIENT_EVENT_MONITORCHANGED 0x012C + +/* TS_MM_DATA_SAMPLE.SampleExtensions */ +#define TSMM_SAMPLE_EXT_CLEANPOINT 0x00000001 +#define TSMM_SAMPLE_EXT_DISCONTINUITY 0x00000002 +#define TSMM_SAMPLE_EXT_INTERLACED 0x00000004 +#define TSMM_SAMPLE_EXT_BOTTOMFIELDFIRST 0x00000008 +#define TSMM_SAMPLE_EXT_REPEATFIELDFIRST 0x00000010 +#define TSMM_SAMPLE_EXT_SINGLEFIELD 0x00000020 +#define TSMM_SAMPLE_EXT_DERIVEDFROMTOPFIELD 0x00000040 +#define TSMM_SAMPLE_EXT_HAS_NO_TIMESTAMPS 0x00000080 +#define TSMM_SAMPLE_EXT_RELATIVE_TIMESTAMPS 0x00000100 +#define TSMM_SAMPLE_EXT_ABSOLUTE_TIMESTAMPS 0x00000200 + +/* MajorType */ +#define TSMF_MAJOR_TYPE_UNKNOWN 0 +#define TSMF_MAJOR_TYPE_VIDEO 1 +#define TSMF_MAJOR_TYPE_AUDIO 2 + +/* SubType */ +#define TSMF_SUB_TYPE_UNKNOWN 0 +#define TSMF_SUB_TYPE_WVC1 1 +#define TSMF_SUB_TYPE_WMA2 2 +#define TSMF_SUB_TYPE_WMA9 3 +#define TSMF_SUB_TYPE_MP3 4 +#define TSMF_SUB_TYPE_MP2A 5 +#define TSMF_SUB_TYPE_MP2V 6 +#define TSMF_SUB_TYPE_WMV3 7 +#define TSMF_SUB_TYPE_AAC 8 +#define TSMF_SUB_TYPE_H264 9 +#define TSMF_SUB_TYPE_AVC1 10 +#define TSMF_SUB_TYPE_AC3 11 +#define TSMF_SUB_TYPE_WMV2 12 +#define TSMF_SUB_TYPE_WMV1 13 +#define TSMF_SUB_TYPE_MP1V 14 +#define TSMF_SUB_TYPE_MP1A 15 +#define TSMF_SUB_TYPE_YUY2 16 +#define TSMF_SUB_TYPE_MP43 17 +#define TSMF_SUB_TYPE_MP4S 18 +#define TSMF_SUB_TYPE_MP42 19 +#define TSMF_SUB_TYPE_OGG 20 +#define TSMF_SUB_TYPE_SPEEX 21 +#define TSMF_SUB_TYPE_THEORA 22 +#define TSMF_SUB_TYPE_FLAC 23 +#define TSMF_SUB_TYPE_VP8 24 +#define TSMF_SUB_TYPE_VP9 25 +#define TSMF_SUB_TYPE_H263 26 +#define TSMF_SUB_TYPE_M4S2 27 +#define TSMF_SUB_TYPE_WMA1 28 + +/* FormatType */ +#define TSMF_FORMAT_TYPE_UNKNOWN 0 +#define TSMF_FORMAT_TYPE_MFVIDEOFORMAT 1 +#define TSMF_FORMAT_TYPE_WAVEFORMATEX 2 +#define TSMF_FORMAT_TYPE_MPEG2VIDEOINFO 3 +#define TSMF_FORMAT_TYPE_VIDEOINFO2 4 +#define TSMF_FORMAT_TYPE_MPEG1VIDEOINFO 5 + +#endif /* FREERDP_CHANNEL_TSMF_CLIENT_CONSTANTS_H */ diff --git a/third_party/FreeRDP/channels/tsmf/client/tsmf_decoder.c b/third_party/FreeRDP/channels/tsmf/client/tsmf_decoder.c new file mode 100644 index 0000000..9aaabee --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/client/tsmf_decoder.c @@ -0,0 +1,121 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Decoder + * + * Copyright 2010-2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include + +#include "tsmf_types.h" +#include "tsmf_constants.h" +#include "tsmf_decoder.h" + +static ITSMFDecoder* tsmf_load_decoder_by_name(const char* name) +{ + ITSMFDecoder* decoder = nullptr; + union + { + PVIRTUALCHANNELENTRY pvce; + TSMF_DECODER_ENTRY entry; + } cnv; + cnv.pvce = freerdp_load_channel_addin_entry("tsmf", name, "decoder", 0); + + if (!cnv.entry) + return nullptr; + + const UINT rc = cnv.entry(&decoder); + + if ((rc != CHANNEL_RC_OK) || !decoder) + { + WLog_ERR(TAG, "failed to call export function in %s", name); + return nullptr; + } + + return decoder; +} + +static BOOL tsmf_decoder_set_format(ITSMFDecoder* decoder, TS_AM_MEDIA_TYPE* media_type) +{ + if (decoder->SetFormat(decoder, media_type)) + return TRUE; + else + return FALSE; +} + +ITSMFDecoder* tsmf_load_decoder(const char* name, TS_AM_MEDIA_TYPE* media_type) +{ + ITSMFDecoder* decoder = nullptr; + + if (name) + decoder = tsmf_load_decoder_by_name(name); + +#if defined(WITH_GSTREAMER_1_0) + if (!decoder) + decoder = tsmf_load_decoder_by_name("gstreamer"); +#endif + +#if defined(WITH_VIDEO_FFMPEG) + if (!decoder) + decoder = tsmf_load_decoder_by_name("ffmpeg"); +#endif + + if (decoder) + { + if (!tsmf_decoder_set_format(decoder, media_type)) + { + decoder->Free(decoder); + decoder = nullptr; + } + } + + return decoder; +} + +BOOL tsmf_check_decoder_available(const char* name) +{ + ITSMFDecoder* decoder = nullptr; + BOOL retValue = FALSE; + + if (name) + { + decoder = tsmf_load_decoder_by_name(name); + } +#if defined(WITH_GSTREAMER_1_0) + if (!decoder) + decoder = tsmf_load_decoder_by_name("gstreamer"); +#endif + +#if defined(WITH_VIDEO_FFMPEG) + if (!decoder) + decoder = tsmf_load_decoder_by_name("ffmpeg"); +#endif + + if (decoder) + { + decoder->Free(decoder); + decoder = nullptr; + retValue = TRUE; + } + + return retValue; +} diff --git a/third_party/FreeRDP/channels/tsmf/client/tsmf_decoder.h b/third_party/FreeRDP/channels/tsmf/client/tsmf_decoder.h new file mode 100644 index 0000000..26795d0 --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/client/tsmf_decoder.h @@ -0,0 +1,87 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Decoder + * + * Copyright 2010-2011 Vic Lee + * Copyright 2012 Hewlett-Packard Development Company, L.P. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_TSMF_CLIENT_DECODER_H +#define FREERDP_CHANNEL_TSMF_CLIENT_DECODER_H + +#include "tsmf_types.h" + +typedef enum +{ + Control_Pause, + Control_Resume, + Control_Restart, + Control_Stop +} ITSMFControlMsg; + +typedef struct s_ITSMFDecoder ITSMFDecoder; + +struct s_ITSMFDecoder +{ + /* Set the decoder format. Return true if supported. */ + WINPR_ATTR_NODISCARD BOOL (*SetFormat)(ITSMFDecoder* decoder, TS_AM_MEDIA_TYPE* media_type); + /* Decode a sample. */ + WINPR_ATTR_NODISCARD BOOL (*Decode)(ITSMFDecoder* decoder, const BYTE* data, UINT32 data_size, + UINT32 extensions); + /* Get the decoded data */ + WINPR_ATTR_NODISCARD BYTE* (*GetDecodedData)(ITSMFDecoder* decoder, UINT32* size); + /* Get the pixel format of decoded video frame */ + WINPR_ATTR_NODISCARD UINT32 (*GetDecodedFormat)(ITSMFDecoder* decoder); + /* Get the width and height of decoded video frame */ + WINPR_ATTR_NODISCARD BOOL (*GetDecodedDimension)(ITSMFDecoder* decoder, UINT32* width, + UINT32* height); + /* Free the decoder */ + void (*Free)(ITSMFDecoder* decoder); + /* Optional Control function */ + WINPR_ATTR_NODISCARD BOOL (*Control)(ITSMFDecoder* decoder, ITSMFControlMsg control_msg, + UINT32* arg); + /* Decode a sample with extended interface. */ + WINPR_ATTR_NODISCARD BOOL (*DecodeEx)(ITSMFDecoder* decoder, const BYTE* data, UINT32 data_size, + UINT32 extensions, UINT64 start_time, UINT64 end_time, + UINT64 duration); + /* Get current play time */ + WINPR_ATTR_NODISCARD UINT64 (*GetRunningTime)(ITSMFDecoder* decoder); + /* Update Gstreamer Rendering Area */ + WINPR_ATTR_NODISCARD BOOL (*UpdateRenderingArea)(ITSMFDecoder* decoder, UINT32 newX, + UINT32 newY, UINT32 newWidth, UINT32 newHeight, + UINT32 numRectangles, + const RECTANGLE_32* rectangles); + /* Change Gstreamer Audio Volume */ + WINPR_ATTR_NODISCARD BOOL (*ChangeVolume)(ITSMFDecoder* decoder, UINT32 newVolume, + UINT32 muted); + /* Check buffer level */ + WINPR_ATTR_NODISCARD BOOL (*BufferLevel)(ITSMFDecoder* decoder); + /* Register a callback for frame ack. */ + WINPR_ATTR_NODISCARD BOOL (*SetAckFunc)(ITSMFDecoder* decoder, BOOL (*cb)(void*, BOOL), + void* stream); + /* Register a callback for stream seek detection. */ + WINPR_ATTR_NODISCARD BOOL (*SetSyncFunc)(ITSMFDecoder* decoder, void (*cb)(void*), + void* stream); +}; + +#define TSMF_DECODER_EXPORT_FUNC_NAME "TSMFDecoderEntry" +typedef UINT(VCAPITYPE* TSMF_DECODER_ENTRY)(ITSMFDecoder** decoder); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL ITSMFDecoder* tsmf_load_decoder(const char* name, + TS_AM_MEDIA_TYPE* media_type); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL tsmf_check_decoder_available(const char* name); + +#endif /* FREERDP_CHANNEL_TSMF_CLIENT_DECODER_H */ diff --git a/third_party/FreeRDP/channels/tsmf/client/tsmf_ifman.c b/third_party/FreeRDP/channels/tsmf/client/tsmf_ifman.c new file mode 100644 index 0000000..4192b67 --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/client/tsmf_ifman.c @@ -0,0 +1,839 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Interface Manipulation + * + * Copyright 2010-2011 Vic Lee + * Copyright 2012 Hewlett-Packard Development Company, L.P. + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include + +#include + +#include + +#include "tsmf_types.h" +#include "tsmf_constants.h" +#include "tsmf_media.h" +#include "tsmf_codec.h" + +#include "tsmf_ifman.h" + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_rim_exchange_capability_request(TSMF_IFMAN* ifman) +{ + UINT32 CapabilityValue = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(ifman->input, CapabilityValue); + DEBUG_TSMF("server CapabilityValue %" PRIu32 "", CapabilityValue); + + if (!Stream_EnsureRemainingCapacity(ifman->output, 8)) + return ERROR_INVALID_DATA; + + Stream_Write_UINT32(ifman->output, 1); /* CapabilityValue */ + Stream_Write_UINT32(ifman->output, 0); /* Result */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_exchange_capability_request(TSMF_IFMAN* ifman) +{ + UINT32 CapabilityType = 0; + UINT32 cbCapabilityLength = 0; + UINT32 numHostCapabilities = 0; + + WINPR_ASSERT(ifman); + if (!Stream_EnsureRemainingCapacity(ifman->output, ifman->input_size + 4)) + return ERROR_OUTOFMEMORY; + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, ifman->input_size)) + return ERROR_INVALID_DATA; + + const size_t xpos = Stream_GetPosition(ifman->output); + Stream_Copy(ifman->input, ifman->output, ifman->input_size); + Stream_SetPosition(ifman->output, xpos); + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->output, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(ifman->output, numHostCapabilities); + + for (UINT32 i = 0; i < numHostCapabilities; i++) + { + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->output, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(ifman->output, CapabilityType); + Stream_Read_UINT32(ifman->output, cbCapabilityLength); + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->output, cbCapabilityLength)) + return ERROR_INVALID_DATA; + + const size_t pos = Stream_GetPosition(ifman->output); + + switch (CapabilityType) + { + case 1: /* Protocol version request */ + { + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->output, 4)) + return ERROR_INVALID_DATA; + + const UINT32 v = Stream_Get_UINT32(ifman->output); + WINPR_UNUSED(v); + DEBUG_TSMF("server protocol version %" PRIu32 "", v); + } + break; + + case 2: /* Supported platform */ + { + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->output, 4)) + return ERROR_INVALID_DATA; + + const UINT32 v = Stream_Get_UINT32(ifman->output); + WINPR_UNUSED(v); + DEBUG_TSMF("server supported platform %" PRIu32 "", v); + /* Claim that we support both MF and DShow platforms. */ + Stream_Write_UINT32(ifman->output, MMREDIR_CAPABILITY_PLATFORM_MF | + MMREDIR_CAPABILITY_PLATFORM_DSHOW); + } + break; + + default: + WLog_ERR(TAG, "skipping unknown capability type %" PRIu32 "", CapabilityType); + break; + } + + Stream_SetPosition(ifman->output, pos + cbCapabilityLength); + } + + Stream_Write_UINT32(ifman->output, 0); /* Result */ + ifman->output_interface_id = TSMF_INTERFACE_DEFAULT | STREAM_ID_STUB; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_check_format_support_request(TSMF_IFMAN* ifman) +{ + UINT32 numMediaType = 0; + UINT32 PlatformCookie = 0; + UINT32 FormatSupported = 1; + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 12)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(ifman->input, PlatformCookie); + Stream_Seek_UINT32(ifman->input); /* NoRolloverFlags (4 bytes) */ + Stream_Read_UINT32(ifman->input, numMediaType); + DEBUG_TSMF("PlatformCookie %" PRIu32 " numMediaType %" PRIu32 "", PlatformCookie, numMediaType); + + if (!tsmf_codec_check_media_type(ifman->decoder_name, ifman->input)) + FormatSupported = 0; + + if (FormatSupported) + DEBUG_TSMF("format ok."); + + if (!Stream_EnsureRemainingCapacity(ifman->output, 12)) + return -1; + + Stream_Write_UINT32(ifman->output, FormatSupported); + Stream_Write_UINT32(ifman->output, PlatformCookie); + Stream_Write_UINT32(ifman->output, 0); /* Result */ + ifman->output_interface_id = TSMF_INTERFACE_DEFAULT | STREAM_ID_STUB; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_new_presentation(TSMF_IFMAN* ifman) +{ + UINT status = CHANNEL_RC_OK; + TSMF_PRESENTATION* presentation = nullptr; + DEBUG_TSMF(""); + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, GUID_SIZE)) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + + if (presentation) + { + DEBUG_TSMF("Presentation already exists"); + ifman->output_pending = FALSE; + return CHANNEL_RC_OK; + } + + presentation = tsmf_presentation_new(Stream_Pointer(ifman->input), ifman->channel_callback); + + if (!presentation) + status = ERROR_OUTOFMEMORY; + else + tsmf_presentation_set_audio_device(presentation, ifman->audio_name, ifman->audio_device); + + ifman->output_pending = TRUE; + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_add_stream(TSMF_IFMAN* ifman, rdpContext* rdpcontext) +{ + UINT32 StreamId = 0; + UINT status = CHANNEL_RC_OK; + TSMF_STREAM* stream = nullptr; + TSMF_PRESENTATION* presentation = nullptr; + DEBUG_TSMF(""); + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, GUID_SIZE + 8)) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + Stream_Seek(ifman->input, GUID_SIZE); + + if (!presentation) + { + WLog_ERR(TAG, "unknown presentation id"); + status = ERROR_NOT_FOUND; + } + else + { + Stream_Read_UINT32(ifman->input, StreamId); + Stream_Seek_UINT32(ifman->input); /* numMediaType */ + stream = tsmf_stream_new(presentation, StreamId, rdpcontext); + + if (!stream) + { + WLog_ERR(TAG, "failed to create stream"); + return ERROR_OUTOFMEMORY; + } + + if (!tsmf_stream_set_format(stream, ifman->decoder_name, ifman->input)) + { + WLog_ERR(TAG, "failed to set stream format"); + return ERROR_OUTOFMEMORY; + } + + tsmf_stream_start_threads(stream); + } + + ifman->output_pending = TRUE; + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_set_topology_request(TSMF_IFMAN* ifman) +{ + DEBUG_TSMF(""); + + if (!Stream_EnsureRemainingCapacity(ifman->output, 8)) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(ifman->output, 1); /* TopologyReady */ + Stream_Write_UINT32(ifman->output, 0); /* Result */ + ifman->output_interface_id = TSMF_INTERFACE_DEFAULT | STREAM_ID_STUB; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_remove_stream(TSMF_IFMAN* ifman) +{ + int status = CHANNEL_RC_OK; + UINT32 StreamId = 0; + TSMF_STREAM* stream = nullptr; + TSMF_PRESENTATION* presentation = nullptr; + DEBUG_TSMF(""); + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 20)) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + Stream_Seek(ifman->input, GUID_SIZE); + + if (!presentation) + { + status = ERROR_NOT_FOUND; + } + else + { + Stream_Read_UINT32(ifman->input, StreamId); + stream = tsmf_stream_find_by_id(presentation, StreamId); + + if (stream) + tsmf_stream_free(stream); + else + status = ERROR_NOT_FOUND; + } + + ifman->output_pending = TRUE; + return status; +} + +static float tsmf_stream_read_float(wStream* s) +{ + float fValue = NAN; + UINT32 iValue = 0; + Stream_Read_UINT32(s, iValue); + CopyMemory(&fValue, &iValue, 4); + return fValue; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_set_source_video_rect(TSMF_IFMAN* ifman) +{ + UINT status = CHANNEL_RC_OK; + TSMF_PRESENTATION* presentation = nullptr; + DEBUG_TSMF(""); + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 32)) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + Stream_Seek(ifman->input, GUID_SIZE); + + if (!presentation) + { + status = ERROR_NOT_FOUND; + } + else + { +#ifdef WITH_DEBUG_TSMF + const float Left = tsmf_stream_read_float(ifman->input); /* Left (4 bytes) */ + const float Top = tsmf_stream_read_float(ifman->input); /* Top (4 bytes) */ + const float Right = tsmf_stream_read_float(ifman->input); /* Right (4 bytes) */ + const float Bottom = tsmf_stream_read_float(ifman->input); /* Bottom (4 bytes) */ + DEBUG_TSMF("SetSourceVideoRect: Left: %f Top: %f Right: %f Bottom: %f", Left, Top, Right, + Bottom); +#endif + } + + ifman->output_pending = TRUE; + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_shutdown_presentation(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation = nullptr; + DEBUG_TSMF(""); + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, GUID_SIZE)) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + + if (presentation) + tsmf_presentation_free(presentation); + else + { + WLog_ERR(TAG, "unknown presentation id"); + return ERROR_NOT_FOUND; + } + + if (!Stream_EnsureRemainingCapacity(ifman->output, 4)) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(ifman->output, 0); /* Result */ + ifman->output_interface_id = TSMF_INTERFACE_DEFAULT | STREAM_ID_STUB; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_stream_volume(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation = nullptr; + UINT32 newVolume = 0; + UINT32 muted = 0; + DEBUG_TSMF("on stream volume"); + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, GUID_SIZE + 8)) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + + if (!presentation) + { + WLog_ERR(TAG, "unknown presentation id"); + return ERROR_NOT_FOUND; + } + + Stream_Seek(ifman->input, 16); + Stream_Read_UINT32(ifman->input, newVolume); + DEBUG_TSMF("on stream volume: new volume=[%" PRIu32 "]", newVolume); + Stream_Read_UINT32(ifman->input, muted); + DEBUG_TSMF("on stream volume: muted=[%" PRIu32 "]", muted); + + if (!tsmf_presentation_volume_changed(presentation, newVolume, muted)) + return ERROR_INVALID_OPERATION; + + ifman->output_pending = TRUE; + return 0; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_channel_volume(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation = nullptr; + DEBUG_TSMF("on channel volume"); + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, GUID_SIZE + 8)) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + + if (presentation) + { + UINT32 channelVolume = 0; + UINT32 changedChannel = 0; + Stream_Seek(ifman->input, 16); + Stream_Read_UINT32(ifman->input, channelVolume); + DEBUG_TSMF("on channel volume: channel volume=[%" PRIu32 "]", channelVolume); + Stream_Read_UINT32(ifman->input, changedChannel); + DEBUG_TSMF("on stream volume: changed channel=[%" PRIu32 "]", changedChannel); + } + + ifman->output_pending = TRUE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_set_video_window(TSMF_IFMAN* ifman) +{ + DEBUG_TSMF(""); + ifman->output_pending = TRUE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_update_geometry_info(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation = nullptr; + UINT32 numGeometryInfo = 0; + UINT32 Left = 0; + UINT32 Top = 0; + UINT32 Width = 0; + UINT32 Height = 0; + UINT32 cbVisibleRect = 0; + RECTANGLE_32* rects = nullptr; + UINT error = CHANNEL_RC_OK; + size_t pos = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, GUID_SIZE + 32)) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + + if (!presentation) + return ERROR_NOT_FOUND; + + Stream_Seek(ifman->input, 16); + Stream_Read_UINT32(ifman->input, numGeometryInfo); + pos = Stream_GetPosition(ifman->input); + Stream_Seek(ifman->input, 12); /* VideoWindowId (8 bytes), VideoWindowState (4 bytes) */ + Stream_Read_UINT32(ifman->input, Width); + Stream_Read_UINT32(ifman->input, Height); + Stream_Read_UINT32(ifman->input, Left); + Stream_Read_UINT32(ifman->input, Top); + Stream_SetPosition(ifman->input, pos + numGeometryInfo); + Stream_Read_UINT32(ifman->input, cbVisibleRect); + const UINT32 num_rects = cbVisibleRect / 16; + DEBUG_TSMF("numGeometryInfo %" PRIu32 " Width %" PRIu32 " Height %" PRIu32 " Left %" PRIu32 + " Top %" PRIu32 " cbVisibleRect %" PRIu32 " num_rects %d", + numGeometryInfo, Width, Height, Left, Top, cbVisibleRect, num_rects); + + if (num_rects > 0) + { + rects = (RECTANGLE_32*)calloc(num_rects, sizeof(RECTANGLE_32)); + + for (size_t i = 0; i < num_rects; i++) + { + Stream_Read_UINT32(ifman->input, rects[i].top); /* Top */ + Stream_Read_UINT32(ifman->input, rects[i].left); /* Left */ + Stream_Read_UINT32(ifman->input, rects[i].height); /* Bottom */ + Stream_Read_UINT32(ifman->input, rects[i].width); /* Right */ + rects[i].width -= rects[i].left; + rects[i].height -= rects[i].top; + DEBUG_TSMF("rect %d: %" PRId16 " %" PRId16 " %" PRId16 " %" PRId16 "", i, rects[i].x, + rects[i].y, rects[i].width, rects[i].height); + } + } + + const BOOL rc = tsmf_presentation_set_geometry_info(presentation, Left, Top, Width, Height, + num_rects, rects); + free(rects); + if (!rc) + return ERROR_INVALID_OPERATION; + + ifman->output_pending = TRUE; + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_set_allocator(TSMF_IFMAN* ifman) +{ + DEBUG_TSMF(""); + ifman->output_pending = TRUE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_notify_preroll(TSMF_IFMAN* ifman) +{ + DEBUG_TSMF(""); + tsmf_ifman_on_playback_paused(ifman); + ifman->output_pending = TRUE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_sample(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation = nullptr; + TSMF_STREAM* stream = nullptr; + UINT32 StreamId = 0; + UINT64 SampleStartTime = 0; + UINT64 SampleEndTime = 0; + UINT64 ThrottleDuration = 0; + UINT32 SampleExtensions = 0; + UINT32 cbData = 0; + UINT error = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 60)) + return ERROR_INVALID_DATA; + + Stream_Seek(ifman->input, 16); + Stream_Read_UINT32(ifman->input, StreamId); + Stream_Seek_UINT32(ifman->input); /* numSample */ + Stream_Read_UINT64(ifman->input, SampleStartTime); + Stream_Read_UINT64(ifman->input, SampleEndTime); + Stream_Read_UINT64(ifman->input, ThrottleDuration); + Stream_Seek_UINT32(ifman->input); /* SampleFlags */ + Stream_Read_UINT32(ifman->input, SampleExtensions); + Stream_Read_UINT32(ifman->input, cbData); + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, cbData)) + return ERROR_INVALID_DATA; + + DEBUG_TSMF("MessageId %" PRIu32 " StreamId %" PRIu32 " SampleStartTime %" PRIu64 + " SampleEndTime %" PRIu64 " " + "ThrottleDuration %" PRIu64 " SampleExtensions %" PRIu32 " cbData %" PRIu32 "", + ifman->message_id, StreamId, SampleStartTime, SampleEndTime, ThrottleDuration, + SampleExtensions, cbData); + presentation = tsmf_presentation_find_by_id(ifman->presentation_id); + + if (!presentation) + { + WLog_ERR(TAG, "unknown presentation id"); + return ERROR_NOT_FOUND; + } + + stream = tsmf_stream_find_by_id(presentation, StreamId); + + if (!stream) + { + WLog_ERR(TAG, "unknown stream id"); + return ERROR_NOT_FOUND; + } + + if (!tsmf_stream_push_sample(stream, ifman->channel_callback, ifman->message_id, + SampleStartTime, SampleEndTime, ThrottleDuration, SampleExtensions, + cbData, Stream_Pointer(ifman->input))) + { + WLog_ERR(TAG, "unable to push sample"); + return ERROR_OUTOFMEMORY; + } + + if ((error = tsmf_presentation_sync(presentation))) + { + WLog_ERR(TAG, "tsmf_presentation_sync failed with error %" PRIu32 "", error); + return error; + } + + ifman->output_pending = TRUE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_flush(TSMF_IFMAN* ifman) +{ + UINT32 StreamId = 0; + TSMF_PRESENTATION* presentation = nullptr; + TSMF_STREAM* stream = nullptr; + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 20)) + return ERROR_INVALID_DATA; + + Stream_Seek(ifman->input, 16); + Stream_Read_UINT32(ifman->input, StreamId); + DEBUG_TSMF("StreamId %" PRIu32 "", StreamId); + presentation = tsmf_presentation_find_by_id(ifman->presentation_id); + + if (!presentation) + { + WLog_ERR(TAG, "unknown presentation id"); + return ERROR_NOT_FOUND; + } + + /* Flush message is for a stream, not the entire presentation + * therefore we only flush the stream as intended per the MS-RDPEV spec + */ + stream = tsmf_stream_find_by_id(presentation, StreamId); + + if (stream) + { + if (!tsmf_stream_flush(stream)) + return ERROR_INVALID_OPERATION; + } + else + WLog_ERR(TAG, "unknown stream id"); + + ifman->output_pending = TRUE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_end_of_stream(TSMF_IFMAN* ifman) +{ + UINT32 StreamId = 0; + TSMF_STREAM* stream = nullptr; + TSMF_PRESENTATION* presentation = nullptr; + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 20)) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + Stream_Seek(ifman->input, 16); + Stream_Read_UINT32(ifman->input, StreamId); + + if (presentation) + { + stream = tsmf_stream_find_by_id(presentation, StreamId); + + if (stream) + tsmf_stream_end(stream, ifman->message_id, ifman->channel_callback); + } + + DEBUG_TSMF("StreamId %" PRIu32 "", StreamId); + ifman->output_pending = TRUE; + ifman->output_interface_id = TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_playback_started(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation = nullptr; + DEBUG_TSMF(""); + + if (!Stream_CheckAndLogRequiredLength(TAG, ifman->input, 16)) + return ERROR_INVALID_DATA; + + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + + if (presentation) + tsmf_presentation_start(presentation); + else + WLog_ERR(TAG, "unknown presentation id"); + + if (!Stream_EnsureRemainingCapacity(ifman->output, 16)) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(ifman->output, CLIENT_EVENT_NOTIFICATION); /* FunctionId */ + Stream_Write_UINT32(ifman->output, 0); /* StreamId */ + Stream_Write_UINT32(ifman->output, TSMM_CLIENT_EVENT_START_COMPLETED); /* EventId */ + Stream_Write_UINT32(ifman->output, 0); /* cbData */ + ifman->output_interface_id = TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_playback_paused(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation = nullptr; + DEBUG_TSMF(""); + ifman->output_pending = TRUE; + /* Added pause control so gstreamer pipeline can be paused accordingly */ + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + + if (presentation) + { + if (!tsmf_presentation_paused(presentation)) + return ERROR_INVALID_OPERATION; + } + else + WLog_ERR(TAG, "unknown presentation id"); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_playback_restarted(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation = nullptr; + DEBUG_TSMF(""); + ifman->output_pending = TRUE; + /* Added restart control so gstreamer pipeline can be resumed accordingly */ + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + + if (presentation) + { + if (!tsmf_presentation_restarted(presentation)) + return ERROR_INVALID_OPERATION; + } + else + WLog_ERR(TAG, "unknown presentation id"); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_playback_stopped(TSMF_IFMAN* ifman) +{ + TSMF_PRESENTATION* presentation = nullptr; + DEBUG_TSMF(""); + presentation = tsmf_presentation_find_by_id(Stream_Pointer(ifman->input)); + + if (presentation) + { + if (!tsmf_presentation_stop(presentation)) + return ERROR_INVALID_OPERATION; + } + else + WLog_ERR(TAG, "unknown presentation id"); + + if (!Stream_EnsureRemainingCapacity(ifman->output, 16)) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(ifman->output, CLIENT_EVENT_NOTIFICATION); /* FunctionId */ + Stream_Write_UINT32(ifman->output, 0); /* StreamId */ + Stream_Write_UINT32(ifman->output, TSMM_CLIENT_EVENT_STOP_COMPLETED); /* EventId */ + Stream_Write_UINT32(ifman->output, 0); /* cbData */ + ifman->output_interface_id = TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_ifman_on_playback_rate_changed(TSMF_IFMAN* ifman) +{ + DEBUG_TSMF(""); + + if (!Stream_EnsureRemainingCapacity(ifman->output, 16)) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(ifman->output, CLIENT_EVENT_NOTIFICATION); /* FunctionId */ + Stream_Write_UINT32(ifman->output, 0); /* StreamId */ + Stream_Write_UINT32(ifman->output, TSMM_CLIENT_EVENT_MONITORCHANGED); /* EventId */ + Stream_Write_UINT32(ifman->output, 0); /* cbData */ + ifman->output_interface_id = TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY; + return CHANNEL_RC_OK; +} diff --git a/third_party/FreeRDP/channels/tsmf/client/tsmf_ifman.h b/third_party/FreeRDP/channels/tsmf/client/tsmf_ifman.h new file mode 100644 index 0000000..363ddaa --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/client/tsmf_ifman.h @@ -0,0 +1,92 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Interface Manipulation + * + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_TSMF_CLIENT_IFMAN_H +#define FREERDP_CHANNEL_TSMF_CLIENT_IFMAN_H + +#include + +typedef struct +{ + IWTSVirtualChannelCallback* channel_callback; + const char* decoder_name; + const char* audio_name; + const char* audio_device; + BYTE presentation_id[16]; + UINT32 stream_id; + UINT32 message_id; + + wStream* input; + UINT32 input_size; + wStream* output; + BOOL output_pending; + UINT32 output_interface_id; +} TSMF_IFMAN; + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT +tsmf_ifman_rim_exchange_capability_request(TSMF_IFMAN* ifman); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_exchange_capability_request(TSMF_IFMAN* ifman); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_check_format_support_request(TSMF_IFMAN* ifman); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_on_new_presentation(TSMF_IFMAN* ifman); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_add_stream(TSMF_IFMAN* ifman, + rdpContext* rdpcontext); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_set_topology_request(TSMF_IFMAN* ifman); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_remove_stream(TSMF_IFMAN* ifman); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_set_source_video_rect(TSMF_IFMAN* ifman); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_shutdown_presentation(TSMF_IFMAN* ifman); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_on_stream_volume(TSMF_IFMAN* ifman); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_on_channel_volume(TSMF_IFMAN* ifman); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_set_video_window(TSMF_IFMAN* ifman); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_update_geometry_info(TSMF_IFMAN* ifman); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_set_allocator(TSMF_IFMAN* ifman); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_notify_preroll(TSMF_IFMAN* ifman); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_on_sample(TSMF_IFMAN* ifman); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_on_flush(TSMF_IFMAN* ifman); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_on_end_of_stream(TSMF_IFMAN* ifman); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_on_playback_started(TSMF_IFMAN* ifman); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_on_playback_paused(TSMF_IFMAN* ifman); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_on_playback_restarted(TSMF_IFMAN* ifman); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_on_playback_stopped(TSMF_IFMAN* ifman); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_ifman_on_playback_rate_changed(TSMF_IFMAN* ifman); + +#endif /* FREERDP_CHANNEL_TSMF_CLIENT_IFMAN_H */ diff --git a/third_party/FreeRDP/channels/tsmf/client/tsmf_main.c b/third_party/FreeRDP/channels/tsmf/client/tsmf_main.c new file mode 100644 index 0000000..85ad530 --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/client/tsmf_main.c @@ -0,0 +1,619 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include + +#include "tsmf_types.h" +#include "tsmf_constants.h" +#include "tsmf_ifman.h" +#include "tsmf_media.h" + +#include "tsmf_main.h" + +BOOL tsmf_send_eos_response(IWTSVirtualChannelCallback* pChannelCallback, UINT32 message_id) +{ + ssize_t status = -1; + TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*)pChannelCallback; + + if (!callback) + { + DEBUG_TSMF("No callback reference - unable to send eos response!"); + return FALSE; + } + + if (callback && callback->stream_id && callback->channel && callback->channel->Write) + { + wStream* s = Stream_New(nullptr, 24); + + if (!s) + return FALSE; + + Stream_Write_UINT32(s, TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY); + Stream_Write_UINT32(s, message_id); + Stream_Write_UINT32(s, CLIENT_EVENT_NOTIFICATION); /* FunctionId */ + Stream_Write_UINT32(s, callback->stream_id); /* StreamId */ + Stream_Write_UINT32(s, TSMM_CLIENT_EVENT_ENDOFSTREAM); /* EventId */ + Stream_Write_UINT32(s, 0); /* cbData */ + const size_t pos = Stream_GetPosition(s); + DEBUG_TSMF("EOS response size %" PRIuz "", pos); + WINPR_ASSERT(pos <= UINT32_MAX); + status = + callback->channel->Write(callback->channel, (UINT32)pos, Stream_Buffer(s), nullptr); + + if (status) + { + WLog_ERR(TAG, "response error %" PRId32, WINPR_CXX_COMPAT_CAST(int32_t, status)); + } + + Stream_Free(s, TRUE); + } + + return (status == 0); +} + +BOOL tsmf_playback_ack(IWTSVirtualChannelCallback* pChannelCallback, UINT32 message_id, + UINT64 duration, UINT32 data_size) +{ + ssize_t status = -1; + TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*)pChannelCallback; + + if (!callback) + return FALSE; + + wStream* s = Stream_New(nullptr, 32); + + if (!s) + return FALSE; + + Stream_Write_UINT32(s, TSMF_INTERFACE_CLIENT_NOTIFICATIONS | STREAM_ID_PROXY); + Stream_Write_UINT32(s, message_id); + Stream_Write_UINT32(s, PLAYBACK_ACK); /* FunctionId */ + Stream_Write_UINT32(s, callback->stream_id); /* StreamId */ + Stream_Write_UINT64(s, duration); /* DataDuration */ + Stream_Write_UINT64(s, data_size); /* cbData */ + + const size_t pos = Stream_GetPosition(s); + DEBUG_TSMF("ACK response size %" PRIuz "", pos); + + if (!callback->channel || !callback->channel->Write) + { + WLog_ERR(TAG, "channel=%p, write=%p", WINPR_CXX_COMPAT_CAST(const void*, callback->channel), + WINPR_CXX_COMPAT_CAST(const void*, + callback->channel ? callback->channel->Write : nullptr)); + } + else + { + status = callback->channel->Write( + callback->channel, WINPR_ASSERTING_INT_CAST(uint32_t, pos), Stream_Buffer(s), nullptr); + } + + if (status) + { + WLog_ERR(TAG, "response error %" PRId32, WINPR_CXX_COMPAT_CAST(int32_t, status)); + } + + Stream_Free(s, TRUE); + return (status == 0); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT tsmf_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data) +{ + wStream* input = nullptr; + wStream* output = nullptr; + UINT error = CHANNEL_RC_OK; + BOOL processed = FALSE; + TSMF_IFMAN ifman = WINPR_C_ARRAY_INIT; + UINT32 MessageId = 0; + UINT32 FunctionId = 0; + UINT32 InterfaceId = 0; + TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*)pChannelCallback; + const size_t cbSize = Stream_GetRemainingLength(data); + + /* 2.2.1 Shared Message Header (SHARED_MSG_HEADER) */ + if (!Stream_CheckAndLogRequiredLength(TAG, data, 12) || (cbSize > UINT32_MAX)) + return ERROR_INVALID_DATA; + + input = data; + output = Stream_New(nullptr, 256); + + if (!output) + return ERROR_OUTOFMEMORY; + + Stream_Seek(output, 8); + Stream_Read_UINT32(input, InterfaceId); /* InterfaceId (4 bytes) */ + Stream_Read_UINT32(input, MessageId); /* MessageId (4 bytes) */ + Stream_Read_UINT32(input, FunctionId); /* FunctionId (4 bytes) */ + DEBUG_TSMF("cbSize=%" PRIu32 " InterfaceId=0x%" PRIX32 " MessageId=0x%" PRIX32 + " FunctionId=0x%" PRIX32 "", + cbSize, InterfaceId, MessageId, FunctionId); + ifman.channel_callback = pChannelCallback; + ifman.decoder_name = ((TSMF_PLUGIN*)callback->plugin)->decoder_name; + ifman.audio_name = ((TSMF_PLUGIN*)callback->plugin)->audio_name; + ifman.audio_device = ((TSMF_PLUGIN*)callback->plugin)->audio_device; + CopyMemory(ifman.presentation_id, callback->presentation_id, GUID_SIZE); + ifman.stream_id = callback->stream_id; + ifman.message_id = MessageId; + ifman.input = input; + ifman.input_size = (UINT32)(cbSize - 12U); + ifman.output = output; + ifman.output_pending = FALSE; + ifman.output_interface_id = InterfaceId; + + // (void)fprintf(stderr, "InterfaceId: 0x%08"PRIX32" MessageId: 0x%08"PRIX32" FunctionId: + // 0x%08"PRIX32"\n", InterfaceId, MessageId, FunctionId); + + switch (InterfaceId) + { + case TSMF_INTERFACE_CAPABILITIES | STREAM_ID_NONE: + switch (FunctionId) + { + case RIM_EXCHANGE_CAPABILITY_REQUEST: + error = tsmf_ifman_rim_exchange_capability_request(&ifman); + processed = TRUE; + break; + + case RIMCALL_RELEASE: + case RIMCALL_QUERYINTERFACE: + break; + + default: + break; + } + + break; + + case TSMF_INTERFACE_DEFAULT | STREAM_ID_PROXY: + switch (FunctionId) + { + case SET_CHANNEL_PARAMS: + if (!Stream_CheckAndLogRequiredLength(TAG, input, GUID_SIZE + 4)) + { + error = ERROR_INVALID_DATA; + goto out; + } + + CopyMemory(callback->presentation_id, Stream_Pointer(input), GUID_SIZE); + Stream_Seek(input, GUID_SIZE); + Stream_Read_UINT32(input, callback->stream_id); + DEBUG_TSMF("SET_CHANNEL_PARAMS StreamId=%" PRIu32 "", callback->stream_id); + ifman.output_pending = TRUE; + processed = TRUE; + break; + + case EXCHANGE_CAPABILITIES_REQ: + error = tsmf_ifman_exchange_capability_request(&ifman); + processed = TRUE; + break; + + case CHECK_FORMAT_SUPPORT_REQ: + error = tsmf_ifman_check_format_support_request(&ifman); + processed = TRUE; + break; + + case ON_NEW_PRESENTATION: + error = tsmf_ifman_on_new_presentation(&ifman); + processed = TRUE; + break; + + case ADD_STREAM: + error = + tsmf_ifman_add_stream(&ifman, ((TSMF_PLUGIN*)callback->plugin)->rdpcontext); + processed = TRUE; + break; + + case SET_TOPOLOGY_REQ: + error = tsmf_ifman_set_topology_request(&ifman); + processed = TRUE; + break; + + case REMOVE_STREAM: + error = tsmf_ifman_remove_stream(&ifman); + processed = TRUE; + break; + + case SET_SOURCE_VIDEO_RECT: + error = tsmf_ifman_set_source_video_rect(&ifman); + processed = TRUE; + break; + + case SHUTDOWN_PRESENTATION_REQ: + error = tsmf_ifman_shutdown_presentation(&ifman); + processed = TRUE; + break; + + case ON_STREAM_VOLUME: + error = tsmf_ifman_on_stream_volume(&ifman); + processed = TRUE; + break; + + case ON_CHANNEL_VOLUME: + error = tsmf_ifman_on_channel_volume(&ifman); + processed = TRUE; + break; + + case SET_VIDEO_WINDOW: + error = tsmf_ifman_set_video_window(&ifman); + processed = TRUE; + break; + + case UPDATE_GEOMETRY_INFO: + error = tsmf_ifman_update_geometry_info(&ifman); + processed = TRUE; + break; + + case SET_ALLOCATOR: + error = tsmf_ifman_set_allocator(&ifman); + processed = TRUE; + break; + + case NOTIFY_PREROLL: + error = tsmf_ifman_notify_preroll(&ifman); + processed = TRUE; + break; + + case ON_SAMPLE: + error = tsmf_ifman_on_sample(&ifman); + processed = TRUE; + break; + + case ON_FLUSH: + error = tsmf_ifman_on_flush(&ifman); + processed = TRUE; + break; + + case ON_END_OF_STREAM: + error = tsmf_ifman_on_end_of_stream(&ifman); + processed = TRUE; + break; + + case ON_PLAYBACK_STARTED: + error = tsmf_ifman_on_playback_started(&ifman); + processed = TRUE; + break; + + case ON_PLAYBACK_PAUSED: + error = tsmf_ifman_on_playback_paused(&ifman); + processed = TRUE; + break; + + case ON_PLAYBACK_RESTARTED: + error = tsmf_ifman_on_playback_restarted(&ifman); + processed = TRUE; + break; + + case ON_PLAYBACK_STOPPED: + error = tsmf_ifman_on_playback_stopped(&ifman); + processed = TRUE; + break; + + case ON_PLAYBACK_RATE_CHANGED: + error = tsmf_ifman_on_playback_rate_changed(&ifman); + processed = TRUE; + break; + + case RIMCALL_RELEASE: + case RIMCALL_QUERYINTERFACE: + break; + + default: + break; + } + + break; + + default: + break; + } + + input = nullptr; + ifman.input = nullptr; + + if (error) + { + WLog_ERR(TAG, "ifman data received processing error %" PRIu32 "", error); + } + + if (!processed) + { + switch (FunctionId) + { + case RIMCALL_RELEASE: + /* [MS-RDPEXPS] 2.2.2.2 Interface Release (IFACE_RELEASE) + This message does not require a reply. */ + processed = TRUE; + ifman.output_pending = 1; + break; + + case RIMCALL_QUERYINTERFACE: + /* [MS-RDPEXPS] 2.2.2.1.2 Query Interface Response (QI_RSP) + This message is not supported in this channel. */ + processed = TRUE; + break; + default: + break; + } + + if (!processed) + { + WLog_ERR(TAG, + "Unknown InterfaceId: 0x%08" PRIX32 " MessageId: 0x%08" PRIX32 + " FunctionId: 0x%08" PRIX32 "\n", + InterfaceId, MessageId, FunctionId); + /* When a request is not implemented we return empty response indicating error */ + } + + processed = TRUE; + } + + if (processed && !ifman.output_pending) + { + /* Response packet does not have FunctionId */ + const size_t length = Stream_GetPosition(output); + if (length > UINT32_MAX) + goto out; + Stream_ResetPosition(output); + Stream_Write_UINT32(output, ifman.output_interface_id); + Stream_Write_UINT32(output, MessageId); + DEBUG_TSMF("response size %d", length); + error = callback->channel->Write(callback->channel, (UINT32)length, Stream_Buffer(output), + nullptr); + + if (error) + { + WLog_ERR(TAG, "response error %" PRIu32 "", error); + } + } + +out: + Stream_Free(output, TRUE); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT tsmf_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + TSMF_STREAM* stream = nullptr; + TSMF_PRESENTATION* presentation = nullptr; + TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*)pChannelCallback; + DEBUG_TSMF(""); + + if (callback->stream_id) + { + presentation = tsmf_presentation_find_by_id(callback->presentation_id); + + if (presentation) + { + stream = tsmf_stream_find_by_id(presentation, callback->stream_id); + + if (stream) + tsmf_stream_free(stream); + } + } + + free(pChannelCallback); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT tsmf_on_new_channel_connection(IWTSListenerCallback* pListenerCallback, + IWTSVirtualChannel* pChannel, + WINPR_ATTR_UNUSED BYTE* Data, + WINPR_ATTR_UNUSED BOOL* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +{ + TSMF_CHANNEL_CALLBACK* callback = nullptr; + TSMF_LISTENER_CALLBACK* listener_callback = (TSMF_LISTENER_CALLBACK*)pListenerCallback; + DEBUG_TSMF(""); + callback = (TSMF_CHANNEL_CALLBACK*)calloc(1, sizeof(TSMF_CHANNEL_CALLBACK)); + + if (!callback) + return CHANNEL_RC_NO_MEMORY; + + callback->iface.OnDataReceived = tsmf_on_data_received; + callback->iface.OnClose = tsmf_on_close; + callback->iface.OnOpen = nullptr; + callback->plugin = listener_callback->plugin; + callback->channel_mgr = listener_callback->channel_mgr; + callback->channel = pChannel; + *ppCallback = (IWTSVirtualChannelCallback*)callback; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT tsmf_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr) +{ + UINT status = 0; + TSMF_PLUGIN* tsmf = (TSMF_PLUGIN*)pPlugin; + DEBUG_TSMF(""); + tsmf->listener_callback = (TSMF_LISTENER_CALLBACK*)calloc(1, sizeof(TSMF_LISTENER_CALLBACK)); + + if (!tsmf->listener_callback) + return CHANNEL_RC_NO_MEMORY; + + tsmf->listener_callback->iface.OnNewChannelConnection = tsmf_on_new_channel_connection; + tsmf->listener_callback->plugin = pPlugin; + tsmf->listener_callback->channel_mgr = pChannelMgr; + status = pChannelMgr->CreateListener( + pChannelMgr, "TSMF", 0, (IWTSListenerCallback*)tsmf->listener_callback, &(tsmf->listener)); + tsmf->listener->pInterface = tsmf->iface.pInterface; + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT tsmf_plugin_terminated(IWTSPlugin* pPlugin) +{ + TSMF_PLUGIN* tsmf = (TSMF_PLUGIN*)pPlugin; + DEBUG_TSMF(""); + free(tsmf->listener_callback); + free(tsmf); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT tsmf_process_addin_args(IWTSPlugin* pPlugin, const ADDIN_ARGV* args) +{ + int status = 0; + DWORD flags = 0; + const COMMAND_LINE_ARGUMENT_A* arg = nullptr; + TSMF_PLUGIN* tsmf = (TSMF_PLUGIN*)pPlugin; + COMMAND_LINE_ARGUMENT_A tsmf_args[] = { { "sys", COMMAND_LINE_VALUE_REQUIRED, "", + nullptr, nullptr, -1, nullptr, "audio subsystem" }, + { "dev", COMMAND_LINE_VALUE_REQUIRED, "", + nullptr, nullptr, -1, nullptr, "audio device name" }, + { "decoder", COMMAND_LINE_VALUE_REQUIRED, "", + nullptr, nullptr, -1, nullptr, "decoder subsystem" }, + { nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, + nullptr } }; + flags = COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON; + status = CommandLineParseArgumentsA(args->argc, args->argv, tsmf_args, flags, tsmf, nullptr, + nullptr); + + if (status != 0) + return ERROR_INVALID_DATA; + + arg = tsmf_args; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "sys") + { + tsmf->audio_name = _strdup(arg->Value); + + if (!tsmf->audio_name) + return ERROR_OUTOFMEMORY; + } + CommandLineSwitchCase(arg, "dev") + { + tsmf->audio_device = _strdup(arg->Value); + + if (!tsmf->audio_device) + return ERROR_OUTOFMEMORY; + } + CommandLineSwitchCase(arg, "decoder") + { + tsmf->decoder_name = _strdup(arg->Value); + + if (!tsmf->decoder_name) + return ERROR_OUTOFMEMORY; + } + CommandLineSwitchDefault(arg) + { + } + CommandLineSwitchEnd(arg) + } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE tsmf_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)) +{ + UINT status = 0; + TSMF_PLUGIN* tsmf = nullptr; + TsmfClientContext* context = nullptr; + UINT error = CHANNEL_RC_NO_MEMORY; + tsmf = (TSMF_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints, "tsmf"); + + if (!tsmf) + { + tsmf = (TSMF_PLUGIN*)calloc(1, sizeof(TSMF_PLUGIN)); + + if (!tsmf) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + tsmf->iface.Initialize = tsmf_plugin_initialize; + tsmf->iface.Connected = nullptr; + tsmf->iface.Disconnected = nullptr; + tsmf->iface.Terminated = tsmf_plugin_terminated; + tsmf->rdpcontext = pEntryPoints->GetRdpContext(pEntryPoints); + context = (TsmfClientContext*)calloc(1, sizeof(TsmfClientContext)); + + if (!context) + { + WLog_ERR(TAG, "calloc failed!"); + goto error_context; + } + + context->handle = (void*)tsmf; + tsmf->iface.pInterface = (void*)context; + + if (!tsmf_media_init()) + { + error = ERROR_INVALID_OPERATION; + goto error_init; + } + + status = pEntryPoints->RegisterPlugin(pEntryPoints, "tsmf", &tsmf->iface); + } + + if (status == CHANNEL_RC_OK) + { + status = tsmf_process_addin_args(&tsmf->iface, pEntryPoints->GetPluginData(pEntryPoints)); + } + + return status; +error_init: + free(context); +error_context: + free(tsmf); + return error; +} diff --git a/third_party/FreeRDP/channels/tsmf/client/tsmf_main.h b/third_party/FreeRDP/channels/tsmf/client/tsmf_main.h new file mode 100644 index 0000000..730c8d7 --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/client/tsmf_main.h @@ -0,0 +1,68 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_TSMF_CLIENT_MAIN_H +#define FREERDP_CHANNEL_TSMF_CLIENT_MAIN_H + +#include + +typedef struct +{ + IWTSListenerCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; +} TSMF_LISTENER_CALLBACK; + +typedef struct +{ + IWTSVirtualChannelCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + IWTSVirtualChannel* channel; + + BYTE presentation_id[GUID_SIZE]; + UINT32 stream_id; +} TSMF_CHANNEL_CALLBACK; + +typedef struct +{ + IWTSPlugin iface; + + IWTSListener* listener; + TSMF_LISTENER_CALLBACK* listener_callback; + + const char* decoder_name; + const char* audio_name; + const char* audio_device; + + rdpContext* rdpcontext; +} TSMF_PLUGIN; + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL +tsmf_send_eos_response(IWTSVirtualChannelCallback* pChannelCallback, UINT32 message_id); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL +tsmf_playback_ack(IWTSVirtualChannelCallback* pChannelCallback, UINT32 message_id, UINT64 duration, + UINT32 data_size); + +#endif /* FREERDP_CHANNEL_TSMF_CLIENT_MAIN_H */ diff --git a/third_party/FreeRDP/channels/tsmf/client/tsmf_media.c b/third_party/FreeRDP/channels/tsmf/client/tsmf_media.c new file mode 100644 index 0000000..f28dbbf --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/client/tsmf_media.c @@ -0,0 +1,1547 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Media Container + * + * Copyright 2010-2011 Vic Lee + * Copyright 2012 Hewlett-Packard Development Company, L.P. + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "tsmf_constants.h" +#include "tsmf_types.h" +#include "tsmf_decoder.h" +#include "tsmf_audio.h" +#include "tsmf_main.h" +#include "tsmf_codec.h" +#include "tsmf_media.h" + +#define AUDIO_TOLERANCE 10000000LL + +/* 1 second = 10,000,000 100ns units*/ +#define VIDEO_ADJUST_MAX (10ULL * 1000ULL * 1000ULL) + +#define MAX_ACK_TIME 666667 + +#define AUDIO_MIN_BUFFER_LEVEL 3 +#define AUDIO_MAX_BUFFER_LEVEL 6 + +#define VIDEO_MIN_BUFFER_LEVEL 10 +#define VIDEO_MAX_BUFFER_LEVEL 30 + +struct S_TSMF_PRESENTATION +{ + BYTE presentation_id[GUID_SIZE]; + + const char* audio_name; + const char* audio_device; + + IWTSVirtualChannelCallback* channel_callback; + + UINT64 audio_start_time; + UINT64 audio_end_time; + + UINT32 volume; + UINT32 muted; + + wArrayList* stream_list; + + RECTANGLE_32 rect; + + UINT32 nr_rects; + RECTANGLE_32* rects; +}; + +struct S_TSMF_STREAM +{ + UINT32 stream_id; + + TSMF_PRESENTATION* presentation; + + ITSMFDecoder* decoder; + + int major_type; + int eos; + UINT32 eos_message_id; + IWTSVirtualChannelCallback* eos_channel_callback; + int delayed_stop; + UINT32 width; + UINT32 height; + + ITSMFAudioDevice* audio; + UINT32 sample_rate; + UINT32 channels; + UINT32 bits_per_sample; + + /* The start time of last played sample */ + UINT64 last_start_time; + /* The end_time of last played sample */ + UINT64 last_end_time; + /* Next sample should not start before this system time. */ + UINT64 next_start_time; + + UINT32 minBufferLevel; + UINT32 maxBufferLevel; + UINT32 currentBufferLevel; + + HANDLE play_thread; + HANDLE ack_thread; + HANDLE stopEvent; + HANDLE ready; + + wQueue* sample_list; + wQueue* sample_ack_list; + rdpContext* rdpcontext; + + BOOL seeking; +}; + +struct S_TSMF_SAMPLE +{ + UINT32 sample_id; + UINT64 start_time; + UINT64 end_time; + UINT64 duration; + UINT32 extensions; + UINT32 data_size; + BYTE* data; + UINT32 decoded_size; + UINT32 pixfmt; + + BOOL invalidTimestamps; + + TSMF_STREAM* stream; + IWTSVirtualChannelCallback* channel_callback; + UINT64 ack_time; +}; + +static wArrayList* presentation_list = nullptr; +static int TERMINATING = 0; + +static void s_tsmf_presentation_free(void* obj); +static void s_tsmf_stream_free(void* obj); + +static UINT64 get_current_time(void) +{ + struct timeval tp; + gettimeofday(&tp, 0); + return ((UINT64)tp.tv_sec) * 10000000LL + ((UINT64)tp.tv_usec) * 10LL; +} + +static TSMF_SAMPLE* tsmf_stream_pop_sample(TSMF_STREAM* stream, int sync) +{ + TSMF_SAMPLE* sample = nullptr; + BOOL pending = FALSE; + + if (!stream) + return nullptr; + + TSMF_PRESENTATION* presentation = stream->presentation; + + if (Queue_Count(stream->sample_list) < 1) + return nullptr; + + if (sync) + { + if (stream->decoder) + { + if (stream->decoder->GetDecodedData) + { + if (stream->major_type == TSMF_MAJOR_TYPE_AUDIO) + { + /* Check if some other stream has earlier sample that needs to be played first + */ + /* Start time is more reliable than end time as some stream types seem to have + * incorrect end times from the server + */ + if (stream->last_start_time > AUDIO_TOLERANCE) + { + ArrayList_Lock(presentation->stream_list); + const size_t count = ArrayList_Count(presentation->stream_list); + + for (size_t index = 0; index < count; index++) + { + TSMF_STREAM* s = + (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index); + + /* Start time is more reliable than end time as some stream types seem + * to have incorrect end times from the server + */ + if (s != stream && !s->eos && s->last_start_time && + s->last_start_time < stream->last_start_time - AUDIO_TOLERANCE) + { + DEBUG_TSMF("Pending due to audio tolerance"); + pending = TRUE; + break; + } + } + + ArrayList_Unlock(presentation->stream_list); + } + } + else + { + /* Start time is more reliable than end time as some stream types seem to have + * incorrect end times from the server + */ + if (stream->last_start_time > presentation->audio_start_time) + { + DEBUG_TSMF("Pending due to stream start time > audio start time"); + pending = TRUE; + } + } + } + } + } + + if (pending) + return nullptr; + + sample = (TSMF_SAMPLE*)Queue_Dequeue(stream->sample_list); + + /* Only update stream last end time if the sample end time is valid and greater than the current + * stream end time */ + if (sample && (sample->end_time > stream->last_end_time) && (!sample->invalidTimestamps)) + stream->last_end_time = sample->end_time; + + /* Only update stream last start time if the sample start time is valid and greater than the + * current stream start time */ + if (sample && (sample->start_time > stream->last_start_time) && (!sample->invalidTimestamps)) + stream->last_start_time = sample->start_time; + + return sample; +} + +static void tsmf_sample_free(void* arg) +{ + TSMF_SAMPLE* sample = arg; + + if (!sample) + return; + + free(sample->data); + free(sample); +} + +static BOOL tsmf_sample_ack(TSMF_SAMPLE* sample) +{ + if (!sample) + return FALSE; + + return tsmf_playback_ack(sample->channel_callback, sample->sample_id, sample->duration, + sample->data_size); +} + +static BOOL tsmf_sample_queue_ack(TSMF_SAMPLE* sample) +{ + if (!sample) + return FALSE; + + if (!sample->stream) + return FALSE; + + return Queue_Enqueue(sample->stream->sample_ack_list, sample); +} + +/* Returns TRUE if no more samples are currently available + * Returns FALSE otherwise + */ +static BOOL tsmf_stream_process_ack(void* arg, BOOL force) +{ + TSMF_STREAM* stream = arg; + TSMF_SAMPLE* sample = nullptr; + UINT64 ack_time = 0; + BOOL rc = FALSE; + + if (!stream) + return TRUE; + + Queue_Lock(stream->sample_ack_list); + sample = (TSMF_SAMPLE*)Queue_Peek(stream->sample_ack_list); + + if (!sample) + { + rc = TRUE; + goto finally; + } + + if (!force) + { + /* Do some min/max ack limiting if we have access to Buffer level information */ + if (stream->decoder && stream->decoder->BufferLevel) + { + /* Try to keep buffer level below max by withholding acks */ + if (stream->currentBufferLevel > stream->maxBufferLevel) + goto finally; + /* Try to keep buffer level above min by pushing acks through quickly */ + else if (stream->currentBufferLevel < stream->minBufferLevel) + goto dequeue; + } + + /* Time based acks only */ + ack_time = get_current_time(); + + if (sample->ack_time > ack_time) + goto finally; + } + +dequeue: + sample = Queue_Dequeue(stream->sample_ack_list); + + if (sample) + { + tsmf_sample_ack(sample); + tsmf_sample_free(sample); + } + +finally: + Queue_Unlock(stream->sample_ack_list); + return rc; +} + +TSMF_PRESENTATION* tsmf_presentation_new(const BYTE* guid, + IWTSVirtualChannelCallback* pChannelCallback) +{ + wObject* obj = nullptr; + TSMF_PRESENTATION* presentation = nullptr; + + if (!guid || !pChannelCallback) + return nullptr; + + presentation = (TSMF_PRESENTATION*)calloc(1, sizeof(TSMF_PRESENTATION)); + + if (!presentation) + { + WLog_ERR(TAG, "calloc failed"); + return nullptr; + } + + CopyMemory(presentation->presentation_id, guid, GUID_SIZE); + presentation->channel_callback = pChannelCallback; + presentation->volume = 5000; /* 50% */ + presentation->muted = 0; + + if (!(presentation->stream_list = ArrayList_New(TRUE))) + goto error_stream_list; + + obj = ArrayList_Object(presentation->stream_list); + if (!obj) + goto error_add; + obj->fnObjectFree = s_tsmf_stream_free; + + if (!ArrayList_Append(presentation_list, presentation)) + goto error_add; + + return presentation; +error_add: + ArrayList_Free(presentation->stream_list); +error_stream_list: + free(presentation); + return nullptr; +} + +static char* guid_to_string(const BYTE* guid, char* str, size_t len) +{ + if (!guid || !str) + return nullptr; + + for (size_t i = 0; i < GUID_SIZE && (len > 2 * i); i++) + (void)sprintf_s(str + (2 * i), len - 2 * i, "%02" PRIX8 "", guid[i]); + + return str; +} + +TSMF_PRESENTATION* tsmf_presentation_find_by_id(const BYTE* guid) +{ + BOOL found = FALSE; + char guid_str[GUID_SIZE * 2ull + 1] = WINPR_C_ARRAY_INIT; + TSMF_PRESENTATION* presentation = nullptr; + ArrayList_Lock(presentation_list); + const size_t count = ArrayList_Count(presentation_list); + + for (size_t index = 0; index < count; index++) + { + presentation = (TSMF_PRESENTATION*)ArrayList_GetItem(presentation_list, index); + + if (memcmp(presentation->presentation_id, guid, GUID_SIZE) == 0) + { + found = TRUE; + break; + } + } + + ArrayList_Unlock(presentation_list); + + if (!found) + WLog_WARN(TAG, "presentation id %s not found", + guid_to_string(guid, guid_str, sizeof(guid_str))); + + return (found) ? presentation : nullptr; +} + +static BOOL tsmf_sample_playback_video(TSMF_SAMPLE* sample) +{ + WINPR_ASSERT(sample); + + TSMF_STREAM* stream = sample->stream; + TSMF_PRESENTATION* presentation = stream->presentation; + TSMF_CHANNEL_CALLBACK* callback = (TSMF_CHANNEL_CALLBACK*)sample->channel_callback; + TsmfClientContext* tsmf = (TsmfClientContext*)callback->plugin->pInterface; + DEBUG_TSMF("MessageId %" PRIu32 " EndTime %" PRIu64 " data_size %" PRIu32 " consumed.", + sample->sample_id, sample->end_time, sample->data_size); + + if (sample->data) + { + const UINT64 t = get_current_time(); + + /* Start time is more reliable than end time as some stream types seem to have incorrect + * end times from the server + */ + if (stream->next_start_time > t && + ((sample->start_time >= presentation->audio_start_time) || + ((sample->start_time < stream->last_start_time) && (!sample->invalidTimestamps)))) + { + size_t delay = (stream->next_start_time - t) / 10; + while (delay > 0) + { + const UINT32 d = (delay > UINT32_MAX) ? UINT32_MAX : (UINT32)delay; + USleep(d); + delay -= d; + } + } + + if (sample->stream->width > INT16_MAX) + return FALSE; + if (sample->stream->height > INT16_MAX) + return FALSE; + if (presentation->rect.left > INT16_MAX) + return FALSE; + if (presentation->rect.top > INT16_MAX) + return FALSE; + if (presentation->rect.width > INT16_MAX) + return FALSE; + if (presentation->rect.height > INT16_MAX) + return FALSE; + if (presentation->nr_rects > UINT16_MAX) + return FALSE; + + stream->next_start_time = t + sample->duration - 50000; + + TSMF_VIDEO_FRAME_EVENT event = WINPR_C_ARRAY_INIT; + event.frameData = sample->data; + event.frameSize = sample->decoded_size; + event.framePixFmt = sample->pixfmt; + event.frameWidth = (INT16)sample->stream->width; + event.frameHeight = (INT16)sample->stream->height; + event.x = (INT16)presentation->rect.left; + event.y = (INT16)presentation->rect.top; + event.width = (INT16)presentation->rect.width; + event.height = (INT16)presentation->rect.height; + + if (presentation->nr_rects > 0) + { + event.numVisibleRects = (UINT16)presentation->nr_rects; + event.visibleRects = (RECTANGLE_16*)calloc(event.numVisibleRects, sizeof(RECTANGLE_16)); + + if (!event.visibleRects) + { + WLog_ERR(TAG, "can't allocate memory for copy rectangles"); + return FALSE; + } + + for (size_t x = 0; x < presentation->nr_rects; x++) + { + const RECTANGLE_32* cur = &presentation->rects[x]; + RECTANGLE_16* dst = &event.visibleRects[x]; + if ((cur->left > UINT16_MAX) || (cur->top > UINT16_MAX) || + (cur->width > UINT16_MAX) || (cur->height > UINT16_MAX)) + { + free(event.visibleRects); + return FALSE; + } + dst->right = dst->left = (UINT16)cur->left; + dst->bottom = dst->top = (UINT16)cur->top; + dst->right += (UINT16)cur->width; + dst->bottom += (UINT16)cur->height; + } + memcpy(event.visibleRects, presentation->rects, + presentation->nr_rects * sizeof(RECTANGLE_16)); + presentation->nr_rects = 0; + } + + /* The frame data ownership is passed to the event object, and is freed after the event is + * processed. */ + sample->data = nullptr; + sample->decoded_size = 0; + + if (tsmf->FrameEvent) + tsmf->FrameEvent(tsmf, &event); + + free(event.frameData); + free(event.visibleRects); + } + + return TRUE; +} + +static BOOL tsmf_sample_playback_audio(TSMF_SAMPLE* sample) +{ + UINT64 latency = 0; + TSMF_STREAM* stream = sample->stream; + BOOL ret = 0; + DEBUG_TSMF("MessageId %" PRIu32 " EndTime %" PRIu64 " consumed.", sample->sample_id, + sample->end_time); + + if (stream->audio && sample->data) + { + ret = + sample->stream->audio->Play(sample->stream->audio, sample->data, sample->decoded_size); + free(sample->data); + sample->data = nullptr; + sample->decoded_size = 0; + + if (stream->audio->GetLatency) + latency = stream->audio->GetLatency(stream->audio); + } + else + { + ret = TRUE; + latency = 0; + } + + sample->ack_time = latency + get_current_time(); + + /* Only update stream times if the sample timestamps are valid */ + if (!sample->invalidTimestamps) + { + stream->last_start_time = sample->start_time + latency; + stream->last_end_time = sample->end_time + latency; + stream->presentation->audio_start_time = sample->start_time + latency; + stream->presentation->audio_end_time = sample->end_time + latency; + } + + return ret; +} + +static BOOL tsmf_sample_playback(TSMF_SAMPLE* sample) +{ + BOOL ret = FALSE; + UINT32 width = 0; + UINT32 height = 0; + UINT32 pixfmt = 0; + TSMF_STREAM* stream = sample->stream; + + if (stream->decoder) + { + if (stream->decoder->DecodeEx) + { + /* Try to "sync" video buffers to audio buffers by looking at the running time for each + * stream The difference between the two running times causes an offset between audio + * and video actual render times. So, we try to adjust timestamps on the video buffer to + * match those on the audio buffer. + */ + if (stream->major_type == TSMF_MAJOR_TYPE_VIDEO) + { + TSMF_STREAM* temp_stream = nullptr; + TSMF_PRESENTATION* presentation = stream->presentation; + ArrayList_Lock(presentation->stream_list); + const size_t count = ArrayList_Count(presentation->stream_list); + + for (size_t index = 0; index < count; index++) + { + UINT64 time_diff = 0; + temp_stream = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index); + + if (temp_stream->major_type == TSMF_MAJOR_TYPE_AUDIO) + { + UINT64 video_time = stream->decoder->GetRunningTime(stream->decoder); + UINT64 audio_time = + temp_stream->decoder->GetRunningTime(temp_stream->decoder); + UINT64 max_adjust = VIDEO_ADJUST_MAX; + + if (video_time < audio_time) + max_adjust = -VIDEO_ADJUST_MAX; + + if (video_time > audio_time) + time_diff = video_time - audio_time; + else + time_diff = audio_time - video_time; + + time_diff = time_diff < VIDEO_ADJUST_MAX ? time_diff : max_adjust; + sample->start_time += time_diff; + sample->end_time += time_diff; + break; + } + } + + ArrayList_Unlock(presentation->stream_list); + } + + ret = stream->decoder->DecodeEx(stream->decoder, sample->data, sample->data_size, + sample->extensions, sample->start_time, + sample->end_time, sample->duration); + } + else + { + ret = stream->decoder->Decode(stream->decoder, sample->data, sample->data_size, + sample->extensions); + } + } + + if (!ret) + { + WLog_ERR(TAG, "decode error, queue ack anyways"); + + if (!tsmf_sample_queue_ack(sample)) + { + WLog_ERR(TAG, "error queuing sample for ack"); + return FALSE; + } + + return TRUE; + } + + free(sample->data); + sample->data = nullptr; + + if (stream->major_type == TSMF_MAJOR_TYPE_VIDEO) + { + if (stream->decoder->GetDecodedFormat) + { + pixfmt = stream->decoder->GetDecodedFormat(stream->decoder); + + if (pixfmt == ((UINT32)-1)) + { + WLog_ERR(TAG, "unable to decode video format"); + + if (!tsmf_sample_queue_ack(sample)) + { + WLog_ERR(TAG, "error queuing sample for ack"); + } + + return FALSE; + } + + sample->pixfmt = pixfmt; + } + + if (stream->decoder->GetDecodedDimension) + { + ret = stream->decoder->GetDecodedDimension(stream->decoder, &width, &height); + + if (ret && (width != stream->width || height != stream->height)) + { + DEBUG_TSMF("video dimension changed to %" PRIu32 " x %" PRIu32 "", width, height); + stream->width = width; + stream->height = height; + } + } + } + + if (stream->decoder->GetDecodedData) + { + sample->data = stream->decoder->GetDecodedData(stream->decoder, &sample->decoded_size); + + switch (sample->stream->major_type) + { + case TSMF_MAJOR_TYPE_VIDEO: + ret = tsmf_sample_playback_video(sample) && tsmf_sample_queue_ack(sample); + break; + + case TSMF_MAJOR_TYPE_AUDIO: + ret = tsmf_sample_playback_audio(sample) && tsmf_sample_queue_ack(sample); + break; + default: + break; + } + } + else + { + UINT64 ack_anticipation_time = get_current_time(); + + ack_anticipation_time += + (sample->duration / 2 < MAX_ACK_TIME) ? sample->duration / 2 : MAX_ACK_TIME; + + switch (sample->stream->major_type) + { + case TSMF_MAJOR_TYPE_VIDEO: + { + break; + } + + case TSMF_MAJOR_TYPE_AUDIO: + { + break; + } + default: + break; + } + + sample->ack_time = ack_anticipation_time; + + if (!tsmf_sample_queue_ack(sample)) + { + WLog_ERR(TAG, "error queuing sample for ack"); + ret = FALSE; + } + } + + return ret; +} + +static DWORD WINAPI tsmf_stream_ack_func(LPVOID arg) +{ + HANDLE hdl[2]; + TSMF_STREAM* stream = (TSMF_STREAM*)arg; + UINT error = CHANNEL_RC_OK; + DEBUG_TSMF("in %" PRIu32 "", stream->stream_id); + hdl[0] = stream->stopEvent; + hdl[1] = Queue_Event(stream->sample_ack_list); + + while (1) + { + DWORD ev = WaitForMultipleObjects(2, hdl, FALSE, 1000); + + if (ev == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "!", error); + break; + } + + if (stream->decoder) + if (stream->decoder->BufferLevel) + stream->currentBufferLevel = stream->decoder->BufferLevel(stream->decoder); + + if (stream->eos) + { + while ((stream->currentBufferLevel > 0) && !(tsmf_stream_process_ack(stream, TRUE))) + { + DEBUG_TSMF("END OF STREAM PROCESSING!"); + + if (stream->decoder && stream->decoder->BufferLevel) + stream->currentBufferLevel = stream->decoder->BufferLevel(stream->decoder); + else + stream->currentBufferLevel = 1; + + USleep(1000); + } + + tsmf_send_eos_response(stream->eos_channel_callback, stream->eos_message_id); + stream->eos = 0; + + if (stream->delayed_stop) + { + DEBUG_TSMF("Finishing delayed stream stop, now that eos has processed."); + tsmf_stream_flush(stream); + + if (stream->decoder && stream->decoder->Control) + stream->decoder->Control(stream->decoder, Control_Stop, nullptr); + } + } + + /* Stream stopped force all of the acks to happen */ + if (ev == WAIT_OBJECT_0) + { + DEBUG_TSMF("ack: Stream stopped!"); + + while (1) + { + if (tsmf_stream_process_ack(stream, TRUE)) + break; + + USleep(1000); + } + + break; + } + + if (tsmf_stream_process_ack(stream, FALSE)) + continue; + + if (stream->currentBufferLevel > stream->minBufferLevel) + USleep(1000); + } + + if (error && stream->rdpcontext) + setChannelError(stream->rdpcontext, error, "tsmf_stream_ack_func reported an error"); + + DEBUG_TSMF("out %" PRIu32 "", stream->stream_id); + ExitThread(error); + return error; +} + +static DWORD WINAPI tsmf_stream_playback_func(LPVOID arg) +{ + HANDLE hdl[2]; + TSMF_SAMPLE* sample = nullptr; + TSMF_STREAM* stream = (TSMF_STREAM*)arg; + TSMF_PRESENTATION* presentation = stream->presentation; + UINT error = CHANNEL_RC_OK; + DWORD status = 0; + DEBUG_TSMF("in %" PRIu32 "", stream->stream_id); + + if (stream->major_type == TSMF_MAJOR_TYPE_AUDIO && stream->sample_rate && stream->channels && + stream->bits_per_sample) + { + if (stream->decoder) + { + if (stream->decoder->GetDecodedData) + { + stream->audio = tsmf_load_audio_device( + presentation->audio_name && presentation->audio_name[0] + ? presentation->audio_name + : nullptr, + presentation->audio_device && presentation->audio_device[0] + ? presentation->audio_device + : nullptr); + + if (stream->audio) + { + stream->audio->SetFormat(stream->audio, stream->sample_rate, stream->channels, + stream->bits_per_sample); + } + } + } + } + + hdl[0] = stream->stopEvent; + hdl[1] = Queue_Event(stream->sample_list); + + while (1) + { + status = WaitForMultipleObjects(2, hdl, FALSE, 1000); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "!", error); + break; + } + + status = WaitForSingleObject(stream->stopEvent, 0); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error); + break; + } + + if (status == WAIT_OBJECT_0) + break; + + if (stream->decoder) + if (stream->decoder->BufferLevel) + stream->currentBufferLevel = stream->decoder->BufferLevel(stream->decoder); + + sample = tsmf_stream_pop_sample(stream, 0); + + if (sample && !tsmf_sample_playback(sample)) + { + WLog_ERR(TAG, "error playing sample"); + error = ERROR_INTERNAL_ERROR; + break; + } + + if (stream->currentBufferLevel > stream->minBufferLevel) + USleep(1000); + } + + if (stream->audio) + { + stream->audio->Free(stream->audio); + stream->audio = nullptr; + } + + if (error && stream->rdpcontext) + setChannelError(stream->rdpcontext, error, "tsmf_stream_playback_func reported an error"); + + DEBUG_TSMF("out %" PRIu32 "", stream->stream_id); + ExitThread(error); + return error; +} + +static BOOL tsmf_stream_start(TSMF_STREAM* stream) +{ + if (!stream || !stream->presentation || !stream->decoder || !stream->decoder->Control) + return TRUE; + + stream->eos = 0; + return stream->decoder->Control(stream->decoder, Control_Restart, nullptr); +} + +static BOOL tsmf_stream_stop(TSMF_STREAM* stream) +{ + if (!stream || !stream->decoder || !stream->decoder->Control) + return TRUE; + + /* If stopping after eos - we delay until the eos has been processed + * this allows us to process any buffers that have been acked even though + * they have not actually been completely processes by the decoder + */ + if (stream->eos) + { + DEBUG_TSMF("Setting up a delayed stop for once the eos has been processed."); + stream->delayed_stop = 1; + return TRUE; + } + /* Otherwise force stop immediately */ + else + { + DEBUG_TSMF("Stop with no pending eos response, so do it immediately."); + tsmf_stream_flush(stream); + return stream->decoder->Control(stream->decoder, Control_Stop, nullptr); + } +} + +static BOOL tsmf_stream_pause(TSMF_STREAM* stream) +{ + if (!stream || !stream->decoder || !stream->decoder->Control) + return TRUE; + + return stream->decoder->Control(stream->decoder, Control_Pause, nullptr); +} + +static BOOL tsmf_stream_restart(TSMF_STREAM* stream) +{ + if (!stream || !stream->decoder || !stream->decoder->Control) + return TRUE; + + stream->eos = 0; + return stream->decoder->Control(stream->decoder, Control_Restart, nullptr); +} + +static BOOL tsmf_stream_change_volume(TSMF_STREAM* stream, UINT32 newVolume, UINT32 muted) +{ + if (!stream || !stream->decoder) + return TRUE; + + if (stream->decoder != nullptr && stream->decoder->ChangeVolume) + { + return stream->decoder->ChangeVolume(stream->decoder, newVolume, muted); + } + else if (stream->audio != nullptr && stream->audio->ChangeVolume) + { + return stream->audio->ChangeVolume(stream->audio, newVolume, muted); + } + + return TRUE; +} + +BOOL tsmf_presentation_volume_changed(TSMF_PRESENTATION* presentation, UINT32 newVolume, + UINT32 muted) +{ + TSMF_STREAM* stream = nullptr; + BOOL ret = TRUE; + presentation->volume = newVolume; + presentation->muted = muted; + ArrayList_Lock(presentation->stream_list); + size_t count = ArrayList_Count(presentation->stream_list); + + for (size_t index = 0; index < count; index++) + { + stream = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index); + ret &= tsmf_stream_change_volume(stream, newVolume, muted); + } + + ArrayList_Unlock(presentation->stream_list); + return ret; +} + +BOOL tsmf_presentation_paused(TSMF_PRESENTATION* presentation) +{ + TSMF_STREAM* stream = nullptr; + BOOL ret = TRUE; + ArrayList_Lock(presentation->stream_list); + size_t count = ArrayList_Count(presentation->stream_list); + + for (size_t index = 0; index < count; index++) + { + stream = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index); + ret &= tsmf_stream_pause(stream); + } + + ArrayList_Unlock(presentation->stream_list); + return ret; +} + +BOOL tsmf_presentation_restarted(TSMF_PRESENTATION* presentation) +{ + TSMF_STREAM* stream = nullptr; + BOOL ret = TRUE; + ArrayList_Lock(presentation->stream_list); + size_t count = ArrayList_Count(presentation->stream_list); + + for (size_t index = 0; index < count; index++) + { + stream = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index); + ret &= tsmf_stream_restart(stream); + } + + ArrayList_Unlock(presentation->stream_list); + return ret; +} + +BOOL tsmf_presentation_start(TSMF_PRESENTATION* presentation) +{ + TSMF_STREAM* stream = nullptr; + BOOL ret = TRUE; + ArrayList_Lock(presentation->stream_list); + size_t count = ArrayList_Count(presentation->stream_list); + + for (size_t index = 0; index < count; index++) + { + stream = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index); + ret &= tsmf_stream_start(stream); + } + + ArrayList_Unlock(presentation->stream_list); + return ret; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT tsmf_presentation_sync(TSMF_PRESENTATION* presentation) +{ + UINT error = 0; + ArrayList_Lock(presentation->stream_list); + size_t count = ArrayList_Count(presentation->stream_list); + + for (size_t index = 0; index < count; index++) + { + TSMF_STREAM* stream = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index); + + if (WaitForSingleObject(stream->ready, 500) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error); + return error; + } + } + + ArrayList_Unlock(presentation->stream_list); + return CHANNEL_RC_OK; +} + +BOOL tsmf_presentation_stop(TSMF_PRESENTATION* presentation) +{ + TSMF_STREAM* stream = nullptr; + BOOL ret = TRUE; + ArrayList_Lock(presentation->stream_list); + size_t count = ArrayList_Count(presentation->stream_list); + + for (size_t index = 0; index < count; index++) + { + stream = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index); + ret &= tsmf_stream_stop(stream); + } + + ArrayList_Unlock(presentation->stream_list); + presentation->audio_start_time = 0; + presentation->audio_end_time = 0; + return ret; +} + +BOOL tsmf_presentation_set_geometry_info(TSMF_PRESENTATION* presentation, UINT32 x, UINT32 y, + UINT32 width, UINT32 height, UINT32 num_rects, + const RECTANGLE_32* rects) +{ + TSMF_STREAM* stream = nullptr; + BOOL ret = TRUE; + + /* The server may send messages with invalid width / height. + * Ignore those messages. */ + if (!width || !height) + return TRUE; + + /* Streams can be added/removed from the presentation and the server will resend geometry info + * when a new stream is added to the presentation. Also, num_rects is used to indicate whether + * or not the window is visible. So, always process a valid message with unchanged position/size + * and/or no visibility rects. + */ + presentation->rect.left = x; + presentation->rect.top = y; + presentation->rect.width = width; + presentation->rect.height = height; + void* tmp_rects = realloc(presentation->rects, sizeof(RECTANGLE_32) * num_rects); + + if (!tmp_rects && num_rects) + return FALSE; + + presentation->nr_rects = num_rects; + presentation->rects = (RECTANGLE_32*)tmp_rects; + if (presentation->rects) + CopyMemory(presentation->rects, rects, sizeof(RECTANGLE_32) * num_rects); + ArrayList_Lock(presentation->stream_list); + size_t count = ArrayList_Count(presentation->stream_list); + + for (size_t index = 0; index < count; index++) + { + stream = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index); + + if (!stream->decoder) + continue; + + if (stream->decoder->UpdateRenderingArea) + { + ret = stream->decoder->UpdateRenderingArea(stream->decoder, x, y, width, height, + num_rects, rects); + } + } + + ArrayList_Unlock(presentation->stream_list); + return ret; +} + +void tsmf_presentation_set_audio_device(TSMF_PRESENTATION* presentation, const char* name, + const char* device) +{ + presentation->audio_name = name; + presentation->audio_device = device; +} + +BOOL tsmf_stream_flush(TSMF_STREAM* stream) +{ + BOOL ret = TRUE; + + // TSMF_SAMPLE* sample; + /* TODO: free lists */ + if (stream->audio) + ret = stream->audio->Flush(stream->audio); + + stream->eos = 0; + stream->eos_message_id = 0; + stream->eos_channel_callback = nullptr; + stream->delayed_stop = 0; + stream->last_end_time = 0; + stream->next_start_time = 0; + + if (stream->major_type == TSMF_MAJOR_TYPE_AUDIO) + { + stream->presentation->audio_start_time = 0; + stream->presentation->audio_end_time = 0; + } + + return ret; +} + +void s_tsmf_presentation_free(void* obj) +{ + TSMF_PRESENTATION* presentation = (TSMF_PRESENTATION*)obj; + + if (presentation) + { + tsmf_presentation_stop(presentation); + ArrayList_Clear(presentation->stream_list); + ArrayList_Free(presentation->stream_list); + free(presentation->rects); + ZeroMemory(presentation, sizeof(TSMF_PRESENTATION)); + free(presentation); + } +} + +void tsmf_presentation_free(TSMF_PRESENTATION* presentation) +{ + ArrayList_Remove(presentation_list, presentation); +} + +TSMF_STREAM* tsmf_stream_new(TSMF_PRESENTATION* presentation, UINT32 stream_id, + rdpContext* rdpcontext) +{ + wObject* obj = nullptr; + TSMF_STREAM* stream = nullptr; + stream = tsmf_stream_find_by_id(presentation, stream_id); + + if (stream) + { + WLog_ERR(TAG, "duplicated stream id %" PRIu32 "!", stream_id); + return nullptr; + } + + stream = (TSMF_STREAM*)calloc(1, sizeof(TSMF_STREAM)); + + if (!stream) + { + WLog_ERR(TAG, "Calloc failed"); + return nullptr; + } + + stream->minBufferLevel = VIDEO_MIN_BUFFER_LEVEL; + stream->maxBufferLevel = VIDEO_MAX_BUFFER_LEVEL; + stream->currentBufferLevel = 1; + stream->seeking = FALSE; + stream->eos = 0; + stream->eos_message_id = 0; + stream->eos_channel_callback = nullptr; + stream->stream_id = stream_id; + stream->presentation = presentation; + stream->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + + if (!stream->stopEvent) + goto error_stopEvent; + + stream->ready = CreateEvent(nullptr, TRUE, TRUE, nullptr); + + if (!stream->ready) + goto error_ready; + + stream->sample_list = Queue_New(TRUE, -1, -1); + + if (!stream->sample_list) + goto error_sample_list; + + obj = Queue_Object(stream->sample_list); + if (!obj) + goto error_sample_ack_list; + obj->fnObjectFree = tsmf_sample_free; + + stream->sample_ack_list = Queue_New(TRUE, -1, -1); + + if (!stream->sample_ack_list) + goto error_sample_ack_list; + + obj = Queue_Object(stream->sample_ack_list); + if (!obj) + goto error_play_thread; + obj->fnObjectFree = tsmf_sample_free; + + stream->play_thread = + CreateThread(nullptr, 0, tsmf_stream_playback_func, stream, CREATE_SUSPENDED, nullptr); + + if (!stream->play_thread) + goto error_play_thread; + + stream->ack_thread = + CreateThread(nullptr, 0, tsmf_stream_ack_func, stream, CREATE_SUSPENDED, nullptr); + + if (!stream->ack_thread) + goto error_ack_thread; + + if (!ArrayList_Append(presentation->stream_list, stream)) + goto error_add; + + stream->rdpcontext = rdpcontext; + return stream; +error_add: + (void)SetEvent(stream->stopEvent); + + if (WaitForSingleObject(stream->ack_thread, INFINITE) == WAIT_FAILED) + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", GetLastError()); + +error_ack_thread: + (void)SetEvent(stream->stopEvent); + + if (WaitForSingleObject(stream->play_thread, INFINITE) == WAIT_FAILED) + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", GetLastError()); + +error_play_thread: + Queue_Free(stream->sample_ack_list); +error_sample_ack_list: + Queue_Free(stream->sample_list); +error_sample_list: + (void)CloseHandle(stream->ready); +error_ready: + (void)CloseHandle(stream->stopEvent); +error_stopEvent: + free(stream); + return nullptr; +} + +void tsmf_stream_start_threads(TSMF_STREAM* stream) +{ + ResumeThread(stream->play_thread); + ResumeThread(stream->ack_thread); +} + +TSMF_STREAM* tsmf_stream_find_by_id(TSMF_PRESENTATION* presentation, UINT32 stream_id) +{ + BOOL found = FALSE; + TSMF_STREAM* stream = nullptr; + ArrayList_Lock(presentation->stream_list); + size_t count = ArrayList_Count(presentation->stream_list); + + for (size_t index = 0; index < count; index++) + { + stream = (TSMF_STREAM*)ArrayList_GetItem(presentation->stream_list, index); + + if (stream->stream_id == stream_id) + { + found = TRUE; + break; + } + } + + ArrayList_Unlock(presentation->stream_list); + return (found) ? stream : nullptr; +} + +static void tsmf_stream_resync(void* arg) +{ + TSMF_STREAM* stream = arg; + (void)ResetEvent(stream->ready); +} + +BOOL tsmf_stream_set_format(TSMF_STREAM* stream, const char* name, wStream* s) +{ + TS_AM_MEDIA_TYPE mediatype; + BOOL ret = TRUE; + + if (stream->decoder) + { + WLog_ERR(TAG, "duplicated call"); + return FALSE; + } + + if (!tsmf_codec_parse_media_type(&mediatype, s)) + { + WLog_ERR(TAG, "unable to parse media type"); + return FALSE; + } + + if (mediatype.MajorType == TSMF_MAJOR_TYPE_VIDEO) + { + DEBUG_TSMF("video width %" PRIu32 " height %" PRIu32 " bit_rate %" PRIu32 + " frame_rate %f codec_data %" PRIu32 "", + mediatype.Width, mediatype.Height, mediatype.BitRate, + (double)mediatype.SamplesPerSecond.Numerator / + (double)mediatype.SamplesPerSecond.Denominator, + mediatype.ExtraDataSize); + stream->minBufferLevel = VIDEO_MIN_BUFFER_LEVEL; + stream->maxBufferLevel = VIDEO_MAX_BUFFER_LEVEL; + } + else if (mediatype.MajorType == TSMF_MAJOR_TYPE_AUDIO) + { + DEBUG_TSMF("audio channel %" PRIu32 " sample_rate %" PRIu32 " bits_per_sample %" PRIu32 + " codec_data %" PRIu32 "", + mediatype.Channels, mediatype.SamplesPerSecond.Numerator, + mediatype.BitsPerSample, mediatype.ExtraDataSize); + stream->sample_rate = mediatype.SamplesPerSecond.Numerator; + stream->channels = mediatype.Channels; + stream->bits_per_sample = mediatype.BitsPerSample; + + if (stream->bits_per_sample == 0) + stream->bits_per_sample = 16; + + stream->minBufferLevel = AUDIO_MIN_BUFFER_LEVEL; + stream->maxBufferLevel = AUDIO_MAX_BUFFER_LEVEL; + } + + stream->major_type = mediatype.MajorType; + stream->width = mediatype.Width; + stream->height = mediatype.Height; + stream->decoder = tsmf_load_decoder(name, &mediatype); + ret &= tsmf_stream_change_volume(stream, stream->presentation->volume, + stream->presentation->muted); + + if (!stream->decoder) + return FALSE; + + if (stream->decoder->SetAckFunc) + ret &= stream->decoder->SetAckFunc(stream->decoder, tsmf_stream_process_ack, stream); + + if (stream->decoder->SetSyncFunc) + ret &= stream->decoder->SetSyncFunc(stream->decoder, tsmf_stream_resync, stream); + + return ret; +} + +void tsmf_stream_end(TSMF_STREAM* stream, UINT32 message_id, + IWTSVirtualChannelCallback* pChannelCallback) +{ + if (!stream) + return; + + stream->eos = 1; + stream->eos_message_id = message_id; + stream->eos_channel_callback = pChannelCallback; +} + +void s_tsmf_stream_free(void* obj) +{ + TSMF_STREAM* stream = (TSMF_STREAM*)obj; + + if (!stream) + return; + + tsmf_stream_stop(stream); + (void)SetEvent(stream->stopEvent); + + if (stream->play_thread) + { + if (WaitForSingleObject(stream->play_thread, INFINITE) == WAIT_FAILED) + { + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", GetLastError()); + return; + } + + (void)CloseHandle(stream->play_thread); + stream->play_thread = nullptr; + } + + if (stream->ack_thread) + { + if (WaitForSingleObject(stream->ack_thread, INFINITE) == WAIT_FAILED) + { + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", GetLastError()); + return; + } + + (void)CloseHandle(stream->ack_thread); + stream->ack_thread = nullptr; + } + + Queue_Free(stream->sample_list); + Queue_Free(stream->sample_ack_list); + + if (stream->decoder && stream->decoder->Free) + { + stream->decoder->Free(stream->decoder); + stream->decoder = nullptr; + } + + (void)CloseHandle(stream->stopEvent); + (void)CloseHandle(stream->ready); + ZeroMemory(stream, sizeof(TSMF_STREAM)); + free(stream); +} + +void tsmf_stream_free(TSMF_STREAM* stream) +{ + TSMF_PRESENTATION* presentation = stream->presentation; + ArrayList_Remove(presentation->stream_list, stream); +} + +BOOL tsmf_stream_push_sample(TSMF_STREAM* stream, IWTSVirtualChannelCallback* pChannelCallback, + UINT32 sample_id, UINT64 start_time, UINT64 end_time, UINT64 duration, + UINT32 extensions, UINT32 data_size, BYTE* data) +{ + (void)SetEvent(stream->ready); + + if (TERMINATING) + return TRUE; + + TSMF_SAMPLE* sample = (TSMF_SAMPLE*)calloc(1, sizeof(TSMF_SAMPLE)); + + if (!sample) + { + WLog_ERR(TAG, "calloc sample failed!"); + return FALSE; + } + + sample->sample_id = sample_id; + sample->start_time = start_time; + sample->end_time = end_time; + sample->duration = duration; + sample->extensions = extensions; + + if ((sample->extensions & 0x00000080) || (sample->extensions & 0x00000040)) + sample->invalidTimestamps = TRUE; + else + sample->invalidTimestamps = FALSE; + + sample->stream = stream; + sample->channel_callback = pChannelCallback; + sample->data_size = data_size; + sample->data = calloc(1, data_size + TSMF_BUFFER_PADDING_SIZE); + + if (!sample->data) + goto fail; + + CopyMemory(sample->data, data, data_size); + if (!Queue_Enqueue(stream->sample_list, sample)) + goto fail; + + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): Queue_Enqueue takes ownership of sample + return TRUE; + +fail: + if (sample) + free(sample->data); + free(sample); + return FALSE; +} + +#ifndef _WIN32 + +static void tsmf_signal_handler(int s) +{ + TERMINATING = 1; + ArrayList_Free(presentation_list); + + if (s == SIGINT) + { + (void)signal(s, SIG_DFL); + kill(getpid(), s); + } + else if (s == SIGUSR1) + { + (void)signal(s, SIG_DFL); + } +} + +#endif + +BOOL tsmf_media_init(void) +{ + wObject* obj = nullptr; +#ifndef _WIN32 + struct sigaction sigtrap; + sigtrap.sa_handler = tsmf_signal_handler; + sigemptyset(&sigtrap.sa_mask); + sigtrap.sa_flags = 0; + sigaction(SIGINT, &sigtrap, 0); + sigaction(SIGUSR1, &sigtrap, 0); +#endif + + if (!presentation_list) + { + presentation_list = ArrayList_New(TRUE); + + if (!presentation_list) + return FALSE; + + obj = ArrayList_Object(presentation_list); + if (!obj) + return FALSE; + obj->fnObjectFree = s_tsmf_presentation_free; + } + + return TRUE; +} diff --git a/third_party/FreeRDP/channels/tsmf/client/tsmf_media.h b/third_party/FreeRDP/channels/tsmf/client/tsmf_media.h new file mode 100644 index 0000000..bd0306f --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/client/tsmf_media.h @@ -0,0 +1,99 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Media Container + * + * Copyright 2010-2011 Vic Lee + * Copyright 2012 Hewlett-Packard Development Company, L.P. + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * 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. + */ + +/** + * The media container maintains a global list of presentations, and a list of + * streams in each presentation. + */ + +#ifndef FREERDP_CHANNEL_TSMF_CLIENT_MEDIA_H +#define FREERDP_CHANNEL_TSMF_CLIENT_MEDIA_H + +#include + +typedef struct S_TSMF_PRESENTATION TSMF_PRESENTATION; + +typedef struct S_TSMF_STREAM TSMF_STREAM; + +typedef struct S_TSMF_SAMPLE TSMF_SAMPLE; + +FREERDP_LOCAL void tsmf_presentation_free(TSMF_PRESENTATION* presentation); + +WINPR_ATTR_MALLOC(tsmf_presentation_free, 1) +WINPR_ATTR_NODISCARD FREERDP_LOCAL TSMF_PRESENTATION* +tsmf_presentation_new(const BYTE* guid, IWTSVirtualChannelCallback* pChannelCallback); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL TSMF_PRESENTATION* +tsmf_presentation_find_by_id(const BYTE* guid); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL tsmf_presentation_start(TSMF_PRESENTATION* presentation); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL tsmf_presentation_stop(TSMF_PRESENTATION* presentation); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL UINT tsmf_presentation_sync(TSMF_PRESENTATION* presentation); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL tsmf_presentation_paused(TSMF_PRESENTATION* presentation); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL +tsmf_presentation_restarted(TSMF_PRESENTATION* presentation); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL +tsmf_presentation_volume_changed(TSMF_PRESENTATION* presentation, UINT32 newVolume, UINT32 muted); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL tsmf_presentation_set_geometry_info( + TSMF_PRESENTATION* presentation, UINT32 x, UINT32 y, UINT32 width, UINT32 height, + UINT32 num_rects, const RECTANGLE_32* rects); + +FREERDP_LOCAL +void tsmf_presentation_set_audio_device(TSMF_PRESENTATION* presentation, const char* name, + const char* device); + +FREERDP_LOCAL +void tsmf_stream_free(TSMF_STREAM* stream); + +WINPR_ATTR_MALLOC(tsmf_stream_free, 1) +WINPR_ATTR_NODISCARD FREERDP_LOCAL TSMF_STREAM* +tsmf_stream_new(TSMF_PRESENTATION* presentation, UINT32 stream_id, rdpContext* rdpcontext); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL TSMF_STREAM* +tsmf_stream_find_by_id(TSMF_PRESENTATION* presentation, UINT32 stream_id); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL tsmf_stream_set_format(TSMF_STREAM* stream, + const char* name, wStream* s); + +FREERDP_LOCAL +void tsmf_stream_end(TSMF_STREAM* stream, UINT32 message_id, + IWTSVirtualChannelCallback* pChannelCallback); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL tsmf_stream_flush(TSMF_STREAM* stream); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL +tsmf_stream_push_sample(TSMF_STREAM* stream, IWTSVirtualChannelCallback* pChannelCallback, + UINT32 sample_id, UINT64 start_time, UINT64 end_time, UINT64 duration, + UINT32 extensions, UINT32 data_size, BYTE* data); + +WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL tsmf_media_init(void); + +FREERDP_LOCAL +void tsmf_stream_start_threads(TSMF_STREAM* stream); + +#endif /* FREERDP_CHANNEL_TSMF_CLIENT_MEDIA_H */ diff --git a/third_party/FreeRDP/channels/tsmf/client/tsmf_types.h b/third_party/FreeRDP/channels/tsmf/client/tsmf_types.h new file mode 100644 index 0000000..708f208 --- /dev/null +++ b/third_party/FreeRDP/channels/tsmf/client/tsmf_types.h @@ -0,0 +1,61 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Redirection Virtual Channel - Types + * + * Copyright 2010-2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_TSMF_CLIENT_TYPES_H +#define FREERDP_CHANNEL_TSMF_CLIENT_TYPES_H + +#include + +#include +#include +#include + +#define TAG CHANNELS_TAG("tsmf.client") + +#ifdef WITH_DEBUG_TSMF +#define DEBUG_TSMF(...) WLog_DBG(TAG, __VA_ARGS__) +#else +#define DEBUG_TSMF(...) \ + do \ + { \ + } while (0) +#endif + +typedef struct +{ + int MajorType; + int SubType; + int FormatType; + + UINT32 Width; + UINT32 Height; + UINT32 BitRate; + struct + { + UINT32 Numerator; + UINT32 Denominator; + } SamplesPerSecond; + UINT32 Channels; + UINT32 BitsPerSample; + UINT32 BlockAlign; + const BYTE* ExtraData; + UINT32 ExtraDataSize; +} TS_AM_MEDIA_TYPE; + +#endif /* FREERDP_CHANNEL_TSMF_CLIENT_TYPES_H */ diff --git a/third_party/FreeRDP/channels/urbdrc/CMakeLists.txt b/third_party/FreeRDP/channels/urbdrc/CMakeLists.txt new file mode 100644 index 0000000..77357a4 --- /dev/null +++ b/third_party/FreeRDP/channels/urbdrc/CMakeLists.txt @@ -0,0 +1,31 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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. + +define_channel("urbdrc") + +include_directories(common) +add_subdirectory(common) + +if(WITH_CLIENT_CHANNELS) + option(WITH_DEBUG_URBDRC "Dump data send/received in URBDRC channel" ${DEFAULT_DEBUG_OPTION}) + + find_package(libusb-1.0 REQUIRED) + freerdp_client_pc_add_requires_private("libusb-1.0") + include_directories(SYSTEM ${LIBUSB_1_INCLUDE_DIRS}) + + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/third_party/FreeRDP/channels/urbdrc/ChannelOptions.cmake b/third_party/FreeRDP/channels/urbdrc/ChannelOptions.cmake new file mode 100644 index 0000000..2612bb1 --- /dev/null +++ b/third_party/FreeRDP/channels/urbdrc/ChannelOptions.cmake @@ -0,0 +1,26 @@ +if(IOS OR ANDROID) + set(OPTION_DEFAULT OFF) + set(OPTION_CLIENT_DEFAULT OFF) + set(OPTION_SERVER_DEFAULT OFF) +else() + set(OPTION_DEFAULT ON) + set(OPTION_CLIENT_DEFAULT ON) + set(OPTION_SERVER_DEFAULT OFF) +endif() + +define_channel_options( + NAME + "urbdrc" + TYPE + "dynamic" + DESCRIPTION + "USB Devices Virtual Channel Extension" + SPECIFICATIONS + "[MS-RDPEUSB]" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/urbdrc/client/CMakeLists.txt b/third_party/FreeRDP/channels/urbdrc/client/CMakeLists.txt new file mode 100644 index 0000000..9f9c8e7 --- /dev/null +++ b/third_party/FreeRDP/channels/urbdrc/client/CMakeLists.txt @@ -0,0 +1,31 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Atrust corp. +# Copyright 2012 Alfred Liu +# +# 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. + +define_channel_client("urbdrc") + +set(${MODULE_PREFIX}_SRCS data_transfer.c data_transfer.h urbdrc_main.c urbdrc_main.h) + +set(${MODULE_PREFIX}_LIBS winpr freerdp urbdrc-common) +if(UDEV_FOUND AND UDEV_LIBRARIES) + list(APPEND ${MODULE_PREFIX}_LIBS ${UDEV_LIBRARIES}) +endif() + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") + +# libusb subsystem +add_channel_client_subsystem(${MODULE_PREFIX} ${CHANNEL_NAME} "libusb" "") diff --git a/third_party/FreeRDP/channels/urbdrc/client/data_transfer.c b/third_party/FreeRDP/channels/urbdrc/client/data_transfer.c new file mode 100644 index 0000000..55eae27 --- /dev/null +++ b/third_party/FreeRDP/channels/urbdrc/client/data_transfer.c @@ -0,0 +1,2051 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX USB Redirection + * + * Copyright 2012 Atrust corp. + * Copyright 2012 Alfred Liu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include +#include + +#include + +#include "urbdrc_types.h" +#include "data_transfer.h" +#include "msusb.h" + +static void usb_process_get_port_status(IUDEVICE* pdev, wStream* out) +{ + int bcdUSB = pdev->query_device_descriptor(pdev, BCD_USB); + + switch (bcdUSB) + { + case USB_v1_0: + Stream_Write_UINT32(out, 0x303); + break; + + case USB_v1_1: + Stream_Write_UINT32(out, 0x103); + break; + + case USB_v2_0: + default: + Stream_Write_UINT32(out, 0x503); + break; + } +} + +/* [MS-RDPEUSB] 2.2.10.1.1TS_URB_RESULT_HEADER */ +static BOOL write_urb_result_header(wStream* s, UINT16 Size, UINT32 status) +{ + if (!Stream_EnsureRemainingCapacity(s, 8ULL + Size)) + return FALSE; + Stream_Write_UINT16(s, Size); + Stream_Seek_UINT16(s); + Stream_Write_UINT32(s, status); + return TRUE; +} + +/* [MS-RDPEUSB] 2.2.7.2 URB Completion (URB_COMPLETION) + * 2.2.7.3 URB Completion No Data (URB_COMPLETION_NO_DATA) + */ +static wStream* create_urb_completion_message(UINT32 InterfaceId, UINT32 MessageId, + UINT32 RequestId, UINT32 FunctionId) +{ + wStream* out = + create_shared_message_header_with_functionid(InterfaceId, MessageId, FunctionId, 4); + if (!out) + return nullptr; + + Stream_Write_UINT32(out, RequestId); + return out; +} + +static UINT send_urb_completion_message(GENERIC_CHANNEL_CALLBACK* callback, wStream* out, + HRESULT hResult, UINT32 OutputSize, const void* data) +{ + WINPR_ASSERT(callback); + UINT status = ERROR_OUTOFMEMORY; + + if (!Stream_EnsureRemainingCapacity(out, 8ULL + OutputSize)) + goto fail; + + Stream_Write_INT32(out, hResult); + Stream_Write_UINT32(out, OutputSize); + Stream_Write(out, data, OutputSize); + return stream_write_and_free(callback->plugin, callback->channel, out); + +fail: + Stream_Free(out, TRUE); + return status; +} + +static UINT urb_write_completion(WINPR_ATTR_UNUSED IUDEVICE* pdev, + GENERIC_CHANNEL_CALLBACK* callback, BOOL noAck, wStream* out, + UINT32 InterfaceId, UINT32 MessageId, UINT32 RequestId, + UINT32 usbd_status, UINT32 OutputBufferSize) +{ + if (!out) + return ERROR_INVALID_PARAMETER; + + if (Stream_Capacity(out) < OutputBufferSize + 36) + { + Stream_Free(out, TRUE); + return ERROR_INVALID_PARAMETER; + } + + Stream_ResetPosition(out); + + const UINT32 FunctionId = (OutputBufferSize != 0) ? URB_COMPLETION : URB_COMPLETION_NO_DATA; + if (!write_shared_message_header_with_functionid(out, InterfaceId, MessageId, FunctionId)) + { + Stream_Free(out, TRUE); + return ERROR_OUTOFMEMORY; + } + + Stream_Write_UINT32(out, RequestId); /** RequestId */ + Stream_Write_UINT32(out, 8); /** CbTsUrbResult */ + + if (!write_urb_result_header(out, 8, usbd_status)) + { + Stream_Free(out, TRUE); + return ERROR_OUTOFMEMORY; + } + + Stream_Write_UINT32(out, 0); /** HResult */ + Stream_Write_UINT32(out, OutputBufferSize); /** OutputBufferSize */ + Stream_Seek(out, OutputBufferSize); + + if (!noAck) + return stream_write_and_free(callback->plugin, callback->channel, out); + else + Stream_Free(out, TRUE); + + return ERROR_SUCCESS; +} + +static wStream* urb_create_iocompletion(UINT32 InterfaceField, UINT32 MessageId, UINT32 RequestId, + UINT32 OutputBufferSize) +{ + const UINT32 InterfaceId = (STREAM_ID_PROXY << 30) | (InterfaceField & 0x3FFFFFFF); + +#if UINT32_MAX >= SIZE_MAX + if (OutputBufferSize > UINT32_MAX - 28ull) + return nullptr; +#endif + + wStream* out = create_shared_message_header_with_functionid( + InterfaceId, MessageId, IOCONTROL_COMPLETION, OutputBufferSize + 16ull); + if (!out) + return nullptr; + + Stream_Write_UINT32(out, RequestId); /** RequestId */ + Stream_Write_UINT32(out, USBD_STATUS_SUCCESS); /** HResult */ + Stream_Write_UINT32(out, OutputBufferSize); /** Information */ + Stream_Write_UINT32(out, OutputBufferSize); /** OutputBufferSize */ + return out; +} + +static UINT urbdrc_process_register_request_callback(IUDEVICE* pdev, + GENERIC_CHANNEL_CALLBACK* callback, wStream* s, + IUDEVMAN* udevman) +{ + UINT32 NumRequestCompletion = 0; + UINT32 RequestCompletion = 0; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + WLog_Print(urbdrc->log, WLOG_DEBUG, "urbdrc_process_register_request_callback"); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4ULL)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, NumRequestCompletion); /** must be 1 */ + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4ULL * NumRequestCompletion)) + return ERROR_INVALID_DATA; + for (uint32_t x = 0; x < NumRequestCompletion; x++) + { + /** RequestCompletion: + * unique Request Completion interface for the client to use */ + Stream_Read_UINT32(s, RequestCompletion); + pdev->set_ReqCompletion(pdev, RequestCompletion); + } + + return ERROR_SUCCESS; +} + +static UINT urbdrc_process_cancel_request(IUDEVICE* pdev, wStream* s, IUDEVMAN* udevman) +{ + UINT32 CancelId = 0; + URBDRC_PLUGIN* urbdrc = nullptr; + + if (!s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)udevman->plugin; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, CancelId); + WLog_Print(urbdrc->log, WLOG_DEBUG, "CANCEL_REQUEST: CancelId=%08" PRIx32 "", CancelId); + + if (pdev->cancel_transfer_request(pdev, CancelId) < 0) + return ERROR_INTERNAL_ERROR; + + return ERROR_SUCCESS; +} + +static UINT urbdrc_process_retract_device_request(WINPR_ATTR_UNUSED IUDEVICE* pdev, wStream* s, + IUDEVMAN* udevman) +{ + UINT32 Reason = 0; + URBDRC_PLUGIN* urbdrc = nullptr; + + if (!s || !udevman) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)udevman->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, Reason); /** Reason */ + + switch (Reason) + { + case UsbRetractReason_BlockedByPolicy: + WLog_Print(urbdrc->log, WLOG_DEBUG, + "UsbRetractReason_BlockedByPolicy: now it is not support"); + return ERROR_ACCESS_DENIED; + + default: + WLog_Print(urbdrc->log, WLOG_DEBUG, + "urbdrc_process_retract_device_request: Unknown Reason %" PRIu32 "", Reason); + return ERROR_ACCESS_DENIED; + } + + return ERROR_SUCCESS; +} + +static UINT urbdrc_process_io_control(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback, + wStream* s, UINT32 MessageId, IUDEVMAN* udevman) +{ + UINT32 InterfaceId = 0; + UINT32 IoControlCode = 0; + UINT32 InputBufferSize = 0; + UINT32 OutputBufferSize = 0; + UINT32 RequestId = 0; + UINT32 usbd_status = USBD_STATUS_SUCCESS; + wStream* out = nullptr; + int success = 0; + URBDRC_PLUGIN* urbdrc = nullptr; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, IoControlCode); + Stream_Read_UINT32(s, InputBufferSize); + + if (!Stream_SafeSeek(s, InputBufferSize)) + return ERROR_INVALID_DATA; + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8ULL)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, OutputBufferSize); + Stream_Read_UINT32(s, RequestId); + + if (OutputBufferSize > UINT32_MAX - 4) + return ERROR_INVALID_DATA; + + InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + out = urb_create_iocompletion(InterfaceId, MessageId, RequestId, OutputBufferSize + 4); + + if (!out) + return ERROR_OUTOFMEMORY; + + switch (IoControlCode) + { + case IOCTL_INTERNAL_USB_SUBMIT_URB: /** 0x00220003 */ + WLog_Print(urbdrc->log, WLOG_DEBUG, "ioctl: IOCTL_INTERNAL_USB_SUBMIT_URB"); + WLog_Print(urbdrc->log, WLOG_ERROR, + " Function IOCTL_INTERNAL_USB_SUBMIT_URB: Unchecked"); + break; + + case IOCTL_INTERNAL_USB_RESET_PORT: /** 0x00220007 */ + WLog_Print(urbdrc->log, WLOG_DEBUG, "ioctl: IOCTL_INTERNAL_USB_RESET_PORT"); + break; + + case IOCTL_INTERNAL_USB_GET_PORT_STATUS: /** 0x00220013 */ + WLog_Print(urbdrc->log, WLOG_DEBUG, "ioctl: IOCTL_INTERNAL_USB_GET_PORT_STATUS"); + success = pdev->query_device_port_status(pdev, &usbd_status, &OutputBufferSize, + Stream_Pointer(out)); + + if (success) + { + if (!Stream_SafeSeek(out, OutputBufferSize)) + { + Stream_Free(out, TRUE); + return ERROR_INVALID_DATA; + } + + if (pdev->isExist(pdev) == 0) + Stream_Write_UINT32(out, 0); + else + usb_process_get_port_status(pdev, out); + } + + break; + + case IOCTL_INTERNAL_USB_CYCLE_PORT: /** 0x0022001F */ + WLog_Print(urbdrc->log, WLOG_DEBUG, "ioctl: IOCTL_INTERNAL_USB_CYCLE_PORT"); + WLog_Print(urbdrc->log, WLOG_ERROR, + " Function IOCTL_INTERNAL_USB_CYCLE_PORT: Unchecked"); + break; + + case IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION: /** 0x00220027 */ + WLog_Print(urbdrc->log, WLOG_DEBUG, + "ioctl: IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION"); + WLog_Print(urbdrc->log, WLOG_ERROR, + " Function IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION: Unchecked"); + break; + + default: + WLog_Print(urbdrc->log, WLOG_DEBUG, + "urbdrc_process_io_control: unknown IoControlCode 0x%" PRIX32 "", + IoControlCode); + Stream_Free(out, TRUE); + return ERROR_INVALID_OPERATION; + } + + return stream_write_and_free(callback->plugin, callback->channel, out); +} + +static UINT urbdrc_process_internal_io_control(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback, + wStream* s, UINT32 MessageId, IUDEVMAN* udevman) +{ + if (!pdev || !callback || !s || !udevman) + return ERROR_INVALID_PARAMETER; + + URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)callback->plugin; + WINPR_ASSERT(urbdrc); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + const UINT32 IoControlCode = Stream_Get_UINT32(s); + if (IoControlCode != IOCTL_TSUSBGD_IOCTL_USBDI_QUERY_BUS_TIME) + { + WLog_ERR( + TAG, + "Invalid [MS-RDPEUSB] 2.2.13 USB Internal IO Control Code::IoControlCode0x%08" PRIx32 + ", must be IOCTL_TSUSBGD_IOCTL_USBDI_QUERY_BUS_TIME [0x00224000]", + IoControlCode); + return ERROR_INVALID_DATA; + } + const UINT32 InputBufferSize = Stream_Get_UINT32(s); + + if (!Stream_SafeSeek(s, InputBufferSize)) + return ERROR_INVALID_DATA; + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8ULL)) + return ERROR_INVALID_DATA; + const UINT32 OutputBufferSize = Stream_Get_UINT32(s); + const UINT32 RequestId = Stream_Get_UINT32(s); + const UINT32 InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + // TODO: Implement control code. + /** Fixme: Currently this is a FALSE bustime... */ + const UINT32 frames = GetTickCount(); + + if (4 > OutputBufferSize) + { + WLog_Print(urbdrc->log, WLOG_DEBUG, "out_size %" PRIu32 " > OutputBufferSize %" PRIu32, 4u, + OutputBufferSize); + return ERROR_BAD_CONFIGURATION; + } + wStream* out = urb_create_iocompletion(InterfaceId, MessageId, RequestId, 4); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(out, frames); /** OutputBuffer */ + return stream_write_and_free(callback->plugin, callback->channel, out); +} + +/* [MS-RDPEUSB] 2.2.6.6 Query Device Text Response Message (QUERY_DEVICE_TEXT_RSP) */ +static UINT urbdrc_send_query_device_text_response(GENERIC_CHANNEL_CALLBACK* callback, + UINT32 InterfaceId, UINT32 MessageId, HRESULT hr, + const BYTE* text, uint8_t bytelen) +{ + WINPR_ASSERT(callback); + + const uint8_t charlen = bytelen / sizeof(WCHAR); + wStream* out = create_shared_message_header_with_functionid(InterfaceId, MessageId, charlen, + 8ULL + bytelen); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Write(out, text, bytelen); /* '\0' terminated unicode */ + Stream_Write_INT32(out, hr); /** HResult */ + return stream_write_and_free(callback->plugin, callback->channel, out); +} + +static UINT urbdrc_process_query_device_text(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback, + wStream* s, UINT32 MessageId, IUDEVMAN* udevman) +{ + UINT32 TextType = 0; + UINT32 LocaleId = 0; + UINT8 bufferSize = 0xFF; + BYTE DeviceDescription[0x100] = WINPR_C_ARRAY_INIT; + + if (!pdev || !callback || !s || !udevman) + return ERROR_INVALID_PARAMETER; + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, TextType); + Stream_Read_UINT32(s, LocaleId); + if (LocaleId > UINT16_MAX) + return ERROR_INVALID_DATA; + + HRESULT hr = (HRESULT)pdev->control_query_device_text(pdev, TextType, (UINT16)LocaleId, + &bufferSize, DeviceDescription); + const UINT32 InterfaceId = ((STREAM_ID_STUB << 30) | pdev->get_UsbDevice(pdev)); + return urbdrc_send_query_device_text_response(callback, InterfaceId, MessageId, hr, + DeviceDescription, bufferSize); +} + +static void func_select_all_interface_for_msconfig(URBDRC_PLUGIN* urbdrc, IUDEVICE* pdev, + MSUSB_CONFIG_DESCRIPTOR* MsConfig) +{ + WINPR_ASSERT(urbdrc); + WINPR_ASSERT(pdev); + WINPR_ASSERT(MsConfig); + + MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces = MsConfig->MsInterfaces; + UINT32 NumInterfaces = MsConfig->NumInterfaces; + + for (UINT32 inum = 0; inum < NumInterfaces; inum++) + { + const BYTE InterfaceNumber = MsInterfaces[inum]->InterfaceNumber; + const BYTE AlternateSetting = MsInterfaces[inum]->AlternateSetting; + const int rc = pdev->select_interface(pdev, InterfaceNumber, AlternateSetting); + if (rc < 0) + { + WLog_Print(urbdrc->log, WLOG_WARN, "select_interface %" PRIu8 " [%" PRIu8 "] failed", + InterfaceNumber, AlternateSetting); + } + } +} + +/* [MS-RDPEUSB] 2.2.10.2 TS_URB_SELECT_CONFIGURATION_RESULT */ +static UINT send_urb_select_configuration_result(GENERIC_CHANNEL_CALLBACK* callback, + UINT32 InterfaceId, UINT32 MessageId, + UINT32 RequestId, UINT32 UrbStatus, + const MSUSB_CONFIG_DESCRIPTOR* MsConfig) +{ + wStream* out = + create_urb_completion_message(InterfaceId, MessageId, RequestId, URB_COMPLETION_NO_DATA); + if (!out) + return ERROR_OUTOFMEMORY; + + const int size = 8 + ((MsConfig) ? MsConfig->MsOutSize : 8); + const uint16_t usize = WINPR_ASSERTING_INT_CAST(uint16_t, size); + + if (!Stream_EnsureRemainingCapacity(out, 4)) + goto fail; + Stream_Write_UINT32(out, usize); /* CbTsUrbResult */ + + if (!write_urb_result_header(out, usize, UrbStatus)) + goto fail; + + /** TS_URB_SELECT_CONFIGURATION_RESULT */ + if (MsConfig) + { + if (!msusb_msconfig_write(MsConfig, out)) + goto fail; + } + else + { + Stream_Write_UINT32(out, 0); /** ConfigurationHandle */ + Stream_Write_UINT32(out, 0); /** NumInterfaces */ + } + + return send_urb_completion_message(callback, out, 0, 0, nullptr); + +fail: + Stream_Free(out, TRUE); + return ERROR_OUTOFMEMORY; +} + +static UINT urb_select_configuration(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback, wStream* s, + UINT32 RequestField, UINT32 MessageId, IUDEVMAN* udevman, + int transferDir) +{ + MSUSB_CONFIG_DESCRIPTOR* MsConfig = nullptr; + UINT32 NumInterfaces = 0; + UINT32 usbd_status = 0; + BYTE ConfigurationDescriptorIsValid = 0; + URBDRC_PLUGIN* urbdrc = nullptr; + const BOOL noAck = (RequestField & 0x80000000U) != 0; + const UINT32 RequestId = RequestField & 0x7FFFFFFF; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (transferDir == 0) + { + WLog_Print(urbdrc->log, WLOG_ERROR, "urb_select_configuration: unsupported transfer out"); + return ERROR_INVALID_PARAMETER; + } + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + const UINT32 InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + Stream_Read_UINT8(s, ConfigurationDescriptorIsValid); + Stream_Seek(s, 3); /* Padding */ + Stream_Read_UINT32(s, NumInterfaces); + + /** if ConfigurationDescriptorIsValid is zero, then just do nothing.*/ + if (ConfigurationDescriptorIsValid) + { + /* parser data for struct config */ + MsConfig = msusb_msconfig_read(s, NumInterfaces); + + if (!MsConfig) + return ERROR_INVALID_DATA; + + /* select config */ + const int lrc = pdev->select_configuration(pdev, MsConfig->bConfigurationValue); + if (lrc != 0) + { + msusb_msconfig_free(MsConfig); + MsConfig = nullptr; + return ERROR_INTERNAL_ERROR; + } + + /* select all interface */ + func_select_all_interface_for_msconfig(urbdrc, pdev, MsConfig); + /* complete configuration setup */ + if (!pdev->complete_msconfig_setup(pdev, MsConfig)) + { + msusb_msconfig_free(MsConfig); + MsConfig = nullptr; + } + } + + if (noAck) + return CHANNEL_RC_OK; + return send_urb_select_configuration_result(callback, InterfaceId, MessageId, RequestId, + usbd_status, MsConfig); +} + +/* [MS-RDPEUSB[ 2.2.10.3 TS_URB_SELECT_INTERFACE_RESULT */ +static UINT urb_select_interface_result(GENERIC_CHANNEL_CALLBACK* callback, UINT32 RequestId, + UINT32 InterfaceId, UINT32 MessageId, + MSUSB_INTERFACE_DESCRIPTOR* MsInterface) +{ + WINPR_ASSERT(callback); + WINPR_ASSERT(MsInterface); + + const uint32_t interface_size = 16U + (MsInterface->NumberOfPipes * 20U); + wStream* out = + create_urb_completion_message(InterfaceId, MessageId, RequestId, URB_COMPLETION_NO_DATA); + + if (!out) + return ERROR_OUTOFMEMORY; + + const uint32_t size = 8U + interface_size; + const uint16_t usize = WINPR_ASSERTING_INT_CAST(uint16_t, size); + + if (!Stream_EnsureRemainingCapacity(out, 4)) + goto fail; + Stream_Write_UINT32(out, usize); /* CbTsUrbResult */ + + if (!write_urb_result_header(out, usize, USBD_STATUS_SUCCESS)) + goto fail; + + if (!msusb_msinterface_write(MsInterface, out)) + goto fail; + + return send_urb_completion_message(callback, out, 0, 0, nullptr); + +fail: + Stream_Free(out, TRUE); + + return ERROR_INTERNAL_ERROR; +} + +static UINT urb_select_interface(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback, wStream* s, + UINT32 RequestField, UINT32 MessageId, IUDEVMAN* udevman, + int transferDir) +{ + const BOOL noAck = (RequestField & 0x80000000U) != 0; + const UINT32 RequestId = RequestField & 0x7FFFFFFF; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (transferDir == 0) + { + WLog_Print(urbdrc->log, WLOG_ERROR, "urb_select_interface: not support transfer out"); + return ERROR_INVALID_PARAMETER; + } + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + const UINT32 InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + const UINT32 ConfigurationHandle = Stream_Get_UINT32(s); + MSUSB_INTERFACE_DESCRIPTOR* MsInterface = msusb_msinterface_read(s); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4) || !MsInterface) + { + msusb_msinterface_free(MsInterface); + return ERROR_INVALID_DATA; + } + + const UINT32 OutputBufferSize = Stream_Get_UINT32(s); + if (OutputBufferSize != 0) + { + WLog_Print(urbdrc->log, WLOG_ERROR, + "[MS-RDPEUSB] 2.2.9.3 TS_URB_SELECT_INTERFACE::OutputBufferSize must be 0, got " + "%" PRIu32, + OutputBufferSize); + msusb_msinterface_free(MsInterface); + return ERROR_INVALID_DATA; + } + + const int lerr = + pdev->select_interface(pdev, MsInterface->InterfaceNumber, MsInterface->AlternateSetting); + if (lerr != 0) + { + msusb_msinterface_free(MsInterface); + return ERROR_INTERNAL_ERROR; + } + + /* replace device's MsInterface */ + MSUSB_CONFIG_DESCRIPTOR* MsConfig = pdev->get_MsConfig(pdev); + const uint8_t InterfaceNumber = MsInterface->InterfaceNumber; + if (!msusb_msinterface_replace(MsConfig, InterfaceNumber, MsInterface)) + return ERROR_BAD_CONFIGURATION; + + /* complete configuration setup */ + if (!pdev->complete_msconfig_setup(pdev, MsConfig)) + return ERROR_BAD_CONFIGURATION; + + if (noAck) + return CHANNEL_RC_OK; + + return urb_select_interface_result(callback, RequestId, InterfaceId, MessageId, MsInterface); +} + +static UINT urb_control_transfer(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback, wStream* s, + UINT32 RequestField, UINT32 MessageId, IUDEVMAN* udevman, + int transferDir, int External) +{ + UINT32 out_size = 0; + UINT32 InterfaceId = 0; + UINT32 EndpointAddress = 0; + UINT32 PipeHandle = 0; + UINT32 TransferFlags = 0; + UINT32 OutputBufferSize = 0; + UINT32 usbd_status = 0; + UINT32 Timeout = 0; + BYTE bmRequestType = 0; + BYTE Request = 0; + UINT16 Value = 0; + UINT16 Index = 0; + UINT16 length = 0; + BYTE* buffer = nullptr; + wStream* out = nullptr; + URBDRC_PLUGIN* urbdrc = nullptr; + const BOOL noAck = (RequestField & 0x80000000U) != 0; + const UINT32 RequestId = RequestField & 0x7FFFFFFF; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + Stream_Read_UINT32(s, PipeHandle); + Stream_Read_UINT32(s, TransferFlags); /** TransferFlags */ + EndpointAddress = (PipeHandle & 0x000000ff); + Timeout = 2000; + + switch (External) + { + case URB_CONTROL_TRANSFER_EXTERNAL: + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, Timeout); /** TransferFlags */ + break; + + case URB_CONTROL_TRANSFER_NONEXTERNAL: + break; + default: + break; + } + + /** SetupPacket 8 bytes */ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 12)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT8(s, bmRequestType); + Stream_Read_UINT8(s, Request); + Stream_Read_UINT16(s, Value); + Stream_Read_UINT16(s, Index); + Stream_Read_UINT16(s, length); + Stream_Read_UINT32(s, OutputBufferSize); + + if (length != OutputBufferSize) + { + WLog_Print(urbdrc->log, WLOG_ERROR, "urb_control_transfer ERROR: buf != length"); + return ERROR_INVALID_DATA; + } + + out_size = 36 + OutputBufferSize; + out = Stream_New(nullptr, out_size); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Seek(out, 36); + /** Get Buffer Data */ + buffer = Stream_Pointer(out); + + if (transferDir == USBD_TRANSFER_DIRECTION_OUT) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, OutputBufferSize)) + { + Stream_Free(out, TRUE); + return ERROR_INVALID_DATA; + } + Stream_Copy(s, out, OutputBufferSize); + } + + /** process TS_URB_CONTROL_TRANSFER */ + if (!pdev->control_transfer(pdev, RequestId, EndpointAddress, TransferFlags, bmRequestType, + Request, Value, Index, &usbd_status, &OutputBufferSize, buffer, + Timeout)) + { + WLog_Print(urbdrc->log, WLOG_ERROR, "control_transfer failed"); + Stream_Free(out, TRUE); + return ERROR_INTERNAL_ERROR; + } + + return urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId, + usbd_status, OutputBufferSize); +} + +static void urb_bulk_transfer_cb(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback, wStream* out, + UINT32 InterfaceId, BOOL noAck, UINT32 MessageId, UINT32 RequestId, + WINPR_ATTR_UNUSED UINT32 NumberOfPackets, UINT32 status, + WINPR_ATTR_UNUSED UINT32 StartFrame, + WINPR_ATTR_UNUSED UINT32 ErrorCount, UINT32 OutputBufferSize) +{ + if (!pdev->isChannelClosed(pdev)) + urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId, status, + OutputBufferSize); + else + Stream_Free(out, TRUE); +} + +static UINT urb_bulk_or_interrupt_transfer(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback, + wStream* s, UINT32 RequestField, UINT32 MessageId, + IUDEVMAN* udevman, int transferDir) +{ + UINT32 EndpointAddress = 0; + UINT32 PipeHandle = 0; + UINT32 TransferFlags = 0; + UINT32 OutputBufferSize = 0; + const BOOL noAck = (RequestField & 0x80000000U) != 0; + const UINT32 RequestId = RequestField & 0x7FFFFFFF; + + if (!pdev || !callback || !s || !udevman) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 12)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, PipeHandle); + Stream_Read_UINT32(s, TransferFlags); /** TransferFlags */ + Stream_Read_UINT32(s, OutputBufferSize); + EndpointAddress = (PipeHandle & 0x000000ff); + + if (transferDir == USBD_TRANSFER_DIRECTION_OUT) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, OutputBufferSize)) + { + return ERROR_INVALID_DATA; + } + } + + /** process TS_URB_BULK_OR_INTERRUPT_TRANSFER */ + const int rc = pdev->bulk_or_interrupt_transfer( + pdev, callback, MessageId, RequestId, EndpointAddress, TransferFlags, noAck, + OutputBufferSize, + (transferDir == USBD_TRANSFER_DIRECTION_OUT) ? Stream_Pointer(s) : nullptr, + urb_bulk_transfer_cb, 10000); + + return (uint32_t)rc; +} + +static void urb_isoch_transfer_cb(WINPR_ATTR_UNUSED IUDEVICE* pdev, + GENERIC_CHANNEL_CALLBACK* callback, wStream* out, + UINT32 InterfaceId, BOOL noAck, UINT32 MessageId, + UINT32 RequestId, UINT32 NumberOfPackets, UINT32 status, + UINT32 StartFrame, UINT32 ErrorCount, UINT32 OutputBufferSize) +{ + if (!noAck) + { + UINT32 packetSize = (status == 0) ? NumberOfPackets * 12 : 0; + Stream_ResetPosition(out); + + const UINT32 FunctionId = (OutputBufferSize == 0) ? URB_COMPLETION_NO_DATA : URB_COMPLETION; + if (!write_shared_message_header_with_functionid(out, InterfaceId, MessageId, FunctionId)) + { + Stream_Free(out, TRUE); + return; + } + + Stream_Write_UINT32(out, RequestId); /** RequestId */ + Stream_Write_UINT32(out, 20 + packetSize); /** CbTsUrbResult */ + if (!write_urb_result_header(out, WINPR_ASSERTING_INT_CAST(uint16_t, 20 + packetSize), + status)) + { + Stream_Free(out, TRUE); + return; + } + + Stream_Write_UINT32(out, StartFrame); /** StartFrame */ + + if (status == 0) + { + /** NumberOfPackets */ + Stream_Write_UINT32(out, NumberOfPackets); + Stream_Write_UINT32(out, ErrorCount); /** ErrorCount */ + Stream_Seek(out, packetSize); + } + else + { + Stream_Write_UINT32(out, 0); /** NumberOfPackets */ + Stream_Write_UINT32(out, ErrorCount); /** ErrorCount */ + } + + Stream_Write_UINT32(out, 0); /** HResult */ + Stream_Write_UINT32(out, OutputBufferSize); /** OutputBufferSize */ + Stream_Seek(out, OutputBufferSize); + + const UINT rc = stream_write_and_free(callback->plugin, callback->channel, out); + if (rc != CHANNEL_RC_OK) + WLog_WARN(TAG, "stream_write_and_free failed with %" PRIu32, rc); + } +} + +static UINT urb_isoch_transfer(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback, wStream* s, + UINT32 RequestField, UINT32 MessageId, IUDEVMAN* udevman, + int transferDir) +{ + int rc = 0; + UINT32 EndpointAddress = 0; + UINT32 PipeHandle = 0; + UINT32 TransferFlags = 0; + UINT32 StartFrame = 0; + UINT32 NumberOfPackets = 0; + UINT32 ErrorCount = 0; + UINT32 OutputBufferSize = 0; + BYTE* packetDescriptorData = nullptr; + const BOOL noAck = (RequestField & 0x80000000U) != 0; + const UINT32 RequestId = RequestField & 0x7FFFFFFF; + + if (!pdev || !callback || !udevman) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 20)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, PipeHandle); + EndpointAddress = (PipeHandle & 0x000000ff); + Stream_Read_UINT32(s, TransferFlags); /** TransferFlags */ + Stream_Read_UINT32(s, StartFrame); /** StartFrame */ + Stream_Read_UINT32(s, NumberOfPackets); /** NumberOfPackets */ + Stream_Read_UINT32(s, ErrorCount); /** ErrorCount */ + + if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, NumberOfPackets, 12ull)) + return ERROR_INVALID_DATA; + + packetDescriptorData = Stream_Pointer(s); + Stream_Seek(s, 12ULL * NumberOfPackets); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, sizeof(UINT32))) + return ERROR_INVALID_DATA; + Stream_Read_UINT32(s, OutputBufferSize); + + if (transferDir == USBD_TRANSFER_DIRECTION_OUT) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, OutputBufferSize)) + return ERROR_INVALID_DATA; + } + + rc = pdev->isoch_transfer( + pdev, callback, MessageId, RequestId, EndpointAddress, TransferFlags, StartFrame, + ErrorCount, noAck, packetDescriptorData, NumberOfPackets, OutputBufferSize, + (transferDir == USBD_TRANSFER_DIRECTION_OUT) ? Stream_Pointer(s) : nullptr, + urb_isoch_transfer_cb, 2000); + + if (rc < 0) + return ERROR_INTERNAL_ERROR; + return (UINT)rc; +} + +static UINT urb_control_descriptor_request(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback, + wStream* s, UINT32 RequestField, UINT32 MessageId, + IUDEVMAN* udevman, BYTE func_recipient, int transferDir) +{ + size_t out_size = 0; + UINT32 InterfaceId = 0; + UINT32 OutputBufferSize = 0; + UINT32 usbd_status = 0; + BYTE bmRequestType = 0; + BYTE desc_index = 0; + BYTE desc_type = 0; + UINT16 langId = 0; + wStream* out = nullptr; + URBDRC_PLUGIN* urbdrc = nullptr; + const BOOL noAck = (RequestField & 0x80000000U) != 0; + const UINT32 RequestId = RequestField & 0x7FFFFFFF; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + Stream_Read_UINT8(s, desc_index); + Stream_Read_UINT8(s, desc_type); + Stream_Read_UINT16(s, langId); + Stream_Read_UINT32(s, OutputBufferSize); + if (OutputBufferSize > UINT32_MAX - 36) + return ERROR_INVALID_DATA; + if (transferDir == USBD_TRANSFER_DIRECTION_OUT) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, OutputBufferSize)) + return ERROR_INVALID_DATA; + } + + out_size = 36ULL + OutputBufferSize; + out = Stream_New(nullptr, out_size); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Seek(out, 36); + bmRequestType = func_recipient; + + switch (transferDir) + { + case USBD_TRANSFER_DIRECTION_IN: + bmRequestType |= 0x80; + break; + + case USBD_TRANSFER_DIRECTION_OUT: + bmRequestType |= 0x00; + Stream_Copy(s, out, OutputBufferSize); + Stream_Rewind(out, OutputBufferSize); + break; + + default: + WLog_Print(urbdrc->log, WLOG_DEBUG, "get error transferDir"); + OutputBufferSize = 0; + usbd_status = USBD_STATUS_STALL_PID; + break; + } + + /** process get usb device descriptor */ + if (!pdev->control_transfer(pdev, RequestId, 0, 0, bmRequestType, + 0x06, /* REQUEST_GET_DESCRIPTOR */ + WINPR_ASSERTING_INT_CAST(UINT16, ((desc_type << 8) | desc_index)), + langId, &usbd_status, &OutputBufferSize, Stream_Pointer(out), 1000)) + { + WLog_Print(urbdrc->log, WLOG_ERROR, "get_descriptor failed"); + Stream_Free(out, TRUE); + return ERROR_INTERNAL_ERROR; + } + + return urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId, + usbd_status, OutputBufferSize); +} + +static UINT urb_control_get_status_request(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback, + wStream* s, UINT32 RequestField, UINT32 MessageId, + IUDEVMAN* udevman, BYTE func_recipient, int transferDir) +{ + size_t out_size = 0; + UINT32 InterfaceId = 0; + UINT32 OutputBufferSize = 0; + UINT32 usbd_status = 0; + UINT16 Index = 0; + BYTE bmRequestType = 0; + wStream* out = nullptr; + URBDRC_PLUGIN* urbdrc = nullptr; + const BOOL noAck = (RequestField & 0x80000000U) != 0; + const UINT32 RequestId = RequestField & 0x7FFFFFFF; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (transferDir == 0) + { + WLog_Print(urbdrc->log, WLOG_DEBUG, + "urb_control_get_status_request: transfer out not supported"); + return ERROR_INVALID_PARAMETER; + } + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + Stream_Read_UINT16(s, Index); /** Index */ + Stream_Seek(s, 2); + Stream_Read_UINT32(s, OutputBufferSize); + if (OutputBufferSize > UINT32_MAX - 36) + return ERROR_INVALID_DATA; + out_size = 36ULL + OutputBufferSize; + out = Stream_New(nullptr, out_size); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Seek(out, 36); + bmRequestType = func_recipient | 0x80; + + if (!pdev->control_transfer(pdev, RequestId, 0, 0, bmRequestType, 0x00, /* REQUEST_GET_STATUS */ + 0, Index, &usbd_status, &OutputBufferSize, Stream_Pointer(out), + 1000)) + { + WLog_Print(urbdrc->log, WLOG_ERROR, "control_transfer failed"); + Stream_Free(out, TRUE); + return ERROR_INTERNAL_ERROR; + } + + return urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId, + usbd_status, OutputBufferSize); +} + +static UINT urb_control_vendor_or_class_request(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback, + wStream* s, UINT32 RequestField, UINT32 MessageId, + IUDEVMAN* udevman, BYTE func_type, + BYTE func_recipient, int transferDir) +{ + UINT32 out_size = 0; + UINT32 InterfaceId = 0; + UINT32 TransferFlags = 0; + UINT32 usbd_status = 0; + UINT32 OutputBufferSize = 0; + BYTE ReqTypeReservedBits = 0; + BYTE Request = 0; + BYTE bmRequestType = 0; + UINT16 Value = 0; + UINT16 Index = 0; + wStream* out = nullptr; + URBDRC_PLUGIN* urbdrc = nullptr; + const BOOL noAck = (RequestField & 0x80000000U) != 0; + const UINT32 RequestId = RequestField & 0x7FFFFFFF; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 16)) + return ERROR_INVALID_DATA; + + InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + Stream_Read_UINT32(s, TransferFlags); /** TransferFlags */ + Stream_Read_UINT8(s, ReqTypeReservedBits); /** ReqTypeReservedBids */ + Stream_Read_UINT8(s, Request); /** Request */ + Stream_Read_UINT16(s, Value); /** value */ + Stream_Read_UINT16(s, Index); /** index */ + Stream_Seek_UINT16(s); /** Padding */ + Stream_Read_UINT32(s, OutputBufferSize); + if (OutputBufferSize > UINT32_MAX - 36) + return ERROR_INVALID_DATA; + + if (transferDir == USBD_TRANSFER_DIRECTION_OUT) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, OutputBufferSize)) + return ERROR_INVALID_DATA; + } + + out_size = 36ULL + OutputBufferSize; + out = Stream_New(nullptr, out_size); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Seek(out, 36); + + /** Get Buffer */ + if (transferDir == USBD_TRANSFER_DIRECTION_OUT) + { + Stream_Copy(s, out, OutputBufferSize); + Stream_Rewind(out, OutputBufferSize); + } + + /** vendor or class command */ + bmRequestType = func_type | func_recipient; + + if (TransferFlags & USBD_TRANSFER_DIRECTION) + bmRequestType |= 0x80; + + WLog_Print(urbdrc->log, WLOG_DEBUG, + "RequestId 0x%" PRIx32 " TransferFlags: 0x%" PRIx32 " ReqTypeReservedBits: 0x%" PRIx8 + " " + "Request:0x%" PRIx8 " Value: 0x%" PRIx16 " Index: 0x%" PRIx16 + " OutputBufferSize: 0x%" PRIx32 " bmRequestType: 0x%" PRIx8, + RequestId, TransferFlags, ReqTypeReservedBits, Request, Value, Index, + OutputBufferSize, bmRequestType); + + if (!pdev->control_transfer(pdev, RequestId, 0, 0, bmRequestType, Request, Value, Index, + &usbd_status, &OutputBufferSize, Stream_Pointer(out), 2000)) + { + WLog_Print(urbdrc->log, WLOG_ERROR, "control_transfer failed"); + Stream_Free(out, TRUE); + return ERROR_INTERNAL_ERROR; + } + + return urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId, + usbd_status, OutputBufferSize); +} + +static UINT urb_os_feature_descriptor_request(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback, + wStream* s, UINT32 RequestField, UINT32 MessageId, + IUDEVMAN* udevman, int transferDir) +{ + size_t out_size = 0; + UINT32 InterfaceId = 0; + UINT32 OutputBufferSize = 0; + UINT32 usbd_status = 0; + BYTE Recipient = 0; + BYTE InterfaceNumber = 0; + BYTE Ms_PageIndex = 0; + UINT16 Ms_featureDescIndex = 0; + wStream* out = nullptr; + int ret = 0; + URBDRC_PLUGIN* urbdrc = nullptr; + const BOOL noAck = (RequestField & 0x80000000U) != 0; + const UINT32 RequestId = RequestField & 0x7FFFFFFF; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 12)) + return ERROR_INVALID_DATA; + + /* 2.2.9.15 TS_URB_OS_FEATURE_DESCRIPTOR_REQUEST */ + Stream_Read_UINT8(s, Recipient); /** Recipient */ + Recipient = (Recipient & 0x1f); /* Mask out Padding1 */ + Stream_Read_UINT8(s, InterfaceNumber); /** InterfaceNumber */ + Stream_Read_UINT8(s, Ms_PageIndex); /** Ms_PageIndex */ + Stream_Read_UINT16(s, Ms_featureDescIndex); /** Ms_featureDescIndex */ + Stream_Seek(s, 3); /* Padding 2 */ + Stream_Read_UINT32(s, OutputBufferSize); + if (OutputBufferSize > UINT32_MAX - 36) + return ERROR_INVALID_DATA; + + switch (transferDir) + { + case USBD_TRANSFER_DIRECTION_OUT: + if (!Stream_CheckAndLogRequiredLength(TAG, s, OutputBufferSize)) + return ERROR_INVALID_DATA; + + break; + + default: + break; + } + + InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + out_size = 36ULL + OutputBufferSize; + out = Stream_New(nullptr, out_size); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Seek(out, 36); + + switch (transferDir) + { + case USBD_TRANSFER_DIRECTION_OUT: + Stream_Copy(s, out, OutputBufferSize); + Stream_Rewind(out, OutputBufferSize); + break; + + case USBD_TRANSFER_DIRECTION_IN: + break; + default: + break; + } + + WLog_Print(urbdrc->log, WLOG_DEBUG, + "Ms descriptor arg: Recipient:0x%" PRIx8 ", " + "InterfaceNumber:0x%" PRIx8 ", Ms_PageIndex:0x%" PRIx8 ", " + "Ms_featureDescIndex:0x%" PRIx16 ", OutputBufferSize:0x%" PRIx32 "", + Recipient, InterfaceNumber, Ms_PageIndex, Ms_featureDescIndex, OutputBufferSize); + /** get ms string */ + ret = pdev->os_feature_descriptor_request(pdev, RequestId, Recipient, InterfaceNumber, + Ms_PageIndex, Ms_featureDescIndex, &usbd_status, + &OutputBufferSize, Stream_Pointer(out), 1000); + + if (ret < 0) + WLog_Print(urbdrc->log, WLOG_DEBUG, "os_feature_descriptor_request: error num %d", ret); + + return urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId, + usbd_status, OutputBufferSize); +} + +static UINT urb_pipe_request(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback, wStream* s, + UINT32 RequestField, UINT32 MessageId, IUDEVMAN* udevman, + int transferDir, int action) +{ + UINT32 usbd_status = 0; + UINT32 ret = USBD_STATUS_REQUEST_FAILED; + int rc = 0; + const BOOL noAck = (RequestField & 0x80000000U) != 0; + const UINT32 RequestId = RequestField & 0x7FFFFFFF; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + if (transferDir == 0) + { + WLog_Print(urbdrc->log, WLOG_DEBUG, "urb_pipe_request: not support transfer out"); + return ERROR_INVALID_PARAMETER; + } + + const UINT32 InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + const UINT32 PipeHandle = Stream_Get_UINT32(s); /** PipeHandle */ + const UINT32 OutputBufferSize = Stream_Get_UINT32(s); + const UINT32 EndpointAddress = (PipeHandle & 0x000000ff); + + if (OutputBufferSize != 0) + { + WLog_Print(urbdrc->log, WLOG_DEBUG, + "2.2.9.4 TS_URB_PIPE_REQUEST OutputBufferSize %" PRIu32 " != 0", + OutputBufferSize); + return ERROR_BAD_CONFIGURATION; + } + + switch (action) + { + case PIPE_CANCEL: + rc = pdev->control_pipe_request(pdev, RequestId, EndpointAddress, &usbd_status, + PIPE_CANCEL); + + if (rc < 0) + WLog_Print(urbdrc->log, WLOG_DEBUG, "PIPE SET HALT: error %u", ret); + else + ret = USBD_STATUS_SUCCESS; + + break; + + case PIPE_RESET: + WLog_Print(urbdrc->log, WLOG_DEBUG, "urb_pipe_request: PIPE_RESET ep 0x%" PRIx32 "", + EndpointAddress); + rc = pdev->control_pipe_request(pdev, RequestId, EndpointAddress, &usbd_status, + PIPE_RESET); + + if (rc < 0) + WLog_Print(urbdrc->log, WLOG_DEBUG, "PIPE RESET: error %u", ret); + else + ret = USBD_STATUS_SUCCESS; + + break; + + default: + WLog_Print(urbdrc->log, WLOG_DEBUG, "urb_pipe_request action: %d not supported", + action); + ret = USBD_STATUS_INVALID_URB_FUNCTION; + break; + } + + /** send data */ + + wStream* out = Stream_New(nullptr, 36); + + if (!out) + return ERROR_OUTOFMEMORY; + + return urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId, ret, + 0); +} +/* [MS-RDPEUSB] 2.2.10.4 TS_URB_GET_CURRENT_FRAME_NUMBER_RESULT */ +static UINT urb_send_current_frame_number_result(GENERIC_CHANNEL_CALLBACK* callback, + UINT32 RequestId, UINT32 MessageId, + UINT32 CompletionId, UINT32 FrameNumber) +{ + WINPR_ASSERT(callback); + + const UINT32 InterfaceId = ((STREAM_ID_PROXY << 30) | CompletionId); + wStream* out = + create_urb_completion_message(InterfaceId, MessageId, RequestId, URB_COMPLETION_NO_DATA); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(out, 12); /** CbTsUrbResult */ + if (!write_urb_result_header(out, 12, USBD_STATUS_SUCCESS)) + { + Stream_Free(out, TRUE); + return ERROR_OUTOFMEMORY; + } + + Stream_Write_UINT32(out, FrameNumber); /** FrameNumber */ + return send_urb_completion_message(callback, out, 0, 0, nullptr); +} + +static UINT urb_get_current_frame_number(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback, + wStream* s, UINT32 RequestField, UINT32 MessageId, + IUDEVMAN* udevman, int transferDir) +{ + const BOOL noAck = (RequestField & 0x80000000U) != 0; + const UINT32 RequestId = RequestField & 0x7FFFFFFF; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + if (transferDir == 0) + { + WLog_Print(urbdrc->log, WLOG_DEBUG, + "urb_get_current_frame_number: not support transfer out"); + return ERROR_INVALID_PARAMETER; + } + + const UINT32 OutputBufferSize = Stream_Get_UINT32(s); + if (OutputBufferSize != 0) + { + WLog_Print(urbdrc->log, WLOG_WARN, "OutputBufferSize=%" PRIu32 ", expected 0", + OutputBufferSize); + } + /** Fixme: Need to fill actual frame number!!*/ + const UINT32 dummy_frames = GetTickCount(); + const UINT32 CompletionId = pdev->get_ReqCompletion(pdev); + + if (noAck) + return CHANNEL_RC_OK; + + return urb_send_current_frame_number_result(callback, RequestId, MessageId, CompletionId, + dummy_frames); +} + +/* Unused function for current server */ +static UINT urb_control_get_configuration_request(IUDEVICE* pdev, + GENERIC_CHANNEL_CALLBACK* callback, wStream* s, + UINT32 RequestField, UINT32 MessageId, + IUDEVMAN* udevman, int transferDir) +{ + size_t out_size = 0; + UINT32 InterfaceId = 0; + UINT32 OutputBufferSize = 0; + UINT32 usbd_status = 0; + wStream* out = nullptr; + URBDRC_PLUGIN* urbdrc = nullptr; + const BOOL noAck = (RequestField & 0x80000000U) != 0; + const UINT32 RequestId = RequestField & 0x7FFFFFFF; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (transferDir == 0) + { + WLog_Print(urbdrc->log, WLOG_DEBUG, + "urb_control_get_configuration_request:" + " not support transfer out"); + return ERROR_INVALID_PARAMETER; + } + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, OutputBufferSize); + if (OutputBufferSize > UINT32_MAX - 36) + return ERROR_INVALID_DATA; + out_size = 36ULL + OutputBufferSize; + out = Stream_New(nullptr, out_size); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Seek(out, 36); + InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + + if (!pdev->control_transfer(pdev, RequestId, 0, 0, 0x80 | 0x00, + 0x08, /* REQUEST_GET_CONFIGURATION */ + 0, 0, &usbd_status, &OutputBufferSize, Stream_Pointer(out), 1000)) + { + WLog_Print(urbdrc->log, WLOG_DEBUG, "control_transfer failed"); + Stream_Free(out, TRUE); + return ERROR_INTERNAL_ERROR; + } + + return urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId, + usbd_status, OutputBufferSize); +} + +/* Unused function for current server */ +static UINT urb_control_get_interface_request(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback, + wStream* s, UINT32 RequestField, UINT32 MessageId, + IUDEVMAN* udevman, int transferDir) +{ + size_t out_size = 0; + UINT32 InterfaceId = 0; + UINT32 OutputBufferSize = 0; + UINT32 usbd_status = 0; + UINT16 InterfaceNr = 0; + wStream* out = nullptr; + URBDRC_PLUGIN* urbdrc = nullptr; + const BOOL noAck = (RequestField & 0x80000000U) != 0; + const UINT32 RequestId = RequestField & 0x7FFFFFFF; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + if (transferDir == 0) + { + WLog_Print(urbdrc->log, WLOG_DEBUG, + "urb_control_get_interface_request: not support transfer out"); + return ERROR_INVALID_PARAMETER; + } + + InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + Stream_Read_UINT16(s, InterfaceNr); + Stream_Seek(s, 2); + Stream_Read_UINT32(s, OutputBufferSize); + if (OutputBufferSize > UINT32_MAX - 36) + return ERROR_INVALID_DATA; + out_size = 36ULL + OutputBufferSize; + out = Stream_New(nullptr, out_size); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Seek(out, 36); + + if (!pdev->control_transfer( + pdev, RequestId, 0, 0, 0x80 | 0x01, 0x0A, /* REQUEST_GET_INTERFACE */ + 0, InterfaceNr, &usbd_status, &OutputBufferSize, Stream_Pointer(out), 1000)) + { + WLog_Print(urbdrc->log, WLOG_DEBUG, "control_transfer failed"); + Stream_Free(out, TRUE); + return ERROR_INTERNAL_ERROR; + } + + return urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId, + usbd_status, OutputBufferSize); +} + +static UINT urb_control_feature_request(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback, + wStream* s, UINT32 RequestField, UINT32 MessageId, + IUDEVMAN* udevman, BYTE func_recipient, BYTE command, + int transferDir) +{ + UINT32 InterfaceId = 0; + UINT32 OutputBufferSize = 0; + UINT32 usbd_status = 0; + UINT16 FeatureSelector = 0; + UINT16 Index = 0; + BYTE bmRequestType = 0; + BYTE bmRequest = 0; + wStream* out = nullptr; + URBDRC_PLUGIN* urbdrc = nullptr; + const BOOL noAck = (RequestField & 0x80000000U) != 0; + const UINT32 RequestId = RequestField & 0x7FFFFFFF; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return ERROR_INVALID_DATA; + + InterfaceId = ((STREAM_ID_PROXY << 30) | pdev->get_ReqCompletion(pdev)); + Stream_Read_UINT16(s, FeatureSelector); + Stream_Read_UINT16(s, Index); + Stream_Read_UINT32(s, OutputBufferSize); + if (OutputBufferSize > UINT32_MAX - 36) + return ERROR_INVALID_DATA; + switch (transferDir) + { + case USBD_TRANSFER_DIRECTION_OUT: + if (!Stream_CheckAndLogRequiredLength(TAG, s, OutputBufferSize)) + return ERROR_INVALID_DATA; + + break; + + default: + break; + } + + out = Stream_New(nullptr, 36ULL + OutputBufferSize); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Seek(out, 36); + bmRequestType = func_recipient; + + switch (transferDir) + { + case USBD_TRANSFER_DIRECTION_OUT: + WLog_Print(urbdrc->log, WLOG_ERROR, + "Function urb_control_feature_request: OUT Unchecked"); + Stream_Copy(s, out, OutputBufferSize); + Stream_Rewind(out, OutputBufferSize); + bmRequestType |= 0x00; + break; + + case USBD_TRANSFER_DIRECTION_IN: + bmRequestType |= 0x80; + break; + default: + break; + } + + switch (command) + { + case URB_SET_FEATURE: + bmRequest = 0x03; /* REQUEST_SET_FEATURE */ + break; + + case URB_CLEAR_FEATURE: + bmRequest = 0x01; /* REQUEST_CLEAR_FEATURE */ + break; + + default: + WLog_Print(urbdrc->log, WLOG_ERROR, + "urb_control_feature_request: Error Command 0x%02" PRIx8 "", command); + Stream_Free(out, TRUE); + return ERROR_INTERNAL_ERROR; + } + + if (!pdev->control_transfer(pdev, RequestId, 0, 0, bmRequestType, bmRequest, FeatureSelector, + Index, &usbd_status, &OutputBufferSize, Stream_Pointer(out), 1000)) + { + WLog_Print(urbdrc->log, WLOG_DEBUG, "feature control transfer failed"); + Stream_Free(out, TRUE); + return ERROR_INTERNAL_ERROR; + } + + return urb_write_completion(pdev, callback, noAck, out, InterfaceId, MessageId, RequestId, + usbd_status, OutputBufferSize); +} + +static UINT urbdrc_process_transfer_request(IUDEVICE* pdev, GENERIC_CHANNEL_CALLBACK* callback, + wStream* s, UINT32 MessageId, IUDEVMAN* udevman, + int transferDir) +{ + UINT32 CbTsUrb = 0; + UINT16 Size = 0; + UINT16 URB_Function = 0; + UINT32 RequestId = 0; + UINT error = ERROR_INTERNAL_ERROR; + URBDRC_PLUGIN* urbdrc = nullptr; + + if (!callback || !s || !udevman || !pdev) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 12)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, CbTsUrb); /** CbTsUrb */ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4ULL + CbTsUrb)) + return ERROR_INVALID_DATA; + Stream_Read_UINT16(s, Size); /** size */ + if (Size != CbTsUrb) + { + const char* section = (transferDir == USBD_TRANSFER_DIRECTION_IN) + ? "2.2.6.7 Transfer In Request (TRANSFER_IN_REQUEST)" + : "2.2.6.8 Transfer Out Request (TRANSFER_OUT_REQUEST)"; + WLog_ERR(TAG, + "[MS-RDPEUSB] 2.2.9.1.1 TS_URB_HEADER::Size 0x%04" PRIx16 + " != %s::CbTsUrb 0x%08" PRIx32, + Size, section, CbTsUrb); + return ERROR_INVALID_DATA; + } + Stream_Read_UINT16(s, URB_Function); + Stream_Read_UINT32(s, RequestId); + WLog_Print(urbdrc->log, WLOG_DEBUG, "URB %s[%" PRIu16 "]", urb_function_string(URB_Function), + URB_Function); + + switch (URB_Function) + { + case TS_URB_SELECT_CONFIGURATION: /** 0x0000 */ + error = urb_select_configuration(pdev, callback, s, RequestId, MessageId, udevman, + transferDir); + break; + + case TS_URB_SELECT_INTERFACE: /** 0x0001 */ + error = + urb_select_interface(pdev, callback, s, RequestId, MessageId, udevman, transferDir); + break; + + case TS_URB_PIPE_REQUEST: /** 0x0002 */ + error = urb_pipe_request(pdev, callback, s, RequestId, MessageId, udevman, transferDir, + PIPE_CANCEL); + break; + + case TS_URB_TAKE_FRAME_LENGTH_CONTROL: /** 0x0003 */ + /** This URB function is obsolete in Windows 2000 + * and later operating systems + * and is not supported by Microsoft. */ + break; + + case TS_URB_RELEASE_FRAME_LENGTH_CONTROL: /** 0x0004 */ + /** This URB function is obsolete in Windows 2000 + * and later operating systems + * and is not supported by Microsoft. */ + break; + + case TS_URB_GET_FRAME_LENGTH: /** 0x0005 */ + /** This URB function is obsolete in Windows 2000 + * and later operating systems + * and is not supported by Microsoft. */ + break; + + case TS_URB_SET_FRAME_LENGTH: /** 0x0006 */ + /** This URB function is obsolete in Windows 2000 + * and later operating systems + * and is not supported by Microsoft. */ + break; + + case TS_URB_GET_CURRENT_FRAME_NUMBER: /** 0x0007 */ + error = urb_get_current_frame_number(pdev, callback, s, RequestId, MessageId, udevman, + transferDir); + break; + + case TS_URB_CONTROL_TRANSFER: /** 0x0008 */ + error = urb_control_transfer(pdev, callback, s, RequestId, MessageId, udevman, + transferDir, URB_CONTROL_TRANSFER_NONEXTERNAL); + break; + + case TS_URB_BULK_OR_INTERRUPT_TRANSFER: /** 0x0009 */ + error = urb_bulk_or_interrupt_transfer(pdev, callback, s, RequestId, MessageId, udevman, + transferDir); + break; + + case TS_URB_ISOCH_TRANSFER: /** 0x000A */ + error = + urb_isoch_transfer(pdev, callback, s, RequestId, MessageId, udevman, transferDir); + break; + + case TS_URB_GET_DESCRIPTOR_FROM_DEVICE: /** 0x000B */ + error = urb_control_descriptor_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x00, transferDir); + break; + + case TS_URB_SET_DESCRIPTOR_TO_DEVICE: /** 0x000C */ + error = urb_control_descriptor_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x00, transferDir); + break; + + case TS_URB_SET_FEATURE_TO_DEVICE: /** 0x000D */ + error = urb_control_feature_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x00, URB_SET_FEATURE, transferDir); + break; + + case TS_URB_SET_FEATURE_TO_INTERFACE: /** 0x000E */ + error = urb_control_feature_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x01, URB_SET_FEATURE, transferDir); + break; + + case TS_URB_SET_FEATURE_TO_ENDPOINT: /** 0x000F */ + error = urb_control_feature_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x02, URB_SET_FEATURE, transferDir); + break; + + case TS_URB_CLEAR_FEATURE_TO_DEVICE: /** 0x0010 */ + error = urb_control_feature_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x00, URB_CLEAR_FEATURE, transferDir); + break; + + case TS_URB_CLEAR_FEATURE_TO_INTERFACE: /** 0x0011 */ + error = urb_control_feature_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x01, URB_CLEAR_FEATURE, transferDir); + break; + + case TS_URB_CLEAR_FEATURE_TO_ENDPOINT: /** 0x0012 */ + error = urb_control_feature_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x02, URB_CLEAR_FEATURE, transferDir); + break; + + case TS_URB_GET_STATUS_FROM_DEVICE: /** 0x0013 */ + error = urb_control_get_status_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x00, transferDir); + break; + + case TS_URB_GET_STATUS_FROM_INTERFACE: /** 0x0014 */ + error = urb_control_get_status_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x01, transferDir); + break; + + case TS_URB_GET_STATUS_FROM_ENDPOINT: /** 0x0015 */ + error = urb_control_get_status_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x02, transferDir); + break; + + case TS_URB_RESERVED_0X0016: /** 0x0016 */ + break; + + case TS_URB_VENDOR_DEVICE: /** 0x0017 */ + error = urb_control_vendor_or_class_request(pdev, callback, s, RequestId, MessageId, + udevman, (0x02 << 5), /* vendor type */ + 0x00, transferDir); + break; + + case TS_URB_VENDOR_INTERFACE: /** 0x0018 */ + error = urb_control_vendor_or_class_request(pdev, callback, s, RequestId, MessageId, + udevman, (0x02 << 5), /* vendor type */ + 0x01, transferDir); + break; + + case TS_URB_VENDOR_ENDPOINT: /** 0x0019 */ + error = urb_control_vendor_or_class_request(pdev, callback, s, RequestId, MessageId, + udevman, (0x02 << 5), /* vendor type */ + 0x02, transferDir); + break; + + case TS_URB_CLASS_DEVICE: /** 0x001A */ + error = urb_control_vendor_or_class_request(pdev, callback, s, RequestId, MessageId, + udevman, (0x01 << 5), /* class type */ + 0x00, transferDir); + break; + + case TS_URB_CLASS_INTERFACE: /** 0x001B */ + error = urb_control_vendor_or_class_request(pdev, callback, s, RequestId, MessageId, + udevman, (0x01 << 5), /* class type */ + 0x01, transferDir); + break; + + case TS_URB_CLASS_ENDPOINT: /** 0x001C */ + error = urb_control_vendor_or_class_request(pdev, callback, s, RequestId, MessageId, + udevman, (0x01 << 5), /* class type */ + 0x02, transferDir); + break; + + case TS_URB_RESERVE_0X001D: /** 0x001D */ + break; + + case TS_URB_SYNC_RESET_PIPE_AND_CLEAR_STALL: /** 0x001E */ + error = urb_pipe_request(pdev, callback, s, RequestId, MessageId, udevman, transferDir, + PIPE_RESET); + break; + + case TS_URB_CLASS_OTHER: /** 0x001F */ + error = urb_control_vendor_or_class_request(pdev, callback, s, RequestId, MessageId, + udevman, (0x01 << 5), /* class type */ + 0x03, transferDir); + break; + + case TS_URB_VENDOR_OTHER: /** 0x0020 */ + error = urb_control_vendor_or_class_request(pdev, callback, s, RequestId, MessageId, + udevman, (0x02 << 5), /* vendor type */ + 0x03, transferDir); + break; + + case TS_URB_GET_STATUS_FROM_OTHER: /** 0x0021 */ + error = urb_control_get_status_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x03, transferDir); + break; + + case TS_URB_CLEAR_FEATURE_TO_OTHER: /** 0x0022 */ + error = urb_control_feature_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x03, URB_CLEAR_FEATURE, transferDir); + break; + + case TS_URB_SET_FEATURE_TO_OTHER: /** 0x0023 */ + error = urb_control_feature_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x03, URB_SET_FEATURE, transferDir); + break; + + case TS_URB_GET_DESCRIPTOR_FROM_ENDPOINT: /** 0x0024 */ + error = urb_control_descriptor_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x02, transferDir); + break; + + case TS_URB_SET_DESCRIPTOR_TO_ENDPOINT: /** 0x0025 */ + error = urb_control_descriptor_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x02, transferDir); + break; + + case TS_URB_CONTROL_GET_CONFIGURATION_REQUEST: /** 0x0026 */ + error = urb_control_get_configuration_request(pdev, callback, s, RequestId, MessageId, + udevman, transferDir); + break; + + case TS_URB_CONTROL_GET_INTERFACE_REQUEST: /** 0x0027 */ + error = urb_control_get_interface_request(pdev, callback, s, RequestId, MessageId, + udevman, transferDir); + break; + + case TS_URB_GET_DESCRIPTOR_FROM_INTERFACE: /** 0x0028 */ + error = urb_control_descriptor_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x01, transferDir); + break; + + case TS_URB_SET_DESCRIPTOR_TO_INTERFACE: /** 0x0029 */ + error = urb_control_descriptor_request(pdev, callback, s, RequestId, MessageId, udevman, + 0x01, transferDir); + break; + + case TS_URB_GET_OS_FEATURE_DESCRIPTOR_REQUEST: /** 0x002A */ + error = urb_os_feature_descriptor_request(pdev, callback, s, RequestId, MessageId, + udevman, transferDir); + break; + + case TS_URB_RESERVE_0X002B: /** 0x002B */ + case TS_URB_RESERVE_0X002C: /** 0x002C */ + case TS_URB_RESERVE_0X002D: /** 0x002D */ + case TS_URB_RESERVE_0X002E: /** 0x002E */ + case TS_URB_RESERVE_0X002F: /** 0x002F */ + break; + + /** USB 2.0 calls start at 0x0030 */ + case TS_URB_SYNC_RESET_PIPE: /** 0x0030 */ + error = urb_pipe_request(pdev, callback, s, RequestId, MessageId, udevman, transferDir, + PIPE_RESET); + break; + + case TS_URB_SYNC_CLEAR_STALL: /** 0x0031 */ + urb_pipe_request(pdev, callback, s, RequestId, MessageId, udevman, transferDir, + PIPE_RESET); + break; + + case TS_URB_CONTROL_TRANSFER_EX: /** 0x0032 */ + error = urb_control_transfer(pdev, callback, s, RequestId, MessageId, udevman, + transferDir, URB_CONTROL_TRANSFER_EXTERNAL); + break; + + default: + WLog_Print(urbdrc->log, WLOG_DEBUG, "URB_Func: %" PRIx16 " is not found!", + URB_Function); + break; + } + + if (error) + { + WLog_Print(urbdrc->log, WLOG_WARN, + "USB transfer request URB Function '%s' [0x%08x] failed with %08" PRIx32, + urb_function_string(URB_Function), URB_Function, error); + } + + return error; +} + +UINT urbdrc_process_udev_data_transfer(GENERIC_CHANNEL_CALLBACK* callback, URBDRC_PLUGIN* urbdrc, + IUDEVMAN* udevman, wStream* data) +{ + UINT32 InterfaceId = 0; + UINT32 MessageId = 0; + UINT32 FunctionId = 0; + IUDEVICE* pdev = nullptr; + UINT error = ERROR_INTERNAL_ERROR; + + if (!urbdrc || !data || !callback || !udevman) + goto fail; + + if (!Stream_CheckAndLogRequiredLength(TAG, data, 8)) + goto fail; + + Stream_Rewind_UINT32(data); + + Stream_Read_UINT32(data, InterfaceId); + Stream_Read_UINT32(data, MessageId); + Stream_Read_UINT32(data, FunctionId); + + pdev = udevman->get_udevice_by_UsbDevice(udevman, InterfaceId); + + /* Device does not exist, ignore this request. */ + if (pdev == nullptr) + { + error = ERROR_SUCCESS; + goto fail; + } + + /* Device has been removed, ignore this request. */ + if (pdev->isChannelClosed(pdev)) + { + error = ERROR_SUCCESS; + goto fail; + } + + /* USB kernel driver detach!! */ + if (!pdev->detach_kernel_driver(pdev)) + { + error = ERROR_SUCCESS; + goto fail; + } + + switch (FunctionId) + { + case CANCEL_REQUEST: + error = urbdrc_process_cancel_request(pdev, data, udevman); + break; + + case REGISTER_REQUEST_CALLBACK: + error = urbdrc_process_register_request_callback(pdev, callback, data, udevman); + break; + + case IO_CONTROL: + error = urbdrc_process_io_control(pdev, callback, data, MessageId, udevman); + break; + + case INTERNAL_IO_CONTROL: + error = urbdrc_process_internal_io_control(pdev, callback, data, MessageId, udevman); + break; + + case QUERY_DEVICE_TEXT: + error = urbdrc_process_query_device_text(pdev, callback, data, MessageId, udevman); + break; + + case TRANSFER_IN_REQUEST: + error = urbdrc_process_transfer_request(pdev, callback, data, MessageId, udevman, + USBD_TRANSFER_DIRECTION_IN); + break; + + case TRANSFER_OUT_REQUEST: + error = urbdrc_process_transfer_request(pdev, callback, data, MessageId, udevman, + USBD_TRANSFER_DIRECTION_OUT); + break; + + case RETRACT_DEVICE: + error = urbdrc_process_retract_device_request(pdev, data, udevman); + break; + + default: + WLog_Print(urbdrc->log, WLOG_WARN, + "urbdrc_process_udev_data_transfer:" + " unknown FunctionId 0x%" PRIX32 "", + FunctionId); + break; + } + +fail: + if (error) + { + WLog_WARN(TAG, "USB request failed with %08" PRIx32, error); + } + + return error; +} diff --git a/third_party/FreeRDP/channels/urbdrc/client/data_transfer.h b/third_party/FreeRDP/channels/urbdrc/client/data_transfer.h new file mode 100644 index 0000000..f10d597 --- /dev/null +++ b/third_party/FreeRDP/channels/urbdrc/client/data_transfer.h @@ -0,0 +1,38 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX USB Redirection + * + * Copyright 2012 Atrust corp. + * Copyright 2012 Alfred Liu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_URBDRC_CLIENT_DATA_TRANSFER_H +#define FREERDP_CHANNEL_URBDRC_CLIENT_DATA_TRANSFER_H + +#include + +#include "urbdrc_main.h" + +#define DEVICE_CTX(dev) ((dev)->ctx) +#define HANDLE_CTX(handle) (DEVICE_CTX((handle)->dev)) +#define TRANSFER_CTX(transfer) (HANDLE_CTX((transfer)->dev_handle)) +#define ITRANSFER_CTX(transfer) (TRANSFER_CTX(__USBI_TRANSFER_TO_LIBUSB_TRANSFER(transfer))) + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT urbdrc_process_udev_data_transfer(GENERIC_CHANNEL_CALLBACK* callback, + URBDRC_PLUGIN* urbdrc, IUDEVMAN* udevman, + wStream* data); + +#endif /* FREERDP_CHANNEL_URBDRC_CLIENT_DATA_TRANSFER_H */ diff --git a/third_party/FreeRDP/channels/urbdrc/client/libusb/CMakeLists.txt b/third_party/FreeRDP/channels/urbdrc/client/libusb/CMakeLists.txt new file mode 100644 index 0000000..5de9381 --- /dev/null +++ b/third_party/FreeRDP/channels/urbdrc/client/libusb/CMakeLists.txt @@ -0,0 +1,27 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Atrust corp. +# Copyright 2012 Alfred Liu +# +# 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. + +define_channel_client_subsystem("urbdrc" "libusb" "") + +set(${MODULE_PREFIX}_SRCS libusb_udevman.c libusb_udevice.c libusb_udevice.h) + +set(${MODULE_PREFIX}_LIBS ${CMAKE_THREAD_LIBS_INIT} ${LIBUSB_1_LIBRARIES} winpr freerdp) + +include_directories(..) + +add_channel_client_subsystem_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} "" TRUE "") diff --git a/third_party/FreeRDP/channels/urbdrc/client/libusb/libusb_udevice.c b/third_party/FreeRDP/channels/urbdrc/client/libusb/libusb_udevice.c new file mode 100644 index 0000000..d70fab4 --- /dev/null +++ b/third_party/FreeRDP/channels/urbdrc/client/libusb/libusb_udevice.c @@ -0,0 +1,1921 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX USB Redirection + * + * Copyright 2012 Atrust corp. + * Copyright 2012 Alfred Liu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "libusb_udevice.h" +#include "msusb.h" +#include "../common/urbdrc_types.h" + +#define BASIC_STATE_FUNC_DEFINED(_arg, _type) \ + static _type udev_get_##_arg(IUDEVICE* idev) \ + { \ + UDEVICE* pdev = (UDEVICE*)idev; \ + return pdev->_arg; \ + } \ + static void udev_set_##_arg(IUDEVICE* idev, _type _t) \ + { \ + UDEVICE* pdev = (UDEVICE*)idev; \ + pdev->_arg = _t; \ + } + +#define BASIC_POINT_FUNC_DEFINED(_arg, _type) \ + static _type udev_get_p_##_arg(IUDEVICE* idev) \ + { \ + UDEVICE* pdev = (UDEVICE*)idev; \ + return pdev->_arg; \ + } \ + static void udev_set_p_##_arg(IUDEVICE* idev, _type _t) \ + { \ + UDEVICE* pdev = (UDEVICE*)idev; \ + pdev->_arg = _t; \ + } + +#define BASIC_STATE_FUNC_REGISTER(_arg, _dev) \ + _dev->iface.get_##_arg = udev_get_##_arg; \ + (_dev)->iface.set_##_arg = udev_set_##_arg + +#if LIBUSB_API_VERSION >= 0x01000103 +#define HAVE_STREAM_ID_API 1 +#endif + +typedef struct +{ + wStream* data; + BOOL noack; + UINT32 MessageId; + UINT32 StartFrame; + UINT32 ErrorCount; + IUDEVICE* idev; + UINT32 OutputBufferSize; + GENERIC_CHANNEL_CALLBACK* callback; + t_isoch_transfer_cb cb; + wArrayList* queue; +#if !defined(HAVE_STREAM_ID_API) + UINT32 streamID; +#endif +} ASYNC_TRANSFER_USER_DATA; + +static void request_free(void* value); + +static struct libusb_transfer* list_contains(wArrayList* list, UINT32 streamID) +{ + size_t count = 0; + if (!list) + return nullptr; + count = ArrayList_Count(list); + for (size_t x = 0; x < count; x++) + { + struct libusb_transfer* transfer = ArrayList_GetItem(list, x); + +#if defined(HAVE_STREAM_ID_API) + const UINT32 currentID = libusb_transfer_get_stream_id(transfer); +#else + const ASYNC_TRANSFER_USER_DATA* user_data = (ASYNC_TRANSFER_USER_DATA*)transfer->user_data; + const UINT32 currentID = user_data->streamID; +#endif + if (currentID == streamID) + return transfer; + } + return nullptr; +} + +static UINT32 stream_id_from_buffer(struct libusb_transfer* transfer) +{ + if (!transfer) + return 0; +#if defined(HAVE_STREAM_ID_API) + return libusb_transfer_get_stream_id(transfer); +#else + ASYNC_TRANSFER_USER_DATA* user_data = (ASYNC_TRANSFER_USER_DATA*)transfer->user_data; + if (!user_data) + return 0; + return user_data->streamID; +#endif +} + +static void set_stream_id_for_buffer(struct libusb_transfer* transfer, UINT32 streamID) +{ +#if defined(HAVE_STREAM_ID_API) + libusb_transfer_set_stream_id(transfer, streamID); +#else + ASYNC_TRANSFER_USER_DATA* user_data = (ASYNC_TRANSFER_USER_DATA*)transfer->user_data; + if (!user_data) + return; + user_data->streamID = streamID; +#endif +} + +WINPR_ATTR_FORMAT_ARG(3, 8) +static BOOL log_libusb_result_(wLog* log, DWORD lvl, WINPR_FORMAT_ARG const char* fmt, + const char* fkt, const char* file, size_t line, int error, ...) +{ + WINPR_UNUSED(file); + + if (error < 0) + { + char buffer[8192] = WINPR_C_ARRAY_INIT; + va_list ap = WINPR_C_ARRAY_INIT; + va_start(ap, error); + (void)vsnprintf(buffer, sizeof(buffer), fmt, ap); + va_end(ap); + + WLog_Print(log, lvl, "[%s:%" PRIuz "]: %s: error %s[%d]", fkt, line, buffer, + libusb_error_name(error), error); + return TRUE; + } + return FALSE; +} + +#define log_libusb_result(log, lvl, fmt, error, ...) \ + log_libusb_result_((log), (lvl), (fmt), __func__, __FILE__, __LINE__, error, ##__VA_ARGS__) + +const char* usb_interface_class_to_string(uint8_t c_class) +{ + switch (c_class) + { + case LIBUSB_CLASS_PER_INTERFACE: + return "LIBUSB_CLASS_PER_INTERFACE"; + case LIBUSB_CLASS_AUDIO: + return "LIBUSB_CLASS_AUDIO"; + case LIBUSB_CLASS_COMM: + return "LIBUSB_CLASS_COMM"; + case LIBUSB_CLASS_HID: + return "LIBUSB_CLASS_HID"; + case LIBUSB_CLASS_PHYSICAL: + return "LIBUSB_CLASS_PHYSICAL"; + case LIBUSB_CLASS_PRINTER: + return "LIBUSB_CLASS_PRINTER"; + case LIBUSB_CLASS_IMAGE: + return "LIBUSB_CLASS_IMAGE"; + case LIBUSB_CLASS_MASS_STORAGE: + return "LIBUSB_CLASS_MASS_STORAGE"; + case LIBUSB_CLASS_HUB: + return "LIBUSB_CLASS_HUB"; + case LIBUSB_CLASS_DATA: + return "LIBUSB_CLASS_DATA"; + case LIBUSB_CLASS_SMART_CARD: + return "LIBUSB_CLASS_SMART_CARD"; + case LIBUSB_CLASS_CONTENT_SECURITY: + return "LIBUSB_CLASS_CONTENT_SECURITY"; + case LIBUSB_CLASS_VIDEO: + return "LIBUSB_CLASS_VIDEO"; + case LIBUSB_CLASS_PERSONAL_HEALTHCARE: + return "LIBUSB_CLASS_PERSONAL_HEALTHCARE"; + case LIBUSB_CLASS_DIAGNOSTIC_DEVICE: + return "LIBUSB_CLASS_DIAGNOSTIC_DEVICE"; + case LIBUSB_CLASS_WIRELESS: + return "LIBUSB_CLASS_WIRELESS"; + case LIBUSB_CLASS_APPLICATION: + return "LIBUSB_CLASS_APPLICATION"; + case LIBUSB_CLASS_VENDOR_SPEC: + return "LIBUSB_CLASS_VENDOR_SPEC"; + default: + return "UNKNOWN_DEVICE_CLASS"; + } +} + +static ASYNC_TRANSFER_USER_DATA* async_transfer_user_data_new(IUDEVICE* idev, UINT32 MessageId, + size_t offset, size_t BufferSize, + const BYTE* data, size_t packetSize, + BOOL NoAck, t_isoch_transfer_cb cb, + GENERIC_CHANNEL_CALLBACK* callback) +{ + ASYNC_TRANSFER_USER_DATA* user_data = nullptr; + UDEVICE* pdev = (UDEVICE*)idev; + + if (BufferSize > UINT32_MAX) + return nullptr; + + user_data = calloc(1, sizeof(ASYNC_TRANSFER_USER_DATA)); + if (!user_data) + return nullptr; + + user_data->data = Stream_New(nullptr, offset + BufferSize + packetSize); + + if (!user_data->data) + { + free(user_data); + return nullptr; + } + + Stream_Seek(user_data->data, offset); /* Skip header offset */ + if (data) + memcpy(Stream_Pointer(user_data->data), data, BufferSize); + else + user_data->OutputBufferSize = (UINT32)BufferSize; + + user_data->noack = NoAck; + user_data->cb = cb; + user_data->callback = callback; + user_data->idev = idev; + user_data->MessageId = MessageId; + + user_data->queue = pdev->request_queue; + + return user_data; +} + +static void async_transfer_user_data_free(ASYNC_TRANSFER_USER_DATA* user_data) +{ + if (user_data) + { + Stream_Free(user_data->data, TRUE); + free(user_data); + } +} + +static void LIBUSB_CALL func_iso_callback(struct libusb_transfer* transfer) +{ + ASYNC_TRANSFER_USER_DATA* user_data = (ASYNC_TRANSFER_USER_DATA*)transfer->user_data; + const UINT32 streamID = stream_id_from_buffer(transfer); + wArrayList* list = user_data->queue; + + ArrayList_Lock(list); + switch (transfer->status) + { + case LIBUSB_TRANSFER_COMPLETED: + { + UINT32 index = 0; + BYTE* dataStart = Stream_Pointer(user_data->data); + Stream_SetPosition(user_data->data, + 40); /* TS_URB_ISOCH_TRANSFER_RESULT IsoPacket offset */ + + for (uint32_t i = 0; i < WINPR_ASSERTING_INT_CAST(uint32_t, transfer->num_iso_packets); + i++) + { + const UINT32 act_len = transfer->iso_packet_desc[i].actual_length; + Stream_Write_UINT32(user_data->data, index); + Stream_Write_UINT32(user_data->data, act_len); + Stream_Write_UINT32(user_data->data, transfer->iso_packet_desc[i].status); + + if (transfer->iso_packet_desc[i].status != USBD_STATUS_SUCCESS) + user_data->ErrorCount++; + else + { + const unsigned char* packetBuffer = + libusb_get_iso_packet_buffer_simple(transfer, i); + BYTE* data = dataStart + index; + + if (data != packetBuffer) + memmove(data, packetBuffer, act_len); + + index += act_len; + } + } + } + /* fallthrough */ + WINPR_FALLTHROUGH + case LIBUSB_TRANSFER_CANCELLED: + /* fallthrough */ + WINPR_FALLTHROUGH + case LIBUSB_TRANSFER_TIMED_OUT: + /* fallthrough */ + WINPR_FALLTHROUGH + case LIBUSB_TRANSFER_ERROR: + { + const UINT32 InterfaceId = + ((STREAM_ID_PROXY << 30) | user_data->idev->get_ReqCompletion(user_data->idev)); + + if (list_contains(list, streamID)) + { + if (!user_data->noack) + { + const UINT32 RequestID = streamID & INTERFACE_ID_MASK; + user_data->cb(user_data->idev, user_data->callback, user_data->data, + InterfaceId, user_data->noack, user_data->MessageId, RequestID, + WINPR_ASSERTING_INT_CAST(uint32_t, transfer->num_iso_packets), + transfer->status, user_data->StartFrame, user_data->ErrorCount, + user_data->OutputBufferSize); + user_data->data = nullptr; + } + ArrayList_Remove(list, transfer); + } + } + break; + default: + break; + } + ArrayList_Unlock(list); +} + +static const LIBUSB_ENDPOINT_DESCEIPTOR* func_get_ep_desc(LIBUSB_CONFIG_DESCRIPTOR* LibusbConfig, + MSUSB_CONFIG_DESCRIPTOR* MsConfig, + UINT32 EndpointAddress) +{ + MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces = MsConfig->MsInterfaces; + const LIBUSB_INTERFACE* interface = LibusbConfig->interface; + + for (UINT32 inum = 0; inum < MsConfig->NumInterfaces; inum++) + { + BYTE alt = MsInterfaces[inum]->AlternateSetting; + const LIBUSB_ENDPOINT_DESCEIPTOR* endpoint = interface[inum].altsetting[alt].endpoint; + + for (UINT32 pnum = 0; pnum < MsInterfaces[inum]->NumberOfPipes; pnum++) + { + if (endpoint[pnum].bEndpointAddress == EndpointAddress) + { + return &endpoint[pnum]; + } + } + } + + return nullptr; +} + +static void LIBUSB_CALL func_bulk_transfer_cb(struct libusb_transfer* transfer) +{ + ASYNC_TRANSFER_USER_DATA* user_data = nullptr; + uint32_t streamID = 0; + wArrayList* list = nullptr; + + user_data = (ASYNC_TRANSFER_USER_DATA*)transfer->user_data; + if (!user_data) + { + WLog_ERR(TAG, "Invalid transfer->user_data!"); + return; + } + list = user_data->queue; + ArrayList_Lock(list); + streamID = stream_id_from_buffer(transfer); + + if (list_contains(list, streamID)) + { + const UINT32 InterfaceId = + ((STREAM_ID_PROXY << 30) | user_data->idev->get_ReqCompletion(user_data->idev)); + const UINT32 RequestID = streamID & INTERFACE_ID_MASK; + + user_data->cb(user_data->idev, user_data->callback, user_data->data, InterfaceId, + user_data->noack, user_data->MessageId, RequestID, + WINPR_ASSERTING_INT_CAST(uint32_t, transfer->num_iso_packets), + transfer->status, user_data->StartFrame, user_data->ErrorCount, + WINPR_ASSERTING_INT_CAST(uint32_t, transfer->actual_length)); + user_data->data = nullptr; + ArrayList_Remove(list, transfer); + } + ArrayList_Unlock(list); +} + +static BOOL func_set_usbd_status(URBDRC_PLUGIN* urbdrc, UDEVICE* pdev, UINT32* status, + int err_result) +{ + if (!urbdrc || !status) + return FALSE; + + switch (err_result) + { + case LIBUSB_SUCCESS: + *status = USBD_STATUS_SUCCESS; + break; + + case LIBUSB_ERROR_IO: + *status = USBD_STATUS_STALL_PID; + break; + + case LIBUSB_ERROR_INVALID_PARAM: + *status = USBD_STATUS_INVALID_PARAMETER; + break; + + case LIBUSB_ERROR_ACCESS: + *status = USBD_STATUS_NOT_ACCESSED; + break; + + case LIBUSB_ERROR_NO_DEVICE: + *status = USBD_STATUS_DEVICE_GONE; + + if (pdev) + { + if (!(pdev->status & URBDRC_DEVICE_NOT_FOUND)) + pdev->status |= URBDRC_DEVICE_NOT_FOUND; + } + + break; + + case LIBUSB_ERROR_NOT_FOUND: + *status = USBD_STATUS_STALL_PID; + break; + + case LIBUSB_ERROR_BUSY: + *status = USBD_STATUS_STALL_PID; + break; + + case LIBUSB_ERROR_TIMEOUT: + *status = USBD_STATUS_TIMEOUT; + break; + + case LIBUSB_ERROR_OVERFLOW: + *status = USBD_STATUS_STALL_PID; + break; + + case LIBUSB_ERROR_PIPE: + *status = USBD_STATUS_STALL_PID; + break; + + case LIBUSB_ERROR_INTERRUPTED: + *status = USBD_STATUS_STALL_PID; + break; + + case LIBUSB_ERROR_NO_MEM: + *status = USBD_STATUS_NO_MEMORY; + break; + + case LIBUSB_ERROR_NOT_SUPPORTED: + *status = USBD_STATUS_NOT_SUPPORTED; + break; + + case LIBUSB_ERROR_OTHER: + *status = USBD_STATUS_STALL_PID; + break; + + default: + *status = USBD_STATUS_SUCCESS; + break; + } + + return TRUE; +} + +static int func_config_release_all_interface(URBDRC_PLUGIN* urbdrc, + LIBUSB_DEVICE_HANDLE* libusb_handle, + UINT32 NumInterfaces) +{ + if (NumInterfaces > INT32_MAX) + return -1; + for (INT32 i = 0; i < (INT32)NumInterfaces; i++) + { + int ret = libusb_release_interface(libusb_handle, i); + + if (log_libusb_result(urbdrc->log, WLOG_WARN, "libusb_release_interface", ret)) + return -1; + } + + return 0; +} + +static int func_claim_all_interface(URBDRC_PLUGIN* urbdrc, LIBUSB_DEVICE_HANDLE* libusb_handle, + int NumInterfaces) +{ + int ret = 0; + + for (int i = 0; i < NumInterfaces; i++) + { + ret = libusb_claim_interface(libusb_handle, i); + + if (log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_claim_interface", ret)) + return -1; + } + + return 0; +} + +static LIBUSB_DEVICE* udev_get_libusb_dev(libusb_context* context, uint8_t bus_number, + uint8_t dev_number) +{ + LIBUSB_DEVICE** libusb_list = nullptr; + LIBUSB_DEVICE* device = nullptr; + const ssize_t total_device = libusb_get_device_list(context, &libusb_list); + + for (ssize_t i = 0; i < total_device; i++) + { + LIBUSB_DEVICE* dev = libusb_list[i]; + if ((bus_number == libusb_get_bus_number(dev)) && + (dev_number == libusb_get_device_address(dev))) + device = dev; + else + libusb_unref_device(dev); + } + + libusb_free_device_list(libusb_list, 0); + return device; +} + +static LIBUSB_DEVICE_DESCRIPTOR* udev_new_descript(URBDRC_PLUGIN* urbdrc, LIBUSB_DEVICE* libusb_dev) +{ + int ret = 0; + LIBUSB_DEVICE_DESCRIPTOR* descriptor = + (LIBUSB_DEVICE_DESCRIPTOR*)calloc(1, sizeof(LIBUSB_DEVICE_DESCRIPTOR)); + if (!descriptor) + return nullptr; + ret = libusb_get_device_descriptor(libusb_dev, descriptor); + + if (log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_get_device_descriptor", ret)) + { + free(descriptor); + return nullptr; + } + + return descriptor; +} + +static int libusb_udev_select_interface(IUDEVICE* idev, BYTE InterfaceNumber, BYTE AlternateSetting) +{ + int error = 0; + int diff = 0; + UDEVICE* pdev = (UDEVICE*)idev; + + if (!pdev || !pdev->urbdrc) + return -1; + + URBDRC_PLUGIN* urbdrc = pdev->urbdrc; + MSUSB_CONFIG_DESCRIPTOR* MsConfig = pdev->MsConfig; + + if (MsConfig) + { + if (InterfaceNumber >= MsConfig->NumInterfaces) + return -2; + + MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces = MsConfig->MsInterfaces; + if (MsInterfaces) + { + const MSUSB_INTERFACE_DESCRIPTOR* ifc = MsInterfaces[InterfaceNumber]; + if (!ifc) + return -3; + + WLog_Print(urbdrc->log, WLOG_INFO, + "select Interface(%" PRIu8 ") curr AlternateSetting(%" PRIu8 + ") new AlternateSetting(%" PRIu8 ")", + InterfaceNumber, ifc->AlternateSetting, AlternateSetting); + + if (ifc->AlternateSetting != AlternateSetting) + { + diff = 1; + } + } + + if (diff) + { + error = libusb_set_interface_alt_setting(pdev->libusb_handle, InterfaceNumber, + AlternateSetting); + + log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_set_interface_alt_setting", error); + } + } + + return error; +} + +static MSUSB_CONFIG_DESCRIPTOR* +libusb_udev_complete_msconfig_setup(IUDEVICE* idev, MSUSB_CONFIG_DESCRIPTOR* MsConfig) +{ + UDEVICE* pdev = (UDEVICE*)idev; + UINT32 MsOutSize = 0; + + if (!pdev || !pdev->LibusbConfig || !pdev->urbdrc || !MsConfig) + return nullptr; + + URBDRC_PLUGIN* urbdrc = pdev->urbdrc; + LIBUSB_CONFIG_DESCRIPTOR* LibusbConfig = pdev->LibusbConfig; + + if (LibusbConfig->bNumInterfaces != MsConfig->NumInterfaces) + { + WLog_Print(urbdrc->log, WLOG_ERROR, + "Select Configuration: Libusb NumberInterfaces(%" PRIu8 ") is different " + "with MsConfig NumberInterfaces(%" PRIu32 ")", + LibusbConfig->bNumInterfaces, MsConfig->NumInterfaces); + return nullptr; + } + + /* replace MsPipes for libusb */ + MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces = MsConfig->MsInterfaces; + + for (UINT32 inum = 0; inum < MsConfig->NumInterfaces; inum++) + { + MSUSB_INTERFACE_DESCRIPTOR* MsInterface = MsInterfaces[inum]; + if (MsInterface->InterfaceNumber >= MsConfig->NumInterfaces) + { + WLog_Print(urbdrc->log, WLOG_ERROR, + "MSUSB_CONFIG_DESCRIPTOR::NumInterfaces (%" PRIu32 + " <= MSUSB_INTERFACE_DESCRIPTOR::InterfaceNumber( %" PRIu8 ")", + MsConfig->NumInterfaces, MsInterface->InterfaceNumber); + return nullptr; + } + + const LIBUSB_INTERFACE* LibusbInterface = + &LibusbConfig->interface[MsInterface->InterfaceNumber]; + if (MsInterface->AlternateSetting >= LibusbInterface->num_altsetting) + { + WLog_Print(urbdrc->log, WLOG_ERROR, + "LIBUSB_INTERFACE::num_altsetting (%" PRId32 + " <= MSUSB_INTERFACE_DESCRIPTOR::AlternateSetting( %" PRIu8 ")", + LibusbInterface->num_altsetting, MsInterface->AlternateSetting); + return nullptr; + } + } + + for (UINT32 inum = 0; inum < MsConfig->NumInterfaces; inum++) + { + MSUSB_INTERFACE_DESCRIPTOR* MsInterface = MsInterfaces[inum]; + + /* get libusb's number of endpoints */ + const LIBUSB_INTERFACE* LibusbInterface = + &LibusbConfig->interface[MsInterface->InterfaceNumber]; + const LIBUSB_INTERFACE_DESCRIPTOR* LibusbAltsetting = + &LibusbInterface->altsetting[MsInterface->AlternateSetting]; + const BYTE LibusbNumEndpoint = LibusbAltsetting->bNumEndpoints; + MSUSB_PIPE_DESCRIPTOR** t_MsPipes = + (MSUSB_PIPE_DESCRIPTOR**)calloc(LibusbNumEndpoint, sizeof(MSUSB_PIPE_DESCRIPTOR*)); + + for (UINT32 pnum = 0; pnum < LibusbNumEndpoint; pnum++) + { + MSUSB_PIPE_DESCRIPTOR* t_MsPipe = + (MSUSB_PIPE_DESCRIPTOR*)calloc(1, sizeof(MSUSB_PIPE_DESCRIPTOR)); + + if (pnum < MsInterface->NumberOfPipes && MsInterface->MsPipes) + { + MSUSB_PIPE_DESCRIPTOR* MsPipe = MsInterface->MsPipes[pnum]; + t_MsPipe->MaximumPacketSize = MsPipe->MaximumPacketSize; + t_MsPipe->MaximumTransferSize = MsPipe->MaximumTransferSize; + t_MsPipe->PipeFlags = MsPipe->PipeFlags; + } + else + { + t_MsPipe->MaximumPacketSize = 0; + t_MsPipe->MaximumTransferSize = 0xffffffff; + t_MsPipe->PipeFlags = 0; + } + + t_MsPipe->PipeHandle = 0; + t_MsPipe->bEndpointAddress = 0; + t_MsPipe->bInterval = 0; + t_MsPipe->PipeType = 0; + t_MsPipe->InitCompleted = 0; + t_MsPipes[pnum] = t_MsPipe; + } + + msusb_mspipes_replace(MsInterface, t_MsPipes, LibusbNumEndpoint); + } + + /* setup configuration */ + MsOutSize = 8; + /* ConfigurationHandle: 4 bytes + * --------------------------------------------------------------- + * ||<<< 1 byte >>>|<<< 1 byte >>>|<<<<<<<<<< 2 byte >>>>>>>>>>>|| + * || bus_number | dev_number | bConfigurationValue || + * --------------------------------------------------------------- + * ***********************/ + MsConfig->ConfigurationHandle = (uint32_t)MsConfig->bConfigurationValue | + ((uint32_t)pdev->bus_number << 24) | + (((uint32_t)pdev->dev_number << 16) & 0xFF0000); + MsInterfaces = MsConfig->MsInterfaces; + + for (UINT32 inum = 0; inum < MsConfig->NumInterfaces; inum++) + { + MsOutSize += 16; + MSUSB_INTERFACE_DESCRIPTOR* MsInterface = MsInterfaces[inum]; + /* get libusb's interface */ + const LIBUSB_INTERFACE* LibusbInterface = + &LibusbConfig->interface[MsInterface->InterfaceNumber]; + const LIBUSB_INTERFACE_DESCRIPTOR* LibusbAltsetting = + &LibusbInterface->altsetting[MsInterface->AlternateSetting]; + /* InterfaceHandle: 4 bytes + * --------------------------------------------------------------- + * ||<<< 1 byte >>>|<<< 1 byte >>>|<<< 1 byte >>>|<<< 1 byte >>>|| + * || bus_number | dev_number | altsetting | interfaceNum || + * --------------------------------------------------------------- + * ***********************/ + MsInterface->InterfaceHandle = + WINPR_ASSERTING_INT_CAST(UINT32, (LibusbAltsetting->bInterfaceNumber | + (LibusbAltsetting->bAlternateSetting << 8) | + (pdev->dev_number << 16) | (pdev->bus_number << 24))); + const size_t len = 16 + (MsInterface->NumberOfPipes * 20); + MsInterface->Length = WINPR_ASSERTING_INT_CAST(UINT16, len); + MsInterface->bInterfaceClass = LibusbAltsetting->bInterfaceClass; + MsInterface->bInterfaceSubClass = LibusbAltsetting->bInterfaceSubClass; + MsInterface->bInterfaceProtocol = LibusbAltsetting->bInterfaceProtocol; + MsInterface->InitCompleted = 1; + MSUSB_PIPE_DESCRIPTOR** MsPipes = MsInterface->MsPipes; + const BYTE LibusbNumEndpoint = LibusbAltsetting->bNumEndpoints; + + for (UINT32 pnum = 0; pnum < LibusbNumEndpoint; pnum++) + { + MsOutSize += 20; + + MSUSB_PIPE_DESCRIPTOR* MsPipe = MsPipes[pnum]; + /* get libusb's endpoint */ + const LIBUSB_ENDPOINT_DESCEIPTOR* LibusbEndpoint = &LibusbAltsetting->endpoint[pnum]; + /* PipeHandle: 4 bytes + * --------------------------------------------------------------- + * ||<<< 1 byte >>>|<<< 1 byte >>>|<<<<<<<<<< 2 byte >>>>>>>>>>>|| + * || bus_number | dev_number | bEndpointAddress || + * --------------------------------------------------------------- + * ***********************/ + MsPipe->PipeHandle = LibusbEndpoint->bEndpointAddress | + (((uint32_t)pdev->dev_number << 16) & 0xFF0000) | + (((uint32_t)pdev->bus_number << 24) & 0xFF000000); + /* count endpoint max packet size */ + unsigned max = LibusbEndpoint->wMaxPacketSize & 0x07ff; + BYTE attr = LibusbEndpoint->bmAttributes; + + if ((attr & 0x3) == 1 || (attr & 0x3) == 3) + { + max *= (1 + ((LibusbEndpoint->wMaxPacketSize >> 11) & 3)); + } + + MsPipe->MaximumPacketSize = WINPR_ASSERTING_INT_CAST(uint16_t, max); + MsPipe->bEndpointAddress = LibusbEndpoint->bEndpointAddress; + MsPipe->bInterval = LibusbEndpoint->bInterval; + MsPipe->PipeType = attr & 0x3; + MsPipe->InitCompleted = 1; + } + } + + MsConfig->MsOutSize = WINPR_ASSERTING_INT_CAST(int, MsOutSize); + MsConfig->InitCompleted = 1; + + /* replace device's MsConfig */ + if (MsConfig != pdev->MsConfig) + { + msusb_msconfig_free(pdev->MsConfig); + pdev->MsConfig = MsConfig; + } + + return MsConfig; +} + +static int libusb_udev_select_configuration(IUDEVICE* idev, UINT32 bConfigurationValue) +{ + UDEVICE* pdev = (UDEVICE*)idev; + MSUSB_CONFIG_DESCRIPTOR* MsConfig = nullptr; + LIBUSB_DEVICE_HANDLE* libusb_handle = nullptr; + LIBUSB_DEVICE* libusb_dev = nullptr; + URBDRC_PLUGIN* urbdrc = nullptr; + LIBUSB_CONFIG_DESCRIPTOR** LibusbConfig = nullptr; + int ret = 0; + + if (!pdev || !pdev->MsConfig || !pdev->LibusbConfig || !pdev->urbdrc) + return -1; + + urbdrc = pdev->urbdrc; + MsConfig = pdev->MsConfig; + libusb_handle = pdev->libusb_handle; + libusb_dev = pdev->libusb_dev; + LibusbConfig = &pdev->LibusbConfig; + + if (MsConfig->InitCompleted) + { + func_config_release_all_interface(pdev->urbdrc, libusb_handle, + (*LibusbConfig)->bNumInterfaces); + } + + /* The configuration value -1 is mean to put the device in unconfigured state. */ + if (bConfigurationValue == 0) + ret = libusb_set_configuration(libusb_handle, -1); + else + ret = libusb_set_configuration(libusb_handle, + WINPR_ASSERTING_INT_CAST(int, bConfigurationValue)); + + if (log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_set_configuration", ret)) + { + func_claim_all_interface(urbdrc, libusb_handle, (*LibusbConfig)->bNumInterfaces); + return -1; + } + else + { + ret = libusb_get_active_config_descriptor(libusb_dev, LibusbConfig); + + if (log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_set_configuration", ret)) + { + func_claim_all_interface(urbdrc, libusb_handle, (*LibusbConfig)->bNumInterfaces); + return -1; + } + } + + func_claim_all_interface(urbdrc, libusb_handle, (*LibusbConfig)->bNumInterfaces); + return 0; +} + +static int libusb_udev_control_pipe_request(IUDEVICE* idev, WINPR_ATTR_UNUSED UINT32 RequestId, + UINT32 EndpointAddress, UINT32* UsbdStatus, int command) +{ + int error = 0; + UDEVICE* pdev = (UDEVICE*)idev; + + WINPR_ASSERT(EndpointAddress <= UINT8_MAX); + /* + pdev->request_queue->register_request(pdev->request_queue, RequestId, nullptr, 0); + */ + switch (command) + { + case PIPE_CANCEL: + /** cancel bulk or int transfer */ + idev->cancel_all_transfer_request(idev); + // dummy_wait_s_obj(1); + /** set feature to ep (set halt)*/ + /* + uint8_t request_type, uint8_t bRequest, + */ + error = libusb_control_transfer(pdev->libusb_handle, + (uint8_t)LIBUSB_ENDPOINT_OUT | + (uint8_t)LIBUSB_RECIPIENT_ENDPOINT, + LIBUSB_REQUEST_SET_FEATURE, ENDPOINT_HALT, + (uint16_t)EndpointAddress, nullptr, 0, 1000); + break; + + case PIPE_RESET: + idev->cancel_all_transfer_request(idev); + error = libusb_clear_halt(pdev->libusb_handle, (uint8_t)EndpointAddress); + // func_set_usbd_status(pdev, UsbdStatus, error); + break; + + default: + error = -0xff; + break; + } + + *UsbdStatus = 0; + return error; +} + +static UINT32 libusb_udev_control_query_device_text(IUDEVICE* idev, UINT32 TextType, + UINT16 LocaleId, UINT8* BufferSize, + BYTE* Buffer) +{ + UDEVICE* pdev = (UDEVICE*)idev; + LIBUSB_DEVICE_DESCRIPTOR* devDescriptor = nullptr; + const char strDesc[] = "Generic Usb String"; + char deviceLocation[25] = WINPR_C_ARRAY_INIT; + BYTE bus_number = 0; + BYTE device_address = 0; + int ret = 0; + size_t len = 0; + URBDRC_PLUGIN* urbdrc = nullptr; + WCHAR* text = (WCHAR*)Buffer; + BYTE slen = 0; + BYTE locale = 0; + const UINT8 inSize = *BufferSize; + + *BufferSize = 0; + if (!pdev || !pdev->devDescriptor || !pdev->urbdrc) + return ERROR_INVALID_DATA; + + urbdrc = pdev->urbdrc; + devDescriptor = pdev->devDescriptor; + + switch (TextType) + { + case DeviceTextDescription: + { + BYTE data[0x100] = WINPR_C_ARRAY_INIT; + ret = libusb_get_string_descriptor(pdev->libusb_handle, devDescriptor->iProduct, + LocaleId, data, 0xFF); + /* The returned data in the buffer is: + * 1 byte length of following data + * 1 byte descriptor type, must be 0x03 for strings + * n WCHAR unicode string (of length / 2 characters) including '\0' + */ + slen = data[0]; + locale = data[1]; + + if ((ret <= 0) || (ret <= 4) || (slen <= 4) || (locale != LIBUSB_DT_STRING) || + (ret > UINT8_MAX)) + { + const char* msg = "SHORT_DESCRIPTOR"; + if (ret < 0) + msg = libusb_error_name(ret); + WLog_Print(urbdrc->log, WLOG_DEBUG, + "libusb_get_string_descriptor: " + "%s [%d], iProduct: %" PRIu8 "!", + msg, ret, devDescriptor->iProduct); + + len = MIN(sizeof(strDesc), inSize); + for (size_t i = 0; i < len; i++) + text[i] = (WCHAR)strDesc[i]; + + *BufferSize = (BYTE)(len * 2); + } + else + { + /* ret and slen should be equals, but you never know creativity + * of device manufacturers... + * So also check the string length returned as server side does + * not honor strings with multi '\0' characters well. + */ + const size_t rchar = _wcsnlen((WCHAR*)&data[2], sizeof(data) / sizeof(WCHAR)); + len = MIN((BYTE)ret - 2, slen); + len = MIN(len, inSize); + len = MIN(len, rchar * sizeof(WCHAR) + sizeof(WCHAR)); + memcpy(Buffer, &data[2], len); + + /* Just as above, the returned WCHAR string should be '\0' + * terminated, but never trust hardware to conform to specs... */ + Buffer[len - 2] = '\0'; + Buffer[len - 1] = '\0'; + *BufferSize = (BYTE)len; + } + } + break; + + case DeviceTextLocationInformation: + bus_number = libusb_get_bus_number(pdev->libusb_dev); + device_address = libusb_get_device_address(pdev->libusb_dev); + (void)sprintf_s(deviceLocation, sizeof(deviceLocation), + "Port_#%04" PRIu8 ".Hub_#%04" PRIu8 "", device_address, bus_number); + + len = strnlen(deviceLocation, + MIN(sizeof(deviceLocation), (inSize > 0) ? inSize - 1U : 0)); + for (size_t i = 0; i < len; i++) + text[i] = (WCHAR)deviceLocation[i]; + text[len++] = '\0'; + *BufferSize = (UINT8)(len * sizeof(WCHAR)); + break; + + default: + WLog_Print(urbdrc->log, WLOG_DEBUG, "Query Text: unknown TextType %" PRIu32 "", + TextType); + return ERROR_INVALID_DATA; + } + + return S_OK; +} + +static int libusb_udev_os_feature_descriptor_request(IUDEVICE* idev, + WINPR_ATTR_UNUSED UINT32 RequestId, + BYTE Recipient, BYTE InterfaceNumber, + BYTE Ms_PageIndex, UINT16 Ms_featureDescIndex, + UINT32* UsbdStatus, UINT32* BufferSize, + BYTE* Buffer, UINT32 Timeout) +{ + UDEVICE* pdev = (UDEVICE*)idev; + BYTE ms_string_desc[0x13] = WINPR_C_ARRAY_INIT; + int error = 0; + + WINPR_ASSERT(idev); + WINPR_ASSERT(UsbdStatus); + WINPR_ASSERT(BufferSize); + WINPR_ASSERT(*BufferSize <= UINT16_MAX); + + /* + pdev->request_queue->register_request(pdev->request_queue, RequestId, nullptr, 0); + */ + error = libusb_control_transfer(pdev->libusb_handle, LIBUSB_ENDPOINT_IN | Recipient, + LIBUSB_REQUEST_GET_DESCRIPTOR, 0x03ee, 0, ms_string_desc, 0x12, + Timeout); + + log_libusb_result(pdev->urbdrc->log, WLOG_DEBUG, "libusb_control_transfer", error); + + if (error > 0) + { + const BYTE bMS_Vendorcode = ms_string_desc[16]; + /** get os descriptor */ + error = libusb_control_transfer( + pdev->libusb_handle, + (uint8_t)LIBUSB_ENDPOINT_IN | (uint8_t)LIBUSB_REQUEST_TYPE_VENDOR | Recipient, + bMS_Vendorcode, (UINT16)((InterfaceNumber << 8) | Ms_PageIndex), Ms_featureDescIndex, + Buffer, (UINT16)*BufferSize, Timeout); + log_libusb_result(pdev->urbdrc->log, WLOG_DEBUG, "libusb_control_transfer", error); + + if (error >= 0) + *BufferSize = (UINT32)error; + } + + if (error < 0) + *UsbdStatus = USBD_STATUS_STALL_PID; + else + *UsbdStatus = USBD_STATUS_SUCCESS; + + return ERROR_SUCCESS; +} + +static int libusb_udev_query_device_descriptor(IUDEVICE* idev, int offset) +{ + UDEVICE* pdev = (UDEVICE*)idev; + + switch (offset) + { + case B_LENGTH: + return pdev->devDescriptor->bLength; + + case B_DESCRIPTOR_TYPE: + return pdev->devDescriptor->bDescriptorType; + + case BCD_USB: + return pdev->devDescriptor->bcdUSB; + + case B_DEVICE_CLASS: + return pdev->devDescriptor->bDeviceClass; + + case B_DEVICE_SUBCLASS: + return pdev->devDescriptor->bDeviceSubClass; + + case B_DEVICE_PROTOCOL: + return pdev->devDescriptor->bDeviceProtocol; + + case B_MAX_PACKET_SIZE0: + return pdev->devDescriptor->bMaxPacketSize0; + + case ID_VENDOR: + return pdev->devDescriptor->idVendor; + + case ID_PRODUCT: + return pdev->devDescriptor->idProduct; + + case BCD_DEVICE: + return pdev->devDescriptor->bcdDevice; + + case I_MANUFACTURER: + return pdev->devDescriptor->iManufacturer; + + case I_PRODUCT: + return pdev->devDescriptor->iProduct; + + case I_SERIAL_NUMBER: + return pdev->devDescriptor->iSerialNumber; + + case B_NUM_CONFIGURATIONS: + return pdev->devDescriptor->bNumConfigurations; + + default: + return 0; + } +} + +static BOOL libusb_udev_detach_kernel_driver(IUDEVICE* idev) +{ + int err = 0; + UDEVICE* pdev = (UDEVICE*)idev; + URBDRC_PLUGIN* urbdrc = nullptr; + + if (!pdev || !pdev->LibusbConfig || !pdev->libusb_handle || !pdev->urbdrc) + return FALSE; + +#ifdef _WIN32 + return TRUE; +#else + urbdrc = pdev->urbdrc; + + if ((pdev->status & URBDRC_DEVICE_DETACH_KERNEL) == 0) + { + for (int i = 0; i < pdev->LibusbConfig->bNumInterfaces; i++) + { + err = libusb_kernel_driver_active(pdev->libusb_handle, i); + log_libusb_result(urbdrc->log, WLOG_DEBUG, "libusb_kernel_driver_active", err); + + if (err) + { + err = libusb_detach_kernel_driver(pdev->libusb_handle, i); + log_libusb_result(urbdrc->log, WLOG_DEBUG, "libusb_detach_kernel_driver", err); + } + } + + pdev->status |= URBDRC_DEVICE_DETACH_KERNEL; + } + + return TRUE; +#endif +} + +static BOOL libusb_udev_attach_kernel_driver(IUDEVICE* idev) +{ + int err = 0; + UDEVICE* pdev = (UDEVICE*)idev; + + if (!pdev || !pdev->LibusbConfig || !pdev->libusb_handle || !pdev->urbdrc) + return FALSE; + + for (int i = 0; i < pdev->LibusbConfig->bNumInterfaces && err != LIBUSB_ERROR_NO_DEVICE; i++) + { + err = libusb_release_interface(pdev->libusb_handle, i); + + log_libusb_result(pdev->urbdrc->log, WLOG_DEBUG, "libusb_release_interface", err); + +#ifndef _WIN32 + if (err != LIBUSB_ERROR_NO_DEVICE) + { + err = libusb_attach_kernel_driver(pdev->libusb_handle, i); + log_libusb_result(pdev->urbdrc->log, WLOG_DEBUG, "libusb_attach_kernel_driver if=%d", + err, i); + } +#endif + } + + return TRUE; +} + +static int libusb_udev_is_composite_device(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*)idev; + return pdev->isCompositeDevice; +} + +static int libusb_udev_is_exist(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*)idev; + return (pdev->status & URBDRC_DEVICE_NOT_FOUND) ? 0 : 1; +} + +static int libusb_udev_is_channel_closed(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*)idev; + IUDEVMAN* udevman = nullptr; + if (!pdev || !pdev->urbdrc) + return 1; + + udevman = pdev->urbdrc->udevman; + if (udevman) + { + if (udevman->status & URBDRC_DEVICE_CHANNEL_CLOSED) + return 1; + } + + if (pdev->status & URBDRC_DEVICE_CHANNEL_CLOSED) + return 1; + + return 0; +} + +static int libusb_udev_is_already_send(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*)idev; + return (pdev->status & URBDRC_DEVICE_ALREADY_SEND) ? 1 : 0; +} + +/* This is called from channel cleanup code. + * Avoid double free, just remove the device and mark the channel closed. */ +static void libusb_udev_mark_channel_closed(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*)idev; + if (pdev && ((pdev->status & URBDRC_DEVICE_CHANNEL_CLOSED) == 0)) + { + URBDRC_PLUGIN* urbdrc = pdev->urbdrc; + const uint8_t busNr = idev->get_bus_number(idev); + const uint8_t devNr = idev->get_dev_number(idev); + + pdev->status |= URBDRC_DEVICE_CHANNEL_CLOSED; + pdev->iface.cancel_all_transfer_request(&pdev->iface); + if (!urbdrc->udevman->unregister_udevice(urbdrc->udevman, busNr, devNr)) + { + WLog_Print(pdev->urbdrc->log, WLOG_WARN, "unregister_udevice failed for %d, %d", busNr, + devNr); + } + } +} + +/* This is called by local events where the device is removed or in an error + * state. Remove the device from redirection and close the channel. */ +static void libusb_udev_channel_closed(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*)idev; + if (pdev && ((pdev->status & URBDRC_DEVICE_CHANNEL_CLOSED) == 0)) + { + URBDRC_PLUGIN* urbdrc = pdev->urbdrc; + const uint8_t busNr = idev->get_bus_number(idev); + const uint8_t devNr = idev->get_dev_number(idev); + IWTSVirtualChannel* channel = nullptr; + + if (pdev->channelManager) + channel = IFCALLRESULT(nullptr, pdev->channelManager->FindChannelById, + pdev->channelManager, pdev->channelID); + + pdev->status |= URBDRC_DEVICE_CHANNEL_CLOSED; + + if (channel) + { + const UINT rc = channel->Write(channel, 0, nullptr, nullptr); + if (rc != CHANNEL_RC_OK) + WLog_Print(urbdrc->log, WLOG_WARN, "channel->Write failed with %" PRIu32, rc); + } + + if (!urbdrc->udevman->unregister_udevice(urbdrc->udevman, busNr, devNr)) + WLog_Print(urbdrc->log, WLOG_WARN, "unregister_udevice failed for %d, %d", busNr, + devNr); + } +} + +static void libusb_udev_set_already_send(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*)idev; + pdev->status |= URBDRC_DEVICE_ALREADY_SEND; +} + +static char* libusb_udev_get_path(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*)idev; + return pdev->path; +} + +static int libusb_udev_query_device_port_status(IUDEVICE* idev, UINT32* UsbdStatus, + UINT32* BufferSize, BYTE* Buffer) +{ + UDEVICE* pdev = (UDEVICE*)idev; + int success = 0; + int ret = 0; + URBDRC_PLUGIN* urbdrc = nullptr; + + if (!pdev || !pdev->urbdrc) + return -1; + + urbdrc = pdev->urbdrc; + + if (pdev->hub_handle != nullptr) + { + ret = idev->control_transfer( + idev, 0xffff, 0, 0, + (uint8_t)LIBUSB_ENDPOINT_IN | (uint8_t)LIBUSB_REQUEST_TYPE_CLASS | + (uint8_t)LIBUSB_RECIPIENT_OTHER, + LIBUSB_REQUEST_GET_STATUS, 0, pdev->port_number, UsbdStatus, BufferSize, Buffer, 1000); + + if (log_libusb_result(urbdrc->log, WLOG_DEBUG, "libusb_control_transfer", ret)) + *BufferSize = 0; + else + { + WLog_Print(urbdrc->log, WLOG_DEBUG, + "PORT STATUS:0x%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "", Buffer[3], + Buffer[2], Buffer[1], Buffer[0]); + success = 1; + } + } + + return success; +} + +static int libusb_udev_isoch_transfer(IUDEVICE* idev, GENERIC_CHANNEL_CALLBACK* callback, + UINT32 MessageId, UINT32 RequestId, UINT32 EndpointAddress, + WINPR_ATTR_UNUSED UINT32 TransferFlags, UINT32 StartFrame, + UINT32 ErrorCount, BOOL NoAck, + WINPR_ATTR_UNUSED const BYTE* packetDescriptorData, + UINT32 NumberOfPackets, UINT32 BufferSize, const BYTE* Buffer, + t_isoch_transfer_cb cb, UINT32 Timeout) +{ + int rc = 0; + UINT32 iso_packet_size = 0; + UDEVICE* pdev = (UDEVICE*)idev; + ASYNC_TRANSFER_USER_DATA* user_data = nullptr; + struct libusb_transfer* iso_transfer = nullptr; + URBDRC_PLUGIN* urbdrc = nullptr; + size_t outSize = (12ULL * NumberOfPackets); + uint32_t streamID = 0x40000000 | RequestId; + + if (!pdev || !pdev->urbdrc) + return -1; + + urbdrc = pdev->urbdrc; + user_data = async_transfer_user_data_new(idev, MessageId, 48, BufferSize, Buffer, + outSize + 1024, NoAck, cb, callback); + + if (!user_data) + return -1; + + user_data->ErrorCount = ErrorCount; + user_data->StartFrame = StartFrame; + + if (!Buffer) + Stream_Seek(user_data->data, (12ULL * NumberOfPackets)); + + if (NumberOfPackets > 0) + { + iso_packet_size = BufferSize / NumberOfPackets; + iso_transfer = libusb_alloc_transfer((int)NumberOfPackets); + } + + if (iso_transfer == nullptr) + { + WLog_Print(urbdrc->log, WLOG_ERROR, + "Error: libusb_alloc_transfer [NumberOfPackets=%" PRIu32 ", BufferSize=%" PRIu32 + " ]", + NumberOfPackets, BufferSize); + async_transfer_user_data_free(user_data); + return -1; + } + + /** process URB_FUNCTION_IOSCH_TRANSFER */ + libusb_fill_iso_transfer( + iso_transfer, pdev->libusb_handle, WINPR_ASSERTING_INT_CAST(uint8_t, EndpointAddress), + Stream_Pointer(user_data->data), WINPR_ASSERTING_INT_CAST(int, BufferSize), + WINPR_ASSERTING_INT_CAST(int, NumberOfPackets), func_iso_callback, user_data, Timeout); + set_stream_id_for_buffer(iso_transfer, streamID); + libusb_set_iso_packet_lengths(iso_transfer, iso_packet_size); + + if (!ArrayList_Append(pdev->request_queue, iso_transfer)) + { + WLog_Print(urbdrc->log, WLOG_WARN, + "Failed to queue iso transfer, streamID %08" PRIx32 " already in use!", + streamID); + request_free(iso_transfer); + return -1; + } + rc = libusb_submit_transfer(iso_transfer); + if (log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_submit_transfer", rc)) + return -1; + return rc; +} + +static BOOL libusb_udev_control_transfer(IUDEVICE* idev, WINPR_ATTR_UNUSED UINT32 RequestId, + WINPR_ATTR_UNUSED UINT32 EndpointAddress, + WINPR_ATTR_UNUSED UINT32 TransferFlags, BYTE bmRequestType, + BYTE Request, UINT16 Value, UINT16 Index, + UINT32* UrbdStatus, UINT32* BufferSize, BYTE* Buffer, + UINT32 Timeout) +{ + int status = 0; + UDEVICE* pdev = (UDEVICE*)idev; + + WINPR_ASSERT(BufferSize); + WINPR_ASSERT(*BufferSize <= UINT16_MAX); + + if (!pdev || !pdev->urbdrc) + return FALSE; + + status = libusb_control_transfer(pdev->libusb_handle, bmRequestType, Request, Value, Index, + Buffer, (UINT16)*BufferSize, Timeout); + + if (status >= 0) + *BufferSize = (UINT32)status; + else + log_libusb_result(pdev->urbdrc->log, WLOG_ERROR, "libusb_control_transfer", status); + + if (!func_set_usbd_status(pdev->urbdrc, pdev, UrbdStatus, status)) + return FALSE; + + return TRUE; +} + +static int libusb_udev_bulk_or_interrupt_transfer(IUDEVICE* idev, + GENERIC_CHANNEL_CALLBACK* callback, + UINT32 MessageId, UINT32 RequestId, + UINT32 EndpointAddress, UINT32 TransferFlags, + BOOL NoAck, UINT32 BufferSize, const BYTE* data, + t_isoch_transfer_cb cb, UINT32 Timeout) +{ + int rc = 0; + UINT32 transfer_type = 0; + UDEVICE* pdev = (UDEVICE*)idev; + const LIBUSB_ENDPOINT_DESCEIPTOR* ep_desc = nullptr; + struct libusb_transfer* transfer = nullptr; + URBDRC_PLUGIN* urbdrc = nullptr; + ASYNC_TRANSFER_USER_DATA* user_data = nullptr; + uint32_t streamID = 0x80000000 | RequestId; + + if (!pdev || !pdev->LibusbConfig || !pdev->urbdrc) + return -1; + + urbdrc = pdev->urbdrc; + user_data = + async_transfer_user_data_new(idev, MessageId, 36, BufferSize, data, 0, NoAck, cb, callback); + + if (!user_data) + return -1; + + /* alloc memory for urb transfer */ + transfer = libusb_alloc_transfer(0); + if (!transfer) + { + async_transfer_user_data_free(user_data); + return -1; + } + transfer->user_data = user_data; + + ep_desc = func_get_ep_desc(pdev->LibusbConfig, pdev->MsConfig, EndpointAddress); + + if (!ep_desc) + { + WLog_Print(urbdrc->log, WLOG_ERROR, "func_get_ep_desc: endpoint 0x%" PRIx32 " not found", + EndpointAddress); + request_free(transfer); + return -1; + } + + transfer_type = (ep_desc->bmAttributes) & 0x3; + WLog_Print(urbdrc->log, WLOG_DEBUG, + "urb_bulk_or_interrupt_transfer: ep:0x%" PRIx32 " " + "transfer_type %" PRIu32 " flag:%" PRIu32 " OutputBufferSize:0x%" PRIx32 "", + EndpointAddress, transfer_type, TransferFlags, BufferSize); + + switch (transfer_type) + { + case BULK_TRANSFER: + /** Bulk Transfer */ + libusb_fill_bulk_transfer( + transfer, pdev->libusb_handle, WINPR_ASSERTING_INT_CAST(uint8_t, EndpointAddress), + Stream_Pointer(user_data->data), WINPR_ASSERTING_INT_CAST(int, BufferSize), + func_bulk_transfer_cb, user_data, Timeout); + break; + + case INTERRUPT_TRANSFER: + /** Interrupt Transfer */ + libusb_fill_interrupt_transfer( + transfer, pdev->libusb_handle, WINPR_ASSERTING_INT_CAST(uint8_t, EndpointAddress), + Stream_Pointer(user_data->data), WINPR_ASSERTING_INT_CAST(int, BufferSize), + func_bulk_transfer_cb, user_data, Timeout); + break; + + default: + WLog_Print(urbdrc->log, WLOG_DEBUG, + "urb_bulk_or_interrupt_transfer:" + " other transfer type 0x%" PRIX32 "", + transfer_type); + request_free(transfer); + return -1; + } + + set_stream_id_for_buffer(transfer, streamID); + + if (!ArrayList_Append(pdev->request_queue, transfer)) + { + WLog_Print(urbdrc->log, WLOG_WARN, + "Failed to queue transfer, streamID %08" PRIx32 " already in use!", streamID); + request_free(transfer); + return -1; + } + rc = libusb_submit_transfer(transfer); + if (log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_submit_transfer", rc)) + return -1; + return rc; +} + +static int func_cancel_xact_request(URBDRC_PLUGIN* urbdrc, struct libusb_transfer* transfer) +{ + int status = 0; + + if (!urbdrc || !transfer) + return -1; + + status = libusb_cancel_transfer(transfer); + + if (log_libusb_result(urbdrc->log, WLOG_WARN, "libusb_cancel_transfer", status)) + { + if (status == LIBUSB_ERROR_NOT_FOUND) + return -1; + } + else + return 1; + + return 0; +} + +static void libusb_udev_cancel_all_transfer_request(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*)idev; + size_t count = 0; + + if (!pdev || !pdev->request_queue || !pdev->urbdrc) + return; + + ArrayList_Lock(pdev->request_queue); + count = ArrayList_Count(pdev->request_queue); + + for (size_t x = 0; x < count; x++) + { + struct libusb_transfer* transfer = ArrayList_GetItem(pdev->request_queue, x); + func_cancel_xact_request(pdev->urbdrc, transfer); + } + + ArrayList_Unlock(pdev->request_queue); +} + +static int libusb_udev_cancel_transfer_request(IUDEVICE* idev, UINT32 RequestId) +{ + int rc = -1; + UDEVICE* pdev = (UDEVICE*)idev; + struct libusb_transfer* transfer = nullptr; + uint32_t cancelID1 = 0x40000000 | RequestId; + uint32_t cancelID2 = 0x80000000 | RequestId; + + if (!idev || !pdev->urbdrc || !pdev->request_queue) + return -1; + + ArrayList_Lock(pdev->request_queue); + transfer = list_contains(pdev->request_queue, cancelID1); + if (!transfer) + transfer = list_contains(pdev->request_queue, cancelID2); + + if (transfer) + { + URBDRC_PLUGIN* urbdrc = pdev->urbdrc; + + rc = func_cancel_xact_request(urbdrc, transfer); + } + ArrayList_Unlock(pdev->request_queue); + return rc; +} + +BASIC_STATE_FUNC_DEFINED(channelManager, IWTSVirtualChannelManager*) +BASIC_STATE_FUNC_DEFINED(channelID, UINT32) +BASIC_STATE_FUNC_DEFINED(ReqCompletion, UINT32) +BASIC_STATE_FUNC_DEFINED(bus_number, BYTE) +BASIC_STATE_FUNC_DEFINED(dev_number, BYTE) +BASIC_STATE_FUNC_DEFINED(port_number, UINT8) +BASIC_STATE_FUNC_DEFINED(MsConfig, MSUSB_CONFIG_DESCRIPTOR*) + +BASIC_POINT_FUNC_DEFINED(udev, void*) +BASIC_POINT_FUNC_DEFINED(prev, void*) +BASIC_POINT_FUNC_DEFINED(next, void*) + +static UINT32 udev_get_UsbDevice(IUDEVICE* idev) +{ + UDEVICE* pdev = (UDEVICE*)idev; + + if (!pdev) + return 0; + + return pdev->UsbDevice; +} + +static void udev_set_UsbDevice(IUDEVICE* idev, UINT32 val) +{ + UDEVICE* pdev = (UDEVICE*)idev; + + if (!pdev) + return; + + pdev->UsbDevice = val; +} + +static void udev_free(IUDEVICE* idev) +{ + int rc = 0; + UDEVICE* udev = (UDEVICE*)idev; + URBDRC_PLUGIN* urbdrc = nullptr; + + if (!idev || !udev->urbdrc) + return; + + urbdrc = udev->urbdrc; + + libusb_udev_cancel_all_transfer_request(&udev->iface); + if (udev->libusb_handle) + { + rc = libusb_reset_device(udev->libusb_handle); + + log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_reset_device", rc); + } + + /* HACK: We need to wait until the cancel transfer has been processed by + * poll_libusb_events + */ + Sleep(100); + + /* release all interface and attach kernel driver */ + if (!udev->iface.attach_kernel_driver(idev)) + WLog_Print(udev->urbdrc->log, WLOG_WARN, "attach_kernel_driver failed for device"); + ArrayList_Free(udev->request_queue); + /* free the config descriptor that send from windows */ + msusb_msconfig_free(udev->MsConfig); + libusb_unref_device(udev->libusb_dev); + libusb_close(udev->libusb_handle); + libusb_close(udev->hub_handle); + free(udev->devDescriptor); + free(idev); +} + +static void udev_load_interface(UDEVICE* pdev) +{ + WINPR_ASSERT(pdev); + + /* load interface */ + /* Basic */ + BASIC_STATE_FUNC_REGISTER(channelManager, pdev); + BASIC_STATE_FUNC_REGISTER(channelID, pdev); + BASIC_STATE_FUNC_REGISTER(UsbDevice, pdev); + BASIC_STATE_FUNC_REGISTER(ReqCompletion, pdev); + BASIC_STATE_FUNC_REGISTER(bus_number, pdev); + BASIC_STATE_FUNC_REGISTER(dev_number, pdev); + BASIC_STATE_FUNC_REGISTER(port_number, pdev); + BASIC_STATE_FUNC_REGISTER(MsConfig, pdev); + BASIC_STATE_FUNC_REGISTER(p_udev, pdev); + BASIC_STATE_FUNC_REGISTER(p_prev, pdev); + BASIC_STATE_FUNC_REGISTER(p_next, pdev); + pdev->iface.isCompositeDevice = libusb_udev_is_composite_device; + pdev->iface.isExist = libusb_udev_is_exist; + pdev->iface.isAlreadySend = libusb_udev_is_already_send; + pdev->iface.isChannelClosed = libusb_udev_is_channel_closed; + pdev->iface.setAlreadySend = libusb_udev_set_already_send; + pdev->iface.setChannelClosed = libusb_udev_channel_closed; + pdev->iface.markChannelClosed = libusb_udev_mark_channel_closed; + pdev->iface.getPath = libusb_udev_get_path; + /* Transfer */ + pdev->iface.isoch_transfer = libusb_udev_isoch_transfer; + pdev->iface.control_transfer = libusb_udev_control_transfer; + pdev->iface.bulk_or_interrupt_transfer = libusb_udev_bulk_or_interrupt_transfer; + pdev->iface.select_interface = libusb_udev_select_interface; + pdev->iface.select_configuration = libusb_udev_select_configuration; + pdev->iface.complete_msconfig_setup = libusb_udev_complete_msconfig_setup; + pdev->iface.control_pipe_request = libusb_udev_control_pipe_request; + pdev->iface.control_query_device_text = libusb_udev_control_query_device_text; + pdev->iface.os_feature_descriptor_request = libusb_udev_os_feature_descriptor_request; + pdev->iface.cancel_all_transfer_request = libusb_udev_cancel_all_transfer_request; + pdev->iface.cancel_transfer_request = libusb_udev_cancel_transfer_request; + pdev->iface.query_device_descriptor = libusb_udev_query_device_descriptor; + pdev->iface.detach_kernel_driver = libusb_udev_detach_kernel_driver; + pdev->iface.attach_kernel_driver = libusb_udev_attach_kernel_driver; + pdev->iface.query_device_port_status = libusb_udev_query_device_port_status; + pdev->iface.free = udev_free; +} + +static int udev_get_device_handle(URBDRC_PLUGIN* urbdrc, libusb_context* ctx, UDEVICE* pdev, + UINT16 bus_number, UINT16 dev_number) +{ + int error = -1; + uint8_t port_numbers[16] = WINPR_C_ARRAY_INIT; + LIBUSB_DEVICE** libusb_list = nullptr; + const ssize_t total_device = libusb_get_device_list(ctx, &libusb_list); + + WINPR_ASSERT(urbdrc); + + /* Look for device. */ + for (ssize_t i = 0; i < total_device; i++) + { + LIBUSB_DEVICE* dev = libusb_list[i]; + + if ((bus_number != libusb_get_bus_number(dev)) || + (dev_number != libusb_get_device_address(dev))) + libusb_unref_device(dev); + else + { + error = libusb_open(dev, &pdev->libusb_handle); + + if (log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_open", error)) + { + libusb_unref_device(dev); + continue; + } + + /* get port number */ + error = libusb_get_port_numbers(dev, port_numbers, sizeof(port_numbers)); + if (error < 1) + { + /* Prevent open hub, treat as error. */ + log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_get_port_numbers", error); + libusb_unref_device(dev); + continue; + } + + pdev->port_number = port_numbers[(error - 1)]; + error = 0; + WLog_Print(urbdrc->log, WLOG_DEBUG, " Port: %" PRIu8, pdev->port_number); + /* gen device path */ + (void)_snprintf(pdev->path, sizeof(pdev->path), "%" PRIu16 "-%d", bus_number, + pdev->port_number); + + WLog_Print(urbdrc->log, WLOG_DEBUG, " DevPath: %s", pdev->path); + } + } + libusb_free_device_list(libusb_list, 0); + + if (error < 0) + return -1; + return 0; +} + +static int udev_get_hub_handle(URBDRC_PLUGIN* urbdrc, libusb_context* ctx, UDEVICE* pdev, + UINT16 bus_number, WINPR_ATTR_UNUSED UINT16 dev_number) +{ + int error = -1; + LIBUSB_DEVICE** libusb_list = nullptr; + LIBUSB_DEVICE_HANDLE* handle = nullptr; + const ssize_t total_device = libusb_get_device_list(ctx, &libusb_list); + + WINPR_ASSERT(urbdrc); + + /* Look for device hub. */ + for (ssize_t i = 0; i < total_device; i++) + { + LIBUSB_DEVICE* dev = libusb_list[i]; + + if ((bus_number != libusb_get_bus_number(dev)) || + (1 != libusb_get_device_address(dev))) /* Root hub always first on bus. */ + libusb_unref_device(dev); + else + { + WLog_Print(urbdrc->log, WLOG_DEBUG, " Open hub: %" PRIu16 "", bus_number); + error = libusb_open(dev, &handle); + + if (!log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_open", error)) + pdev->hub_handle = handle; + else + libusb_unref_device(dev); + } + } + + libusb_free_device_list(libusb_list, 0); + + if (error < 0) + return -1; + + return 0; +} + +static void request_free(void* value) +{ + ASYNC_TRANSFER_USER_DATA* user_data = nullptr; + struct libusb_transfer* transfer = (struct libusb_transfer*)value; + if (!transfer) + return; + + user_data = (ASYNC_TRANSFER_USER_DATA*)transfer->user_data; + async_transfer_user_data_free(user_data); + transfer->user_data = nullptr; + libusb_free_transfer(transfer); +} + +static IUDEVICE* udev_init(URBDRC_PLUGIN* urbdrc, libusb_context* context, LIBUSB_DEVICE* device, + BYTE bus_number, BYTE dev_number) +{ + UDEVICE* pdev = nullptr; + int status = LIBUSB_ERROR_OTHER; + LIBUSB_DEVICE_DESCRIPTOR* devDescriptor = nullptr; + LIBUSB_CONFIG_DESCRIPTOR* config_temp = nullptr; + LIBUSB_INTERFACE_DESCRIPTOR interface_temp; + + WINPR_ASSERT(urbdrc); + + pdev = (PUDEVICE)calloc(1, sizeof(UDEVICE)); + + if (!pdev) + return nullptr; + + pdev->urbdrc = urbdrc; + udev_load_interface(pdev); + + if (device) + pdev->libusb_dev = device; + else + pdev->libusb_dev = udev_get_libusb_dev(context, bus_number, dev_number); + + if (pdev->libusb_dev == nullptr) + goto fail; + + if (urbdrc->listener_callback) + udev_set_channelManager(&pdev->iface, urbdrc->listener_callback->channel_mgr); + + /* Get DEVICE handle */ + status = udev_get_device_handle(urbdrc, context, pdev, bus_number, dev_number); + if (status != LIBUSB_SUCCESS) + { + struct libusb_device_descriptor desc; + const uint8_t port = libusb_get_port_number(pdev->libusb_dev); + libusb_get_device_descriptor(pdev->libusb_dev, &desc); + + log_libusb_result(urbdrc->log, WLOG_ERROR, + "libusb_open [b=0x%02X,p=0x%02X,a=0x%02X,VID=0x%04X,PID=0x%04X]", status, + bus_number, port, dev_number, desc.idVendor, desc.idProduct); + goto fail; + } + + /* Get HUB handle */ + status = udev_get_hub_handle(urbdrc, context, pdev, bus_number, dev_number); + + if (status < 0) + pdev->hub_handle = nullptr; + + pdev->devDescriptor = udev_new_descript(urbdrc, pdev->libusb_dev); + + if (!pdev->devDescriptor) + goto fail; + + status = libusb_get_active_config_descriptor(pdev->libusb_dev, &pdev->LibusbConfig); + + if (status == LIBUSB_ERROR_NOT_FOUND) + status = libusb_get_config_descriptor(pdev->libusb_dev, 0, &pdev->LibusbConfig); + + if (status < 0) + { + log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_get_config_descriptor", status); + goto fail; + } + + config_temp = pdev->LibusbConfig; + /* get the first interface and first altsetting */ + interface_temp = config_temp->interface[0].altsetting[0]; + WLog_Print(urbdrc->log, WLOG_DEBUG, + "Registered Device: Vid: 0x%04" PRIX16 " Pid: 0x%04" PRIX16 "" + " InterfaceClass = %s", + pdev->devDescriptor->idVendor, pdev->devDescriptor->idProduct, + usb_interface_class_to_string(interface_temp.bInterfaceClass)); + /* Check composite device */ + devDescriptor = pdev->devDescriptor; + + if ((devDescriptor->bNumConfigurations == 1) && (config_temp->bNumInterfaces > 1) && + (devDescriptor->bDeviceClass == LIBUSB_CLASS_PER_INTERFACE)) + { + pdev->isCompositeDevice = 1; + } + else if ((devDescriptor->bDeviceClass == 0xef) && + (devDescriptor->bDeviceSubClass == LIBUSB_CLASS_COMM) && + (devDescriptor->bDeviceProtocol == 0x01)) + { + pdev->isCompositeDevice = 1; + } + else + pdev->isCompositeDevice = 0; + + /* set device class to first interface class */ + devDescriptor->bDeviceClass = interface_temp.bInterfaceClass; + devDescriptor->bDeviceSubClass = interface_temp.bInterfaceSubClass; + devDescriptor->bDeviceProtocol = interface_temp.bInterfaceProtocol; + /* initialize pdev */ + pdev->bus_number = bus_number; + pdev->dev_number = dev_number; + pdev->request_queue = ArrayList_New(TRUE); + + if (!pdev->request_queue) + goto fail; + + ArrayList_Object(pdev->request_queue)->fnObjectFree = request_free; + + /* set config of windows */ + pdev->MsConfig = msusb_msconfig_new(); + + if (!pdev->MsConfig) + goto fail; + + // deb_config_msg(pdev->libusb_dev, config_temp, devDescriptor->bNumConfigurations); + return &pdev->iface; +fail: + pdev->iface.free(&pdev->iface); + return nullptr; +} + +size_t udev_new_by_id(URBDRC_PLUGIN* urbdrc, libusb_context* ctx, UINT16 idVendor, UINT16 idProduct, + IUDEVICE*** devArray) +{ + WINPR_ASSERT(urbdrc); + WINPR_ASSERT(devArray); + + size_t num = 0; + LIBUSB_DEVICE** libusb_list = nullptr; + + *devArray = nullptr; + + WLog_Print(urbdrc->log, WLOG_INFO, "VID: 0x%04" PRIX16 ", PID: 0x%04" PRIX16 "", idVendor, + idProduct); + const ssize_t total_device = libusb_get_device_list(ctx, &libusb_list); + if (total_device < 0) + { + WLog_Print(urbdrc->log, WLOG_ERROR, "libusb_get_device_list -> [%" PRIdz "]", total_device); + return 0; + } + if (total_device == 0) + { + WLog_Print(urbdrc->log, WLOG_WARN, "libusb_get_device_list -> [%" PRIdz "]", total_device); + return 0; + } + + UDEVICE** array = (UDEVICE**)calloc((size_t)total_device, sizeof(UDEVICE*)); + + if (!array) + goto fail; + + for (ssize_t i = 0; i < total_device; i++) + { + LIBUSB_DEVICE* dev = libusb_list[i]; + LIBUSB_DEVICE_DESCRIPTOR* descriptor = udev_new_descript(urbdrc, dev); + + if ((descriptor->idVendor == idVendor) && (descriptor->idProduct == idProduct)) + { + const uint8_t nr = libusb_get_bus_number(dev); + const uint8_t addr = libusb_get_device_address(dev); + array[num] = (PUDEVICE)udev_init(urbdrc, ctx, dev, nr, addr); + + if (array[num] != nullptr) + num++; + else + { + WLog_Print(urbdrc->log, WLOG_WARN, + "udev_init(nr=%" PRIu8 ", addr=%" PRIu8 ") failed", nr, addr); + } + } + else + libusb_unref_device(dev); + + free(descriptor); + } + +fail: + libusb_free_device_list(libusb_list, 0); + *devArray = (IUDEVICE**)array; + return num; +} + +IUDEVICE* udev_new_by_addr(URBDRC_PLUGIN* urbdrc, libusb_context* context, BYTE bus_number, + BYTE dev_number) +{ + WLog_Print(urbdrc->log, WLOG_DEBUG, "bus:%d dev:%d", bus_number, dev_number); + return udev_init(urbdrc, context, nullptr, bus_number, dev_number); +} diff --git a/third_party/FreeRDP/channels/urbdrc/client/libusb/libusb_udevice.h b/third_party/FreeRDP/channels/urbdrc/client/libusb/libusb_udevice.h new file mode 100644 index 0000000..8a7f0fa --- /dev/null +++ b/third_party/FreeRDP/channels/urbdrc/client/libusb/libusb_udevice.h @@ -0,0 +1,82 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX USB Redirection + * + * Copyright 2012 Atrust corp. + * Copyright 2012 Alfred Liu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_URBDRC_CLIENT_LIBUSB_UDEVICE_H +#define FREERDP_CHANNEL_URBDRC_CLIENT_LIBUSB_UDEVICE_H + +#include +#include + +#include "urbdrc_types.h" +#include "urbdrc_main.h" +#include "msusb.h" + +typedef struct libusb_device LIBUSB_DEVICE; +typedef struct libusb_device_handle LIBUSB_DEVICE_HANDLE; +typedef struct libusb_device_descriptor LIBUSB_DEVICE_DESCRIPTOR; +typedef struct libusb_config_descriptor LIBUSB_CONFIG_DESCRIPTOR; +typedef struct libusb_interface LIBUSB_INTERFACE; +typedef struct libusb_interface_descriptor LIBUSB_INTERFACE_DESCRIPTOR; +typedef struct libusb_endpoint_descriptor LIBUSB_ENDPOINT_DESCEIPTOR; + +typedef struct +{ + IUDEVICE iface; + + void* udev; + void* prev; + void* next; + + UINT32 UsbDevice; /* An unique interface ID */ + UINT32 ReqCompletion; /* An unique interface ID */ + IWTSVirtualChannelManager* channelManager; + UINT32 channelID; + UINT16 status; + BYTE bus_number; + BYTE dev_number; + char path[17]; + UINT8 port_number; + int isCompositeDevice; + + LIBUSB_DEVICE_HANDLE* libusb_handle; + LIBUSB_DEVICE_HANDLE* hub_handle; + LIBUSB_DEVICE* libusb_dev; + LIBUSB_DEVICE_DESCRIPTOR* devDescriptor; + MSUSB_CONFIG_DESCRIPTOR* MsConfig; + LIBUSB_CONFIG_DESCRIPTOR* LibusbConfig; + + wArrayList* request_queue; + + URBDRC_PLUGIN* urbdrc; +} UDEVICE; +typedef UDEVICE* PUDEVICE; + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL size_t udev_new_by_id(URBDRC_PLUGIN* urbdrc, libusb_context* ctx, UINT16 idVendor, + UINT16 idProduct, IUDEVICE*** devArray); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL IUDEVICE* udev_new_by_addr(URBDRC_PLUGIN* urbdrc, libusb_context* context, + BYTE bus_number, BYTE dev_number); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL const char* usb_interface_class_to_string(uint8_t c_class); + +#endif /* FREERDP_CHANNEL_URBDRC_CLIENT_LIBUSB_UDEVICE_H */ diff --git a/third_party/FreeRDP/channels/urbdrc/client/libusb/libusb_udevman.c b/third_party/FreeRDP/channels/urbdrc/client/libusb/libusb_udevman.c new file mode 100644 index 0000000..13aed81 --- /dev/null +++ b/third_party/FreeRDP/channels/urbdrc/client/libusb/libusb_udevman.c @@ -0,0 +1,969 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX USB Redirection + * + * Copyright 2012 Atrust corp. + * Copyright 2012 Alfred Liu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "urbdrc_types.h" +#include "urbdrc_main.h" + +#include "libusb_udevice.h" + +#include + +#if !defined(LIBUSB_HOTPLUG_NO_FLAGS) +#define LIBUSB_HOTPLUG_NO_FLAGS 0 +#endif + +#define BASIC_STATE_FUNC_DEFINED(_arg, _type) \ + static _type udevman_get_##_arg(IUDEVMAN* idevman) \ + { \ + UDEVMAN* udevman = (UDEVMAN*)idevman; \ + return udevman->_arg; \ + } \ + static void udevman_set_##_arg(IUDEVMAN* idevman, _type _t) \ + { \ + UDEVMAN* udevman = (UDEVMAN*)idevman; \ + udevman->_arg = _t; \ + } + +#define BASIC_STATE_FUNC_REGISTER(_arg, _man) \ + _man->iface.get_##_arg = udevman_get_##_arg; \ + (_man)->iface.set_##_arg = udevman_set_##_arg + +typedef struct +{ + UINT16 vid; + UINT16 pid; +} VID_PID_PAIR; + +typedef struct +{ + IUDEVMAN iface; + + IUDEVICE* idev; /* iterator device */ + IUDEVICE* head; /* head device in linked list */ + IUDEVICE* tail; /* tail device in linked list */ + + LPCSTR devices_vid_pid; + LPCSTR devices_addr; + wArrayList* hotplug_vid_pids; + UINT16 flags; + UINT32 device_num; + UINT32 next_device_id; + UINT32 channel_id; + + HANDLE devman_loading; + libusb_context* context; + HANDLE thread; + BOOL running; +} UDEVMAN; +typedef UDEVMAN* PUDEVMAN; + +static BOOL poll_libusb_events(UDEVMAN* udevman); + +static void udevman_rewind(IUDEVMAN* idevman) +{ + UDEVMAN* udevman = (UDEVMAN*)idevman; + udevman->idev = udevman->head; +} + +static BOOL udevman_has_next(IUDEVMAN* idevman) +{ + UDEVMAN* udevman = (UDEVMAN*)idevman; + + return !(!udevman || !udevman->idev); +} + +static IUDEVICE* udevman_get_next(IUDEVMAN* idevman) +{ + UDEVMAN* udevman = (UDEVMAN*)idevman; + IUDEVICE* pdev = nullptr; + pdev = udevman->idev; + udevman->idev = (IUDEVICE*)((UDEVICE*)udevman->idev)->next; + return pdev; +} + +static IUDEVICE* udevman_get_udevice_by_addr(IUDEVMAN* idevman, BYTE bus_number, BYTE dev_number) +{ + IUDEVICE* dev = nullptr; + + if (!idevman) + return nullptr; + + idevman->loading_lock(idevman); + idevman->rewind(idevman); + + while (idevman->has_next(idevman)) + { + IUDEVICE* pdev = idevman->get_next(idevman); + + if ((pdev->get_bus_number(pdev) == bus_number) && + (pdev->get_dev_number(pdev) == dev_number)) + { + dev = pdev; + break; + } + } + + idevman->loading_unlock(idevman); + return dev; +} + +static size_t udevman_register_udevice(IUDEVMAN* idevman, BYTE bus_number, BYTE dev_number, + UINT16 idVendor, UINT16 idProduct, UINT32 flag) +{ + UDEVMAN* udevman = (UDEVMAN*)idevman; + IUDEVICE* pdev = nullptr; + IUDEVICE** devArray = nullptr; + URBDRC_PLUGIN* urbdrc = nullptr; + size_t num = 0; + size_t addnum = 0; + + if (!idevman || !idevman->plugin) + return 0; + + urbdrc = (URBDRC_PLUGIN*)idevman->plugin; + pdev = udevman_get_udevice_by_addr(idevman, bus_number, dev_number); + + if (pdev != nullptr) + return 0; + + if (flag & UDEVMAN_FLAG_ADD_BY_ADDR) + { + UINT32 id = 0; + IUDEVICE* tdev = udev_new_by_addr(urbdrc, udevman->context, bus_number, dev_number); + + if (tdev == nullptr) + return 0; + + id = idevman->get_next_device_id(idevman); + tdev->set_UsbDevice(tdev, id); + idevman->loading_lock(idevman); + + if (udevman->head == nullptr) + { + /* linked list is empty */ + udevman->head = tdev; + udevman->tail = tdev; + } + else + { + /* append device to the end of the linked list */ + udevman->tail->set_p_next(udevman->tail, tdev); + tdev->set_p_prev(tdev, udevman->tail); + udevman->tail = tdev; + } + + udevman->device_num += 1; + idevman->loading_unlock(idevman); + } + else if (flag & UDEVMAN_FLAG_ADD_BY_VID_PID) + { + addnum = 0; + /* register all device that match pid vid */ + num = udev_new_by_id(urbdrc, udevman->context, idVendor, idProduct, &devArray); + + if (num == 0) + { + WLog_Print(urbdrc->log, WLOG_WARN, + "Could not find or redirect any usb devices by id %04x:%04x", idVendor, + idProduct); + } + + for (size_t i = 0; i < num; i++) + { + UINT32 id = 0; + IUDEVICE* tdev = devArray[i]; + + if (udevman_get_udevice_by_addr(idevman, tdev->get_bus_number(tdev), + tdev->get_dev_number(tdev)) != nullptr) + { + tdev->free(tdev); + devArray[i] = nullptr; + continue; + } + + id = idevman->get_next_device_id(idevman); + tdev->set_UsbDevice(tdev, id); + idevman->loading_lock(idevman); + + if (udevman->head == nullptr) + { + /* linked list is empty */ + udevman->head = tdev; + udevman->tail = tdev; + } + else + { + /* append device to the end of the linked list */ + udevman->tail->set_p_next(udevman->tail, tdev); + tdev->set_p_prev(tdev, udevman->tail); + udevman->tail = tdev; + } + + udevman->device_num += 1; + idevman->loading_unlock(idevman); + addnum++; + } + + free((void*)devArray); + return addnum; + } + else + { + WLog_Print(urbdrc->log, WLOG_ERROR, "udevman_register_udevice: Invalid flag=%08" PRIx32, + flag); + return 0; + } + + return 1; +} + +static BOOL udevman_unregister_udevice(IUDEVMAN* idevman, BYTE bus_number, BYTE dev_number) +{ + UDEVMAN* udevman = (UDEVMAN*)idevman; + UDEVICE* pdev = nullptr; + UDEVICE* dev = (UDEVICE*)udevman_get_udevice_by_addr(idevman, bus_number, dev_number); + + if (!dev || !idevman) + return FALSE; + + idevman->loading_lock(idevman); + idevman->rewind(idevman); + + while (idevman->has_next(idevman)) + { + pdev = (UDEVICE*)idevman->get_next(idevman); + + if (pdev == dev) /* device exists */ + { + /* set previous device to point to next device */ + if (dev->prev != nullptr) + { + /* unregistered device is not the head */ + pdev = dev->prev; + pdev->next = dev->next; + } + else + { + /* unregistered device is the head, update head */ + udevman->head = (IUDEVICE*)dev->next; + } + + /* set next device to point to previous device */ + + if (dev->next != nullptr) + { + /* unregistered device is not the tail */ + pdev = (UDEVICE*)dev->next; + pdev->prev = dev->prev; + } + else + { + /* unregistered device is the tail, update tail */ + udevman->tail = (IUDEVICE*)dev->prev; + } + + udevman->device_num--; + break; + } + } + + idevman->loading_unlock(idevman); + + if (dev) + { + dev->iface.free(&dev->iface); + return TRUE; /* unregistration successful */ + } + + /* if we reach this point, the device wasn't found */ + return FALSE; +} + +static BOOL udevman_unregister_all_udevices(IUDEVMAN* idevman) +{ + UDEVMAN* udevman = (UDEVMAN*)idevman; + + if (!idevman) + return FALSE; + + if (!udevman->head) + return TRUE; + + idevman->loading_lock(idevman); + idevman->rewind(idevman); + + while (idevman->has_next(idevman)) + { + UDEVICE* dev = (UDEVICE*)idevman->get_next(idevman); + + if (!dev) + continue; + + /* set previous device to point to next device */ + if (dev->prev != nullptr) + { + /* unregistered device is not the head */ + UDEVICE* pdev = dev->prev; + pdev->next = dev->next; + } + else + { + /* unregistered device is the head, update head */ + udevman->head = (IUDEVICE*)dev->next; + } + + /* set next device to point to previous device */ + + if (dev->next != nullptr) + { + /* unregistered device is not the tail */ + UDEVICE* pdev = (UDEVICE*)dev->next; + pdev->prev = dev->prev; + } + else + { + /* unregistered device is the tail, update tail */ + udevman->tail = (IUDEVICE*)dev->prev; + } + + dev->iface.free(&dev->iface); + udevman->device_num--; + } + + idevman->loading_unlock(idevman); + + return TRUE; +} + +static int udevman_is_auto_add(IUDEVMAN* idevman) +{ + UDEVMAN* udevman = (UDEVMAN*)idevman; + return (udevman->flags & UDEVMAN_FLAG_ADD_BY_AUTO) ? 1 : 0; +} + +static IUDEVICE* udevman_get_udevice_by_UsbDevice(IUDEVMAN* idevman, UINT32 UsbDevice) +{ + UDEVICE* pdev = nullptr; + URBDRC_PLUGIN* urbdrc = nullptr; + + if (!idevman || !idevman->plugin) + return nullptr; + + /* Mask highest 2 bits, must be ignored */ + UsbDevice = UsbDevice & INTERFACE_ID_MASK; + urbdrc = (URBDRC_PLUGIN*)idevman->plugin; + idevman->loading_lock(idevman); + idevman->rewind(idevman); + + while (idevman->has_next(idevman)) + { + pdev = (UDEVICE*)idevman->get_next(idevman); + + if (pdev->UsbDevice == UsbDevice) + { + idevman->loading_unlock(idevman); + return (IUDEVICE*)pdev; + } + } + + idevman->loading_unlock(idevman); + WLog_Print(urbdrc->log, WLOG_WARN, "Failed to find a USB device mapped to deviceId=%08" PRIx32, + UsbDevice); + return nullptr; +} + +static IUDEVICE* udevman_get_udevice_by_ChannelID(IUDEVMAN* idevman, UINT32 channelID) +{ + UDEVICE* pdev = nullptr; + URBDRC_PLUGIN* urbdrc = nullptr; + + if (!idevman || !idevman->plugin) + return nullptr; + + /* Mask highest 2 bits, must be ignored */ + urbdrc = (URBDRC_PLUGIN*)idevman->plugin; + idevman->loading_lock(idevman); + idevman->rewind(idevman); + + while (idevman->has_next(idevman)) + { + pdev = (UDEVICE*)idevman->get_next(idevman); + + if (pdev->channelID == channelID) + { + idevman->loading_unlock(idevman); + return (IUDEVICE*)pdev; + } + } + + idevman->loading_unlock(idevman); + WLog_Print(urbdrc->log, WLOG_WARN, "Failed to find a USB device mapped to channelID=%08" PRIx32, + channelID); + return nullptr; +} + +static void udevman_loading_lock(IUDEVMAN* idevman) +{ + UDEVMAN* udevman = (UDEVMAN*)idevman; + (void)WaitForSingleObject(udevman->devman_loading, INFINITE); +} + +static void udevman_loading_unlock(IUDEVMAN* idevman) +{ + UDEVMAN* udevman = (UDEVMAN*)idevman; + (void)ReleaseMutex(udevman->devman_loading); +} + +BASIC_STATE_FUNC_DEFINED(device_num, UINT32) + +static UINT32 udevman_get_next_device_id(IUDEVMAN* idevman) +{ + UDEVMAN* udevman = (UDEVMAN*)idevman; + return udevman->next_device_id++; +} + +static void udevman_set_next_device_id(IUDEVMAN* idevman, UINT32 _t) +{ + UDEVMAN* udevman = (UDEVMAN*)idevman; + udevman->next_device_id = _t; +} + +static void udevman_free(IUDEVMAN* idevman) +{ + UDEVMAN* udevman = (UDEVMAN*)idevman; + + if (!udevman) + return; + + udevman->running = FALSE; + if (udevman->thread) + { + (void)WaitForSingleObject(udevman->thread, INFINITE); + (void)CloseHandle(udevman->thread); + } + + udevman_unregister_all_udevices(idevman); + + if (udevman->devman_loading) + (void)CloseHandle(udevman->devman_loading); + + libusb_exit(udevman->context); + + ArrayList_Free(udevman->hotplug_vid_pids); + free(udevman); +} + +static BOOL filter_by_class(uint8_t bDeviceClass, uint8_t bDeviceSubClass) +{ + switch (bDeviceClass) + { + case LIBUSB_CLASS_AUDIO: + case LIBUSB_CLASS_HID: + case LIBUSB_CLASS_MASS_STORAGE: + case LIBUSB_CLASS_HUB: + case LIBUSB_CLASS_SMART_CARD: + return TRUE; + default: + break; + } + + switch (bDeviceSubClass) + { + default: + break; + } + + return FALSE; +} + +static BOOL append(char* dst, size_t length, const char* src) +{ + return winpr_str_append(src, dst, length, nullptr); +} + +static BOOL device_is_filtered(struct libusb_device* dev, + const struct libusb_device_descriptor* desc, + libusb_hotplug_event event) +{ + char buffer[8192] = WINPR_C_ARRAY_INIT; + char* what = nullptr; + BOOL filtered = FALSE; + append(buffer, sizeof(buffer), usb_interface_class_to_string(desc->bDeviceClass)); + if (filter_by_class(desc->bDeviceClass, desc->bDeviceSubClass)) + filtered = TRUE; + + switch (desc->bDeviceClass) + { + case LIBUSB_CLASS_PER_INTERFACE: + { + struct libusb_config_descriptor* config = nullptr; + int rc = libusb_get_active_config_descriptor(dev, &config); + if (rc == LIBUSB_SUCCESS) + { + for (uint8_t x = 0; x < config->bNumInterfaces; x++) + { + const struct libusb_interface* ifc = &config->interface[x]; + for (int y = 0; y < ifc->num_altsetting; y++) + { + const struct libusb_interface_descriptor* const alt = &ifc->altsetting[y]; + if (filter_by_class(alt->bInterfaceClass, alt->bInterfaceSubClass)) + filtered = TRUE; + + append(buffer, sizeof(buffer), "|"); + append(buffer, sizeof(buffer), + usb_interface_class_to_string(alt->bInterfaceClass)); + } + } + } + libusb_free_config_descriptor(config); + } + break; + default: + break; + } + + if (filtered) + what = "Filtered"; + else + { + switch (event) + { + case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT: + what = "Hotplug remove"; + break; + case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED: + what = "Hotplug add"; + break; + default: + what = "Hotplug unknown"; + break; + } + } + + WLog_DBG(TAG, "%s device VID=0x%04X,PID=0x%04X class %s", what, desc->idVendor, desc->idProduct, + buffer); + return filtered; +} + +static int LIBUSB_CALL hotplug_callback(struct libusb_context* ctx, struct libusb_device* dev, + libusb_hotplug_event event, void* user_data) +{ + VID_PID_PAIR pair; + struct libusb_device_descriptor desc; + UDEVMAN* udevman = (UDEVMAN*)user_data; + const uint8_t bus = libusb_get_bus_number(dev); + const uint8_t addr = libusb_get_device_address(dev); + int rc = libusb_get_device_descriptor(dev, &desc); + + WINPR_UNUSED(ctx); + + if (rc != LIBUSB_SUCCESS) + return rc; + + switch (event) + { + case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED: + pair.vid = desc.idVendor; + pair.pid = desc.idProduct; + if ((ArrayList_Contains(udevman->hotplug_vid_pids, &pair)) || + (udevman->iface.isAutoAdd(&udevman->iface) && + !device_is_filtered(dev, &desc, event))) + { + add_device(&udevman->iface, DEVICE_ADD_FLAG_ALL, bus, addr, desc.idVendor, + desc.idProduct); + } + break; + + case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT: + del_device(&udevman->iface, DEVICE_ADD_FLAG_ALL, bus, addr, desc.idVendor, + desc.idProduct); + break; + + default: + break; + } + + return 0; +} + +static BOOL udevman_initialize(IUDEVMAN* idevman, UINT32 channelId) +{ + UDEVMAN* udevman = (UDEVMAN*)idevman; + + if (!udevman) + return FALSE; + + idevman->status &= (uint32_t)~URBDRC_DEVICE_CHANNEL_CLOSED; + idevman->controlChannelId = channelId; + return TRUE; +} + +static BOOL udevman_vid_pid_pair_equals(const void* objA, const void* objB) +{ + const VID_PID_PAIR* a = objA; + const VID_PID_PAIR* b = objB; + + return (a->vid == b->vid) && (a->pid == b->pid); +} + +static BOOL udevman_parse_device_id_addr(const char** str, UINT16* id1, UINT16* id2, UINT16 max, + char split_sign, char delimiter) +{ + char* mid = nullptr; + char* end = nullptr; + unsigned long rc = 0; + + rc = strtoul(*str, &mid, 16); + + if ((mid == *str) || (*mid != split_sign) || (rc > max)) + return FALSE; + + *id1 = (UINT16)rc; + rc = strtoul(++mid, &end, 16); + + if ((end == mid) || (rc > max)) + return FALSE; + + *id2 = (UINT16)rc; + + *str += end - *str; + if (*end == '\0') + return TRUE; + if (*end == delimiter) + { + (*str)++; + return TRUE; + } + + return FALSE; +} + +static UINT urbdrc_udevman_register_devices(UDEVMAN* udevman, const char* devices, BOOL add_by_addr) +{ + const char* pos = devices; + + while (*pos != '\0') + { + UINT16 id1 = 0; + UINT16 id2 = 0; + if (!udevman_parse_device_id_addr(&pos, &id1, &id2, (add_by_addr) ? UINT8_MAX : UINT16_MAX, + ':', '#')) + { + WLog_ERR(TAG, "Invalid device argument: \"%s\"", devices); + return CHANNEL_RC_INITIALIZATION_ERROR; + } + + if (add_by_addr) + { + if (!add_device(&udevman->iface, DEVICE_ADD_FLAG_BUS | DEVICE_ADD_FLAG_DEV, (UINT8)id1, + (UINT8)id2, 0, 0)) + return CHANNEL_RC_INITIALIZATION_ERROR; + } + else + { + VID_PID_PAIR* idpair = calloc(1, sizeof(VID_PID_PAIR)); + if (!idpair) + return CHANNEL_RC_NO_MEMORY; + idpair->vid = id1; + idpair->pid = id2; + if (!ArrayList_Append(udevman->hotplug_vid_pids, idpair)) + { + free(idpair); + return CHANNEL_RC_NO_MEMORY; + } + + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): ArrayList_Append owns idpair + if (!add_device(&udevman->iface, DEVICE_ADD_FLAG_VENDOR | DEVICE_ADD_FLAG_PRODUCT, 0, 0, + id1, id2)) + { + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): ArrayList_Append owns idpair + return CHANNEL_RC_INITIALIZATION_ERROR; + } + } + } + + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): ArrayList_Append owns idpair + return CHANNEL_RC_OK; +} + +static UINT urbdrc_udevman_parse_addin_args(UDEVMAN* udevman, const ADDIN_ARGV* args) +{ + LPCSTR devices = nullptr; + + for (int x = 0; x < args->argc; x++) + { + const char* arg = args->argv[x]; + if (strcmp(arg, "dbg") == 0) + { + WLog_SetLogLevel(WLog_Get(TAG), WLOG_TRACE); + } + else if (_strnicmp(arg, "device:", 7) == 0) + { + /* Redirect all local devices */ + const char* val = &arg[7]; + const size_t len = strlen(val); + if (strcmp(val, "*") == 0) + { + udevman->flags |= UDEVMAN_FLAG_ADD_BY_AUTO; + } + else if (_strnicmp(arg, "USBInstanceID:", 14) == 0) + { + // TODO: Usb instance ID + } + else if ((val[0] == '{') && (val[len - 1] == '}')) + { + // TODO: Usb device class + } + } + else if (_strnicmp(arg, "dev:", 4) == 0) + { + devices = &arg[4]; + } + else if (_strnicmp(arg, "id", 2) == 0) + { + const char* p = strchr(arg, ':'); + if (p) + udevman->devices_vid_pid = p + 1; + else + udevman->flags = UDEVMAN_FLAG_ADD_BY_VID_PID; + } + else if (_strnicmp(arg, "addr", 4) == 0) + { + const char* p = strchr(arg, ':'); + if (p) + udevman->devices_addr = p + 1; + else + udevman->flags = UDEVMAN_FLAG_ADD_BY_ADDR; + } + else if (strcmp(arg, "auto") == 0) + { + udevman->flags |= UDEVMAN_FLAG_ADD_BY_AUTO; + } + else + { + const size_t len = strlen(arg); + if ((arg[0] == '{') && (arg[len - 1] == '}')) + { + // TODO: Check for {Device Setup Class GUID}: + } + } + } + if (devices) + { + if (udevman->flags & UDEVMAN_FLAG_ADD_BY_VID_PID) + udevman->devices_vid_pid = devices; + else if (udevman->flags & UDEVMAN_FLAG_ADD_BY_ADDR) + udevman->devices_addr = devices; + } + + return CHANNEL_RC_OK; +} + +static UINT udevman_listener_created_callback(IUDEVMAN* iudevman) +{ + UDEVMAN* udevman = (UDEVMAN*)iudevman; + WINPR_ASSERT(udevman); + + if (udevman->devices_vid_pid) + return urbdrc_udevman_register_devices(udevman, udevman->devices_vid_pid, FALSE); + + if (udevman->devices_addr) + return urbdrc_udevman_register_devices(udevman, udevman->devices_addr, TRUE); + + return CHANNEL_RC_OK; +} + +static void udevman_load_interface(UDEVMAN* udevman) +{ + /* standard */ + udevman->iface.free = udevman_free; + /* manage devices */ + udevman->iface.rewind = udevman_rewind; + udevman->iface.get_next = udevman_get_next; + udevman->iface.has_next = udevman_has_next; + udevman->iface.register_udevice = udevman_register_udevice; + udevman->iface.unregister_udevice = udevman_unregister_udevice; + udevman->iface.get_udevice_by_UsbDevice = udevman_get_udevice_by_UsbDevice; + udevman->iface.get_udevice_by_ChannelID = udevman_get_udevice_by_ChannelID; + /* Extension */ + udevman->iface.isAutoAdd = udevman_is_auto_add; + /* Basic state */ + BASIC_STATE_FUNC_REGISTER(device_num, udevman); + BASIC_STATE_FUNC_REGISTER(next_device_id, udevman); + + /* control semaphore or mutex lock */ + udevman->iface.loading_lock = udevman_loading_lock; + udevman->iface.loading_unlock = udevman_loading_unlock; + udevman->iface.initialize = udevman_initialize; + udevman->iface.listener_created_callback = udevman_listener_created_callback; +} + +static BOOL poll_libusb_events(UDEVMAN* udevman) +{ + int rc = LIBUSB_SUCCESS; + struct timeval tv = { 0, 500 }; + if (libusb_try_lock_events(udevman->context) == 0) + { + if (libusb_event_handling_ok(udevman->context)) + { + rc = libusb_handle_events_locked(udevman->context, &tv); + if (rc != LIBUSB_SUCCESS) + WLog_WARN(TAG, "libusb_handle_events_locked %d", rc); + } + libusb_unlock_events(udevman->context); + } + else + { + libusb_lock_event_waiters(udevman->context); + if (libusb_event_handler_active(udevman->context)) + { + rc = libusb_wait_for_event(udevman->context, &tv); + if (rc < LIBUSB_SUCCESS) + WLog_WARN(TAG, "libusb_wait_for_event %d", rc); + } + libusb_unlock_event_waiters(udevman->context); + } + + return rc > 0; +} + +static DWORD WINAPI poll_thread(LPVOID lpThreadParameter) +{ + libusb_hotplug_callback_handle handle = 0; + UDEVMAN* udevman = (UDEVMAN*)lpThreadParameter; + BOOL hasHotplug = libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG); + + if (hasHotplug) + { + int rc = libusb_hotplug_register_callback( + udevman->context, + LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, + LIBUSB_HOTPLUG_NO_FLAGS, LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY, + LIBUSB_HOTPLUG_MATCH_ANY, hotplug_callback, udevman, &handle); + + if (rc != LIBUSB_SUCCESS) + udevman->running = FALSE; + } + else + WLog_WARN(TAG, "Platform does not support libusb hotplug. USB devices plugged in later " + "will not be detected."); + + while (udevman->running) + { + poll_libusb_events(udevman); + } + + if (hasHotplug) + libusb_hotplug_deregister_callback(udevman->context, handle); + + /* Process remaining usb events */ + while (poll_libusb_events(udevman)) + ; + + ExitThread(0); + return 0; +} + +FREERDP_ENTRY_POINT(UINT VCAPITYPE libusb_freerdp_urbdrc_client_subsystem_entry( + PFREERDP_URBDRC_SERVICE_ENTRY_POINTS pEntryPoints)) +{ + wObject* obj = nullptr; + UINT status = 0; + UDEVMAN* udevman = nullptr; + const ADDIN_ARGV* args = pEntryPoints->args; + udevman = (PUDEVMAN)calloc(1, sizeof(UDEVMAN)); + + if (!udevman) + goto fail; + + udevman->hotplug_vid_pids = ArrayList_New(TRUE); + if (!udevman->hotplug_vid_pids) + goto fail; + obj = ArrayList_Object(udevman->hotplug_vid_pids); + obj->fnObjectFree = free; + obj->fnObjectEquals = udevman_vid_pid_pair_equals; + + udevman->next_device_id = BASE_USBDEVICE_NUM; + udevman->iface.plugin = pEntryPoints->plugin; + + { + const int res = libusb_init(&udevman->context); + if (res != LIBUSB_SUCCESS) + goto fail; + } + +#ifdef _WIN32 +#if LIBUSB_API_VERSION >= 0x01000106 + /* Prefer usbDK backend on windows. Not supported on other platforms. */ + const int rc = libusb_set_option(udevman->context, LIBUSB_OPTION_USE_USBDK); + switch (rc) + { + case LIBUSB_SUCCESS: + break; + case LIBUSB_ERROR_NOT_FOUND: + case LIBUSB_ERROR_NOT_SUPPORTED: + WLog_WARN(TAG, "LIBUSB_OPTION_USE_USBDK %s [%d]", libusb_strerror(rc), rc); + break; + default: + WLog_ERR(TAG, "LIBUSB_OPTION_USE_USBDK %s [%d]", libusb_strerror(rc), rc); + goto fail; + } +#endif +#endif + + udevman->flags = UDEVMAN_FLAG_ADD_BY_VID_PID; + udevman->devman_loading = CreateMutexA(nullptr, FALSE, "devman_loading"); + + if (!udevman->devman_loading) + goto fail; + + /* load usb device service management */ + udevman_load_interface(udevman); + status = urbdrc_udevman_parse_addin_args(udevman, args); + + if (status != CHANNEL_RC_OK) + goto fail; + + udevman->running = TRUE; + udevman->thread = CreateThread(nullptr, 0, poll_thread, udevman, 0, nullptr); + + if (!udevman->thread) + goto fail; + + if (!pEntryPoints->pRegisterUDEVMAN(pEntryPoints->plugin, (IUDEVMAN*)udevman)) + goto fail; + + WLog_DBG(TAG, "UDEVMAN device registered."); + return 0; +fail: + udevman_free(&udevman->iface); + return ERROR_INTERNAL_ERROR; +} diff --git a/third_party/FreeRDP/channels/urbdrc/client/urbdrc_main.c b/third_party/FreeRDP/channels/urbdrc/client/urbdrc_main.c new file mode 100644 index 0000000..7cc437a --- /dev/null +++ b/third_party/FreeRDP/channels/urbdrc/client/urbdrc_main.c @@ -0,0 +1,1080 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX USB Redirection + * + * Copyright 2012 Atrust corp. + * Copyright 2012 Alfred Liu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "urbdrc_types.h" +#include "urbdrc_main.h" +#include "data_transfer.h" + +#include + +static IWTSVirtualChannel* get_channel(IUDEVMAN* idevman) +{ + IWTSVirtualChannelManager* channel_mgr = nullptr; + URBDRC_PLUGIN* urbdrc = nullptr; + + if (!idevman) + return nullptr; + + urbdrc = (URBDRC_PLUGIN*)idevman->plugin; + + if (!urbdrc || !urbdrc->listener_callback) + return nullptr; + + channel_mgr = urbdrc->listener_callback->channel_mgr; + + if (!channel_mgr) + return nullptr; + + return channel_mgr->FindChannelById(channel_mgr, idevman->controlChannelId); +} + +static int func_container_id_generate(IUDEVICE* pdev, char* strContainerId) +{ + char* p = nullptr; + char* path = nullptr; + UINT8 containerId[17] = WINPR_C_ARRAY_INIT; + UINT16 idVendor = 0; + UINT16 idProduct = 0; + idVendor = (UINT16)pdev->query_device_descriptor(pdev, ID_VENDOR); + idProduct = (UINT16)pdev->query_device_descriptor(pdev, ID_PRODUCT); + path = pdev->getPath(pdev); + + if (strlen(path) > 8) + p = (path + strlen(path)) - 8; + else + p = path; + + (void)sprintf_s((char*)containerId, sizeof(containerId), "%04" PRIX16 "%04" PRIX16 "%s", + idVendor, idProduct, p); + /* format */ + (void)sprintf_s(strContainerId, DEVICE_CONTAINER_STR_SIZE, + "{%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "-%02" PRIx8 "%02" PRIx8 + "-%02" PRIx8 "%02" PRIx8 "-%02" PRIx8 "%02" PRIx8 "-%02" PRIx8 "%02" PRIx8 + "%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "}", + containerId[0], containerId[1], containerId[2], containerId[3], containerId[4], + containerId[5], containerId[6], containerId[7], containerId[8], containerId[9], + containerId[10], containerId[11], containerId[12], containerId[13], + containerId[14], containerId[15]); + return 0; +} + +static int func_instance_id_generate(IUDEVICE* pdev, char* strInstanceId, size_t len) +{ + char instanceId[17] = WINPR_C_ARRAY_INIT; + (void)sprintf_s(instanceId, sizeof(instanceId), "\\%s", pdev->getPath(pdev)); + /* format */ + (void)sprintf_s(strInstanceId, len, + "%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "-%02" PRIx8 "%02" PRIx8 + "-%02" PRIx8 "%02" PRIx8 "-%02" PRIx8 "%02" PRIx8 "-%02" PRIx8 "%02" PRIx8 + "%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "", + instanceId[0], instanceId[1], instanceId[2], instanceId[3], instanceId[4], + instanceId[5], instanceId[6], instanceId[7], instanceId[8], instanceId[9], + instanceId[10], instanceId[11], instanceId[12], instanceId[13], instanceId[14], + instanceId[15]); + return 0; +} + +/* [MS-RDPEUSB] 2.2.3.2 Interface Manipulation Exchange Capabilities Response + * (RIM_EXCHANGE_CAPABILITY_RESPONSE) */ +static UINT urbdrc_send_capability_response(GENERIC_CHANNEL_CALLBACK* callback, UINT32 MessageId, + UINT32 Version) +{ + const UINT32 InterfaceId = ((STREAM_ID_NONE << 30) | CAPABILITIES_NEGOTIATOR); + wStream* out = create_shared_message_header_with_functionid(InterfaceId, MessageId, Version, 4); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(out, 0x00000000); /* HRESULT */ + return stream_write_and_free(callback->plugin, callback->channel, out); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urbdrc_process_capability_request(GENERIC_CHANNEL_CALLBACK* callback, wStream* s, + UINT32 MessageId) +{ + WINPR_ASSERT(callback); + URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)callback->plugin; + WINPR_ASSERT(urbdrc); + + if (!callback || !s) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLengthWLog(urbdrc->log, s, 4)) + return ERROR_INVALID_DATA; + + UINT32 Version = Stream_Get_UINT32(s); + + if (Version > RIM_CAPABILITY_VERSION_01) + { + WLog_Print(urbdrc->log, WLOG_WARN, "Unknown capability version %" PRIu32 ", expected %d", + Version, RIM_CAPABILITY_VERSION_01); + Version = RIM_CAPABILITY_VERSION_01; + } + + return urbdrc_send_capability_response(callback, MessageId, Version); +} + +/* [MS-RDPEUSB] 2.2.5.1 Channel Created Message (CHANNEL_CREATED) */ +static UINT urbdrc_send_channel_created(GENERIC_CHANNEL_CALLBACK* callback, UINT32 MessageId, + UINT32 MajorVersion, UINT32 MinorVersion, + UINT32 Capabilities) +{ + WINPR_ASSERT(callback); + + const UINT32 InterfaceId = ((STREAM_ID_PROXY << 30) | CLIENT_CHANNEL_NOTIFICATION); + wStream* out = + create_shared_message_header_with_functionid(InterfaceId, MessageId, CHANNEL_CREATED, 12); + + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(out, MajorVersion); + Stream_Write_UINT32(out, MinorVersion); + Stream_Write_UINT32(out, Capabilities); /* capabilities version */ + return stream_write_and_free(callback->plugin, callback->channel, out); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urbdrc_process_channel_create(GENERIC_CHANNEL_CALLBACK* callback, wStream* s, + UINT32 MessageId) +{ + UINT32 MajorVersion = 0; + UINT32 MinorVersion = 0; + UINT32 Capabilities = 0; + URBDRC_PLUGIN* urbdrc = nullptr; + + if (!callback || !s || !callback->plugin) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 12)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, MajorVersion); + Stream_Read_UINT32(s, MinorVersion); + Stream_Read_UINT32(s, Capabilities); + + /* Version check, we only support version 1.0 */ + if ((MajorVersion != 1) || (MinorVersion != 0)) + { + WLog_Print(urbdrc->log, WLOG_WARN, + "server supports USB channel version %" PRIu32 ".%" PRIu32, MajorVersion, + MinorVersion); + WLog_Print(urbdrc->log, WLOG_WARN, "we only support channel version 1.0"); + MajorVersion = 1; + MinorVersion = 0; + } + if (Capabilities != 0) + { + WLog_Print(urbdrc->log, WLOG_WARN, + "[MS-RDPEUSB] 2.2.5.1 Channel Created Message (CHANNEL_CREATED) states " + "Capabilities must be 0, got %" PRIu32, + Capabilities); + Capabilities = 0; + } + + return urbdrc_send_channel_created(callback, MessageId, MajorVersion, MinorVersion, + Capabilities); +} + +static UINT urdbrc_send_virtual_channel_add(IWTSPlugin* plugin, IWTSVirtualChannel* channel, + UINT32 MessageId) +{ + const UINT32 InterfaceId = ((STREAM_ID_PROXY << 30) | CLIENT_DEVICE_SINK); + wStream* out = create_shared_message_header_with_functionid(InterfaceId, MessageId, + ADD_VIRTUAL_CHANNEL, 0); + + if (!out) + return ERROR_OUTOFMEMORY; + + return stream_write_and_free(plugin, channel, out); +} + +static BOOL write_string_block(wStream* s, size_t count, const char** strings, const size_t* length, + BOOL isMultiSZ) +{ + size_t len = 0; + for (size_t x = 0; x < count; x++) + { + len += length[x] + 1ULL; + } + + if (isMultiSZ) + len++; + + if (!Stream_EnsureRemainingCapacity(s, len * sizeof(WCHAR) + sizeof(UINT32))) + return FALSE; + + /* Write number of characters (including '\0') of all strings */ + Stream_Write_UINT32(s, (UINT32)len); /* cchHwIds */ + /* HardwareIds 1 */ + + for (size_t x = 0; x < count; x++) + { + size_t clength = length[x]; + const char* str = strings[x]; + + const SSIZE_T w = Stream_Write_UTF16_String_From_UTF8(s, clength, str, clength, TRUE); + if ((w < 0) || ((size_t)w != clength)) + return FALSE; + Stream_Write_UINT16(s, 0); + } + + if (isMultiSZ) + Stream_Write_UINT16(s, 0); + return TRUE; +} + +/* [MS-RDPEUSB] 2.2.4.2 Add Device Message (ADD_DEVICE) */ +static UINT urbdrc_send_add_device(GENERIC_CHANNEL_CALLBACK* callback, UINT32 UsbDevice, + UINT32 bcdUSB, const char* strInstanceId, size_t InstanceIdLen, + size_t nrHwIds, const char* HardwareIds[], + const size_t HardwareIdsLen[], size_t nrCompatIds, + const char* CompatibilityIds[], + const size_t CompatibilityIdsLen[], const char* strContainerId, + size_t ContainerIdLen) +{ + WINPR_ASSERT(callback); + WINPR_ASSERT(HardwareIds); + WINPR_ASSERT(HardwareIdsLen); + WINPR_ASSERT(CompatibilityIds); + WINPR_ASSERT(CompatibilityIdsLen); + + const UINT32 InterfaceId = ((STREAM_ID_PROXY << 30) | CLIENT_DEVICE_SINK); + wStream* out = create_shared_message_header_with_functionid(InterfaceId, 0, ADD_DEVICE, 8); + if (!out) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(out, 0x00000001); /* NumUsbDevice */ + Stream_Write_UINT32(out, UsbDevice); /* UsbDevice */ + + if (!write_string_block(out, 1, &strInstanceId, &InstanceIdLen, FALSE)) + goto fail; + + if (!write_string_block(out, nrHwIds, HardwareIds, HardwareIdsLen, TRUE)) + goto fail; + + if (!write_string_block(out, nrCompatIds, CompatibilityIds, CompatibilityIdsLen, TRUE)) + goto fail; + + if (!write_string_block(out, 1, &strContainerId, &ContainerIdLen, FALSE)) + goto fail; + + /* USB_DEVICE_CAPABILITIES 28 bytes */ + if (!Stream_EnsureRemainingCapacity(out, 28)) + goto fail; + + Stream_Write_UINT32(out, 0x0000001c); /* CbSize */ + Stream_Write_UINT32(out, 2); /* UsbBusInterfaceVersion, 0 ,1 or 2 */ // TODO: Get from libusb + Stream_Write_UINT32(out, 0x600); /* USBDI_Version, 0x500 or 0x600 */ // TODO: Get from libusb + /* Supported_USB_Version, 0x110,0x110 or 0x200(usb2.0) */ + Stream_Write_UINT32(out, bcdUSB); + Stream_Write_UINT32(out, 0x00000000); /* HcdCapabilities, MUST always be zero */ + + if (bcdUSB < 0x200) + Stream_Write_UINT32(out, 0x00000000); /* DeviceIsHighSpeed */ + else + Stream_Write_UINT32(out, 0x00000001); /* DeviceIsHighSpeed */ + + Stream_Write_UINT32(out, 0x50); /* NoAckIsochWriteJitterBufferSizeInMs, >=10 or <=512 */ + return stream_write_and_free(callback->plugin, callback->channel, out); + +fail: + Stream_Free(out, TRUE); + return ERROR_INTERNAL_ERROR; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urdbrc_send_usb_device_add(GENERIC_CHANNEL_CALLBACK* callback, IUDEVICE* pdev) +{ + char HardwareIds[2][DEVICE_HARDWARE_ID_SIZE] = { { 0 } }; + const char* CHardwareIds[2] = { HardwareIds[0], HardwareIds[1] }; + char CompatibilityIds[4][DEVICE_COMPATIBILITY_ID_SIZE] = { { 0 } }; + const char* CCompatibilityIds[4] = { CompatibilityIds[0], CompatibilityIds[1], + CompatibilityIds[2], CompatibilityIds[3] }; + char strContainerId[DEVICE_CONTAINER_STR_SIZE] = WINPR_C_ARRAY_INIT; + char strInstanceId[DEVICE_INSTANCE_STR_SIZE] = WINPR_C_ARRAY_INIT; + size_t CompatibilityIdLen[4] = WINPR_C_ARRAY_INIT; + size_t HardwareIdsLen[2] = WINPR_C_ARRAY_INIT; + const size_t nrHwIds = ARRAYSIZE(HardwareIds); + size_t nrCompatIds = 3; + + /* USB kernel driver detach!! */ + if (!pdev->detach_kernel_driver(pdev)) + return ERROR_INTERNAL_ERROR; + + { + const UINT16 idVendor = (UINT16)pdev->query_device_descriptor(pdev, ID_VENDOR); + const UINT16 idProduct = (UINT16)pdev->query_device_descriptor(pdev, ID_PRODUCT); + const UINT16 bcdDevice = (UINT16)pdev->query_device_descriptor(pdev, BCD_DEVICE); + (void)sprintf_s(HardwareIds[1], DEVICE_HARDWARE_ID_SIZE, + "USB\\VID_%04" PRIX16 "&PID_%04" PRIX16 "", idVendor, idProduct); + (void)sprintf_s(HardwareIds[0], DEVICE_HARDWARE_ID_SIZE, + "USB\\VID_%04" PRIX16 "&PID_%04" PRIX16 "&REV_%04" PRIX16 "", idVendor, + idProduct, bcdDevice); + } + { + const UINT8 bDeviceClass = (UINT8)pdev->query_device_descriptor(pdev, B_DEVICE_CLASS); + const UINT8 bDeviceSubClass = (UINT8)pdev->query_device_descriptor(pdev, B_DEVICE_SUBCLASS); + const UINT8 bDeviceProtocol = (UINT8)pdev->query_device_descriptor(pdev, B_DEVICE_PROTOCOL); + + if (!(pdev->isCompositeDevice(pdev))) + { + (void)sprintf_s(CompatibilityIds[2], DEVICE_COMPATIBILITY_ID_SIZE, + "USB\\Class_%02" PRIX8 "", bDeviceClass); + (void)sprintf_s(CompatibilityIds[1], DEVICE_COMPATIBILITY_ID_SIZE, + "USB\\Class_%02" PRIX8 "&SubClass_%02" PRIX8 "", bDeviceClass, + bDeviceSubClass); + (void)sprintf_s(CompatibilityIds[0], DEVICE_COMPATIBILITY_ID_SIZE, + "USB\\Class_%02" PRIX8 "&SubClass_%02" PRIX8 "&Prot_%02" PRIX8 "", + bDeviceClass, bDeviceSubClass, bDeviceProtocol); + } + else + { + (void)sprintf_s(CompatibilityIds[3], DEVICE_COMPATIBILITY_ID_SIZE, "USB\\COMPOSITE"); + (void)sprintf_s(CompatibilityIds[2], DEVICE_COMPATIBILITY_ID_SIZE, "USB\\DevClass_00"); + (void)sprintf_s(CompatibilityIds[1], DEVICE_COMPATIBILITY_ID_SIZE, + "USB\\DevClass_00&SubClass_00"); + (void)sprintf_s(CompatibilityIds[0], DEVICE_COMPATIBILITY_ID_SIZE, + "USB\\DevClass_00&SubClass_00&Prot_00"); + nrCompatIds = 4; + } + } + func_instance_id_generate(pdev, strInstanceId, DEVICE_INSTANCE_STR_SIZE); + func_container_id_generate(pdev, strContainerId); + + for (size_t x = 0; x < nrHwIds; x++) + { + HardwareIdsLen[x] = strnlen(HardwareIds[x], DEVICE_HARDWARE_ID_SIZE); + } + + for (size_t x = 0; x < nrCompatIds; x++) + { + CompatibilityIdLen[x] = strnlen(CompatibilityIds[x], DEVICE_COMPATIBILITY_ID_SIZE); + } + + const size_t InstanceIdLen = strnlen(strInstanceId, sizeof(strInstanceId)); + const size_t ContainerIdLen = strnlen(strContainerId, sizeof(strContainerId)); + + const UINT32 UsbDevice = pdev->get_UsbDevice(pdev); + const UINT32 bcdUSB = + WINPR_ASSERTING_INT_CAST(uint32_t, pdev->query_device_descriptor(pdev, BCD_USB)); + return urbdrc_send_add_device(callback, UsbDevice, bcdUSB, strInstanceId, InstanceIdLen, + nrHwIds, CHardwareIds, HardwareIdsLen, nrCompatIds, + CCompatibilityIds, CompatibilityIdLen, strContainerId, + ContainerIdLen); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urbdrc_exchange_capabilities(GENERIC_CHANNEL_CALLBACK* callback, wStream* data) +{ + UINT32 MessageId = 0; + UINT32 FunctionId = 0; + UINT32 InterfaceId = 0; + UINT error = CHANNEL_RC_OK; + + if (!data) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, data, 8)) + return ERROR_INVALID_DATA; + + Stream_Rewind_UINT32(data); + Stream_Read_UINT32(data, InterfaceId); + Stream_Read_UINT32(data, MessageId); + Stream_Read_UINT32(data, FunctionId); + + switch (FunctionId) + { + case RIM_EXCHANGE_CAPABILITY_REQUEST: + if (InterfaceId != 0) + { + WLog_ERR( + TAG, + "[MS-RDPEUSB] 2.2.3.1 Interface Manipulation Exchange Capabilities Request " + "(RIM_EXCHANGE_CAPABILITY_REQUEST))::InterfaceId expected 0, got %" PRIu32, + InterfaceId); + return ERROR_INVALID_DATA; + } + error = urbdrc_process_capability_request(callback, data, MessageId); + break; + + case RIMCALL_RELEASE: + break; + + default: + error = ERROR_NOT_FOUND; + break; + } + + return error; +} + +static BOOL urbdrc_announce_devices(IUDEVMAN* udevman) +{ + UINT error = ERROR_SUCCESS; + + udevman->loading_lock(udevman); + udevman->rewind(udevman); + + while (udevman->has_next(udevman)) + { + IUDEVICE* pdev = udevman->get_next(udevman); + + if (!pdev->isAlreadySend(pdev)) + { + const UINT32 deviceId = pdev->get_UsbDevice(pdev); + UINT cerror = + urdbrc_send_virtual_channel_add(udevman->plugin, get_channel(udevman), deviceId); + + if (cerror != ERROR_SUCCESS) + break; + } + } + + udevman->loading_unlock(udevman); + + return error == ERROR_SUCCESS; +} + +static UINT urbdrc_device_control_channel(GENERIC_CHANNEL_CALLBACK* callback, + WINPR_ATTR_UNUSED wStream* s) +{ + URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)callback->plugin; + IUDEVMAN* udevman = urbdrc->udevman; + IWTSVirtualChannel* channel = callback->channel; + IUDEVICE* pdev = nullptr; + BOOL found = FALSE; + UINT error = ERROR_INTERNAL_ERROR; + UINT32 channelId = callback->channel_mgr->GetChannelId(channel); + + switch (urbdrc->vchannel_status) + { + case INIT_CHANNEL_IN: + /* Control channel was established */ + error = ERROR_SUCCESS; + if (!udevman->initialize(udevman, channelId)) + goto fail; + + if (!urbdrc_announce_devices(udevman)) + goto fail; + + urbdrc->vchannel_status = INIT_CHANNEL_OUT; + break; + + case INIT_CHANNEL_OUT: + /* A new device channel was created, add the channel + * to the device */ + udevman->loading_lock(udevman); + udevman->rewind(udevman); + + while (udevman->has_next(udevman)) + { + pdev = udevman->get_next(udevman); + + if (!pdev->isAlreadySend(pdev)) + { + const UINT32 channelID = callback->channel_mgr->GetChannelId(channel); + found = TRUE; + pdev->setAlreadySend(pdev); + pdev->set_channelManager(pdev, callback->channel_mgr); + pdev->set_channelID(pdev, channelID); + break; + } + } + + udevman->loading_unlock(udevman); + error = ERROR_SUCCESS; + + if (found && pdev->isAlreadySend(pdev)) + error = urdbrc_send_usb_device_add(callback, pdev); + + break; + + default: + WLog_Print(urbdrc->log, WLOG_ERROR, "vchannel_status unknown value %" PRIu32 "", + urbdrc->vchannel_status); + break; + } + +fail: + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urbdrc_process_channel_notification(GENERIC_CHANNEL_CALLBACK* callback, wStream* data) +{ + UINT32 MessageId = 0; + UINT32 FunctionId = 0; + UINT32 InterfaceId = 0; + UINT error = CHANNEL_RC_OK; + URBDRC_PLUGIN* urbdrc = nullptr; + + if (!callback || !data) + return ERROR_INVALID_PARAMETER; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (!urbdrc) + return ERROR_INVALID_PARAMETER; + + if (!Stream_CheckAndLogRequiredLength(TAG, data, 8)) + return ERROR_INVALID_DATA; + + Stream_Rewind(data, 4); + Stream_Read_UINT32(data, InterfaceId); + Stream_Read_UINT32(data, MessageId); + Stream_Read_UINT32(data, FunctionId); + WLog_Print(urbdrc->log, WLOG_TRACE, "%s [%" PRIu32 "]", + call_to_string(FALSE, InterfaceId, FunctionId), FunctionId); + + switch (FunctionId) + { + case CHANNEL_CREATED: + error = urbdrc_process_channel_create(callback, data, MessageId); + break; + + case RIMCALL_RELEASE: + error = urbdrc_device_control_channel(callback, data); + break; + + default: + WLog_Print(urbdrc->log, WLOG_TRACE, "unknown FunctionId 0x%" PRIX32 "", FunctionId); + error = 1; + break; + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urbdrc_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data) +{ + GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + URBDRC_PLUGIN* urbdrc = nullptr; + IUDEVMAN* udevman = nullptr; + UINT32 InterfaceId = 0; + UINT error = ERROR_INTERNAL_ERROR; + + if (callback == nullptr) + return ERROR_INVALID_PARAMETER; + + if (callback->plugin == nullptr) + return error; + + urbdrc = (URBDRC_PLUGIN*)callback->plugin; + + if (urbdrc->udevman == nullptr) + return error; + + udevman = urbdrc->udevman; + + if (!Stream_CheckAndLogRequiredLength(TAG, data, 12)) + return ERROR_INVALID_DATA; + + urbdrc_dump_message(urbdrc->log, FALSE, FALSE, data); + Stream_Read_UINT32(data, InterfaceId); + + /* Need to check InterfaceId and mask values */ + switch (InterfaceId) + { + case CAPABILITIES_NEGOTIATOR | (STREAM_ID_NONE << 30): + error = urbdrc_exchange_capabilities(callback, data); + break; + + case SERVER_CHANNEL_NOTIFICATION | (STREAM_ID_PROXY << 30): + error = urbdrc_process_channel_notification(callback, data); + break; + + default: + error = urbdrc_process_udev_data_transfer(callback, urbdrc, udevman, data); + WLog_DBG(TAG, "urbdrc_process_udev_data_transfer returned 0x%08" PRIx32, error); + error = ERROR_SUCCESS; /* Ignore errors, the device may have been unplugged. */ + break; + } + + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urbdrc_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + if (callback) + { + URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)callback->plugin; + if (urbdrc) + { + IUDEVMAN* udevman = urbdrc->udevman; + if (udevman && callback->channel_mgr) + { + UINT32 control = callback->channel_mgr->GetChannelId(callback->channel); + if (udevman->controlChannelId == control) + udevman->status |= URBDRC_DEVICE_CHANNEL_CLOSED; + else + { /* Need to notify the local backend the device is gone */ + IUDEVICE* pdev = udevman->get_udevice_by_ChannelID(udevman, control); + if (pdev) + pdev->markChannelClosed(pdev); + } + } + } + } + free(callback); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urbdrc_on_new_channel_connection(IWTSListenerCallback* pListenerCallback, + IWTSVirtualChannel* pChannel, + WINPR_ATTR_UNUSED BYTE* pData, + WINPR_ATTR_UNUSED BOOL* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +{ + GENERIC_LISTENER_CALLBACK* listener_callback = (GENERIC_LISTENER_CALLBACK*)pListenerCallback; + GENERIC_CHANNEL_CALLBACK* callback = nullptr; + + if (!ppCallback) + return ERROR_INVALID_PARAMETER; + + callback = (GENERIC_CHANNEL_CALLBACK*)calloc(1, sizeof(GENERIC_CHANNEL_CALLBACK)); + + if (!callback) + return ERROR_OUTOFMEMORY; + + callback->iface.OnDataReceived = urbdrc_on_data_received; + callback->iface.OnClose = urbdrc_on_close; + callback->plugin = listener_callback->plugin; + callback->channel_mgr = listener_callback->channel_mgr; + callback->channel = pChannel; + *ppCallback = (IWTSVirtualChannelCallback*)callback; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urbdrc_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr) +{ + UINT status = 0; + URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)pPlugin; + IUDEVMAN* udevman = nullptr; + char channelName[sizeof(URBDRC_CHANNEL_NAME)] = { URBDRC_CHANNEL_NAME }; + + if (!urbdrc || !urbdrc->udevman) + return ERROR_INVALID_PARAMETER; + + if (urbdrc->initialized) + { + WLog_ERR(TAG, "[%s] channel initialized twice, aborting", URBDRC_CHANNEL_NAME); + return ERROR_INVALID_DATA; + } + udevman = urbdrc->udevman; + urbdrc->listener_callback = + (GENERIC_LISTENER_CALLBACK*)calloc(1, sizeof(GENERIC_LISTENER_CALLBACK)); + + if (!urbdrc->listener_callback) + return CHANNEL_RC_NO_MEMORY; + + urbdrc->listener_callback->iface.OnNewChannelConnection = urbdrc_on_new_channel_connection; + urbdrc->listener_callback->plugin = pPlugin; + urbdrc->listener_callback->channel_mgr = pChannelMgr; + + /* [MS-RDPEUSB] 2.1 Transport defines the channel name in uppercase letters */ + CharUpperA(channelName); + status = pChannelMgr->CreateListener(pChannelMgr, channelName, 0, + &urbdrc->listener_callback->iface, &urbdrc->listener); + if (status != CHANNEL_RC_OK) + return status; + + status = CHANNEL_RC_OK; + if (udevman->listener_created_callback) + status = udevman->listener_created_callback(udevman); + + urbdrc->initialized = status == CHANNEL_RC_OK; + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urbdrc_plugin_terminated(IWTSPlugin* pPlugin) +{ + URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)pPlugin; + IUDEVMAN* udevman = nullptr; + + if (!urbdrc) + return ERROR_INVALID_DATA; + if (urbdrc->listener_callback) + { + IWTSVirtualChannelManager* mgr = urbdrc->listener_callback->channel_mgr; + if (mgr) + IFCALL(mgr->DestroyListener, mgr, urbdrc->listener); + } + udevman = urbdrc->udevman; + + if (udevman) + { + udevman->free(udevman); + udevman = nullptr; + } + + free(urbdrc->subsystem); + free(urbdrc->listener_callback); + free(urbdrc); + return CHANNEL_RC_OK; +} + +static BOOL urbdrc_register_udevman_addin(IWTSPlugin* pPlugin, IUDEVMAN* udevman) +{ + URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)pPlugin; + + if (urbdrc->udevman) + { + WLog_Print(urbdrc->log, WLOG_ERROR, "existing device, abort."); + return FALSE; + } + + DEBUG_DVC("device registered."); + urbdrc->udevman = udevman; + return TRUE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urbdrc_load_udevman_addin(IWTSPlugin* pPlugin, LPCSTR name, const ADDIN_ARGV* args) +{ + URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)pPlugin; + FREERDP_URBDRC_SERVICE_ENTRY_POINTS entryPoints = WINPR_C_ARRAY_INIT; + + PVIRTUALCHANNELENTRY pvce = + freerdp_load_channel_addin_entry(URBDRC_CHANNEL_NAME, name, nullptr, 0); + PFREERDP_URBDRC_DEVICE_ENTRY entry = WINPR_FUNC_PTR_CAST(pvce, PFREERDP_URBDRC_DEVICE_ENTRY); + + if (!entry) + return ERROR_INVALID_OPERATION; + + entryPoints.plugin = pPlugin; + entryPoints.pRegisterUDEVMAN = urbdrc_register_udevman_addin; + entryPoints.args = args; + + const UINT error = entry(&entryPoints); + if (error) + { + WLog_Print(urbdrc->log, WLOG_ERROR, "%s entry returns error.", name); + return error; + } + + return CHANNEL_RC_OK; +} + +static BOOL urbdrc_set_subsystem(URBDRC_PLUGIN* urbdrc, const char* subsystem) +{ + free(urbdrc->subsystem); + urbdrc->subsystem = _strdup(subsystem); + return (urbdrc->subsystem != nullptr); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT urbdrc_process_addin_args(URBDRC_PLUGIN* urbdrc, const ADDIN_ARGV* args) +{ + int status = 0; + COMMAND_LINE_ARGUMENT_A urbdrc_args[] = { + { "dbg", COMMAND_LINE_VALUE_FLAG, "", nullptr, BoolValueFalse, -1, nullptr, "debug" }, + { "sys", COMMAND_LINE_VALUE_REQUIRED, "", nullptr, nullptr, -1, nullptr, + "subsystem" }, + { "dev", COMMAND_LINE_VALUE_REQUIRED, "", nullptr, nullptr, -1, nullptr, + "devices" }, + { "encode", COMMAND_LINE_VALUE_FLAG, "", nullptr, nullptr, -1, nullptr, "encode" }, + { "quality", COMMAND_LINE_VALUE_REQUIRED, "<[0-2] -> [high-medium-low]>", nullptr, nullptr, + -1, nullptr, "quality" }, + { nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr } + }; + + const DWORD flags = + COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + const COMMAND_LINE_ARGUMENT_A* arg = nullptr; + status = CommandLineParseArgumentsA(args->argc, args->argv, urbdrc_args, flags, urbdrc, nullptr, + nullptr); + + if (status < 0) + return ERROR_INVALID_DATA; + + arg = urbdrc_args; + + do + { + if (!(arg->Flags & COMMAND_LINE_VALUE_PRESENT)) + continue; + + CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "dbg") + { + WLog_SetLogLevel(urbdrc->log, WLOG_TRACE); + } + CommandLineSwitchCase(arg, "sys") + { + if (!urbdrc_set_subsystem(urbdrc, arg->Value)) + return ERROR_OUTOFMEMORY; + } + CommandLineSwitchDefault(arg) + { + } + CommandLineSwitchEnd(arg) + } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr); + + return CHANNEL_RC_OK; +} + +BOOL add_device(IUDEVMAN* idevman, UINT32 flags, BYTE busnum, BYTE devnum, UINT16 idVendor, + UINT16 idProduct) +{ + UINT32 regflags = 0; + + if (!idevman) + return FALSE; + + URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)idevman->plugin; + + if (!urbdrc || !urbdrc->listener_callback) + return FALSE; + + UINT32 mask = (DEVICE_ADD_FLAG_VENDOR | DEVICE_ADD_FLAG_PRODUCT); + if ((flags & mask) == mask) + regflags |= UDEVMAN_FLAG_ADD_BY_VID_PID; + mask = (DEVICE_ADD_FLAG_BUS | DEVICE_ADD_FLAG_DEV); + if ((flags & mask) == mask) + regflags |= UDEVMAN_FLAG_ADD_BY_ADDR; + + const size_t success = + idevman->register_udevice(idevman, busnum, devnum, idVendor, idProduct, regflags); + + if ((success > 0) && (flags & DEVICE_ADD_FLAG_REGISTER)) + { + if (!urbdrc_announce_devices(idevman)) + return FALSE; + } + + return TRUE; +} + +BOOL del_device(IUDEVMAN* idevman, UINT32 flags, BYTE busnum, BYTE devnum, UINT16 idVendor, + UINT16 idProduct) +{ + IUDEVICE* pdev = nullptr; + URBDRC_PLUGIN* urbdrc = nullptr; + + if (!idevman) + return FALSE; + + urbdrc = (URBDRC_PLUGIN*)idevman->plugin; + + if (!urbdrc || !urbdrc->listener_callback) + return FALSE; + + idevman->loading_lock(idevman); + idevman->rewind(idevman); + + while (idevman->has_next(idevman)) + { + BOOL match = TRUE; + IUDEVICE* dev = idevman->get_next(idevman); + + if ((flags & (DEVICE_ADD_FLAG_BUS | DEVICE_ADD_FLAG_DEV | DEVICE_ADD_FLAG_VENDOR | + DEVICE_ADD_FLAG_PRODUCT)) == 0) + match = FALSE; + if (flags & DEVICE_ADD_FLAG_BUS) + { + if (dev->get_bus_number(dev) != busnum) + match = FALSE; + } + if (flags & DEVICE_ADD_FLAG_DEV) + { + if (dev->get_dev_number(dev) != devnum) + match = FALSE; + } + if (flags & DEVICE_ADD_FLAG_VENDOR) + { + int vid = dev->query_device_descriptor(dev, ID_VENDOR); + if (vid != idVendor) + match = FALSE; + } + if (flags & DEVICE_ADD_FLAG_PRODUCT) + { + int pid = dev->query_device_descriptor(dev, ID_PRODUCT); + if (pid != idProduct) + match = FALSE; + } + + if (match) + { + pdev = dev; + break; + } + } + + if (pdev) + pdev->setChannelClosed(pdev); + + idevman->loading_unlock(idevman); + return TRUE; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE urbdrc_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)) +{ + UINT status = 0; + const ADDIN_ARGV* args = nullptr; + URBDRC_PLUGIN* urbdrc = nullptr; + urbdrc = (URBDRC_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints, URBDRC_CHANNEL_NAME); + args = pEntryPoints->GetPluginData(pEntryPoints); + + if (urbdrc == nullptr) + { + urbdrc = (URBDRC_PLUGIN*)calloc(1, sizeof(URBDRC_PLUGIN)); + + if (!urbdrc) + return CHANNEL_RC_NO_MEMORY; + + urbdrc->iface.Initialize = urbdrc_plugin_initialize; + urbdrc->iface.Terminated = urbdrc_plugin_terminated; + urbdrc->vchannel_status = INIT_CHANNEL_IN; + status = pEntryPoints->RegisterPlugin(pEntryPoints, URBDRC_CHANNEL_NAME, &urbdrc->iface); + + /* After we register the plugin free will be taken care of by dynamic channel */ + if (status != CHANNEL_RC_OK) + { + free(urbdrc); + goto fail; + } + + urbdrc->log = WLog_Get(TAG); + + if (!urbdrc->log) + goto fail; + } + + status = urbdrc_process_addin_args(urbdrc, args); + + if (status != CHANNEL_RC_OK) + goto fail; + + if (!urbdrc->subsystem && !urbdrc_set_subsystem(urbdrc, "libusb")) + goto fail; + + return urbdrc_load_udevman_addin(&urbdrc->iface, urbdrc->subsystem, args); +fail: + return status; +} + +UINT stream_write_and_free(IWTSPlugin* plugin, IWTSVirtualChannel* channel, wStream* out) +{ + URBDRC_PLUGIN* urbdrc = (URBDRC_PLUGIN*)plugin; + + if (!out) + return ERROR_INVALID_PARAMETER; + + if (!channel || !out || !urbdrc) + { + Stream_Free(out, TRUE); + return ERROR_INVALID_PARAMETER; + } + + if (!channel->Write) + { + Stream_Free(out, TRUE); + return ERROR_INTERNAL_ERROR; + } + + urbdrc_dump_message(urbdrc->log, TRUE, TRUE, out); + const size_t len = Stream_GetPosition(out); + UINT rc = ERROR_INTERNAL_ERROR; + if (len <= UINT32_MAX) + rc = channel->Write(channel, (UINT32)len, Stream_Buffer(out), nullptr); + Stream_Free(out, TRUE); + return rc; +} diff --git a/third_party/FreeRDP/channels/urbdrc/client/urbdrc_main.h b/third_party/FreeRDP/channels/urbdrc/client/urbdrc_main.h new file mode 100644 index 0000000..a5791c9 --- /dev/null +++ b/third_party/FreeRDP/channels/urbdrc/client/urbdrc_main.h @@ -0,0 +1,233 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX USB Redirection + * + * Copyright 2012 Atrust corp. + * Copyright 2012 Alfred Liu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_URBDRC_CLIENT_MAIN_H +#define FREERDP_CHANNEL_URBDRC_CLIENT_MAIN_H + +#include +#include +#include + +#include + +#define DEVICE_HARDWARE_ID_SIZE 32 +#define DEVICE_COMPATIBILITY_ID_SIZE 36 +#define DEVICE_INSTANCE_STR_SIZE 37 +#define DEVICE_CONTAINER_STR_SIZE 39 + +#define TAG CHANNELS_TAG("urbdrc.client") +#ifdef WITH_DEBUG_DVC +#define DEBUG_DVC(...) WLog_DBG(TAG, __VA_ARGS__) +#else +#define DEBUG_DVC(...) \ + do \ + { \ + } while (0) +#endif + +typedef struct S_IUDEVICE IUDEVICE; +typedef struct S_IUDEVMAN IUDEVMAN; + +#define BASIC_DEV_STATE_DEFINED(_arg, _type) \ + WINPR_ATTR_NODISCARD _type (*get_##_arg)(IUDEVICE * pdev); \ + void (*set_##_arg)(IUDEVICE * pdev, _type _arg) + +#define BASIC_DEVMAN_STATE_DEFINED(_arg, _type) \ + WINPR_ATTR_NODISCARD _type (*get_##_arg)(IUDEVMAN * udevman); \ + void (*set_##_arg)(IUDEVMAN * udevman, _type _arg) + +typedef struct +{ + IWTSPlugin iface; + + GENERIC_LISTENER_CALLBACK* listener_callback; + + IUDEVMAN* udevman; + UINT32 vchannel_status; + char* subsystem; + + wLog* log; + IWTSListener* listener; + BOOL initialized; +} URBDRC_PLUGIN; + +typedef BOOL (*PREGISTERURBDRCSERVICE)(IWTSPlugin* plugin, IUDEVMAN* udevman); +typedef struct +{ + IWTSPlugin* plugin; + WINPR_ATTR_NODISCARD PREGISTERURBDRCSERVICE pRegisterUDEVMAN; + const ADDIN_ARGV* args; +} FREERDP_URBDRC_SERVICE_ENTRY_POINTS; +typedef FREERDP_URBDRC_SERVICE_ENTRY_POINTS* PFREERDP_URBDRC_SERVICE_ENTRY_POINTS; + +typedef UINT(VCAPITYPE* PFREERDP_URBDRC_DEVICE_ENTRY)( + PFREERDP_URBDRC_SERVICE_ENTRY_POINTS pEntryPoints); + +typedef struct +{ + GENERIC_CHANNEL_CALLBACK* callback; + URBDRC_PLUGIN* urbdrc; + IUDEVMAN* udevman; + IWTSVirtualChannel* channel; + wStream* s; +} TRANSFER_DATA; + +typedef void (*t_isoch_transfer_cb)(IUDEVICE* idev, GENERIC_CHANNEL_CALLBACK* callback, + wStream* out, UINT32 InterfaceId, BOOL noAck, UINT32 MessageId, + UINT32 RequestId, UINT32 NumberOfPackets, UINT32 status, + UINT32 StartFrame, UINT32 ErrorCount, UINT32 OutputBufferSize); + +struct S_IUDEVICE +{ + /* Transfer */ + WINPR_ATTR_NODISCARD int (*isoch_transfer)( + IUDEVICE* idev, GENERIC_CHANNEL_CALLBACK* callback, UINT32 MessageId, UINT32 RequestId, + UINT32 EndpointAddress, UINT32 TransferFlags, UINT32 StartFrame, UINT32 ErrorCount, + BOOL NoAck, const BYTE* packetDescriptorData, UINT32 NumberOfPackets, UINT32 BufferSize, + const BYTE* Buffer, t_isoch_transfer_cb cb, UINT32 Timeout); + + WINPR_ATTR_NODISCARD BOOL (*control_transfer)(IUDEVICE* idev, UINT32 RequestId, + UINT32 EndpointAddress, UINT32 TransferFlags, + BYTE bmRequestType, BYTE Request, UINT16 Value, + UINT16 Index, UINT32* UrbdStatus, + UINT32* BufferSize, BYTE* Buffer, UINT32 Timeout); + + WINPR_ATTR_NODISCARD int (*bulk_or_interrupt_transfer)( + IUDEVICE* idev, GENERIC_CHANNEL_CALLBACK* callback, UINT32 MessageId, UINT32 RequestId, + UINT32 EndpointAddress, UINT32 TransferFlags, BOOL NoAck, UINT32 BufferSize, + const BYTE* data, t_isoch_transfer_cb cb, UINT32 Timeout); + + WINPR_ATTR_NODISCARD int (*select_configuration)(IUDEVICE* idev, UINT32 bConfigurationValue); + + WINPR_ATTR_NODISCARD int (*select_interface)(IUDEVICE* idev, BYTE InterfaceNumber, + BYTE AlternateSetting); + + WINPR_ATTR_NODISCARD int (*control_pipe_request)(IUDEVICE* idev, UINT32 RequestId, + UINT32 EndpointAddress, UINT32* UsbdStatus, + int command); + + WINPR_ATTR_NODISCARD UINT32 (*control_query_device_text)(IUDEVICE* idev, UINT32 TextType, + UINT16 LocaleId, UINT8* BufferSize, + BYTE* Buffer); + + WINPR_ATTR_NODISCARD int (*os_feature_descriptor_request)( + IUDEVICE* idev, UINT32 RequestId, BYTE Recipient, BYTE InterfaceNumber, BYTE Ms_PageIndex, + UINT16 Ms_featureDescIndex, UINT32* UsbdStatus, UINT32* BufferSize, BYTE* Buffer, + UINT32 Timeout); + + void (*cancel_all_transfer_request)(IUDEVICE* idev); + + WINPR_ATTR_NODISCARD int (*cancel_transfer_request)(IUDEVICE* idev, UINT32 RequestId); + + WINPR_ATTR_NODISCARD int (*query_device_descriptor)(IUDEVICE* idev, int offset); + + WINPR_ATTR_NODISCARD BOOL (*detach_kernel_driver)(IUDEVICE* idev); + + WINPR_ATTR_NODISCARD BOOL (*attach_kernel_driver)(IUDEVICE* idev); + + WINPR_ATTR_NODISCARD int (*query_device_port_status)(IUDEVICE* idev, UINT32* UsbdStatus, + UINT32* BufferSize, BYTE* Buffer); + + WINPR_ATTR_NODISCARD MSUSB_CONFIG_DESCRIPTOR* (*complete_msconfig_setup)( + IUDEVICE* idev, MSUSB_CONFIG_DESCRIPTOR* MsConfig); + /* Basic state */ + WINPR_ATTR_NODISCARD int (*isCompositeDevice)(IUDEVICE* idev); + + WINPR_ATTR_NODISCARD int (*isExist)(IUDEVICE* idev); + WINPR_ATTR_NODISCARD int (*isAlreadySend)(IUDEVICE* idev); + WINPR_ATTR_NODISCARD int (*isChannelClosed)(IUDEVICE* idev); + + void (*setAlreadySend)(IUDEVICE* idev); + void (*setChannelClosed)(IUDEVICE* idev); + void (*markChannelClosed)(IUDEVICE* idev); + WINPR_ATTR_NODISCARD char* (*getPath)(IUDEVICE* idev); + + void (*free)(IUDEVICE* idev); + + BASIC_DEV_STATE_DEFINED(channelManager, IWTSVirtualChannelManager*); + BASIC_DEV_STATE_DEFINED(channelID, UINT32); + BASIC_DEV_STATE_DEFINED(UsbDevice, UINT32); + BASIC_DEV_STATE_DEFINED(ReqCompletion, UINT32); + BASIC_DEV_STATE_DEFINED(bus_number, BYTE); + BASIC_DEV_STATE_DEFINED(dev_number, BYTE); + BASIC_DEV_STATE_DEFINED(port_number, UINT8); + BASIC_DEV_STATE_DEFINED(MsConfig, MSUSB_CONFIG_DESCRIPTOR*); + + BASIC_DEV_STATE_DEFINED(p_udev, void*); + BASIC_DEV_STATE_DEFINED(p_prev, void*); + BASIC_DEV_STATE_DEFINED(p_next, void*); +}; + +struct S_IUDEVMAN +{ + /* Standard */ + void (*free)(IUDEVMAN* idevman); + + /* Manage devices */ + void (*rewind)(IUDEVMAN* idevman); + WINPR_ATTR_NODISCARD BOOL (*has_next)(IUDEVMAN* idevman); + WINPR_ATTR_NODISCARD BOOL (*unregister_udevice)(IUDEVMAN* idevman, BYTE bus_number, + BYTE dev_number); + WINPR_ATTR_NODISCARD size_t (*register_udevice)(IUDEVMAN* idevman, BYTE bus_number, + BYTE dev_number, UINT16 idVendor, + UINT16 idProduct, UINT32 flag); + WINPR_ATTR_NODISCARD IUDEVICE* (*get_next)(IUDEVMAN* idevman); + WINPR_ATTR_NODISCARD IUDEVICE* (*get_udevice_by_UsbDevice)(IUDEVMAN* idevman, UINT32 UsbDevice); + WINPR_ATTR_NODISCARD IUDEVICE* (*get_udevice_by_ChannelID)(IUDEVMAN* idevman, UINT32 channelID); + + /* Extension */ + WINPR_ATTR_NODISCARD int (*isAutoAdd)(IUDEVMAN* idevman); + + /* Basic state */ + BASIC_DEVMAN_STATE_DEFINED(device_num, UINT32); + BASIC_DEVMAN_STATE_DEFINED(next_device_id, UINT32); + + /* control semaphore or mutex lock */ + void (*loading_lock)(IUDEVMAN* idevman); + void (*loading_unlock)(IUDEVMAN* idevman); + WINPR_ATTR_NODISCARD BOOL (*initialize)(IUDEVMAN* idevman, UINT32 channelId); + WINPR_ATTR_NODISCARD UINT (*listener_created_callback)(IUDEVMAN* idevman); + + IWTSPlugin* plugin; + UINT32 controlChannelId; + UINT32 status; +}; + +#define DEVICE_ADD_FLAG_BUS 0x01 +#define DEVICE_ADD_FLAG_DEV 0x02 +#define DEVICE_ADD_FLAG_VENDOR 0x04 +#define DEVICE_ADD_FLAG_PRODUCT 0x08 +#define DEVICE_ADD_FLAG_REGISTER 0x10 + +#define DEVICE_ADD_FLAG_ALL \ + (DEVICE_ADD_FLAG_BUS | DEVICE_ADD_FLAG_DEV | DEVICE_ADD_FLAG_VENDOR | \ + DEVICE_ADD_FLAG_PRODUCT | DEVICE_ADD_FLAG_REGISTER) + +FREERDP_LOCAL BOOL add_device(IUDEVMAN* idevman, UINT32 flags, BYTE busnum, BYTE devnum, + UINT16 idVendor, UINT16 idProduct); + +FREERDP_LOCAL BOOL del_device(IUDEVMAN* idevman, UINT32 flags, BYTE busnum, BYTE devnum, + UINT16 idVendor, UINT16 idProduct); + +WINPR_ATTR_NODISCARD +FREERDP_LOCAL UINT stream_write_and_free(IWTSPlugin* plugin, IWTSVirtualChannel* channel, + wStream* out); + +#endif /* FREERDP_CHANNEL_URBDRC_CLIENT_MAIN_H */ diff --git a/third_party/FreeRDP/channels/urbdrc/common/CMakeLists.txt b/third_party/FreeRDP/channels/urbdrc/common/CMakeLists.txt new file mode 100644 index 0000000..ba8726f --- /dev/null +++ b/third_party/FreeRDP/channels/urbdrc/common/CMakeLists.txt @@ -0,0 +1,25 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2019 Armin Novak +# Copyright 2019 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 urbdrc_types.h urbdrc_helpers.h urbdrc_helpers.c msusb.h msusb.c) + +add_library(urbdrc-common STATIC ${SRCS}) +set_property(TARGET urbdrc-common PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Common") + +freerdp_client_pc_add_library_private(urbdrc-common) +channel_install(urbdrc-common ${FREERDP_ADDIN_PATH} "FreeRDPTargets") diff --git a/third_party/FreeRDP/channels/urbdrc/common/msusb.c b/third_party/FreeRDP/channels/urbdrc/common/msusb.c new file mode 100644 index 0000000..f5bc2c8 --- /dev/null +++ b/third_party/FreeRDP/channels/urbdrc/common/msusb.c @@ -0,0 +1,392 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX USB Redirection + * + * Copyright 2012 Atrust corp. + * Copyright 2012 Alfred Liu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include +#include + +#define TAG FREERDP_TAG("utils") + +static MSUSB_PIPE_DESCRIPTOR* msusb_mspipe_new(void) +{ + return (MSUSB_PIPE_DESCRIPTOR*)calloc(1, sizeof(MSUSB_PIPE_DESCRIPTOR)); +} + +static void msusb_mspipes_free(MSUSB_PIPE_DESCRIPTOR** MsPipes, UINT32 NumberOfPipes) +{ + if (MsPipes) + { + for (UINT32 pnum = 0; pnum < NumberOfPipes && MsPipes[pnum]; pnum++) + free(MsPipes[pnum]); + + free((void*)MsPipes); + } +} + +void msusb_mspipes_replace(MSUSB_INTERFACE_DESCRIPTOR* MsInterface, + MSUSB_PIPE_DESCRIPTOR** NewMsPipes, UINT32 NewNumberOfPipes) +{ + WINPR_ASSERT(MsInterface); + WINPR_ASSERT(NewMsPipes || (NewNumberOfPipes == 0)); + + /* free original MsPipes */ + msusb_mspipes_free(MsInterface->MsPipes, MsInterface->NumberOfPipes); + /* And replace it */ + MsInterface->MsPipes = NewMsPipes; + MsInterface->NumberOfPipes = NewNumberOfPipes; +} + +static MSUSB_PIPE_DESCRIPTOR** msusb_mspipes_read(wStream* s, UINT32 NumberOfPipes) +{ + MSUSB_PIPE_DESCRIPTOR** MsPipes = nullptr; + + if (!Stream_CheckAndLogRequiredCapacityOfSize(TAG, (s), NumberOfPipes, 12ull)) + return nullptr; + + MsPipes = (MSUSB_PIPE_DESCRIPTOR**)calloc(NumberOfPipes, sizeof(MSUSB_PIPE_DESCRIPTOR*)); + + if (!MsPipes) + return nullptr; + + for (UINT32 pnum = 0; pnum < NumberOfPipes; pnum++) + { + MSUSB_PIPE_DESCRIPTOR* MsPipe = msusb_mspipe_new(); + + if (!MsPipe) + goto out_error; + + Stream_Read_UINT16(s, MsPipe->MaximumPacketSize); + Stream_Seek(s, 2); + Stream_Read_UINT32(s, MsPipe->MaximumTransferSize); + Stream_Read_UINT32(s, MsPipe->PipeFlags); + /* Already set to zero by memset + MsPipe->PipeHandle = 0; + MsPipe->bEndpointAddress = 0; + MsPipe->bInterval = 0; + MsPipe->PipeType = 0; + MsPipe->InitCompleted = 0; + */ + MsPipes[pnum] = MsPipe; + } + + return MsPipes; +out_error: + + for (UINT32 pnum = 0; pnum < NumberOfPipes; pnum++) + free(MsPipes[pnum]); + + free((void*)MsPipes); + return nullptr; +} + +WINPR_ATTR_MALLOC(msusb_msinterface_free, 1) +static MSUSB_INTERFACE_DESCRIPTOR* msusb_msinterface_new(void) +{ + return (MSUSB_INTERFACE_DESCRIPTOR*)calloc(1, sizeof(MSUSB_INTERFACE_DESCRIPTOR)); +} + +void msusb_msinterface_free(MSUSB_INTERFACE_DESCRIPTOR* MsInterface) +{ + if (MsInterface) + { + msusb_mspipes_free(MsInterface->MsPipes, MsInterface->NumberOfPipes); + MsInterface->MsPipes = nullptr; + free(MsInterface); + } +} + +static void msusb_msinterface_free_list(MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces, + UINT32 NumInterfaces) +{ + if (MsInterfaces) + { + for (UINT32 inum = 0; inum < NumInterfaces; inum++) + { + msusb_msinterface_free(MsInterfaces[inum]); + } + + free((void*)MsInterfaces); + } +} + +BOOL msusb_msinterface_replace(MSUSB_CONFIG_DESCRIPTOR* MsConfig, BYTE InterfaceNumber, + MSUSB_INTERFACE_DESCRIPTOR* NewMsInterface) +{ + if (!MsConfig || !MsConfig->MsInterfaces) + return FALSE; + if (MsConfig->NumInterfaces <= InterfaceNumber) + return FALSE; + + msusb_msinterface_free(MsConfig->MsInterfaces[InterfaceNumber]); + MsConfig->MsInterfaces[InterfaceNumber] = NewMsInterface; + return TRUE; +} + +MSUSB_INTERFACE_DESCRIPTOR* msusb_msinterface_read(wStream* s) +{ + if (!Stream_CheckAndLogRequiredCapacity(TAG, (s), 12)) + return nullptr; + + MSUSB_INTERFACE_DESCRIPTOR* MsInterface = msusb_msinterface_new(); + + if (!MsInterface) + return nullptr; + + Stream_Read_UINT16(s, MsInterface->Length); + Stream_Read_UINT16(s, MsInterface->NumberOfPipesExpected); + Stream_Read_UINT8(s, MsInterface->InterfaceNumber); + Stream_Read_UINT8(s, MsInterface->AlternateSetting); + Stream_Seek(s, 2); + Stream_Read_UINT32(s, MsInterface->NumberOfPipes); + MsInterface->InterfaceHandle = 0; + MsInterface->bInterfaceClass = 0; + MsInterface->bInterfaceSubClass = 0; + MsInterface->bInterfaceProtocol = 0; + MsInterface->InitCompleted = 0; + MsInterface->MsPipes = nullptr; + + if (MsInterface->NumberOfPipes > 0) + { + MsInterface->MsPipes = msusb_mspipes_read(s, MsInterface->NumberOfPipes); + + if (!MsInterface->MsPipes) + goto out_error; + } + + return MsInterface; +out_error: + msusb_msinterface_free(MsInterface); + return nullptr; +} + +BOOL msusb_msinterface_write(const MSUSB_INTERFACE_DESCRIPTOR* MsInterface, wStream* out) +{ + MSUSB_PIPE_DESCRIPTOR** MsPipes = nullptr; + MSUSB_PIPE_DESCRIPTOR* MsPipe = nullptr; + + if (!MsInterface) + return FALSE; + + if (!Stream_EnsureRemainingCapacity(out, 16 + MsInterface->NumberOfPipes * 20)) + return FALSE; + + /* Length */ + Stream_Write_UINT16(out, MsInterface->Length); + /* InterfaceNumber */ + Stream_Write_UINT8(out, MsInterface->InterfaceNumber); + /* AlternateSetting */ + Stream_Write_UINT8(out, MsInterface->AlternateSetting); + /* bInterfaceClass */ + Stream_Write_UINT8(out, MsInterface->bInterfaceClass); + /* bInterfaceSubClass */ + Stream_Write_UINT8(out, MsInterface->bInterfaceSubClass); + /* bInterfaceProtocol */ + Stream_Write_UINT8(out, MsInterface->bInterfaceProtocol); + /* Padding */ + Stream_Write_UINT8(out, 0); + /* InterfaceHandle */ + Stream_Write_UINT32(out, MsInterface->InterfaceHandle); + /* NumberOfPipes */ + Stream_Write_UINT32(out, MsInterface->NumberOfPipes); + /* Pipes */ + MsPipes = MsInterface->MsPipes; + + for (UINT32 pnum = 0; pnum < MsInterface->NumberOfPipes; pnum++) + { + MsPipe = MsPipes[pnum]; + /* MaximumPacketSize */ + Stream_Write_UINT16(out, MsPipe->MaximumPacketSize); + /* EndpointAddress */ + Stream_Write_UINT8(out, MsPipe->bEndpointAddress); + /* Interval */ + Stream_Write_UINT8(out, MsPipe->bInterval); + /* PipeType */ + Stream_Write_UINT32(out, MsPipe->PipeType); + /* PipeHandle */ + Stream_Write_UINT32(out, MsPipe->PipeHandle); + /* MaximumTransferSize */ + Stream_Write_UINT32(out, MsPipe->MaximumTransferSize); + /* PipeFlags */ + Stream_Write_UINT32(out, MsPipe->PipeFlags); + } + + return TRUE; +} + +static MSUSB_INTERFACE_DESCRIPTOR** msusb_msinterface_read_list(wStream* s, UINT32 NumInterfaces) +{ + MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces = nullptr; + MsInterfaces = + (MSUSB_INTERFACE_DESCRIPTOR**)calloc(NumInterfaces, sizeof(MSUSB_INTERFACE_DESCRIPTOR*)); + + if (!MsInterfaces) + return nullptr; + + for (UINT32 inum = 0; inum < NumInterfaces; inum++) + { + MsInterfaces[inum] = msusb_msinterface_read(s); + + if (!MsInterfaces[inum]) + goto fail; + } + + return MsInterfaces; +fail: + + for (UINT32 inum = 0; inum < NumInterfaces; inum++) + msusb_msinterface_free(MsInterfaces[inum]); + + free((void*)MsInterfaces); + return nullptr; +} + +BOOL msusb_msconfig_write(const MSUSB_CONFIG_DESCRIPTOR* MsConfig, wStream* out) +{ + if (!MsConfig) + return FALSE; + + if (!Stream_EnsureRemainingCapacity(out, 8)) + return FALSE; + + /* ConfigurationHandle*/ + Stream_Write_UINT32(out, MsConfig->ConfigurationHandle); + /* NumInterfaces*/ + Stream_Write_UINT32(out, MsConfig->NumInterfaces); + /* Interfaces */ + MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces = MsConfig->MsInterfaces; + + for (UINT32 inum = 0; inum < MsConfig->NumInterfaces; inum++) + { + const MSUSB_INTERFACE_DESCRIPTOR* MsInterface = MsInterfaces[inum]; + + if (!msusb_msinterface_write(MsInterface, out)) + return FALSE; + } + + return TRUE; +} + +MSUSB_CONFIG_DESCRIPTOR* msusb_msconfig_new(void) +{ + return (MSUSB_CONFIG_DESCRIPTOR*)calloc(1, sizeof(MSUSB_CONFIG_DESCRIPTOR)); +} + +void msusb_msconfig_free(MSUSB_CONFIG_DESCRIPTOR* MsConfig) +{ + if (MsConfig) + { + msusb_msinterface_free_list(MsConfig->MsInterfaces, MsConfig->NumInterfaces); + MsConfig->MsInterfaces = nullptr; + free(MsConfig); + } +} + +MSUSB_CONFIG_DESCRIPTOR* msusb_msconfig_read(wStream* s, UINT32 NumInterfaces) +{ + MSUSB_CONFIG_DESCRIPTOR* MsConfig = nullptr; + BYTE lenConfiguration = 0; + BYTE typeConfiguration = 0; + + if (!Stream_CheckAndLogRequiredCapacityOfSize(TAG, (s), 3ULL + NumInterfaces, 2ULL)) + return nullptr; + + MsConfig = msusb_msconfig_new(); + + if (!MsConfig) + goto fail; + + MsConfig->MsInterfaces = msusb_msinterface_read_list(s, NumInterfaces); + + if (!MsConfig->MsInterfaces) + goto fail; + + Stream_Read_UINT8(s, lenConfiguration); + Stream_Read_UINT8(s, typeConfiguration); + + if (lenConfiguration != 0x9 || typeConfiguration != 0x2) + { + WLog_ERR(TAG, "len and type must be 0x9 and 0x2 , but it is 0x%" PRIx8 " and 0x%" PRIx8 "", + lenConfiguration, typeConfiguration); + goto fail; + } + + Stream_Read_UINT16(s, MsConfig->wTotalLength); + Stream_Seek(s, 1); + Stream_Read_UINT8(s, MsConfig->bConfigurationValue); + MsConfig->NumInterfaces = NumInterfaces; + return MsConfig; +fail: + msusb_msconfig_free(MsConfig); + return nullptr; +} + +void msusb_msconfig_dump(const MSUSB_CONFIG_DESCRIPTOR* MsConfig) +{ + MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces = nullptr; + MSUSB_INTERFACE_DESCRIPTOR* MsInterface = nullptr; + MSUSB_PIPE_DESCRIPTOR** MsPipes = nullptr; + MSUSB_PIPE_DESCRIPTOR* MsPipe = nullptr; + + WLog_INFO(TAG, "=================MsConfig:========================"); + WLog_INFO(TAG, "wTotalLength:%" PRIu16 "", MsConfig->wTotalLength); + WLog_INFO(TAG, "bConfigurationValue:%" PRIu8 "", MsConfig->bConfigurationValue); + WLog_INFO(TAG, "ConfigurationHandle:0x%08" PRIx32 "", MsConfig->ConfigurationHandle); + WLog_INFO(TAG, "InitCompleted:%d", MsConfig->InitCompleted); + WLog_INFO(TAG, "MsOutSize:%d", MsConfig->MsOutSize); + WLog_INFO(TAG, "NumInterfaces:%" PRIu32 "", MsConfig->NumInterfaces); + MsInterfaces = MsConfig->MsInterfaces; + + for (UINT32 inum = 0; inum < MsConfig->NumInterfaces; inum++) + { + MsInterface = MsInterfaces[inum]; + WLog_INFO(TAG, " Interface: %" PRIu8 "", MsInterface->InterfaceNumber); + WLog_INFO(TAG, " Length: %" PRIu16 "", MsInterface->Length); + WLog_INFO(TAG, " NumberOfPipesExpected: %" PRIu16 "", + MsInterface->NumberOfPipesExpected); + WLog_INFO(TAG, " AlternateSetting: %" PRIu8 "", MsInterface->AlternateSetting); + WLog_INFO(TAG, " NumberOfPipes: %" PRIu32 "", MsInterface->NumberOfPipes); + WLog_INFO(TAG, " InterfaceHandle: 0x%08" PRIx32 "", MsInterface->InterfaceHandle); + WLog_INFO(TAG, " bInterfaceClass: 0x%02" PRIx8 "", MsInterface->bInterfaceClass); + WLog_INFO(TAG, " bInterfaceSubClass: 0x%02" PRIx8 "", MsInterface->bInterfaceSubClass); + WLog_INFO(TAG, " bInterfaceProtocol: 0x%02" PRIx8 "", MsInterface->bInterfaceProtocol); + WLog_INFO(TAG, " InitCompleted: %d", MsInterface->InitCompleted); + MsPipes = MsInterface->MsPipes; + + for (UINT32 pnum = 0; pnum < MsInterface->NumberOfPipes; pnum++) + { + MsPipe = MsPipes[pnum]; + WLog_INFO(TAG, " Pipe: %" PRIu32, pnum); + WLog_INFO(TAG, " MaximumPacketSize: 0x%04" PRIx16 "", MsPipe->MaximumPacketSize); + WLog_INFO(TAG, " MaximumTransferSize: 0x%08" PRIx32 "", + MsPipe->MaximumTransferSize); + WLog_INFO(TAG, " PipeFlags: 0x%08" PRIx32 "", MsPipe->PipeFlags); + WLog_INFO(TAG, " PipeHandle: 0x%08" PRIx32 "", MsPipe->PipeHandle); + WLog_INFO(TAG, " bEndpointAddress: 0x%02" PRIx8 "", MsPipe->bEndpointAddress); + WLog_INFO(TAG, " bInterval: %" PRIu8 "", MsPipe->bInterval); + WLog_INFO(TAG, " PipeType: 0x%02" PRIx8 "", MsPipe->PipeType); + WLog_INFO(TAG, " InitCompleted: %d", MsPipe->InitCompleted); + } + } + + WLog_INFO(TAG, "=================================================="); +} diff --git a/third_party/FreeRDP/channels/urbdrc/common/msusb.h b/third_party/FreeRDP/channels/urbdrc/common/msusb.h new file mode 100644 index 0000000..cbe4e3d --- /dev/null +++ b/third_party/FreeRDP/channels/urbdrc/common/msusb.h @@ -0,0 +1,111 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX USB Redirection + * + * Copyright 2012 Atrust corp. + * Copyright 2012 Alfred Liu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_UTILS_MSCONFIG_H +#define FREERDP_UTILS_MSCONFIG_H + +#include +#include + +typedef struct +{ + UINT16 MaximumPacketSize; + UINT32 MaximumTransferSize; + UINT32 PipeFlags; + UINT32 PipeHandle; + BYTE bEndpointAddress; + BYTE bInterval; + BYTE PipeType; + int InitCompleted; +} MSUSB_PIPE_DESCRIPTOR; + +typedef struct +{ + UINT16 Length; + UINT16 NumberOfPipesExpected; + BYTE InterfaceNumber; + BYTE AlternateSetting; + UINT32 NumberOfPipes; + UINT32 InterfaceHandle; + BYTE bInterfaceClass; + BYTE bInterfaceSubClass; + BYTE bInterfaceProtocol; + MSUSB_PIPE_DESCRIPTOR** MsPipes; + int InitCompleted; +} MSUSB_INTERFACE_DESCRIPTOR; + +typedef struct +{ + UINT16 wTotalLength; + BYTE bConfigurationValue; + UINT32 ConfigurationHandle; + UINT32 NumInterfaces; + MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces; + int InitCompleted; + int MsOutSize; +} MSUSB_CONFIG_DESCRIPTOR; + +#ifdef __cplusplus +extern "C" +{ +#endif + + /* MSUSB_CONFIG exported functions */ + FREERDP_LOCAL void msusb_msconfig_free(MSUSB_CONFIG_DESCRIPTOR* MsConfig); + + WINPR_ATTR_MALLOC(msusb_msconfig_free, 1) + WINPR_ATTR_NODISCARD + FREERDP_LOCAL MSUSB_CONFIG_DESCRIPTOR* msusb_msconfig_new(void); + + WINPR_ATTR_MALLOC(msusb_msconfig_free, 1) + WINPR_ATTR_NODISCARD + FREERDP_LOCAL MSUSB_CONFIG_DESCRIPTOR* msusb_msconfig_read(wStream* s, UINT32 NumInterfaces); + + WINPR_ATTR_NODISCARD + FREERDP_LOCAL BOOL msusb_msconfig_write(const MSUSB_CONFIG_DESCRIPTOR* MsConfig, wStream* out); + + FREERDP_LOCAL void msusb_msconfig_dump(const MSUSB_CONFIG_DESCRIPTOR* MsConfig); + + /* MSUSB_PIPE exported functions */ + FREERDP_LOCAL void msusb_mspipes_replace(MSUSB_INTERFACE_DESCRIPTOR* MsInterface, + MSUSB_PIPE_DESCRIPTOR** NewMsPipes, + UINT32 NewNumberOfPipes); + + /* MSUSB_INTERFACE exported functions */ + WINPR_ATTR_NODISCARD + FREERDP_LOCAL BOOL msusb_msinterface_replace(MSUSB_CONFIG_DESCRIPTOR* MsConfig, + BYTE InterfaceNumber, + MSUSB_INTERFACE_DESCRIPTOR* NewMsInterface); + + FREERDP_LOCAL void msusb_msinterface_free(MSUSB_INTERFACE_DESCRIPTOR* MsInterface); + + WINPR_ATTR_MALLOC(msusb_msinterface_free, 1) + WINPR_ATTR_NODISCARD + FREERDP_LOCAL MSUSB_INTERFACE_DESCRIPTOR* msusb_msinterface_read(wStream* s); + + WINPR_ATTR_NODISCARD + FREERDP_LOCAL BOOL msusb_msinterface_write(const MSUSB_INTERFACE_DESCRIPTOR* MsInterface, + wStream* out); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_UTILS_MSCONFIG_H */ diff --git a/third_party/FreeRDP/channels/urbdrc/common/urbdrc_helpers.c b/third_party/FreeRDP/channels/urbdrc/common/urbdrc_helpers.c new file mode 100644 index 0000000..7730a7f --- /dev/null +++ b/third_party/FreeRDP/channels/urbdrc/common/urbdrc_helpers.c @@ -0,0 +1,454 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Server USB redirection channel - helper functions + * + * Copyright 2019 Armin Novak + * Copyright 2019 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 + +#include "urbdrc_helpers.h" +#include "urbdrc_types.h" +#include + +const char* mask_to_string(UINT32 mask) +{ + switch (mask) + { + case STREAM_ID_NONE: + return "STREAM_ID_NONE"; + + case STREAM_ID_PROXY: + return "STREAM_ID_PROXY"; + + case STREAM_ID_STUB: + return "STREAM_ID_STUB"; + + default: + return "UNKNOWN"; + } +} +const char* interface_to_string(UINT32 id) +{ + switch (id) + { + case CAPABILITIES_NEGOTIATOR: + return "CAPABILITIES_NEGOTIATOR"; + + case SERVER_CHANNEL_NOTIFICATION: + return "SERVER_CHANNEL_NOTIFICATION"; + + case CLIENT_CHANNEL_NOTIFICATION: + return "CLIENT_CHANNEL_NOTIFICATION"; + + default: + return "DEVICE_MESSAGE"; + } +} + +static const char* call_to_string_none(BOOL client, UINT32 interfaceId, UINT32 functionId) +{ + WINPR_UNUSED(interfaceId); + + if (client) + return "RIM_EXCHANGE_CAPABILITY_RESPONSE [none |client]"; + else + { + switch (functionId) + { + case RIM_EXCHANGE_CAPABILITY_REQUEST: + return "RIM_EXCHANGE_CAPABILITY_REQUEST [none |server]"; + + case RIMCALL_RELEASE: + return "RIMCALL_RELEASE [none |server]"; + + case RIMCALL_QUERYINTERFACE: + return "RIMCALL_QUERYINTERFACE [none |server]"; + + default: + return "UNKNOWN [none |server]"; + } + } +} + +static const char* call_to_string_proxy_server(UINT32 functionId) +{ + switch (functionId) + { + case QUERY_DEVICE_TEXT: + return "QUERY_DEVICE_TEXT [proxy|server]"; + + case INTERNAL_IO_CONTROL: + return "INTERNAL_IO_CONTROL [proxy|server]"; + + case IO_CONTROL: + return "IO_CONTROL [proxy|server]"; + + case REGISTER_REQUEST_CALLBACK: + return "REGISTER_REQUEST_CALLBACK [proxy|server]"; + + case CANCEL_REQUEST: + return "CANCEL_REQUEST [proxy|server]"; + + case RETRACT_DEVICE: + return "RETRACT_DEVICE [proxy|server]"; + + case TRANSFER_IN_REQUEST: + return "TRANSFER_IN_REQUEST [proxy|server]"; + + case TRANSFER_OUT_REQUEST: + return "TRANSFER_OUT_REQUEST [proxy|server]"; + + default: + return "UNKNOWN [proxy|server]"; + } +} + +static const char* call_to_string_proxy_client(UINT32 functionId) +{ + switch (functionId) + { + case URB_COMPLETION_NO_DATA: + return "URB_COMPLETION_NO_DATA [proxy|client]"; + + case URB_COMPLETION: + return "URB_COMPLETION [proxy|client]"; + + case IOCONTROL_COMPLETION: + return "IOCONTROL_COMPLETION [proxy|client]"; + + case TRANSFER_OUT_REQUEST: + return "TRANSFER_OUT_REQUEST [proxy|client]"; + + default: + return "UNKNOWN [proxy|client]"; + } +} + +static const char* call_to_string_proxy(BOOL client, UINT32 interfaceId, UINT32 functionId) +{ + switch (interfaceId & INTERFACE_ID_MASK) + { + case CLIENT_DEVICE_SINK: + switch (functionId) + { + case ADD_VIRTUAL_CHANNEL: + return "ADD_VIRTUAL_CHANNEL [proxy|sink ]"; + + case ADD_DEVICE: + return "ADD_DEVICE [proxy|sink ]"; + case RIMCALL_RELEASE: + return "RIMCALL_RELEASE [proxy|sink ]"; + + case RIMCALL_QUERYINTERFACE: + return "RIMCALL_QUERYINTERFACE [proxy|sink ]"; + default: + return "UNKNOWN [proxy|sink ]"; + } + + case SERVER_CHANNEL_NOTIFICATION: + switch (functionId) + { + case CHANNEL_CREATED: + return "CHANNEL_CREATED [proxy|server]"; + + case RIMCALL_RELEASE: + return "RIMCALL_RELEASE [proxy|server]"; + + case RIMCALL_QUERYINTERFACE: + return "RIMCALL_QUERYINTERFACE [proxy|server]"; + + default: + return "UNKNOWN [proxy|server]"; + } + + case CLIENT_CHANNEL_NOTIFICATION: + switch (functionId) + { + case CHANNEL_CREATED: + return "CHANNEL_CREATED [proxy|client]"; + case RIMCALL_RELEASE: + return "RIMCALL_RELEASE [proxy|client]"; + case RIMCALL_QUERYINTERFACE: + return "RIMCALL_QUERYINTERFACE [proxy|client]"; + default: + return "UNKNOWN [proxy|client]"; + } + + default: + if (client) + return call_to_string_proxy_client(functionId); + else + return call_to_string_proxy_server(functionId); + } +} + +static const char* call_to_string_stub(WINPR_ATTR_UNUSED BOOL client, + WINPR_ATTR_UNUSED UINT32 interfaceNr, + WINPR_ATTR_UNUSED UINT32 functionId) +{ + return "QUERY_DEVICE_TEXT_RSP [stub |client]"; +} + +const char* call_to_string(BOOL client, UINT32 interfaceNr, UINT32 functionId) +{ + const UINT32 mask = (interfaceNr & STREAM_ID_MASK) >> 30; + const UINT32 interfaceId = interfaceNr & INTERFACE_ID_MASK; + + switch (mask) + { + case STREAM_ID_NONE: + return call_to_string_none(client, interfaceId, functionId); + + case STREAM_ID_PROXY: + return call_to_string_proxy(client, interfaceId, functionId); + + case STREAM_ID_STUB: + return call_to_string_stub(client, interfaceId, functionId); + + default: + return "UNKNOWN[mask]"; + } +} + +const char* urb_function_string(UINT16 urb) +{ + switch (urb) + { + case TS_URB_SELECT_CONFIGURATION: + return "TS_URB_SELECT_CONFIGURATION"; + + case TS_URB_SELECT_INTERFACE: + return "TS_URB_SELECT_INTERFACE"; + + case TS_URB_PIPE_REQUEST: + return "TS_URB_PIPE_REQUEST"; + + case TS_URB_TAKE_FRAME_LENGTH_CONTROL: + return "TS_URB_TAKE_FRAME_LENGTH_CONTROL"; + + case TS_URB_RELEASE_FRAME_LENGTH_CONTROL: + return "TS_URB_RELEASE_FRAME_LENGTH_CONTROL"; + + case TS_URB_GET_FRAME_LENGTH: + return "TS_URB_GET_FRAME_LENGTH"; + + case TS_URB_SET_FRAME_LENGTH: + return "TS_URB_SET_FRAME_LENGTH"; + + case TS_URB_GET_CURRENT_FRAME_NUMBER: + return "TS_URB_GET_CURRENT_FRAME_NUMBER"; + + case TS_URB_CONTROL_TRANSFER: + return "TS_URB_CONTROL_TRANSFER"; + + case TS_URB_BULK_OR_INTERRUPT_TRANSFER: + return "TS_URB_BULK_OR_INTERRUPT_TRANSFER"; + + case TS_URB_ISOCH_TRANSFER: + return "TS_URB_ISOCH_TRANSFER"; + + case TS_URB_GET_DESCRIPTOR_FROM_DEVICE: + return "TS_URB_GET_DESCRIPTOR_FROM_DEVICE"; + + case TS_URB_SET_DESCRIPTOR_TO_DEVICE: + return "TS_URB_SET_DESCRIPTOR_TO_DEVICE"; + + case TS_URB_SET_FEATURE_TO_DEVICE: + return "TS_URB_SET_FEATURE_TO_DEVICE"; + + case TS_URB_SET_FEATURE_TO_INTERFACE: + return "TS_URB_SET_FEATURE_TO_INTERFACE"; + + case TS_URB_SET_FEATURE_TO_ENDPOINT: + return "TS_URB_SET_FEATURE_TO_ENDPOINT"; + + case TS_URB_CLEAR_FEATURE_TO_DEVICE: + return "TS_URB_CLEAR_FEATURE_TO_DEVICE"; + + case TS_URB_CLEAR_FEATURE_TO_INTERFACE: + return "TS_URB_CLEAR_FEATURE_TO_INTERFACE"; + + case TS_URB_CLEAR_FEATURE_TO_ENDPOINT: + return "TS_URB_CLEAR_FEATURE_TO_ENDPOINT"; + + case TS_URB_GET_STATUS_FROM_DEVICE: + return "TS_URB_GET_STATUS_FROM_DEVICE"; + + case TS_URB_GET_STATUS_FROM_INTERFACE: + return "TS_URB_GET_STATUS_FROM_INTERFACE"; + + case TS_URB_GET_STATUS_FROM_ENDPOINT: + return "TS_URB_GET_STATUS_FROM_ENDPOINT"; + + case TS_URB_RESERVED_0X0016: + return "TS_URB_RESERVED_0X0016"; + + case TS_URB_VENDOR_DEVICE: + return "TS_URB_VENDOR_DEVICE"; + + case TS_URB_VENDOR_INTERFACE: + return "TS_URB_VENDOR_INTERFACE"; + + case TS_URB_VENDOR_ENDPOINT: + return "TS_URB_VENDOR_ENDPOINT"; + + case TS_URB_CLASS_DEVICE: + return "TS_URB_CLASS_DEVICE"; + + case TS_URB_CLASS_INTERFACE: + return "TS_URB_CLASS_INTERFACE"; + + case TS_URB_CLASS_ENDPOINT: + return "TS_URB_CLASS_ENDPOINT"; + + case TS_URB_RESERVE_0X001D: + return "TS_URB_RESERVE_0X001D"; + + case TS_URB_SYNC_RESET_PIPE_AND_CLEAR_STALL: + return "TS_URB_SYNC_RESET_PIPE_AND_CLEAR_STALL"; + + case TS_URB_CLASS_OTHER: + return "TS_URB_CLASS_OTHER"; + + case TS_URB_VENDOR_OTHER: + return "TS_URB_VENDOR_OTHER"; + + case TS_URB_GET_STATUS_FROM_OTHER: + return "TS_URB_GET_STATUS_FROM_OTHER"; + + case TS_URB_CLEAR_FEATURE_TO_OTHER: + return "TS_URB_CLEAR_FEATURE_TO_OTHER"; + + case TS_URB_SET_FEATURE_TO_OTHER: + return "TS_URB_SET_FEATURE_TO_OTHER"; + + case TS_URB_GET_DESCRIPTOR_FROM_ENDPOINT: + return "TS_URB_GET_DESCRIPTOR_FROM_ENDPOINT"; + + case TS_URB_SET_DESCRIPTOR_TO_ENDPOINT: + return "TS_URB_SET_DESCRIPTOR_TO_ENDPOINT"; + + case TS_URB_CONTROL_GET_CONFIGURATION_REQUEST: + return "TS_URB_CONTROL_GET_CONFIGURATION_REQUEST"; + + case TS_URB_CONTROL_GET_INTERFACE_REQUEST: + return "TS_URB_CONTROL_GET_INTERFACE_REQUEST"; + + case TS_URB_GET_DESCRIPTOR_FROM_INTERFACE: + return "TS_URB_GET_DESCRIPTOR_FROM_INTERFACE"; + + case TS_URB_SET_DESCRIPTOR_TO_INTERFACE: + return "TS_URB_SET_DESCRIPTOR_TO_INTERFACE"; + + case TS_URB_GET_OS_FEATURE_DESCRIPTOR_REQUEST: + return "TS_URB_GET_OS_FEATURE_DESCRIPTOR_REQUEST"; + + case TS_URB_RESERVE_0X002B: + return "TS_URB_RESERVE_0X002B"; + + case TS_URB_RESERVE_0X002C: + return "TS_URB_RESERVE_0X002C"; + + case TS_URB_RESERVE_0X002D: + return "TS_URB_RESERVE_0X002D"; + + case TS_URB_RESERVE_0X002E: + return "TS_URB_RESERVE_0X002E"; + + case TS_URB_RESERVE_0X002F: + return "TS_URB_RESERVE_0X002F"; + + case TS_URB_SYNC_RESET_PIPE: + return "TS_URB_SYNC_RESET_PIPE"; + + case TS_URB_SYNC_CLEAR_STALL: + return "TS_URB_SYNC_CLEAR_STALL"; + + case TS_URB_CONTROL_TRANSFER_EX: + return "TS_URB_CONTROL_TRANSFER_EX"; + + default: + return "UNKNOWN"; + } +} + +void urbdrc_dump_message(wLog* log, BOOL client, BOOL write, wStream* s) +{ + const char* type = write ? "WRITE" : "READ"; + UINT32 InterfaceId = 0; + UINT32 MessageId = 0; + UINT32 FunctionId = 0; + size_t length = 0; + size_t pos = 0; + + pos = Stream_GetPosition(s); + if (write) + { + length = pos; + Stream_ResetPosition(s); + } + else + length = Stream_GetRemainingLength(s); + + if (length < 12) + return; + + Stream_Read_UINT32(s, InterfaceId); + Stream_Read_UINT32(s, MessageId); + Stream_Read_UINT32(s, FunctionId); + Stream_SetPosition(s, pos); + + WLog_Print(log, WLOG_DEBUG, + "[%-5s] %s [%08" PRIx32 "] InterfaceId=%08" PRIx32 ", MessageId=%08" PRIx32 + ", FunctionId=%08" PRIx32 ", length=%" PRIuz, + type, call_to_string(client, InterfaceId, FunctionId), FunctionId, InterfaceId, + MessageId, FunctionId, length); +#if defined(WITH_DEBUG_URBDRC) + if (write) + WLog_Print(log, WLOG_TRACE, "-------------------------- URBDRC sent: ---"); + else + WLog_Print(log, WLOG_TRACE, "-------------------------- URBDRC received:"); + winpr_HexLogDump(log, WLOG_TRACE, Stream_Buffer(s), length); + WLog_Print(log, WLOG_TRACE, "-------------------------- URBDRC end -----"); +#endif +} + +/* [MS-RDPEUSB] 2.2.1 Shared Message Header (SHARED_MSG_HEADER) */ +BOOL write_shared_message_header_with_functionid(wStream* s, UINT32 InterfaceId, UINT32 MessageId, + UINT32 FunctionId) +{ + if (!Stream_EnsureRemainingCapacity(s, 12)) + return FALSE; + + Stream_Write_UINT32(s, InterfaceId); + Stream_Write_UINT32(s, MessageId); + Stream_Write_UINT32(s, FunctionId); + return TRUE; +} + +wStream* create_shared_message_header_with_functionid(UINT32 InterfaceId, UINT32 MessageId, + UINT32 FunctionId, size_t OutputSize) +{ + wStream* out = Stream_New(nullptr, 12ULL + OutputSize); + if (!out) + return nullptr; + if (!write_shared_message_header_with_functionid(out, InterfaceId, MessageId, FunctionId)) + { + Stream_Free(out, TRUE); + return nullptr; + } + return out; +} diff --git a/third_party/FreeRDP/channels/urbdrc/common/urbdrc_helpers.h b/third_party/FreeRDP/channels/urbdrc/common/urbdrc_helpers.h new file mode 100644 index 0000000..b4d9bea --- /dev/null +++ b/third_party/FreeRDP/channels/urbdrc/common/urbdrc_helpers.h @@ -0,0 +1,59 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Server USB redirection channel - helper functions + * + * Copyright 2019 Armin Novak + * Copyright 2019 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. + */ + +#ifndef FREERDP_CHANNEL_URBDRC_HELPERS_H +#define FREERDP_CHANNEL_URBDRC_HELPERS_H + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include +#include + + WINPR_ATTR_NODISCARD FREERDP_LOCAL const char* urb_function_string(UINT16 urb); + + WINPR_ATTR_NODISCARD FREERDP_LOCAL const char* mask_to_string(UINT32 mask); + + WINPR_ATTR_NODISCARD FREERDP_LOCAL const char* interface_to_string(UINT32 id); + + WINPR_ATTR_NODISCARD FREERDP_LOCAL const char* call_to_string(BOOL client, UINT32 interfaceNr, + UINT32 functionId); + + FREERDP_LOCAL + void urbdrc_dump_message(wLog* log, BOOL client, BOOL write, wStream* s); + + WINPR_ATTR_MALLOC(Stream_Free, 1) + WINPR_ATTR_NODISCARD FREERDP_LOCAL wStream* + create_shared_message_header_with_functionid(UINT32 InterfaceId, UINT32 MessageId, + UINT32 FunctionId, size_t OutputSize); + + WINPR_ATTR_NODISCARD FREERDP_LOCAL BOOL write_shared_message_header_with_functionid( + wStream* s, UINT32 InterfaceId, UINT32 MessageId, UINT32 FunctionId); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_CHANNEL_URBDRC_HELPERS_H */ diff --git a/third_party/FreeRDP/channels/urbdrc/common/urbdrc_types.h b/third_party/FreeRDP/channels/urbdrc/common/urbdrc_types.h new file mode 100644 index 0000000..63ff2cf --- /dev/null +++ b/third_party/FreeRDP/channels/urbdrc/common/urbdrc_types.h @@ -0,0 +1,308 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX USB Redirection + * + * Copyright 2012 Atrust corp. + * Copyright 2012 Alfred Liu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_URBDRC_CLIENT_TYPES_H +#define FREERDP_CHANNEL_URBDRC_CLIENT_TYPES_H + +#include + +#include +#include + +#include + +#include + +#define RIM_CAPABILITY_VERSION_01 0x00000001 + +#define CAPABILITIES_NEGOTIATOR 0x00000000 +#define CLIENT_DEVICE_SINK 0x00000001 +#define SERVER_CHANNEL_NOTIFICATION 0x00000002 +#define CLIENT_CHANNEL_NOTIFICATION 0x00000003 +#define BASE_USBDEVICE_NUM 0x00000005 + +#define RIMCALL_RELEASE 0x00000001 +#define RIMCALL_QUERYINTERFACE 0x00000002 +#define RIM_EXCHANGE_CAPABILITY_REQUEST 0x00000100 +#define CHANNEL_CREATED 0x00000100 +#define ADD_VIRTUAL_CHANNEL 0x00000100 +#define ADD_DEVICE 0x00000101 + +#define INIT_CHANNEL_IN 1 +#define INIT_CHANNEL_OUT 0 + +/* InterfaceClass */ +#define CLASS_RESERVE 0x00 +#define CLASS_AUDIO 0x01 +#define CLASS_COMMUNICATION_IF 0x02 +#define CLASS_HID 0x03 +#define CLASS_PHYSICAL 0x05 +#define CLASS_IMAGE 0x06 +#define CLASS_PRINTER 0x07 +#define CLASS_MASS_STORAGE 0x08 +#define CLASS_HUB 0x09 +#define CLASS_COMMUNICATION_DATA_IF 0x0a +#define CLASS_SMART_CARD 0x0b +#define CLASS_CONTENT_SECURITY 0x0d +#define CLASS_VIDEO 0x0e +#define CLASS_PERSONAL_HEALTHCARE 0x0f +#define CLASS_DIAGNOSTIC 0xdc +#define CLASS_WIRELESS_CONTROLLER 0xe0 +#define CLASS_ELSE_DEVICE 0xef +#define CLASS_DEPENDENCE 0xfe +#define CLASS_VENDOR_DEPENDENCE 0xff + +/* usb version */ +#define USB_v1_0 0x100 +#define USB_v1_1 0x110 +#define USB_v2_0 0x200 +#define USB_v3_0 0x300 + +#define STREAM_ID_NONE 0x0UL +#define STREAM_ID_PROXY 0x1UL +#define STREAM_ID_STUB 0x2UL +#define STREAM_ID_MASK 0xC0000000 +#define INTERFACE_ID_MASK 0x3FFFFFFF + +#define CANCEL_REQUEST 0x00000100 +#define REGISTER_REQUEST_CALLBACK 0x00000101 +#define IO_CONTROL 0x00000102 +#define INTERNAL_IO_CONTROL 0x00000103 +#define QUERY_DEVICE_TEXT 0x00000104 + +#define TRANSFER_IN_REQUEST 0x00000105 +#define TRANSFER_OUT_REQUEST 0x00000106 +#define RETRACT_DEVICE 0x00000107 + +#define IOCONTROL_COMPLETION 0x00000100 +#define URB_COMPLETION 0x00000101 +#define URB_COMPLETION_NO_DATA 0x00000102 + +/* The USB device is to be stopped from being redirected because the + * device is blocked by the server's policy. */ +#define UsbRetractReason_BlockedByPolicy 0x00000001 + +#define IOCTL_TSUSBGD_IOCTL_USBDI_QUERY_BUS_TIME 0x00224000 + +enum device_text_type +{ + DeviceTextDescription = 0, + DeviceTextLocationInformation = 1, +}; + +enum device_descriptor_table +{ + B_LENGTH = 0, + B_DESCRIPTOR_TYPE = 1, + BCD_USB = 2, + B_DEVICE_CLASS = 4, + B_DEVICE_SUBCLASS = 5, + B_DEVICE_PROTOCOL = 6, + B_MAX_PACKET_SIZE0 = 7, + ID_VENDOR = 8, + ID_PRODUCT = 10, + BCD_DEVICE = 12, + I_MANUFACTURER = 14, + I_PRODUCT = 15, + I_SERIAL_NUMBER = 16, + B_NUM_CONFIGURATIONS = 17 +}; + +#define PIPE_CANCEL 0 +#define PIPE_RESET 1 + +#define IOCTL_INTERNAL_USB_SUBMIT_URB 0x00220003 +#define IOCTL_INTERNAL_USB_RESET_PORT 0x00220007 +#define IOCTL_INTERNAL_USB_GET_PORT_STATUS 0x00220013 +#define IOCTL_INTERNAL_USB_CYCLE_PORT 0x0022001F +#define IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION 0x00220027 + +#define TS_URB_SELECT_CONFIGURATION 0x0000 +#define TS_URB_SELECT_INTERFACE 0x0001 +#define TS_URB_PIPE_REQUEST 0x0002 +#define TS_URB_TAKE_FRAME_LENGTH_CONTROL 0x0003 +#define TS_URB_RELEASE_FRAME_LENGTH_CONTROL 0x0004 +#define TS_URB_GET_FRAME_LENGTH 0x0005 +#define TS_URB_SET_FRAME_LENGTH 0x0006 +#define TS_URB_GET_CURRENT_FRAME_NUMBER 0x0007 +#define TS_URB_CONTROL_TRANSFER 0x0008 +#define TS_URB_BULK_OR_INTERRUPT_TRANSFER 0x0009 +#define TS_URB_ISOCH_TRANSFER 0x000A +#define TS_URB_GET_DESCRIPTOR_FROM_DEVICE 0x000B +#define TS_URB_SET_DESCRIPTOR_TO_DEVICE 0x000C +#define TS_URB_SET_FEATURE_TO_DEVICE 0x000D +#define TS_URB_SET_FEATURE_TO_INTERFACE 0x000E +#define TS_URB_SET_FEATURE_TO_ENDPOINT 0x000F +#define TS_URB_CLEAR_FEATURE_TO_DEVICE 0x0010 +#define TS_URB_CLEAR_FEATURE_TO_INTERFACE 0x0011 +#define TS_URB_CLEAR_FEATURE_TO_ENDPOINT 0x0012 +#define TS_URB_GET_STATUS_FROM_DEVICE 0x0013 +#define TS_URB_GET_STATUS_FROM_INTERFACE 0x0014 +#define TS_URB_GET_STATUS_FROM_ENDPOINT 0x0015 +#define TS_URB_RESERVED_0X0016 0x0016 +#define TS_URB_VENDOR_DEVICE 0x0017 +#define TS_URB_VENDOR_INTERFACE 0x0018 +#define TS_URB_VENDOR_ENDPOINT 0x0019 +#define TS_URB_CLASS_DEVICE 0x001A +#define TS_URB_CLASS_INTERFACE 0x001B +#define TS_URB_CLASS_ENDPOINT 0x001C +#define TS_URB_RESERVE_0X001D 0x001D +#define TS_URB_SYNC_RESET_PIPE_AND_CLEAR_STALL 0x001E +#define TS_URB_CLASS_OTHER 0x001F +#define TS_URB_VENDOR_OTHER 0x0020 +#define TS_URB_GET_STATUS_FROM_OTHER 0x0021 +#define TS_URB_CLEAR_FEATURE_TO_OTHER 0x0022 +#define TS_URB_SET_FEATURE_TO_OTHER 0x0023 +#define TS_URB_GET_DESCRIPTOR_FROM_ENDPOINT 0x0024 +#define TS_URB_SET_DESCRIPTOR_TO_ENDPOINT 0x0025 +#define TS_URB_CONTROL_GET_CONFIGURATION_REQUEST 0x0026 +#define TS_URB_CONTROL_GET_INTERFACE_REQUEST 0x0027 +#define TS_URB_GET_DESCRIPTOR_FROM_INTERFACE 0x0028 +#define TS_URB_SET_DESCRIPTOR_TO_INTERFACE 0x0029 +#define TS_URB_GET_OS_FEATURE_DESCRIPTOR_REQUEST 0x002A +#define TS_URB_RESERVE_0X002B 0x002B +#define TS_URB_RESERVE_0X002C 0x002C +#define TS_URB_RESERVE_0X002D 0x002D +#define TS_URB_RESERVE_0X002E 0x002E +#define TS_URB_RESERVE_0X002F 0x002F +// USB 2.0 calls start at 0x0030 +#define TS_URB_SYNC_RESET_PIPE 0x0030 +#define TS_URB_SYNC_CLEAR_STALL 0x0031 +#define TS_URB_CONTROL_TRANSFER_EX 0x0032 + +#define USBD_STATUS_SUCCESS 0x0 +#define USBD_STATUS_PENDING 0x40000000 +#define USBD_STATUS_CANCELED 0xC0010000 + +#define USBD_STATUS_INVALID_URB_FUNCTION 0x80000200 +#define USBD_STATUS_CRC 0xC0000001 +#define USBD_STATUS_BTSTUFF 0xC0000002 +#define USBD_STATUS_DATA_TOGGLE_MISMATCH 0xC0000003 +#define USBD_STATUS_STALL_PID 0xC0000004 +#define USBD_STATUS_DEV_NOT_RESPONDING 0xC0000005 +#define USBD_STATUS_PID_CHECK_FAILURE 0xC0000006 +#define USBD_STATUS_UNEXPECTED_PID 0xC0000007 +#define USBD_STATUS_DATA_OVERRUN 0xC0000008 +#define USBD_STATUS_DATA_UNDERRUN 0xC0000009 +#define USBD_STATUS_RESERVED1 0xC000000A +#define USBD_STATUS_RESERVED2 0xC000000B +#define USBD_STATUS_BUFFER_OVERRUN 0xC000000C +#define USBD_STATUS_BUFFER_UNDERRUN 0xC000000D + +/* unknown */ +#define USBD_STATUS_NO_DATA 0xC000000E + +#define USBD_STATUS_NOT_ACCESSED 0xC000000F +#define USBD_STATUS_FIFO 0xC0000010 +#define USBD_STATUS_XACT_ERROR 0xC0000011 +#define USBD_STATUS_BABBLE_DETECTED 0xC0000012 +#define USBD_STATUS_DATA_BUFFER_ERROR 0xC0000013 + +#define USBD_STATUS_NOT_SUPPORTED 0xC0000E00 +#define USBD_STATUS_BUFFER_TOO_SMALL 0xC0003000 +#define USBD_STATUS_TIMEOUT 0xC0006000 +#define USBD_STATUS_DEVICE_GONE 0xC0007000 + +#define USBD_STATUS_NO_MEMORY 0x80000100 +#define USBD_STATUS_INVALID_URB_FUNCTION 0x80000200 +#define USBD_STATUS_INVALID_PARAMETER 0x80000300 +#define USBD_STATUS_REQUEST_FAILED 0x80000500 +#define USBD_STATUS_INVALID_PIPE_HANDLE 0x80000600 +#define USBD_STATUS_ERROR_SHORT_TRANSFER 0x80000900 + +// Values for URB TransferFlags Field +// + +/* + Set if data moves device->host +*/ +#define USBD_TRANSFER_DIRECTION 0x00000001 +/* + This bit if not set indicates that a short packet, and hence, + a short transfer is an error condition +*/ +#define USBD_SHORT_TRANSFER_OK 0x00000002 +/* + Subit the iso transfer on the next frame +*/ +#define USBD_START_ISO_TRANSFER_ASAP 0x00000004 +#define USBD_DEFAULT_PIPE_TRANSFER 0x00000008 + +#define USBD_TRANSFER_DIRECTION_FLAG(flags) ((flags)&USBD_TRANSFER_DIRECTION) + +#define USBD_TRANSFER_DIRECTION_OUT 0 +#define USBD_TRANSFER_DIRECTION_IN 1 + +#define VALID_TRANSFER_FLAGS_MASK USBD_SHORT_TRANSFER_OK | \ + USBD_TRANSFER_DIRECTION | \ + USBD_START_ISO_TRANSFER_ASAP | \ + USBD_DEFAULT_PIPE_TRANSFER) + +#define ENDPOINT_HALT 0x00 +#define DEVICE_REMOTE_WAKEUP 0x01 + +/* transfer type */ +#define CONTROL_TRANSFER 0x00 +#define ISOCHRONOUS_TRANSFER 0x01 +#define BULK_TRANSFER 0x02 +#define INTERRUPT_TRANSFER 0x03 + +#define ClearHubFeature (0x2000 | LIBUSB_REQUEST_CLEAR_FEATURE) +#define ClearPortFeature (0x2300 | LIBUSB_REQUEST_CLEAR_FEATURE) +#define GetHubDescriptor (0xa000 | LIBUSB_REQUEST_GET_DESCRIPTOR) +#define GetHubStatus (0xa000 | LIBUSB_REQUEST_GET_STATUS) +#define GetPortStatus (0xa300 | LIBUSB_REQUEST_GET_STATUS) +#define SetHubFeature (0x2000 | LIBUSB_REQUEST_SET_FEATURE) +#define SetPortFeature (0x2300 | LIBUSB_REQUEST_SET_FEATURE) + +#define USBD_PF_CHANGE_MAX_PACKET 0x00000001 +#define USBD_PF_SHORT_PACKET_OPT 0x00000002 +#define USBD_PF_ENABLE_RT_THREAD_ACCESS 0x00000004 +#define USBD_PF_MAP_ADD_TRANSFERS 0x00000008 + +/* feature request */ +#define URB_SET_FEATURE 0x00 +#define URB_CLEAR_FEATURE 0x01 + +#define USBD_PF_CHANGE_MAX_PACKET 0x00000001 +#define USBD_PF_SHORT_PACKET_OPT 0x00000002 +#define USBD_PF_ENABLE_RT_THREAD_ACCESS 0x00000004 +#define USBD_PF_MAP_ADD_TRANSFERS 0x00000008 + +#define URB_CONTROL_TRANSFER_EXTERNAL 0x1 +#define URB_CONTROL_TRANSFER_NONEXTERNAL 0x0 + +#define USBFS_URB_SHORT_NOT_OK 0x01 +#define USBFS_URB_ISO_ASAP 0x02 +#define USBFS_URB_BULK_CONTINUATION 0x04 +#define USBFS_URB_QUEUE_BULK 0x10 + +#define URBDRC_DEVICE_INITIALIZED 0x01 +#define URBDRC_DEVICE_NOT_FOUND 0x02 +#define URBDRC_DEVICE_CHANNEL_CLOSED 0x08 +#define URBDRC_DEVICE_ALREADY_SEND 0x10 +#define URBDRC_DEVICE_DETACH_KERNEL 0x20 + +#define UDEVMAN_FLAG_ADD_BY_VID_PID 0x01 +#define UDEVMAN_FLAG_ADD_BY_ADDR 0x02 +#define UDEVMAN_FLAG_ADD_BY_AUTO 0x04 +#define UDEVMAN_FLAG_DEBUG 0x08 + +#endif /* FREERDP_CHANNEL_URBDRC_CLIENT_TYPES_H */ diff --git a/third_party/FreeRDP/channels/video/CMakeLists.txt b/third_party/FreeRDP/channels/video/CMakeLists.txt new file mode 100644 index 0000000..389a884 --- /dev/null +++ b/third_party/FreeRDP/channels/video/CMakeLists.txt @@ -0,0 +1,22 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2017 David Fort +# +# 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. + +define_channel("video") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/third_party/FreeRDP/channels/video/ChannelOptions.cmake b/third_party/FreeRDP/channels/video/ChannelOptions.cmake new file mode 100644 index 0000000..c477a74 --- /dev/null +++ b/third_party/FreeRDP/channels/video/ChannelOptions.cmake @@ -0,0 +1,20 @@ +set(OPTION_DEFAULT ON) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT OFF) + +define_channel_options( + NAME + "video" + TYPE + "dynamic" + DESCRIPTION + "Video optimized remoting Virtual Channel Extension" + SPECIFICATIONS + "[MS-RDPEVOR]" + DEFAULT + ${OPTION_DEFAULT} + CLIENT_DEFAULT + ${OPTION_CLIENT_DEFAULT} + SERVER_DEFAULT + ${OPTION_SERVER_DEFAULT} +) diff --git a/third_party/FreeRDP/channels/video/client/CMakeLists.txt b/third_party/FreeRDP/channels/video/client/CMakeLists.txt new file mode 100644 index 0000000..41e371d --- /dev/null +++ b/third_party/FreeRDP/channels/video/client/CMakeLists.txt @@ -0,0 +1,25 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2018 David Fort +# +# 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. + +define_channel_client("video") + +set(${MODULE_PREFIX}_SRCS video_main.c video_main.h) + +set(${MODULE_PREFIX}_LIBS winpr) +include_directories(..) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") diff --git a/third_party/FreeRDP/channels/video/client/video_main.c b/third_party/FreeRDP/channels/video/client/video_main.c new file mode 100644 index 0000000..0dc046f --- /dev/null +++ b/third_party/FreeRDP/channels/video/client/video_main.c @@ -0,0 +1,1267 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Optimized Remoting Virtual Channel Extension + * + * Copyright 2017 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TAG CHANNELS_TAG("video") + +#include "video_main.h" + +typedef struct +{ + IWTSPlugin wtsPlugin; + + IWTSListener* controlListener; + IWTSListener* dataListener; + GENERIC_LISTENER_CALLBACK* control_callback; + GENERIC_LISTENER_CALLBACK* data_callback; + + VideoClientContext* context; + BOOL initialized; + rdpContext* rdpcontext; +} VIDEO_PLUGIN; + +#define XF_VIDEO_UNLIMITED_RATE 31 + +static const BYTE MFVideoFormat_H264[] = { 'H', '2', '6', '4', 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 }; + +typedef struct +{ + VideoClientContext* video; + BYTE PresentationId; + UINT32 ScaledWidth, ScaledHeight; + MAPPED_GEOMETRY* geometry; + + UINT64 startTimeStamp; + UINT64 publishOffset; + H264_CONTEXT* h264; + wStream* currentSample; + UINT64 lastPublishTime, nextPublishTime; + volatile LONG refCounter; + VideoSurface* surface; +} PresentationContext; + +typedef struct +{ + UINT64 publishTime; + UINT64 hnsDuration; + MAPPED_GEOMETRY* geometry; + UINT32 w, h; + UINT32 scanline; + BYTE* surfaceData; + PresentationContext* presentation; +} VideoFrame; + +/** @brief private data for the channel */ +struct s_VideoClientContextPriv +{ + VideoClientContext* video; + GeometryClientContext* geometry; + wQueue* frames; + CRITICAL_SECTION framesLock; + wBufferPool* surfacePool; + UINT32 publishedFrames; + UINT32 droppedFrames; + UINT32 lastSentRate; + UINT64 nextFeedbackTime; + PresentationContext* currentPresentation; + FreeRDP_TimerID timerID; +}; + +static void PresentationContext_unref(PresentationContext** presentation); +static void VideoClientContextPriv_free(VideoClientContextPriv* priv); + +static const char* video_command_name(BYTE cmd) +{ + switch (cmd) + { + case TSMM_START_PRESENTATION: + return "start"; + case TSMM_STOP_PRESENTATION: + return "stop"; + default: + return ""; + } +} + +static void video_client_context_set_geometry(VideoClientContext* video, + GeometryClientContext* geometry) +{ + WINPR_ASSERT(video); + WINPR_ASSERT(video->priv); + video->priv->geometry = geometry; +} + +static VideoClientContextPriv* VideoClientContextPriv_new(VideoClientContext* video) +{ + VideoClientContextPriv* ret = nullptr; + + WINPR_ASSERT(video); + ret = calloc(1, sizeof(*ret)); + if (!ret) + return nullptr; + + ret->frames = Queue_New(TRUE, 10, 2); + if (!ret->frames) + { + WLog_ERR(TAG, "unable to allocate frames queue"); + goto fail; + } + + ret->surfacePool = BufferPool_New(FALSE, 0, 16); + if (!ret->surfacePool) + { + WLog_ERR(TAG, "unable to create surface pool"); + goto fail; + } + + if (!InitializeCriticalSectionAndSpinCount(&ret->framesLock, 4 * 1000)) + { + WLog_ERR(TAG, "unable to initialize frames lock"); + goto fail; + } + + ret->video = video; + + /* don't set to unlimited so that we have the chance to send a feedback in + * the first second (for servers that want feedback directly) + */ + ret->lastSentRate = 30; + return ret; + +fail: + VideoClientContextPriv_free(ret); + return nullptr; +} + +static BOOL PresentationContext_ref(PresentationContext* presentation) +{ + WINPR_ASSERT(presentation); + + InterlockedIncrement(&presentation->refCounter); + return TRUE; +} + +static PresentationContext* PresentationContext_new(VideoClientContext* video, BYTE PresentationId, + UINT32 x, UINT32 y, UINT32 width, UINT32 height) +{ + size_t s = 4ULL * width * height; + PresentationContext* ret = nullptr; + + WINPR_ASSERT(video); + + if (s > INT32_MAX) + return nullptr; + + ret = calloc(1, sizeof(*ret)); + if (!ret) + return nullptr; + + ret->video = video; + ret->PresentationId = PresentationId; + + ret->h264 = h264_context_new(FALSE); + if (!ret->h264) + { + WLog_ERR(TAG, "unable to create a h264 context"); + goto fail; + } + if (!h264_context_reset(ret->h264, width, height)) + goto fail; + + ret->currentSample = Stream_New(nullptr, 4096); + if (!ret->currentSample) + { + WLog_ERR(TAG, "unable to create current packet stream"); + goto fail; + } + + ret->surface = video->createSurface(video, x, y, width, height); + if (!ret->surface) + { + WLog_ERR(TAG, "unable to create surface"); + goto fail; + } + + if (!PresentationContext_ref(ret)) + goto fail; + + return ret; + +fail: + PresentationContext_unref(&ret); + return nullptr; +} + +static void PresentationContext_unref(PresentationContext** ppresentation) +{ + PresentationContext* presentation = nullptr; + MAPPED_GEOMETRY* geometry = nullptr; + + WINPR_ASSERT(ppresentation); + + presentation = *ppresentation; + if (!presentation) + return; + + if (InterlockedDecrement(&presentation->refCounter) > 0) + return; + + geometry = presentation->geometry; + if (geometry) + { + geometry->MappedGeometryUpdate = nullptr; + geometry->MappedGeometryClear = nullptr; + geometry->custom = nullptr; + mappedGeometryUnref(geometry); + } + + h264_context_free(presentation->h264); + Stream_Free(presentation->currentSample, TRUE); + presentation->video->deleteSurface(presentation->video, presentation->surface); + free(presentation); + *ppresentation = nullptr; +} + +static void VideoFrame_free(VideoFrame** pframe) +{ + VideoFrame* frame = nullptr; + + WINPR_ASSERT(pframe); + frame = *pframe; + if (!frame) + return; + + mappedGeometryUnref(frame->geometry); + + WINPR_ASSERT(frame->presentation); + WINPR_ASSERT(frame->presentation->video); + WINPR_ASSERT(frame->presentation->video->priv); + BufferPool_Return(frame->presentation->video->priv->surfacePool, frame->surfaceData); + PresentationContext_unref(&frame->presentation); + free(frame); + *pframe = nullptr; +} + +static VideoFrame* VideoFrame_new(VideoClientContextPriv* priv, PresentationContext* presentation, + MAPPED_GEOMETRY* geom) +{ + VideoFrame* frame = nullptr; + const VideoSurface* surface = nullptr; + + WINPR_ASSERT(priv); + WINPR_ASSERT(presentation); + WINPR_ASSERT(geom); + + surface = presentation->surface; + WINPR_ASSERT(surface); + + frame = calloc(1, sizeof(VideoFrame)); + if (!frame) + goto fail; + + mappedGeometryRef(geom); + + frame->publishTime = presentation->lastPublishTime; + frame->geometry = geom; + frame->w = surface->alignedWidth; + frame->h = surface->alignedHeight; + frame->scanline = surface->scanline; + + frame->surfaceData = BufferPool_Take(priv->surfacePool, 1ll * frame->scanline * frame->h); + if (!frame->surfaceData) + goto fail; + + frame->presentation = presentation; + if (!PresentationContext_ref(frame->presentation)) + goto fail; + + return frame; + +fail: + VideoFrame_free(&frame); + return nullptr; +} + +void VideoClientContextPriv_free(VideoClientContextPriv* priv) +{ + if (!priv) + return; + + EnterCriticalSection(&priv->framesLock); + + if (priv->frames) + { + while (Queue_Count(priv->frames)) + { + VideoFrame* frame = Queue_Dequeue(priv->frames); + if (frame) + VideoFrame_free(&frame); + } + } + + Queue_Free(priv->frames); + LeaveCriticalSection(&priv->framesLock); + + DeleteCriticalSection(&priv->framesLock); + + if (priv->currentPresentation) + PresentationContext_unref(&priv->currentPresentation); + + BufferPool_Free(priv->surfacePool); + free(priv); +} + +static UINT video_control_send_presentation_response(VideoClientContext* context, + TSMM_PRESENTATION_RESPONSE* resp) +{ + BYTE buf[12] = WINPR_C_ARRAY_INIT; + wStream* s = nullptr; + VIDEO_PLUGIN* video = nullptr; + IWTSVirtualChannel* channel = nullptr; + UINT ret = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(resp); + + video = (VIDEO_PLUGIN*)context->handle; + WINPR_ASSERT(video); + + s = Stream_New(buf, 12); + if (!s) + return CHANNEL_RC_NO_MEMORY; + + Stream_Write_UINT32(s, 12); /* cbSize */ + Stream_Write_UINT32(s, TSMM_PACKET_TYPE_PRESENTATION_RESPONSE); /* PacketType */ + Stream_Write_UINT8(s, resp->PresentationId); + Stream_Zero(s, 3); + Stream_SealLength(s); + + channel = video->control_callback->channel_callback->channel; + ret = channel->Write(channel, 12, buf, nullptr); + Stream_Free(s, FALSE); + + return ret; +} + +static BOOL video_onMappedGeometryUpdate(MAPPED_GEOMETRY* geometry) +{ + PresentationContext* presentation = nullptr; + RDP_RECT* r = nullptr; + + WINPR_ASSERT(geometry); + + presentation = (PresentationContext*)geometry->custom; + WINPR_ASSERT(presentation); + + r = &geometry->geometry.boundingRect; + WLog_DBG(TAG, + "geometry updated topGeom=(%" PRId32 ",%" PRId32 "-%" PRId32 "x%" PRId32 + ") geom=(%" PRId32 ",%" PRId32 "-%" PRId32 "x%" PRId32 ") rects=(%" PRId16 ",%" PRId16 + "-%" PRId16 "x%" PRId16 ")", + geometry->topLevelLeft, geometry->topLevelTop, + geometry->topLevelRight - geometry->topLevelLeft, + geometry->topLevelBottom - geometry->topLevelTop, + + geometry->left, geometry->top, geometry->right - geometry->left, + geometry->bottom - geometry->top, + + r->x, r->y, r->width, r->height); + + presentation->surface->x = + WINPR_ASSERTING_INT_CAST(uint32_t, geometry->topLevelLeft + geometry->left); + presentation->surface->y = + WINPR_ASSERTING_INT_CAST(uint32_t, geometry->topLevelTop + geometry->top); + + return TRUE; +} + +static BOOL video_onMappedGeometryClear(MAPPED_GEOMETRY* geometry) +{ + PresentationContext* presentation = nullptr; + + WINPR_ASSERT(geometry); + + presentation = (PresentationContext*)geometry->custom; + WINPR_ASSERT(presentation); + + mappedGeometryUnref(presentation->geometry); + presentation->geometry = nullptr; + return TRUE; +} + +static UINT video_PresentationRequest(VideoClientContext* video, + const TSMM_PRESENTATION_REQUEST* req) +{ + UINT ret = CHANNEL_RC_OK; + + WINPR_ASSERT(video); + WINPR_ASSERT(req); + + VideoClientContextPriv* priv = video->priv; + WINPR_ASSERT(priv); + + if (req->Command == TSMM_START_PRESENTATION) + { + MAPPED_GEOMETRY* geom = nullptr; + TSMM_PRESENTATION_RESPONSE resp; + + if (memcmp(req->VideoSubtypeId, MFVideoFormat_H264, 16) != 0) + { + WLog_ERR(TAG, "not a H264 video, ignoring request"); + return CHANNEL_RC_OK; + } + + if (priv->currentPresentation) + { + if (priv->currentPresentation->PresentationId == req->PresentationId) + { + WLog_ERR(TAG, "ignoring start request for existing presentation %" PRIu8, + req->PresentationId); + return CHANNEL_RC_OK; + } + + WLog_ERR(TAG, "releasing current presentation %" PRIu8, req->PresentationId); + PresentationContext_unref(&priv->currentPresentation); + } + + if (!priv->geometry) + { + WLog_ERR(TAG, "geometry channel not ready, ignoring request"); + return CHANNEL_RC_OK; + } + + geom = HashTable_GetItemValue(priv->geometry->geometries, &(req->GeometryMappingId)); + if (!geom) + { + WLog_ERR(TAG, "geometry mapping 0x%" PRIx64 " not registered", req->GeometryMappingId); + return CHANNEL_RC_OK; + } + + WLog_DBG(TAG, "creating presentation 0x%x", req->PresentationId); + priv->currentPresentation = PresentationContext_new( + video, req->PresentationId, + WINPR_ASSERTING_INT_CAST(uint32_t, geom->topLevelLeft + geom->left), + WINPR_ASSERTING_INT_CAST(uint32_t, geom->topLevelTop + geom->top), req->SourceWidth, + req->SourceHeight); + if (!priv->currentPresentation) + { + WLog_ERR(TAG, "unable to create presentation video"); + return CHANNEL_RC_NO_MEMORY; + } + + mappedGeometryRef(geom); + priv->currentPresentation->geometry = geom; + + priv->currentPresentation->video = video; + priv->currentPresentation->ScaledWidth = req->ScaledWidth; + priv->currentPresentation->ScaledHeight = req->ScaledHeight; + + geom->custom = priv->currentPresentation; + geom->MappedGeometryUpdate = video_onMappedGeometryUpdate; + geom->MappedGeometryClear = video_onMappedGeometryClear; + + /* send back response */ + resp.PresentationId = req->PresentationId; + ret = video_control_send_presentation_response(video, &resp); + } + else if (req->Command == TSMM_STOP_PRESENTATION) + { + WLog_DBG(TAG, "stopping presentation 0x%x", req->PresentationId); + if (!priv->currentPresentation) + { + WLog_ERR(TAG, "unknown presentation to stop %" PRIu8, req->PresentationId); + return CHANNEL_RC_OK; + } + + priv->droppedFrames = 0; + priv->publishedFrames = 0; + PresentationContext_unref(&priv->currentPresentation); + } + + return ret; +} + +static UINT video_read_tsmm_presentation_req(VideoClientContext* context, wStream* s) +{ + TSMM_PRESENTATION_REQUEST req = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(context); + WINPR_ASSERT(s); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 60)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT8(s, req.PresentationId); + Stream_Read_UINT8(s, req.Version); + Stream_Read_UINT8(s, req.Command); + Stream_Read_UINT8(s, req.FrameRate); /* FrameRate - reserved and ignored */ + + Stream_Seek_UINT16(s); /* AverageBitrateKbps reserved and ignored */ + Stream_Seek_UINT16(s); /* reserved */ + + Stream_Read_UINT32(s, req.SourceWidth); + Stream_Read_UINT32(s, req.SourceHeight); + Stream_Read_UINT32(s, req.ScaledWidth); + Stream_Read_UINT32(s, req.ScaledHeight); + Stream_Read_UINT64(s, req.hnsTimestampOffset); + Stream_Read_UINT64(s, req.GeometryMappingId); + Stream_Read(s, req.VideoSubtypeId, 16); + + Stream_Read_UINT32(s, req.cbExtra); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, req.cbExtra)) + return ERROR_INVALID_DATA; + + req.pExtraData = Stream_Pointer(s); + + WLog_DBG(TAG, + "presentationReq: id:%" PRIu8 " version:%" PRIu8 + " command:%s srcWidth/srcHeight=%" PRIu32 "x%" PRIu32 " scaled Width/Height=%" PRIu32 + "x%" PRIu32 " timestamp=%" PRIu64 " mappingId=%" PRIx64 "", + req.PresentationId, req.Version, video_command_name(req.Command), req.SourceWidth, + req.SourceHeight, req.ScaledWidth, req.ScaledHeight, req.hnsTimestampOffset, + req.GeometryMappingId); + + return video_PresentationRequest(context, &req); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT video_control_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* s) +{ + GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + VIDEO_PLUGIN* video = nullptr; + VideoClientContext* context = nullptr; + UINT ret = CHANNEL_RC_OK; + UINT32 cbSize = 0; + UINT32 packetType = 0; + + WINPR_ASSERT(callback); + WINPR_ASSERT(s); + + video = (VIDEO_PLUGIN*)callback->plugin; + WINPR_ASSERT(video); + + context = (VideoClientContext*)video->wtsPlugin.pInterface; + WINPR_ASSERT(context); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, cbSize); + if (cbSize < 8) + { + WLog_ERR(TAG, "invalid cbSize %" PRIu32 ", expected 8", cbSize); + return ERROR_INVALID_DATA; + } + if (!Stream_CheckAndLogRequiredLength(TAG, s, cbSize - 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, packetType); + switch (packetType) + { + case TSMM_PACKET_TYPE_PRESENTATION_REQUEST: + ret = video_read_tsmm_presentation_req(context, s); + break; + default: + WLog_ERR(TAG, "not expecting packet type %" PRIu32 "", packetType); + ret = ERROR_UNSUPPORTED_TYPE; + break; + } + + return ret; +} + +static UINT video_control_send_client_notification(VideoClientContext* context, + const TSMM_CLIENT_NOTIFICATION* notif) +{ + BYTE buf[100]; + wStream* s = nullptr; + VIDEO_PLUGIN* video = nullptr; + IWTSVirtualChannel* channel = nullptr; + UINT ret = 0; + UINT32 cbSize = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(notif); + + video = (VIDEO_PLUGIN*)context->handle; + WINPR_ASSERT(video); + + s = Stream_New(buf, 32); + if (!s) + return CHANNEL_RC_NO_MEMORY; + + cbSize = 16; + Stream_Seek_UINT32(s); /* cbSize */ + Stream_Write_UINT32(s, TSMM_PACKET_TYPE_CLIENT_NOTIFICATION); /* PacketType */ + Stream_Write_UINT8(s, notif->PresentationId); + Stream_Write_UINT8(s, notif->NotificationType); + Stream_Zero(s, 2); + if (notif->NotificationType == TSMM_CLIENT_NOTIFICATION_TYPE_FRAMERATE_OVERRIDE) + { + Stream_Write_UINT32(s, 16); /* cbData */ + + /* TSMM_CLIENT_NOTIFICATION_FRAMERATE_OVERRIDE */ + Stream_Write_UINT32(s, notif->FramerateOverride.Flags); + Stream_Write_UINT32(s, notif->FramerateOverride.DesiredFrameRate); + Stream_Zero(s, 4ULL * 2ULL); + + cbSize += 4UL * 4UL; + } + else + { + Stream_Write_UINT32(s, 0); /* cbData */ + } + + Stream_SealLength(s); + Stream_ResetPosition(s); + Stream_Write_UINT32(s, cbSize); + Stream_Free(s, FALSE); + + WINPR_ASSERT(video->control_callback); + WINPR_ASSERT(video->control_callback->channel_callback); + + channel = video->control_callback->channel_callback->channel; + WINPR_ASSERT(channel); + WINPR_ASSERT(channel->Write); + + ret = channel->Write(channel, cbSize, buf, nullptr); + + return ret; +} + +static void video_timer(VideoClientContext* video, UINT64 now) +{ + PresentationContext* presentation = nullptr; + VideoFrame* frame = nullptr; + + WINPR_ASSERT(video); + + VideoClientContextPriv* priv = video->priv; + WINPR_ASSERT(priv); + + EnterCriticalSection(&priv->framesLock); + do + { + VideoFrame* peekFrame = (VideoFrame*)Queue_Peek(priv->frames); + if (!peekFrame) + break; + + if (peekFrame->publishTime > now) + break; + + if (frame) + { + WLog_DBG(TAG, "dropping frame @%" PRIu64, frame->publishTime); + priv->droppedFrames++; + VideoFrame_free(&frame); + } + frame = peekFrame; + Queue_Dequeue(priv->frames); + } while (1); + LeaveCriticalSection(&priv->framesLock); + + if (frame) + { + presentation = frame->presentation; + + priv->publishedFrames++; + memcpy(presentation->surface->data, frame->surfaceData, 1ull * frame->scanline * frame->h); + + WINPR_ASSERT(video->showSurface); + if (!video->showSurface(video, presentation->surface, presentation->ScaledWidth, + presentation->ScaledHeight)) + WLog_WARN(TAG, "showSurface failed"); + + VideoFrame_free(&frame); + } + + if (priv->nextFeedbackTime < now) + { + /* we can compute some feedback only if we have some published frames and + * a current presentation + */ + if (priv->publishedFrames && priv->currentPresentation) + { + UINT32 computedRate = 0; + + PresentationContext_ref(priv->currentPresentation); + + if (priv->droppedFrames) + { + /** + * some dropped frames, looks like we're asking too many frames per seconds, + * try lowering rate. We go directly from unlimited rate to 24 frames/seconds + * otherwise we lower rate by 2 frames by seconds + */ + if (priv->lastSentRate == XF_VIDEO_UNLIMITED_RATE) + computedRate = 24; + else + { + computedRate = priv->lastSentRate - 2; + if (!computedRate) + computedRate = 2; + } + } + else + { + /** + * we treat all frames ok, so either ask the server to send more, + * or stay unlimited + */ + if (priv->lastSentRate == XF_VIDEO_UNLIMITED_RATE) + computedRate = XF_VIDEO_UNLIMITED_RATE; /* stay unlimited */ + else + { + computedRate = priv->lastSentRate + 2; + if (computedRate > XF_VIDEO_UNLIMITED_RATE) + computedRate = XF_VIDEO_UNLIMITED_RATE; + } + } + + if (computedRate != priv->lastSentRate) + { + TSMM_CLIENT_NOTIFICATION notif; + + WINPR_ASSERT(priv->currentPresentation); + notif.PresentationId = priv->currentPresentation->PresentationId; + notif.NotificationType = TSMM_CLIENT_NOTIFICATION_TYPE_FRAMERATE_OVERRIDE; + if (computedRate == XF_VIDEO_UNLIMITED_RATE) + { + notif.FramerateOverride.Flags = 0x01; + notif.FramerateOverride.DesiredFrameRate = 0x00; + } + else + { + notif.FramerateOverride.Flags = 0x02; + notif.FramerateOverride.DesiredFrameRate = computedRate; + } + + video_control_send_client_notification(video, ¬if); + priv->lastSentRate = computedRate; + + WLog_VRB(TAG, + "server notified with rate %" PRIu32 " published=%" PRIu32 + " dropped=%" PRIu32, + priv->lastSentRate, priv->publishedFrames, priv->droppedFrames); + } + + PresentationContext_unref(&priv->currentPresentation); + } + + priv->droppedFrames = 0; + priv->publishedFrames = 0; + priv->nextFeedbackTime = now + 1000; + } +} + +static UINT video_VideoData(VideoClientContext* context, const TSMM_VIDEO_DATA* data) +{ + VideoClientContextPriv* priv = nullptr; + PresentationContext* presentation = nullptr; + int status = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(data); + + priv = context->priv; + WINPR_ASSERT(priv); + + presentation = priv->currentPresentation; + if (!presentation) + { + WLog_ERR(TAG, "no current presentation"); + return CHANNEL_RC_OK; + } + + if (presentation->PresentationId != data->PresentationId) + { + WLog_ERR(TAG, "current presentation id=%" PRIu8 " doesn't match data id=%" PRIu8, + presentation->PresentationId, data->PresentationId); + return CHANNEL_RC_OK; + } + + if (!Stream_EnsureRemainingCapacity(presentation->currentSample, data->cbSample)) + { + WLog_ERR(TAG, "unable to expand the current packet"); + return CHANNEL_RC_NO_MEMORY; + } + + Stream_Write(presentation->currentSample, data->pSample, data->cbSample); + + if (data->CurrentPacketIndex == data->PacketsInSample) + { + VideoSurface* surface = presentation->surface; + H264_CONTEXT* h264 = presentation->h264; + const UINT64 startTime = winpr_GetTickCount64NS(); + MAPPED_GEOMETRY* geom = presentation->geometry; + + const RECTANGLE_16 rect = { 0, 0, WINPR_ASSERTING_INT_CAST(UINT16, surface->alignedWidth), + WINPR_ASSERTING_INT_CAST(UINT16, surface->alignedHeight) }; + Stream_SealLength(presentation->currentSample); + Stream_ResetPosition(presentation->currentSample); + + const UINT64 timeAfterH264 = winpr_GetTickCount64NS(); + if (data->SampleNumber == 1) + { + presentation->lastPublishTime = startTime; + } + + presentation->lastPublishTime += 100ull * data->hnsDuration; + if (presentation->lastPublishTime <= (10000000ull + timeAfterH264)) + { + int dropped = 0; + + const size_t len = Stream_Length(presentation->currentSample); + if (len > UINT32_MAX) + return CHANNEL_RC_OK; + + /* if the frame is to be published in less than 10 ms, let's consider it's now */ + status = + avc420_decompress(h264, Stream_Pointer(presentation->currentSample), (UINT32)len, + surface->data, surface->format, surface->scanline, + surface->alignedWidth, surface->alignedHeight, &rect, 1); + + if (status < 0) + return CHANNEL_RC_OK; + + WINPR_ASSERT(context->showSurface); + if (!context->showSurface(context, presentation->surface, presentation->ScaledWidth, + presentation->ScaledHeight)) + return CHANNEL_RC_NOT_INITIALIZED; + + priv->publishedFrames++; + + /* cleanup previously scheduled frames */ + EnterCriticalSection(&priv->framesLock); + while (Queue_Count(priv->frames) > 0) + { + VideoFrame* frame = Queue_Dequeue(priv->frames); + if (frame) + { + priv->droppedFrames++; + VideoFrame_free(&frame); + dropped++; + } + } + LeaveCriticalSection(&priv->framesLock); + + if (dropped) + WLog_DBG(TAG, "showing frame (%d dropped)", dropped); + } + else + { + const size_t len = Stream_Length(presentation->currentSample); + if (len > UINT32_MAX) + return CHANNEL_RC_OK; + + BOOL enqueueResult = 0; + VideoFrame* frame = VideoFrame_new(priv, presentation, geom); + if (!frame) + { + WLog_ERR(TAG, "unable to create frame"); + return CHANNEL_RC_NO_MEMORY; + } + + status = + avc420_decompress(h264, Stream_Pointer(presentation->currentSample), (UINT32)len, + frame->surfaceData, surface->format, surface->scanline, + surface->alignedWidth, surface->alignedHeight, &rect, 1); + if (status < 0) + { + VideoFrame_free(&frame); + return CHANNEL_RC_OK; + } + + EnterCriticalSection(&priv->framesLock); + enqueueResult = Queue_Enqueue(priv->frames, frame); + LeaveCriticalSection(&priv->framesLock); + + if (!enqueueResult) + { + WLog_ERR(TAG, "unable to enqueue frame"); + VideoFrame_free(&frame); + return CHANNEL_RC_NO_MEMORY; + } + + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): Queue_Enqueue owns frame + WLog_DBG(TAG, "scheduling frame in %" PRIu64 " ms", (frame->publishTime - startTime)); + } + } + + return CHANNEL_RC_OK; +} + +static UINT video_data_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* s) +{ + GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + VIDEO_PLUGIN* video = nullptr; + VideoClientContext* context = nullptr; + UINT32 cbSize = 0; + UINT32 packetType = 0; + TSMM_VIDEO_DATA data; + + video = (VIDEO_PLUGIN*)callback->plugin; + context = (VideoClientContext*)video->wtsPlugin.pInterface; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, cbSize); + if (cbSize < 8) + { + WLog_ERR(TAG, "invalid cbSize %" PRIu32 ", expected >= 8", cbSize); + return ERROR_INVALID_DATA; + } + + if (!Stream_CheckAndLogRequiredLength(TAG, s, cbSize - 4)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, packetType); + if (packetType != TSMM_PACKET_TYPE_VIDEO_DATA) + { + WLog_ERR(TAG, "only expecting VIDEO_DATA on the data channel"); + return ERROR_INVALID_DATA; + } + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 32)) + return ERROR_INVALID_DATA; + + Stream_Read_UINT8(s, data.PresentationId); + Stream_Read_UINT8(s, data.Version); + Stream_Read_UINT8(s, data.Flags); + Stream_Seek_UINT8(s); /* reserved */ + Stream_Read_UINT64(s, data.hnsTimestamp); + Stream_Read_UINT64(s, data.hnsDuration); + Stream_Read_UINT16(s, data.CurrentPacketIndex); + Stream_Read_UINT16(s, data.PacketsInSample); + Stream_Read_UINT32(s, data.SampleNumber); + Stream_Read_UINT32(s, data.cbSample); + if (!Stream_CheckAndLogRequiredLength(TAG, s, data.cbSample)) + return ERROR_INVALID_DATA; + data.pSample = Stream_Pointer(s); + + /* + WLog_DBG(TAG, "videoData: id:%"PRIu8" version:%"PRIu8" flags:0x%"PRIx8" timestamp=%"PRIu64" + duration=%"PRIu64 " curPacketIndex:%"PRIu16" packetInSample:%"PRIu16" sampleNumber:%"PRIu32" + cbSample:%"PRIu32"", data.PresentationId, data.Version, data.Flags, data.hnsTimestamp, + data.hnsDuration, data.CurrentPacketIndex, data.PacketsInSample, data.SampleNumber, + data.cbSample); + */ + + return video_VideoData(context, &data); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT video_control_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + free(pChannelCallback); + return CHANNEL_RC_OK; +} + +static UINT video_data_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + free(pChannelCallback); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +// NOLINTBEGIN(readability-non-const-parameter) +static UINT video_control_on_new_channel_connection(IWTSListenerCallback* listenerCallback, + IWTSVirtualChannel* channel, BYTE* Data, + BOOL* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +// NOLINTEND(readability-non-const-parameter) +{ + GENERIC_LISTENER_CALLBACK* listener_callback = (GENERIC_LISTENER_CALLBACK*)listenerCallback; + + WINPR_UNUSED(Data); + WINPR_UNUSED(pbAccept); + + GENERIC_CHANNEL_CALLBACK* callback = + (GENERIC_CHANNEL_CALLBACK*)calloc(1, sizeof(GENERIC_CHANNEL_CALLBACK)); + if (!callback) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + callback->iface.OnDataReceived = video_control_on_data_received; + callback->iface.OnClose = video_control_on_close; + callback->plugin = listener_callback->plugin; + callback->channel_mgr = listener_callback->channel_mgr; + callback->channel = channel; + listener_callback->channel_callback = callback; + + *ppCallback = &callback->iface; + + return CHANNEL_RC_OK; +} + +// NOLINTBEGIN(readability-non-const-parameter) +static UINT video_data_on_new_channel_connection(IWTSListenerCallback* pListenerCallback, + IWTSVirtualChannel* pChannel, BYTE* Data, + BOOL* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +// NOLINTEND(readability-non-const-parameter) +{ + GENERIC_CHANNEL_CALLBACK* callback = nullptr; + GENERIC_LISTENER_CALLBACK* listener_callback = (GENERIC_LISTENER_CALLBACK*)pListenerCallback; + + WINPR_UNUSED(Data); + WINPR_UNUSED(pbAccept); + + callback = (GENERIC_CHANNEL_CALLBACK*)calloc(1, sizeof(GENERIC_CHANNEL_CALLBACK)); + if (!callback) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + callback->iface.OnDataReceived = video_data_on_data_received; + callback->iface.OnClose = video_data_on_close; + callback->plugin = listener_callback->plugin; + callback->channel_mgr = listener_callback->channel_mgr; + callback->channel = pChannel; + listener_callback->channel_callback = callback; + + *ppCallback = &callback->iface; + + return CHANNEL_RC_OK; +} + +static uint64_t timer_cb(WINPR_ATTR_UNUSED rdpContext* context, void* userdata, + WINPR_ATTR_UNUSED FreeRDP_TimerID timerID, uint64_t timestamp, + uint64_t interval) +{ + VideoClientContext* video = userdata; + if (!video) + return 0; + if (!video->timer) + return 0; + + video->timer(video, timestamp); + + return interval; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT video_plugin_initialize(IWTSPlugin* plugin, IWTSVirtualChannelManager* channelMgr) +{ + UINT status = 0; + VIDEO_PLUGIN* video = (VIDEO_PLUGIN*)plugin; + GENERIC_LISTENER_CALLBACK* callback = nullptr; + + if (video->initialized) + { + WLog_ERR(TAG, "[%s] channel initialized twice, aborting", VIDEO_CONTROL_DVC_CHANNEL_NAME); + return ERROR_INVALID_DATA; + } + video->control_callback = callback = + (GENERIC_LISTENER_CALLBACK*)calloc(1, sizeof(GENERIC_LISTENER_CALLBACK)); + if (!callback) + { + WLog_ERR(TAG, "calloc for control callback failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + callback->iface.OnNewChannelConnection = video_control_on_new_channel_connection; + callback->plugin = plugin; + callback->channel_mgr = channelMgr; + + status = channelMgr->CreateListener(channelMgr, VIDEO_CONTROL_DVC_CHANNEL_NAME, 0, + &callback->iface, &(video->controlListener)); + + if (status != CHANNEL_RC_OK) + return status; + video->controlListener->pInterface = video->wtsPlugin.pInterface; + + video->data_callback = callback = + (GENERIC_LISTENER_CALLBACK*)calloc(1, sizeof(GENERIC_LISTENER_CALLBACK)); + if (!callback) + { + WLog_ERR(TAG, "calloc for data callback failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + callback->iface.OnNewChannelConnection = video_data_on_new_channel_connection; + callback->plugin = plugin; + callback->channel_mgr = channelMgr; + + status = channelMgr->CreateListener(channelMgr, VIDEO_DATA_DVC_CHANNEL_NAME, 0, + &callback->iface, &(video->dataListener)); + + if (status == CHANNEL_RC_OK) + video->dataListener->pInterface = video->wtsPlugin.pInterface; + + if (status == CHANNEL_RC_OK) + video->context->priv->timerID = + freerdp_timer_add(video->rdpcontext, 20000000, timer_cb, video->context, true); + video->initialized = video->context->priv->timerID != 0; + if (!video->initialized) + status = ERROR_INTERNAL_ERROR; + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT video_plugin_terminated(IWTSPlugin* pPlugin) +{ + VIDEO_PLUGIN* video = (VIDEO_PLUGIN*)pPlugin; + if (!video) + return CHANNEL_RC_INVALID_INSTANCE; + + if (video->context && video->context->priv) + freerdp_timer_remove(video->rdpcontext, video->context->priv->timerID); + + if (video->control_callback) + { + IWTSVirtualChannelManager* mgr = video->control_callback->channel_mgr; + if (mgr) + IFCALL(mgr->DestroyListener, mgr, video->controlListener); + } + if (video->data_callback) + { + IWTSVirtualChannelManager* mgr = video->data_callback->channel_mgr; + if (mgr) + IFCALL(mgr->DestroyListener, mgr, video->dataListener); + } + + if (video->context) + VideoClientContextPriv_free(video->context->priv); + + free(video->control_callback); + free(video->data_callback); + free(video->wtsPlugin.pInterface); + free(pPlugin); + return CHANNEL_RC_OK; +} + +/** + * Channel Client Interface + */ +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT VCAPITYPE video_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)) +{ + UINT error = ERROR_INTERNAL_ERROR; + VIDEO_PLUGIN* videoPlugin = nullptr; + VideoClientContext* videoContext = nullptr; + VideoClientContextPriv* priv = nullptr; + + videoPlugin = (VIDEO_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints, "video"); + if (!videoPlugin) + { + videoPlugin = (VIDEO_PLUGIN*)calloc(1, sizeof(VIDEO_PLUGIN)); + if (!videoPlugin) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + videoPlugin->wtsPlugin.Initialize = video_plugin_initialize; + videoPlugin->wtsPlugin.Connected = nullptr; + videoPlugin->wtsPlugin.Disconnected = nullptr; + videoPlugin->wtsPlugin.Terminated = video_plugin_terminated; + + videoContext = (VideoClientContext*)calloc(1, sizeof(VideoClientContext)); + if (!videoContext) + { + WLog_ERR(TAG, "calloc failed!"); + free(videoPlugin); + return CHANNEL_RC_NO_MEMORY; + } + + priv = VideoClientContextPriv_new(videoContext); + if (!priv) + { + WLog_ERR(TAG, "VideoClientContextPriv_new failed!"); + free(videoContext); + free(videoPlugin); + return CHANNEL_RC_NO_MEMORY; + } + + videoContext->handle = (void*)videoPlugin; + videoContext->priv = priv; + videoContext->timer = video_timer; + videoContext->setGeometry = video_client_context_set_geometry; + + videoPlugin->wtsPlugin.pInterface = (void*)videoContext; + videoPlugin->context = videoContext; + videoPlugin->rdpcontext = pEntryPoints->GetRdpContext(pEntryPoints); + if (videoPlugin->rdpcontext) + error = pEntryPoints->RegisterPlugin(pEntryPoints, "video", &videoPlugin->wtsPlugin); + } + else + { + WLog_ERR(TAG, "could not get video Plugin."); + return CHANNEL_RC_BAD_CHANNEL; + } + + return error; +} diff --git a/third_party/FreeRDP/channels/video/client/video_main.h b/third_party/FreeRDP/channels/video/client/video_main.h new file mode 100644 index 0000000..d09efab --- /dev/null +++ b/third_party/FreeRDP/channels/video/client/video_main.h @@ -0,0 +1,31 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Optimized Remoting Virtual Channel Extension + * + * Copyright 2017 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_VIDEO_CLIENT_MAIN_H +#define FREERDP_CHANNEL_VIDEO_CLIENT_MAIN_H + +#include + +#include +#include +#include + +#include + +#endif /* FREERDP_CHANNEL_GEOMETRY_CLIENT_MAIN_H */ diff --git a/third_party/FreeRDP/ci/cmake-preloads/config-abi.txt b/third_party/FreeRDP/ci/cmake-preloads/config-abi.txt new file mode 100644 index 0000000..d496629 --- /dev/null +++ b/third_party/FreeRDP/ci/cmake-preloads/config-abi.txt @@ -0,0 +1,41 @@ +message("PRELOADING cache") +set(CMAKE_VERBOSE_MAKEFILE ON CACHE BOOL "preload") +set(WITH_MANPAGES OFF CACHE BOOL "preload") +set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "preload") +#set (UWAC_FORCE_STATIC_BUILD ON CACHE BOOL "preload") +#set (RDTK_FORCE_STATIC_BUILD ON CACHE BOOL "preload") +set(WITH_RDTK ON CACHE BOOL "preload") +set(WITH_WAYLAND OFF CACHE BOOL "preload") +set(WITH_SAMPLE OFF CACHE BOOL "preload") +set(WITH_SERVER_SHADOW_CLI OFF CACHE BOOL "preload") +set(WITH_CLIENT OFF CACHE BOOL "preload") +set(WITH_WINPR_TOOLS_CLI OFF CACHE BOOL "preload") +set(WITH_PROXY_APP OFF CACHE BOOL "preload") +set(WITH_WINPR_TOOLS ON CACHE BOOL "preload") +set(WITH_PLATFORM_SERVER OFF CACHE BOOL "preload") +set(WINPR_UTILS_IMAGE_PNG ON CACHE BOOL "preload") +set(WINPR_UTILS_IMAGE_JPEG ON CACHE BOOL "preload") +set(WINPR_UTILS_IMAGE_WEBP ON CACHE BOOL "preload") +set(WITH_BINARY_VERSIONING ON CACHE BOOL "preload") +set(WITH_INTERNAL_RC4 ON CACHE BOOL "preload") +set(WITH_INTERNAL_MD4 ON CACHE BOOL "preload") +set(WITH_INTERNAL_MD5 ON CACHE BOOL "preload") +set(WITH_SAMPLE ON CACHE BOOL "preload") +set(WITH_FFMPEG ON CACHE BOOL "preload") +set(WITH_SWSCALE ON CACHE BOOL "preload") +set(WITH_DSP_FFMPEG ON CACHE BOOL "preload") +set(WITH_FREERDP_DEPRECATED_COMMANDLINE ON CACHE BOOL "preload") +set(WITH_PULSE ON CACHE BOOL "preload") +set(WITH_CLIENT_SDL OFF CACHE BOOL "preload") +set(WITH_CLIENT_X11 OFF CACHE BOOL "preload") +set(WITH_CLIENT_WAYLAND OFF CACHE BOOL "preload") +set(WITH_SERVER_SHADOW OFF CACHE BOOL "preload") +set(WITH_OPAQUE_SETTINGS ON CACHE BOOL "preload") +set(WITH_VERBOSE_WINPR_ASSERT OFF CACHE BOOL "preload") +set(CHANNEL_RDPECAM ON CACHE BOOL "qa default") +set(CHANNEL_RDPECAM_CLIENT ON CACHE BOOL "qa default") +set(CHANNEL_RDPEAR ON CACHE BOOL "qa default") +set(CHANNEL_RDPEAR_CLIENT ON CACHE BOOL "qa default") +set(CHANNEL_GFXREDIR ON CACHE BOOL "qa default") +set(CHANNEL_RDP2TCP ON CACHE BOOL "qa default") +set(CHANNEL_SSHAGENT ON CACHE BOOL "qa default") diff --git a/third_party/FreeRDP/ci/cmake-preloads/config-android.txt b/third_party/FreeRDP/ci/cmake-preloads/config-android.txt new file mode 100644 index 0000000..36db9fe --- /dev/null +++ b/third_party/FreeRDP/ci/cmake-preloads/config-android.txt @@ -0,0 +1,17 @@ +message("PRELOADING android cache") +set(CMAKE_VERBOSE_MAKEFILE ON CACHE BOOL "preload") +set(CMAKE_TOOLCHAIN_FILE "$ANDROID_NDK/build/cmake/android.toolchain.cmake" CACHE PATH "ToolChain file") +set(WITH_SANITIZE_ADDRESS ON CACHE BOOL "build with address sanitizer") +set(FREERDP_EXTERNAL_SSL_PATH $ENV{ANDROID_SSL_PATH} CACHE PATH "android ssl") +# ANDROID_NDK and ANDROID_SDK must be set as environment variable +#set(ANDROID_NDK $ENV{ANDROID_SDK} CACHE PATH "Android NDK") +#set(ANDROID_SDK "${ANDROID_NDK}" CACHE PATH "android SDK") +set(CMAKE_C_STANDARD 23 CACHE STRING "preload") +set(CMAKE_C_FLAGS "-Wno-pre-c23-compat" CACHE STRING "preload") +set(WITH_FREERDP_DEPRECATED_COMMANDLINE ON CACHE BOOL "Enable deprecated command line options") +set(WITH_KRB5 OFF CACHE BOOL "Kerberos support") +set(WITH_CLIENT_SDL OFF CACHE BOOL "SDL client") +set(WITH_SERVER OFF CACHE BOOL "ci default") +set(WITH_X11 OFF CACHE BOOL "ci default") +set(WITH_MANPAGES OFF CACHE BOOL "ci default") +set(WITH_LIBRARY_VERSIONING OFF CACHE BOOL "ci default") diff --git a/third_party/FreeRDP/ci/cmake-preloads/config-coverity.txt b/third_party/FreeRDP/ci/cmake-preloads/config-coverity.txt new file mode 100644 index 0000000..c2add01 --- /dev/null +++ b/third_party/FreeRDP/ci/cmake-preloads/config-coverity.txt @@ -0,0 +1,19 @@ +set(CMAKE_VERBOSE_MAKEFILE ON CACHE BOOL "preload") +set(CMAKE_C_STANDARD 23 CACHE STRING "preload") +set(CMAKE_C_FLAGS "-Wno-pre-c23-compat" CACHE STRING "preload") +set(WINPR_UTILS_IMAGE_JPEG ON CACHE BOOL "preload") +set(WINPR_UTILS_IMAGE_WEBP ON CACHE BOOL "preload") +set(WINPR_UTILS_IMAGE_PNG ON CACHE BOOL "preload") +set(WITH_CAIRO ON CACHE BOOL "preload") +set(WITH_DSP_EXPERIMENTAL ON CACHE BOOL "preload") +set(WITH_DSP_FFMPEG ON CACHE BOOL "preload") +set(WITH_FFMPEG ON CACHE BOOL "preload") +set(WITH_INTERNAL_RC4 ON CACHE BOOL "preload") +set(WITH_INTERNAL_MD4 ON CACHE BOOL "preload") +set(WITH_INTERNAL_MD5 ON CACHE BOOL "preload") +set(WITH_OPUS ON CACHE BOOL "preload") +set(WITH_PROXY_EMULATE_SMARTCARD ON CACHE BOOL "preload") +set(WITH_PULSE ON CACHE BOOL "preload") +set(WITH_SMARTCARD_INSPECT ON CACHE BOOL "preload") +set(WITH_SOXR ON CACHE BOOL "preload") +set(WITH_UNICODE_BUILTIN ON CACHE BOOL "preload") diff --git a/third_party/FreeRDP/ci/cmake-preloads/config-freebsd.txt b/third_party/FreeRDP/ci/cmake-preloads/config-freebsd.txt new file mode 100644 index 0000000..755a4ec --- /dev/null +++ b/third_party/FreeRDP/ci/cmake-preloads/config-freebsd.txt @@ -0,0 +1,57 @@ +message("PRELOADING cache") +set(CMAKE_VERBOSE_MAKEFILE ON CACHE BOOL "preload") +set(BUILD_TESTING_INTERNAL ON CACHE BOOL "preload") +set(WITH_MANPAGES ON CACHE BOOL "preload") +set(CMAKE_C_STANDARD 23 CACHE STRING "preload") +set(CMAKE_C_FLAGS "-Wno-pre-c23-compat" CACHE STRING "preload") +set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "preload") +set(WITH_CAIRO ON CACHE BOOL "preload") +set(WITH_ALSA ON CACHE BOOL "preload") +set(WITH_PULSE ON CACHE BOOL "preload") +set(WITH_CHANNELS ON CACHE BOOL "preload") +set(WITH_CUPS ON CACHE BOOL "preload") +set(WITH_LIBRESSL OFF CACHE BOOL "preload") +set(WITH_GSM OFF CACHE BOOL "preload") +set(WITH_CLIENT_SDL3 OFF CACHE BOOL "preload") +set(WITH_SDL_IMAGE_DIALOGS ON CACHE BOOL "preload") +set(WITH_WAYLAND ON CACHE BOOL "preload") +set(WITH_KRB5 ON CACHE BOOL "preload") +set(WITH_PCSC ON CACHE BOOL "preload") +set(WITH_JPEG ON CACHE BOOL "preload") +set(WITH_GSM ON CACHE BOOL "preload") +set(WITH_INTERNAL_RC4 ON CACHE BOOL "preload") +set(WITH_INTERNAL_MD4 ON CACHE BOOL "preload") +set(WITH_INTERNAL_MD5 ON CACHE BOOL "preload") +set(CHANNEL_SSHAGENT ON CACHE BOOL "preload") +set(CHANNEL_RDPECAM ON CACHE BOOL "preload") +set(CHANNEL_RDPECAM_CLIENT OFF CACHE BOOL "preload") +set(WINPR_UTILS_IMAGE_JPEG ON CACHE BOOL "preload") +set(WINPR_UTILS_IMAGE_PNG ON CACHE BOOL "preload") +set(WINPR_UTILS_IMAGE_WEBP ON CACHE BOOL "preload") +set(CHANNEL_URBDRC ON CACHE BOOL "preload") +set(CHANNEL_URBDRC_CLIENT ON CACHE BOOL "preload") +set(WITH_SERVER ON CACHE BOOL "preload") +set(WITH_SAMPLE ON CACHE BOOL "preload") +set(WITH_FAAC ON CACHE BOOL "preload") +set(WITH_FAAD ON CACHE BOOL "preload") +set(WITH_LAME ON CACHE BOOL "preload") +set(WITH_OPENCL ON CACHE BOOL "preload") +set(WITH_OPUS ON CACHE BOOL "preload") +set(WITH_SOXR ON CACHE BOOL "preload") +set(WITH_OPENH264 ON CACHE BOOL "preload") +set(WITH_FDK_AAC ON CACHE BOOL "preload") +set(WITH_NO_UNDEFINED OFF CACHE BOOL "preload") +set(WITH_SANITIZE_ADDRESS ON CACHE BOOL "preload") +set(WITH_FFMPEG ON CACHE BOOL "preload") +set(WITH_SWSCALE ON CACHE BOOL "preload") +set(WITH_DSP_FFMPEG ON CACHE BOOL "preload") +set(WITH_PROXY ON CACHE BOOL "preload") +set(WITH_PROXY_MODULES ON CACHE BOOL "preload") +set(WITH_FREERDP_DEPRECATED_COMMANDLINE ON CACHE BOOL "preload") +set(CHANNEL_RDPECAM ON CACHE BOOL "qa default") +set(CHANNEL_RDPECAM_CLIENT ON CACHE BOOL "qa default") +set(CHANNEL_RDPEAR ON CACHE BOOL "qa default") +set(CHANNEL_RDPEAR_CLIENT ON CACHE BOOL "qa default") +set(CHANNEL_GFXREDIR ON CACHE BOOL "qa default") +set(CHANNEL_RDP2TCP ON CACHE BOOL "qa default") +set(CHANNEL_SSHAGENT ON CACHE BOOL "qa default") diff --git a/third_party/FreeRDP/ci/cmake-preloads/config-ios-shared.txt b/third_party/FreeRDP/ci/cmake-preloads/config-ios-shared.txt new file mode 100644 index 0000000..0a983eb --- /dev/null +++ b/third_party/FreeRDP/ci/cmake-preloads/config-ios-shared.txt @@ -0,0 +1,21 @@ +message("PRELOADING iOS cache") +set(CMAKE_C_STANDARD 23 CACHE STRING "preload") +set(CMAKE_C_FLAGS "-Wno-pre-c23-compat" CACHE STRING "preload") +set(CMAKE_VERBOSE_MAKEFILE ON CACHE BOOL "preload") +set(CMAKE_TOOLCHAIN_FILE "${CMAKE_SOURCE_DIR}/cmake/ios.toolchain.cmake" CACHE PATH "cmake toolchain file") +set(CMAKE_BUILD_TYPE "Release" CACHE STRING "build type") +set(CMAKE_OSX_ARCHITECTURES "arm64" CACHE STRING "iOS platform to build") +set(CMAKE_OSX_DEPLOYMENT_TARGET "10.0" CACHE STRING "iOS minimum target") +set(ENABLE_BITCODE OFF CACHE BOOL "iOS default") +set(BUILD_TESTING ON CACHE BOOL "iOS default") +set(WITH_SANITIZE_ADDRESS ON CACHE BOOL "build with address sanitizer") +set(WITH_CLIENT OFF CACHE BOOL "disable iOS client") +set(WITH_SERVER OFF CACHE BOOL "disable iOS server") +set(WITH_KRB5 OFF CACHE BOOL "Kerberos support") +set(WITH_CLIENT_SDL OFF CACHE BOOL "iOS preload") +set(WITH_FFMPEG OFF CACHE BOOL "iOS preload") +set(WITH_SWSCALE OFF CACHE BOOL "iOS preload") +set(WITH_SIMD ON CACHE BOOL "iOS preload") +set(WITH_OPUS OFF CACHE BOOL "iOS preload") +set(WITH_MANPAGES OFF CACHE BOOL "iOS preload") +set(BUILD_SHARED_LIBS ON CACHE BOOL "iOS preload") diff --git a/third_party/FreeRDP/ci/cmake-preloads/config-ios.txt b/third_party/FreeRDP/ci/cmake-preloads/config-ios.txt new file mode 100644 index 0000000..0cc967f --- /dev/null +++ b/third_party/FreeRDP/ci/cmake-preloads/config-ios.txt @@ -0,0 +1,21 @@ +message("PRELOADING iOS cache") +set(CMAKE_VERBOSE_MAKEFILE ON CACHE BOOL "preload") +set(CMAKE_C_STANDARD 23 CACHE STRING "preload") +set(CMAKE_C_FLAGS "-Wno-pre-c23-compat" CACHE STRING "preload") +set(CMAKE_TOOLCHAIN_FILE "${CMAKE_SOURCE_DIR}/cmake/ios.toolchain.cmake" CACHE PATH "cmake toolchain file") +set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "build type") +set(CMAKE_OSX_ARCHITECTURES "arm64" CACHE STRING "iOS platform to build") +set(CMAKE_OSX_DEPLOYMENT_TARGET "10.0" CACHE STRING "iOS minimum target") +set(ENABLE_BITCODE OFF CACHE BOOL "iOS default") +set(BUILD_TESTING ON CACHE BOOL "iOS default") +set(WITH_SANITIZE_ADDRESS ON CACHE BOOL "build with address sanitizer") +set(WITH_CLIENT OFF CACHE BOOL "disable iOS client") +set(WITH_SERVER OFF CACHE BOOL "disable iOS server") +set(WITH_KRB5 OFF CACHE BOOL "Kerberos support") +set(WITH_CLIENT_SDL OFF CACHE BOOL "iOS preload") +set(WITH_FFMPEG OFF CACHE BOOL "iOS preload") +set(WITH_SWSCALE OFF CACHE BOOL "iOS preload") +set(WITH_SIMD ON CACHE BOOL "iOS preload") +set(WITH_OPUS OFF CACHE BOOL "iOS preload") +set(WITH_MANPAGES OFF CACHE BOOL "iOS preload") +set(BUILD_SHARED_LIBS OFF CACHE BOOL "iOS preload") diff --git a/third_party/FreeRDP/ci/cmake-preloads/config-linux-all.txt b/third_party/FreeRDP/ci/cmake-preloads/config-linux-all.txt new file mode 100644 index 0000000..1a3063c --- /dev/null +++ b/third_party/FreeRDP/ci/cmake-preloads/config-linux-all.txt @@ -0,0 +1,65 @@ +message("PRELOADING cache") +set(CMAKE_C_STANDARD 23 CACHE STRING "preload") +set(CMAKE_C_FLAGS "-Wno-pre-c23-compat" CACHE STRING "preload") +set(CMAKE_VERBOSE_MAKEFILE ON CACHE BOOL "preload") +set(BUILD_TESTING_INTERNAL ON CACHE BOOL "preload") +set(WITH_MANPAGES ON CACHE BOOL "preload") +set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "preload") +set(WITH_PULSE ON CACHE BOOL "preload") +set(WITH_RDTK ON CACHE BOOL "preload") +set(WITH_CHANNELS ON CACHE BOOL "preload") +set(WITH_CUPS ON CACHE BOOL "preload") +set(WITH_WAYLAND ON CACHE BOOL "preload") +set(WITH_KRB5 ON CACHE BOOL "preload") +set(WITH_PCSC ON CACHE BOOL "preload") +set(WITH_JPEG ON CACHE BOOL "preload") +set(WITH_GSM ON CACHE BOOL "preload") +set(CHANNEL_URBDRC ON CACHE BOOL "preload") +set(CHANNEL_URBDRC_CLIENT ON CACHE BOOL "preload") +set(WITH_SERVER ON CACHE BOOL "preload") +set(WITH_DEBUG_ALL OFF CACHE BOOL "preload") +set(WITH_DEBUG_CAPABILITIES OFF CACHE BOOL "preload") +set(WITH_DEBUG_CERTIFICATE OFF CACHE BOOL "preload") +set(WITH_DEBUG_CHANNELS OFF CACHE BOOL "preload") +set(WITH_DEBUG_CLIPRDR OFF CACHE BOOL "preload") +set(WITH_DEBUG_RDPGFX OFF CACHE BOOL "preload") +set(WITH_DEBUG_DVC OFF CACHE BOOL "preload") +set(WITH_DEBUG_KBD OFF CACHE BOOL "preload") +set(WITH_DEBUG_LICENSE OFF CACHE BOOL "preload") +set(WITH_DEBUG_NEGO OFF CACHE BOOL "preload") +set(WITH_DEBUG_NLA OFF CACHE BOOL "preload") +set(WITH_DEBUG_NTLM OFF CACHE BOOL "preload") +set(WITH_DEBUG_RAIL OFF CACHE BOOL "preload") +set(WITH_DEBUG_RDP OFF CACHE BOOL "preload") +set(WITH_DEBUG_RDPEI OFF CACHE BOOL "preload") +set(WITH_DEBUG_REDIR OFF CACHE BOOL "preload") +set(WITH_DEBUG_RDPDR OFF CACHE BOOL "preload") +set(WITH_DEBUG_RFX OFF CACHE BOOL "preload") +set(WITH_DEBUG_SCARD OFF CACHE BOOL "preload") +set(WITH_DEBUG_SND OFF CACHE BOOL "preload") +set(WITH_DEBUG_SVC OFF CACHE BOOL "preload") +set(WITH_DEBUG_THREADS OFF CACHE BOOL "preload") +set(WITH_DEBUG_TIMEZONE OFF CACHE BOOL "preload") +set(WITH_DEBUG_TRANSPORT OFF CACHE BOOL "preload") +set(WITH_DEBUG_TSG OFF CACHE BOOL "preload") +set(WITH_DEBUG_TSMF OFF CACHE BOOL "preload") +set(WITH_DEBUG_WND OFF CACHE BOOL "preload") +set(WITH_DEBUG_X11 OFF CACHE BOOL "preload") +set(WITH_DEBUG_X11_LOCAL_MOVESIZE OFF CACHE BOOL "preload") +set(WITH_DEBUG_XV OFF CACHE BOOL "preload") +set(WITH_SAMPLE ON CACHE BOOL "preload") +set(WITH_NO_UNDEFINED ON CACHE BOOL "preload") +set(WITH_SANITIZE_ADDRESS ON CACHE BOOL "preload") +set(WITH_FFMPEG ON CACHE BOOL "preload") +set(WITH_SWSCALE ON CACHE BOOL "preload") +set(WITH_DSP_FFMPEG ON CACHE BOOL "preload") +set(WITH_PROXY ON CACHE BOOL "preload") +set(WITH_PROXY_MODULES ON CACHE BOOL "preload") +set(WITH_FREERDP_DEPRECATED_COMMANDLINE ON CACHE BOOL "preload") +set(CHANNEL_RDPECAM ON CACHE BOOL "qa default") +set(CHANNEL_RDPECAM_CLIENT ON CACHE BOOL "qa default") +set(CHANNEL_RDPEAR ON CACHE BOOL "qa default") +set(CHANNEL_RDPEAR_CLIENT ON CACHE BOOL "qa default") +set(CHANNEL_GFXREDIR ON CACHE BOOL "qa default") +set(CHANNEL_RDP2TCP ON CACHE BOOL "qa default") +set(CHANNEL_SSHAGENT ON CACHE BOOL "qa default") diff --git a/third_party/FreeRDP/ci/cmake-preloads/config-linux-alt-arch.txt b/third_party/FreeRDP/ci/cmake-preloads/config-linux-alt-arch.txt new file mode 100644 index 0000000..824cfdf --- /dev/null +++ b/third_party/FreeRDP/ci/cmake-preloads/config-linux-alt-arch.txt @@ -0,0 +1,66 @@ +message("PRELOADING cache") +set(CMAKE_C_STANDARD 23 CACHE STRING "preload") +set(CMAKE_C_FLAGS "-Wno-pre-c23-compat" CACHE STRING "preload") +set(CMAKE_VERBOSE_MAKEFILE ON CACHE BOOL "preload") +set(BUILD_TESTING_INTERNAL ON CACHE BOOL "preload") +set(WITH_MANPAGES OFF CACHE BOOL "preload") +set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "preload") +set(WITH_PULSE ON CACHE BOOL "preload") +set(WITH_CHANNELS ON CACHE BOOL "preload") +set(WITH_CUPS ON CACHE BOOL "preload") +set(WITH_WAYLAND ON CACHE BOOL "preload") +set(WITH_KRB5 ON CACHE BOOL "preload") +set(WITH_PCSC ON CACHE BOOL "preload") +set(WITH_JPEG ON CACHE BOOL "preload") +set(WITH_GSM ON CACHE BOOL "preload") +set(CHANNEL_URBDRC ON CACHE BOOL "preload") +set(CHANNEL_URBDRC_CLIENT ON CACHE BOOL "preload") +set(WITH_SERVER ON CACHE BOOL "preload") +set(WITH_DEBUG_ALL OFF CACHE BOOL "preload") +set(WITH_DEBUG_CAPABILITIES OFF CACHE BOOL "preload") +set(WITH_DEBUG_CERTIFICATE OFF CACHE BOOL "preload") +set(WITH_DEBUG_CHANNELS OFF CACHE BOOL "preload") +set(WITH_DEBUG_CLIPRDR OFF CACHE BOOL "preload") +set(WITH_DEBUG_RDPGFX OFF CACHE BOOL "preload") +set(WITH_DEBUG_DVC OFF CACHE BOOL "preload") +set(WITH_DEBUG_KBD OFF CACHE BOOL "preload") +set(WITH_DEBUG_LICENSE OFF CACHE BOOL "preload") +set(WITH_DEBUG_NEGO OFF CACHE BOOL "preload") +set(WITH_DEBUG_NLA OFF CACHE BOOL "preload") +set(WITH_DEBUG_NTLM OFF CACHE BOOL "preload") +set(WITH_DEBUG_RAIL OFF CACHE BOOL "preload") +set(WITH_DEBUG_RDP OFF CACHE BOOL "preload") +set(WITH_DEBUG_RDPEI OFF CACHE BOOL "preload") +set(WITH_DEBUG_REDIR OFF CACHE BOOL "preload") +set(WITH_DEBUG_RDPDR OFF CACHE BOOL "preload") +set(WITH_DEBUG_RFX OFF CACHE BOOL "preload") +set(WITH_DEBUG_SCARD OFF CACHE BOOL "preload") +set(WITH_DEBUG_SND OFF CACHE BOOL "preload") +set(WITH_DEBUG_SVC OFF CACHE BOOL "preload") +set(WITH_DEBUG_THREADS OFF CACHE BOOL "preload") +set(WITH_DEBUG_TIMEZONE OFF CACHE BOOL "preload") +set(WITH_DEBUG_TRANSPORT OFF CACHE BOOL "preload") +set(WITH_DEBUG_TSG OFF CACHE BOOL "preload") +set(WITH_DEBUG_TSMF OFF CACHE BOOL "preload") +set(WITH_DEBUG_WND OFF CACHE BOOL "preload") +set(WITH_DEBUG_X11 OFF CACHE BOOL "preload") +set(WITH_DEBUG_X11_LOCAL_MOVESIZE OFF CACHE BOOL "preload") +set(WITH_DEBUG_XV OFF CACHE BOOL "preload") +set(WITH_SAMPLE ON CACHE BOOL "preload") +set(WITH_NO_UNDEFINED ON CACHE BOOL "preload") +set(WITH_SANITIZE_ADDRESS OFF CACHE BOOL "preload") +set(USE_UNWIND ON CACHE BOOL "preload") +set(USE_EXECINFO OFF CACHE BOOL "preload") +set(WITH_FFMPEG ON CACHE BOOL "preload") +set(WITH_SWSCALE ON CACHE BOOL "preload") +set(WITH_DSP_FFMPEG ON CACHE BOOL "preload") +set(WITH_PROXY ON CACHE BOOL "preload") +set(WITH_PROXY_MODULES ON CACHE BOOL "preload") +set(WITH_FREERDP_DEPRECATED_COMMANDLINE ON CACHE BOOL "preload") +set(CHANNEL_RDPECAM ON CACHE BOOL "qa default") +set(CHANNEL_RDPECAM_CLIENT ON CACHE BOOL "qa default") +set(CHANNEL_RDPEAR ON CACHE BOOL "qa default") +set(CHANNEL_RDPEAR_CLIENT ON CACHE BOOL "qa default") +set(CHANNEL_GFXREDIR ON CACHE BOOL "qa default") +set(CHANNEL_RDP2TCP ON CACHE BOOL "qa default") +set(CHANNEL_SSHAGENT ON CACHE BOOL "qa default") diff --git a/third_party/FreeRDP/ci/cmake-preloads/config-macosx.txt b/third_party/FreeRDP/ci/cmake-preloads/config-macosx.txt new file mode 100644 index 0000000..ef433c0 --- /dev/null +++ b/third_party/FreeRDP/ci/cmake-preloads/config-macosx.txt @@ -0,0 +1,19 @@ +message("PRELOADING mac cache") +set(CMAKE_C_STANDARD 23 CACHE STRING "preload") +set(CMAKE_C_FLAGS "-Wno-pre-c23-compat" CACHE STRING "preload") +set(CMAKE_VERBOSE_MAKEFILE ON CACHE BOOL "preload") +set(WITH_MANPAGES OFF CACHE BOOL "man pages") +set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "build type") +set(WITH_CUPS ON CACHE BOOL "CUPS printing") +set(CHANNEL_URBDRC OFF CACHE BOOL "USB redirection") +set(WITH_X11 ON CACHE BOOL "Enable X11") +set(WITH_SERVER ON CACHE BOOL "build with server") +set(WITH_SAMPLE ON CACHE BOOL "build with sample") +set(BUILD_TESTING_INTERNAL ON CACHE BOOL "build testing") +set(WITH_SANITIZE_ADDRESS ON CACHE BOOL "build with address sanitizer") +set(WITH_FREERDP_DEPRECATED_COMMANDLINE ON CACHE BOOL "Enable deprecated command line options") +set(WITH_KRB5 OFF CACHE BOOL "Kerberos support") +set(WITH_WEBVIEW OFF CACHE BOOL "ci default") +set(WITH_FFMPEG OFF CACHE BOOL "ci default") +set(WITH_OPUS OFF CACHE BOOL "ci default") +set(WITH_SWSCALE OFF CACHE BOOL "ci default") diff --git a/third_party/FreeRDP/ci/cmake-preloads/config-oss-fuzz.cmake b/third_party/FreeRDP/ci/cmake-preloads/config-oss-fuzz.cmake new file mode 100644 index 0000000..ac9fb5a --- /dev/null +++ b/third_party/FreeRDP/ci/cmake-preloads/config-oss-fuzz.cmake @@ -0,0 +1,38 @@ +message("PRELOADING cache") +set(CMAKE_C_STANDARD 23 CACHE STRING "preload") +set(CMAKE_C_FLAGS "-Wno-pre-c23-compat" CACHE STRING "preload") +set(CMAKE_VERBOSE_MAKEFILE ON CACHE BOOL "preload") +set(WITH_VERBOSE_WINPR_ASSERT ON CACHE BOOL "oss fuzz") + +set(WITH_SERVER ON CACHE BOOL "oss fuzz") +set(WITH_SAMPLE OFF CACHE BOOL "oss fuzz") +set(WITH_PROXY OFF CACHE BOOL "oss fuzz") +set(WITH_SHADOW OFF CACHE BOOL "oss fuzz") +set(WITH_CLIENT OFF CACHE BOOL "oss fuzz") +set(WITH_ALSA OFF CACHE BOOL "oss fuzz") +set(WITH_X11 OFF CACHE BOOL "oss fuzz") +set(WITH_FUSE OFF CACHE BOOL "oss fuzz") +set(WITH_AAD OFF CACHE BOOL "oss fuzz") +set(WITH_FFMPEG OFF CACHE BOOL "oss fuzz") +set(CHANNEL_RDPECAM_CLIENT OFF CACHE BOOL "oss fuzz") +set(WITH_SWSCALE OFF CACHE BOOL "oss fuzz") +set(WITH_LIBSYSTEMD OFF CACHE BOOL "oss fuzz") +set(WITH_UNICODE_BUILTIN ON CACHE BOOL "oss fuzz") +set(WITH_OPUS OFF CACHE BOOL "oss fuzz") +set(WITH_CUPS OFF CACHE BOOL "oss fuzz") +set(CHANNEL_URBDRC OFF CACHE BOOL "oss fuzz") + +set(BUILD_SHARED_LIBS OFF CACHE BOOL "oss fuzz") + +set(BUILD_WITH_CLANG_TIDY OFF CACHE BOOL "oss fuzz") +set(OSS_FUZZ ON CACHE BOOL "oss fuzz") +set(BUILD_FUZZERS ON CACHE BOOL "oss fuzz") +set(BUILD_TESTING_INTERNAL ON CACHE BOOL "oss fuzz") +set(WITH_STREAMPOOL_DEBUG ON CACHE BOOL "oss fuzz") +set(CHANNEL_RDPECAM ON CACHE BOOL "qa default") +set(CHANNEL_RDPECAM_CLIENT ON CACHE BOOL "qa default") +set(CHANNEL_RDPEAR ON CACHE BOOL "qa default") +set(CHANNEL_RDPEAR_CLIENT ON CACHE BOOL "qa default") +set(CHANNEL_GFXREDIR ON CACHE BOOL "qa default") +set(CHANNEL_RDP2TCP ON CACHE BOOL "qa default") +set(CHANNEL_SSHAGENT ON CACHE BOOL "qa default") diff --git a/third_party/FreeRDP/ci/cmake-preloads/config-qa-static.cmake b/third_party/FreeRDP/ci/cmake-preloads/config-qa-static.cmake new file mode 100644 index 0000000..7358578 --- /dev/null +++ b/third_party/FreeRDP/ci/cmake-preloads/config-qa-static.cmake @@ -0,0 +1,29 @@ +message("PRELOADING cache") +set(CMAKE_C_STANDARD 23 CACHE STRING "preload") +set(CMAKE_C_FLAGS "-Wno-pre-c23-compat" CACHE STRING "preload") +set(CMAKE_VERBOSE_MAKEFILE ON CACHE BOOL "preload") +set(WITH_SERVER ON CACHE BOOL "qa default") +set(WITH_SAMPLE ON CACHE BOOL "qa default") +set(WITH_SIMD ON CACHE BOOL "qa default") +set(WITH_OPAQUE_SETTINGS ON CACHE BOOL "qa default") +set(WITH_STREAMPOOL_DEBUG ON CACHE BOOL "preload") +set(WITH_VERBOSE_WINPR_ASSERT OFF CACHE BOOL "qa default") +set(ENABLE_WARNING_VERBOSE ON CACHE BOOL "preload") +set(BUILD_SHARED_LIBS OFF CACHE BOOL "qa default") +set(CHANNEL_RDPECAM ON CACHE BOOL "qa default") +set(CHANNEL_RDPECAM_CLIENT ON CACHE BOOL "qa default") +set(CHANNEL_RDPEAR ON CACHE BOOL "qa default") +set(CHANNEL_RDPEAR_CLIENT ON CACHE BOOL "qa default") +set(CHANNEL_GFXREDIR ON CACHE BOOL "qa default") +set(CHANNEL_RDP2TCP ON CACHE BOOL "qa default") +set(CHANNEL_SSHAGENT ON CACHE BOOL "qa default") + +set(BUILD_WITH_CLANG_TIDY OFF CACHE BOOL "qa default") + +include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/ClangDetectTool.cmake) +clang_detect_tool(CLANG_EXE clang REQUIRED) +clang_detect_tool(CLANG_XX_EXE clang++ REQUIRED) + +set(CMAKE_C_COMPILER "${CLANG_EXE}" CACHE STRING "qa default") +set(CMAKE_CXX_COMPILER "${CLANG_XX_EXE}" CACHE STRING "qa default") +set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++" CACHE STRING "qa-default") diff --git a/third_party/FreeRDP/ci/cmake-preloads/config-qa.cmake b/third_party/FreeRDP/ci/cmake-preloads/config-qa.cmake new file mode 100644 index 0000000..990cdb6 --- /dev/null +++ b/third_party/FreeRDP/ci/cmake-preloads/config-qa.cmake @@ -0,0 +1,44 @@ +set(BUILD_TESTING_INTERNAL ON CACHE BOOL "qa default") +set(CMAKE_C_STANDARD 23 CACHE STRING "preload") +set(CMAKE_C_FLAGS "-Wno-pre-c23-compat" CACHE STRING "preload") +set(WITH_STREAMPOOL_DEBUG ON CACHE BOOL "preload") +set(CMAKE_VERBOSE_MAKEFILE ON CACHE BOOL "preload") +set(ENABLE_WARNING_VERBOSE ON CACHE BOOL "preload") +set(WITH_MANPAGES ON CACHE BOOL "qa default") +set(WITH_SAMPLE ON CACHE BOOL "qa default") +set(WITH_SERVER ON CACHE BOOL "qa default") +set(WITH_SHADOW ON CACHE BOOL "qa default") +set(WITH_PROXY ON CACHE BOOL "qa default") +set(WITH_PULSE ON CACHE BOOL "qa default") +set(WITH_CUPS ON CACHE BOOL "qa default") +set(WITH_OPENCL ON CACHE BOOL "qa default") +set(WITH_OPAQUE_SETTINGS ON CACHE BOOL "qa default") +set(WITH_PCSC ON CACHE BOOL "qa default") +set(WITH_SOXR ON CACHE BOOL "qa default") +set(WITH_SIMD ON CACHE BOOL "qa default") +set(WITH_SWSCALE ON CACHE BOOL "qa default") +set(WITH_DSP_FFMPEG ON CACHE BOOL "qa default") +set(WITH_FFMPEG ON CACHE BOOL "qa default") +set(WITH_SANITIZE_ADDRESS ON CACHE BOOL "qa default") +set(WINPR_UTILS_IMAGE_JPEG ON CACHE BOOL "qa default") +set(WINPR_UTILS_IMAGE_WEBP ON CACHE BOOL "qa default") +set(WINPR_UTILS_IMAGE_PNG ON CACHE BOOL "qa default") +set(WITH_INTERNAL_RC4 ON CACHE BOOL "qa default") +set(WITH_INTERNAL_MD4 ON CACHE BOOL "qa default") +set(WITH_INTERNAL_MD5 ON CACHE BOOL "qa default") +set(CHANNEL_RDPECAM ON CACHE BOOL "qa default") +set(CHANNEL_RDPECAM_CLIENT ON CACHE BOOL "qa default") +set(CHANNEL_RDPEAR ON CACHE BOOL "qa default") +set(CHANNEL_RDPEAR_CLIENT ON CACHE BOOL "qa default") +set(CHANNEL_GFXREDIR ON CACHE BOOL "qa default") +set(CHANNEL_RDP2TCP ON CACHE BOOL "qa default") +set(CHANNEL_SSHAGENT ON CACHE BOOL "qa default") + +set(BUILD_WITH_CLANG_TIDY ON CACHE BOOL "qa default") + +include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/ClangDetectTool.cmake) +clang_detect_tool(CLANG_EXE clang REQUIRED) +clang_detect_tool(CLANG_XX_EXE clang++ REQUIRED) + +set(CMAKE_C_COMPILER "${CLANG_EXE}" CACHE STRING "qa default") +set(CMAKE_CXX_COMPILER "${CLANG_XX_EXE}" CACHE STRING "qa default") diff --git a/third_party/FreeRDP/ci/cmake-preloads/config-windows.txt b/third_party/FreeRDP/ci/cmake-preloads/config-windows.txt new file mode 100644 index 0000000..68387d2 --- /dev/null +++ b/third_party/FreeRDP/ci/cmake-preloads/config-windows.txt @@ -0,0 +1,22 @@ +message("PRELOADING windows cache") +set(CMAKE_C_STANDARD 23 CACHE STRING "preload") +set(CMAKE_VERBOSE_MAKEFILE ON CACHE BOOL "preload") +set(CMAKE_WINDOWS_VERSION "WIN7" CACHE STRING "windows build version") +set(BUILD_SHARED_LIBS OFF CACHE BOOL "build static linked executable") +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded" CACHE STRING "MSVC runtime to use") +set(OPENSSL_USE_STATIC_LIBS ON CACHE BOOL "link OpenSSL static") +set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "build type") +set(WITH_SERVER ON CACHE BOOL "build with server") +set(WITH_SAMPLE ON CACHE BOOL "build with sample") +set(WITH_SHADOW OFF CACHE BOOL "Do not build shadow server") +set(WITH_PLATFORM_SERVER OFF CACHE BOOL "Do not build platform server") +set(WITH_CLIENT_SDL ON CACHE BOOL "build with SDL client") +set(WITH_PROXY_MODULES "ON" CACHE BOOL "build proxy modules") +set(CHANNEL_URBDRC OFF CACHE BOOL "USB redirection") +set(BUILD_TESTING_INTERNAL ON CACHE BOOL "build testing") +set(WITH_FFMPEG OFF CACHE BOOL "ci default") +set(WITH_SWSCALE OFF CACHE BOOL "ci default") +set(WITH_WEBVIEW ON CACHE BOOL "ci default") +set(ZLIB_USE_STATIC_LIBS ON CACHE BOOL "ci default") +set(WITH_FREERDP_DEPRECATED_COMMANDLINE ON CACHE BOOL "Enable deprecated command line options") +set(WITH_SDL_LINK_SHARED OFF CACHE BOOL "ci default") diff --git a/third_party/FreeRDP/client/Android/BuildFlags.java.in b/third_party/FreeRDP/client/Android/BuildFlags.java.in new file mode 100644 index 0000000..9b15e47 --- /dev/null +++ b/third_party/FreeRDP/client/Android/BuildFlags.java.in @@ -0,0 +1,6 @@ +package com.freerdp.freerdpcore.utils; + +public class BuildFlags +{ + private final static boolean USE_OPENSSL_DEFAULT_NAMES = @USE_OPENSSL_DEFAULT_NAMES@; +} diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/build.gradle b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/build.gradle new file mode 100644 index 0000000..a02e8ee --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/build.gradle @@ -0,0 +1,51 @@ +plugins { + id 'com.gladed.androidgitversion' version '0.4.14' +} + +androidGitVersion { + abis = ['armeabi':1, 'armeabi-v7a':2, 'arm64-v8a':3, 'mips':5, 'mips64':6, 'x86':8, 'x86_64':9 ] + prefix '' +} + +println 'Version Name: ' + androidGitVersion.name() +println 'Version Code: ' + androidGitVersion.code() + +apply plugin: 'com.android.application' +android { + compileSdkVersion = rootProject.ext.compileApi + buildToolsVersion = rootProject.ext.toolsVersion + + defaultConfig { + applicationId "com.freerdp.afreerdp" + minSdkVersion rootProject.ext.minApi + targetSdkVersion rootProject.ext.targetApi + vectorDrawables.useSupportLibrary = true + versionName = androidGitVersion.name() + versionCode = androidGitVersion.code() + } + + signingConfigs { + release { + storeFile file(RELEASE_STORE_FILE) + storePassword RELEASE_STORE_PASSWORD + keyAlias RELEASE_KEY_ALIAS + keyPassword RELEASE_KEY_PASSWORD + storeType "jks" + } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + debug { + jniDebuggable true + } + } +} + +dependencies { + implementation project(':freeRDPCore') +} diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/lint.xml b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/lint.xml new file mode 100644 index 0000000..c70207f --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/lint.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/AndroidManifest.xml b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/AndroidManifest.xml new file mode 100644 index 0000000..6180a7b --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/AndroidManifest.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/FreeRDP_Logo.png b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/FreeRDP_Logo.png new file mode 100644 index 0000000..1e27262 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/FreeRDP_Logo.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/about.css b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/about.css new file mode 100644 index 0000000..604e505 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/about.css @@ -0,0 +1,147 @@ +p { + border: none; + padding: 0in; + font-variant: normal; + font-family: "Helvetica"; + font-style: normal; + font-weight: normal; + line-height: 100%; + text-align: center; +} + +td p { + border: none; + padding: 0in; + font-variant: normal; + font-family: "Helvetica"; + font-style: normal; + font-weight: normal; + line-height: 100%; + text-align: center; +} + +h2 { + border: none; + padding: 0in; + direction: inherit; + font-variant: normal; + color: #ffffff; + line-height: 100%; + text-align: center; +} + +h2.western { + font-style: normal; + } + +h2.cjk { + font-family: "AR PL SungtiL GB"; + font-style: normal; +} + +h2.ctl { + font-family: "Lohit Devanagari"; + font-style: normal; +} + +h3 { + border: none; + padding: 0in; + direction: inherit; + font-variant: normal; + color: #ffffff; + line-height: 100%; + text-align: center; + page-break-before: auto; + page-break-after: auto; +} + +h3.western { + font-style: normal; +} + +h3.cjk { + font-family: "AR PL SungtiL GB"; + font-style: normal; +} + +h3.ctl { + font-family: "Lohit Devanagari"; + font-style: normal; +} + +h4 { + border: none; + padding: 0in; + direction: inherit; + font-variant: normal; + color: #ffffff; + line-height: 100%; + text-align: center; + page-break-before: auto; + page-break-after: auto; +} + +h4.western { + font-style: normal; +} + +h4.cjk { + font-family: "AR PL SungtiL GB"; + font-style: normal; +} + +h4.ctl { + font-family: "Lohit Devanagari"; + font-style: normal; +} + +pre { + direction: inherit; + font-variant: normal; + line-height: 100%; + text-align: center; + page-break-before: auto; + white-space: pre-wrap; /* css-3 */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ +} + +pre.western { + font-size: 8pt; + font-style: normal; + font-weight: normal; + white-space: pre-wrap; /* css-3 */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ +} + +pre.cjk { + font-family: "AR PL SungtiL GB", monospace; + font-size: 8pt; + font-style: normal; + font-weight: normal; + white-space: pre-wrap; /* css-3 */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ +} + +pre.ctl { + font-style: normal; + font-weight: normal; + white-space: pre-wrap; /* css-3 */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ +} + +a:link { + color: #0000ff +} \ No newline at end of file diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/about_page/about.html b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/about_page/about.html new file mode 100644 index 0000000..29cf3bc --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/about_page/about.html @@ -0,0 +1,397 @@ + + + + + + + + + + + +
+


+
+ +

+
+

aFreeRDP
+ Remote + Desktop Client

+
+

+ +

+
+

aFreeRDP is an open source client + capable of natively using
+ Remote Desktop Protocol (RDP) in + order to remotely access your Windows desktop.
+

+
+
+

+
+
+ +

+
+

+ Version Information

+
+ + + + + + + + + + + + + +
+

aFreeRDP Version

+
+

%AFREERDP_VERSION%

+
+

System Version

+
+

%SYSTEM_VERSION%

+
+

Model

+
+

%DEVICE_MODEL%

+
+
+

+ Credits

+
+

aFreeRDP + is a part of FreeRDP +

+
+

+ + Data protection

+
+

Details + about data collection and usage by aFreeRDP are available at

+

http://www.freerdp.com/privacy +

+
+

+ + Licenses

+
+
+

+ + aFreeRDP

+
+
This program is free software;
+
+you can redistribute it and/or modify it under the terms
+
+of the Mozilla Public License, v. 2.0.
+
+You can obtain an online version of the License from
+
+http://mozilla.org/MPL/2.0/. 
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+
+without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
+A copy of the product's source code can be obtained from the FreeRDP GitHub repository at
+
+https://github.com/FreeRDP/FreeRDP.
+

+
+
+ +

+
+

+ + FreeRDP

+
+
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. 
+A copy of the product's source code can be obtained from the FreeRDP GitHub repository at
+
+https://github.com/FreeRDP/FreeRDP.
+
+

+ + OpenSSL

+
+
LICENSE ISSUES
+
+==============
+
+
+The OpenSSL toolkit stays under a dual license, i.e. both the conditions of
+
+the OpenSSL License and the original SSLeay license apply to the toolkit.
+
+See below for the actual license texts.
+
+
+OpenSSL License
+
+---------------
+
+
+/* ====================================================================
+
+* Copyright (c) 1998-2016 The OpenSSL Project. All rights reserved.
+
+*
+
+* Redistribution and use in source and binary forms, with or without
+
+* modification, are permitted provided that the following conditions
+
+* are met:
+
+*
+
+* 1. Redistributions of source code must retain the above copyright
+
+* notice, this list of conditions and the following disclaimer.
+
+*
+
+* 2. Redistributions in binary form must reproduce the above copyright
+
+* notice, this list of conditions and the following disclaimer in
+
+* the documentation and/or other materials provided with the
+
+* distribution.
+
+*
+
+* 3. All advertising materials mentioning features or use of this
+
+* software must display the following acknowledgment:
+
+* "This product includes software developed by the OpenSSL Project
+
+* for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
+
+*
+
+* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
+
+* endorse or promote products derived from this software without
+
+* prior written permission. For written permission, please contact
+
+* openssl-core@openssl.org.
+
+*
+
+* 5. Products derived from this software may not be called "OpenSSL"
+
+* nor may "OpenSSL" appear in their names without prior written
+
+* permission of the OpenSSL Project.
+
+*
+
+* 6. Redistributions of any form whatsoever must retain the following
+
+* acknowledgment:
+
+* "This product includes software developed by the OpenSSL Project
+
+* for use in the OpenSSL Toolkit (http://www.openssl.org/)"
+
+*
+
+* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
+
+* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+
+* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
+
+* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+
+* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+
+* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+
+* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
+* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+
+* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+
+* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+
+* OF THE POSSIBILITY OF SUCH DAMAGE.
+
+* ====================================================================
+
+*
+
+* This product includes cryptographic software written by Eric Young
+
+* (eay@cryptsoft.com). This product includes software written by Tim
+
+* Hudson (tjh@cryptsoft.com).
+
+*
+
+*/
+
+
+Original SSLeay License
+
+-----------------------
+
+
+/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
+
+* All rights reserved.
+
+*
+
+* This package is an SSL implementation written
+
+* by Eric Young (eay@cryptsoft.com).
+
+* The implementation was written so as to conform with Netscapes SSL.
+
+*
+
+* This library is free for commercial and non-commercial use as long as
+
+* the following conditions are adhered to. The following conditions
+
+* apply to all code found in this distribution, be it the RC4, RSA,
+
+* lhash, DES, etc., code; not just the SSL code. The SSL documentation
+
+* included with this distribution is covered by the same copyright terms
+
+* except that the holder is Tim Hudson (tjh@cryptsoft.com).
+
+*
+
+* Copyright remains Eric Young's, and as such any Copyright notices in
+
+* the code are not to be removed.
+
+* If this package is used in a product, Eric Young should be given attribution
+
+* as the author of the parts of the library used.
+
+* This can be in the form of a textual message at program startup or
+
+* in documentation (online or textual) provided with the package.
+
+*
+
+* Redistribution and use in source and binary forms, with or without
+
+* modification, are permitted provided that the following conditions
+
+* are met:
+
+* 1. Redistributions of source code must retain the copyright
+
+* notice, this list of conditions and the following disclaimer.
+
+* 2. Redistributions in binary form must reproduce the above copyright
+
+* notice, this list of conditions and the following disclaimer in the
+
+* documentation and/or other materials provided with the distribution.
+
+* 3. All advertising materials mentioning features or use of this software
+
+* must display the following acknowledgement:
+
+* "This product includes cryptographic software written by
+
+* Eric Young (eay@cryptsoft.com)"
+
+* The word 'cryptographic' can be left out if the routines from the library
+
+* being used are not cryptographic related :-).
+
+* 4. If you include any Windows specific code (or a derivative thereof) from
+
+* the apps directory (application code) you must include an acknowledgement:
+
+* "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
+
+*
+
+* THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
+
+* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+
+* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+
+* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+
+* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+
+* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
+* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+
+* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+
+* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+
+* SUCH DAMAGE.
+
+*
+
+* The licence and distribution terms for any publicly available version or
+
+* derivative of this code cannot be changed. i.e. this code cannot simply be
+
+* copied and put under another distribution licence
+
+* [including the GNU Public Licence.]
+
+*/
+A copy of the product's source code can be obtained from the project page at
+
+https://www.openssl.org/.
+
+
+ + diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/about_page/about_phone.html b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/about_page/about_phone.html new file mode 100644 index 0000000..29cf3bc --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/about_page/about_phone.html @@ -0,0 +1,397 @@ + + + + + + + + + + + +
+


+
+ +

+
+

aFreeRDP
+ Remote + Desktop Client

+
+

+ +

+
+

aFreeRDP is an open source client + capable of natively using
+ Remote Desktop Protocol (RDP) in + order to remotely access your Windows desktop.
+

+
+
+

+
+
+ +

+
+

+ Version Information

+
+ + + + + + + + + + + + + +
+

aFreeRDP Version

+
+

%AFREERDP_VERSION%

+
+

System Version

+
+

%SYSTEM_VERSION%

+
+

Model

+
+

%DEVICE_MODEL%

+
+
+

+ Credits

+
+

aFreeRDP + is a part of FreeRDP +

+
+

+ + Data protection

+
+

Details + about data collection and usage by aFreeRDP are available at

+

http://www.freerdp.com/privacy +

+
+

+ + Licenses

+
+
+

+ + aFreeRDP

+
+
This program is free software;
+
+you can redistribute it and/or modify it under the terms
+
+of the Mozilla Public License, v. 2.0.
+
+You can obtain an online version of the License from
+
+http://mozilla.org/MPL/2.0/. 
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+
+without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
+A copy of the product's source code can be obtained from the FreeRDP GitHub repository at
+
+https://github.com/FreeRDP/FreeRDP.
+

+
+
+ +

+
+

+ + FreeRDP

+
+
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. 
+A copy of the product's source code can be obtained from the FreeRDP GitHub repository at
+
+https://github.com/FreeRDP/FreeRDP.
+
+

+ + OpenSSL

+
+
LICENSE ISSUES
+
+==============
+
+
+The OpenSSL toolkit stays under a dual license, i.e. both the conditions of
+
+the OpenSSL License and the original SSLeay license apply to the toolkit.
+
+See below for the actual license texts.
+
+
+OpenSSL License
+
+---------------
+
+
+/* ====================================================================
+
+* Copyright (c) 1998-2016 The OpenSSL Project. All rights reserved.
+
+*
+
+* Redistribution and use in source and binary forms, with or without
+
+* modification, are permitted provided that the following conditions
+
+* are met:
+
+*
+
+* 1. Redistributions of source code must retain the above copyright
+
+* notice, this list of conditions and the following disclaimer.
+
+*
+
+* 2. Redistributions in binary form must reproduce the above copyright
+
+* notice, this list of conditions and the following disclaimer in
+
+* the documentation and/or other materials provided with the
+
+* distribution.
+
+*
+
+* 3. All advertising materials mentioning features or use of this
+
+* software must display the following acknowledgment:
+
+* "This product includes software developed by the OpenSSL Project
+
+* for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
+
+*
+
+* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
+
+* endorse or promote products derived from this software without
+
+* prior written permission. For written permission, please contact
+
+* openssl-core@openssl.org.
+
+*
+
+* 5. Products derived from this software may not be called "OpenSSL"
+
+* nor may "OpenSSL" appear in their names without prior written
+
+* permission of the OpenSSL Project.
+
+*
+
+* 6. Redistributions of any form whatsoever must retain the following
+
+* acknowledgment:
+
+* "This product includes software developed by the OpenSSL Project
+
+* for use in the OpenSSL Toolkit (http://www.openssl.org/)"
+
+*
+
+* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
+
+* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+
+* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
+
+* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+
+* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+
+* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+
+* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
+* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+
+* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+
+* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+
+* OF THE POSSIBILITY OF SUCH DAMAGE.
+
+* ====================================================================
+
+*
+
+* This product includes cryptographic software written by Eric Young
+
+* (eay@cryptsoft.com). This product includes software written by Tim
+
+* Hudson (tjh@cryptsoft.com).
+
+*
+
+*/
+
+
+Original SSLeay License
+
+-----------------------
+
+
+/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
+
+* All rights reserved.
+
+*
+
+* This package is an SSL implementation written
+
+* by Eric Young (eay@cryptsoft.com).
+
+* The implementation was written so as to conform with Netscapes SSL.
+
+*
+
+* This library is free for commercial and non-commercial use as long as
+
+* the following conditions are adhered to. The following conditions
+
+* apply to all code found in this distribution, be it the RC4, RSA,
+
+* lhash, DES, etc., code; not just the SSL code. The SSL documentation
+
+* included with this distribution is covered by the same copyright terms
+
+* except that the holder is Tim Hudson (tjh@cryptsoft.com).
+
+*
+
+* Copyright remains Eric Young's, and as such any Copyright notices in
+
+* the code are not to be removed.
+
+* If this package is used in a product, Eric Young should be given attribution
+
+* as the author of the parts of the library used.
+
+* This can be in the form of a textual message at program startup or
+
+* in documentation (online or textual) provided with the package.
+
+*
+
+* Redistribution and use in source and binary forms, with or without
+
+* modification, are permitted provided that the following conditions
+
+* are met:
+
+* 1. Redistributions of source code must retain the copyright
+
+* notice, this list of conditions and the following disclaimer.
+
+* 2. Redistributions in binary form must reproduce the above copyright
+
+* notice, this list of conditions and the following disclaimer in the
+
+* documentation and/or other materials provided with the distribution.
+
+* 3. All advertising materials mentioning features or use of this software
+
+* must display the following acknowledgement:
+
+* "This product includes cryptographic software written by
+
+* Eric Young (eay@cryptsoft.com)"
+
+* The word 'cryptographic' can be left out if the routines from the library
+
+* being used are not cryptographic related :-).
+
+* 4. If you include any Windows specific code (or a derivative thereof) from
+
+* the apps directory (application code) you must include an acknowledgement:
+
+* "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
+
+*
+
+* THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
+
+* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+
+* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+
+* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+
+* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+
+* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
+* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+
+* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+
+* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+
+* SUCH DAMAGE.
+
+*
+
+* The licence and distribution terms for any publicly available version or
+
+* derivative of this code cannot be changed. i.e. this code cannot simply be
+
+* copied and put under another distribution licence
+
+* [including the GNU Public Licence.]
+
+*/
+A copy of the product's source code can be obtained from the project page at
+
+https://www.openssl.org/.
+
+
+ + diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/background.jpg b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/background.jpg new file mode 100644 index 0000000..fd4e1d3 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/background.jpg differ diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_about_page/about.html b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_about_page/about.html new file mode 100644 index 0000000..90760c1 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_about_page/about.html @@ -0,0 +1,410 @@ + + + + + + + + + + + +
+


+
+ +

+
+

aFreeRDP
+ Remote + Desktop Client
+

+
+
+

+ + +

+
+
+

+ aFreeRDP ist ein Open Source Programm + mit nativer Unterstützung des Remote Desktop Protocol (RDP)
+ um + einen entfernten Zugriff auf Windows Desktops zu ermöglichen.
+

+
+

+ Versions Information

+
+ + + + + + + + + + + + + +
+

aFreeRDP Version

+
+

%AFREERDP_VERSION%

+
+

System Version

+
+

%SYSTEM_VERSION%

+
+

Model

+
+

%DEVICE_MODEL%

+
+
+
+

+ +
+
+ +

+
+

+ + Credits

+
+

aFreeRDP + ist ein Teil von FreeRDP +

+
+
+

+ +
+
+ +

+
+

+ + Datenschutz

+
+

Details + zu den Daten die aFreeRDP sammelt und verarbeitet sind unter

+

http://www.freerdp.com/privacy + zu finden.

+
+

+ + Lizenzen

+
+
+

+ + aFreeRDP

+
+
This program is free software;
+
+you can redistribute it and/or modify it under the terms
+
+of the Mozilla Public License, v. 2.0.
+
+You can obtain an online version of the License from
+
+http://mozilla.org/MPL/2.0/. 
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+
+without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
+A copy of the product's source code can be obtained from the FreeRDP GitHub repository at
+
+https://github.com/FreeRDP/FreeRDP.
+
+

+ + FreeRDP

+
+
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. 
+A copy of the product's source code can be obtained from the FreeRDP GitHub repository at
+
+https://github.com/FreeRDP/FreeRDP.
+
+

+ + OpenSSL

+
+
LICENSE ISSUES
+
+==============
+
+
+The OpenSSL toolkit stays under a dual license, i.e. both the conditions of
+
+the OpenSSL License and the original SSLeay license apply to the toolkit.
+
+See below for the actual license texts.
+
+
+OpenSSL License
+
+---------------
+
+
+/* ====================================================================
+
+* Copyright (c) 1998-2016 The OpenSSL Project. All rights reserved.
+
+*
+
+* Redistribution and use in source and binary forms, with or without
+
+* modification, are permitted provided that the following conditions
+
+* are met:
+
+*
+
+* 1. Redistributions of source code must retain the above copyright
+
+* notice, this list of conditions and the following disclaimer.
+
+*
+
+* 2. Redistributions in binary form must reproduce the above copyright
+
+* notice, this list of conditions and the following disclaimer in
+
+* the documentation and/or other materials provided with the
+
+* distribution.
+
+*
+
+* 3. All advertising materials mentioning features or use of this
+
+* software must display the following acknowledgment:
+
+* "This product includes software developed by the OpenSSL Project
+
+* for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
+
+*
+
+* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
+
+* endorse or promote products derived from this software without
+
+* prior written permission. For written permission, please contact
+
+* openssl-core@openssl.org.
+
+*
+
+* 5. Products derived from this software may not be called "OpenSSL"
+
+* nor may "OpenSSL" appear in their names without prior written
+
+* permission of the OpenSSL Project.
+
+*
+
+* 6. Redistributions of any form whatsoever must retain the following
+
+* acknowledgment:
+
+* "This product includes software developed by the OpenSSL Project
+
+* for use in the OpenSSL Toolkit (http://www.openssl.org/)"
+
+*
+
+* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
+
+* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+
+* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
+
+* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+
+* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+
+* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+
+* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
+* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+
+* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+
+* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+
+* OF THE POSSIBILITY OF SUCH DAMAGE.
+
+* ====================================================================
+
+*
+
+* This product includes cryptographic software written by Eric Young
+
+* (eay@cryptsoft.com). This product includes software written by Tim
+
+* Hudson (tjh@cryptsoft.com).
+
+*
+
+*/
+
+
+Original SSLeay License
+
+-----------------------
+
+
+/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
+
+* All rights reserved.
+
+*
+
+* This package is an SSL implementation written
+
+* by Eric Young (eay@cryptsoft.com).
+
+* The implementation was written so as to conform with Netscapes SSL.
+
+*
+
+* This library is free for commercial and non-commercial use as long as
+
+* the following conditions are aheared to. The following conditions
+
+* apply to all code found in this distribution, be it the RC4, RSA,
+
+* lhash, DES, etc., code; not just the SSL code. The SSL documentation
+
+* included with this distribution is covered by the same copyright terms
+
+* except that the holder is Tim Hudson (tjh@cryptsoft.com).
+
+*
+
+* Copyright remains Eric Young's, and as such any Copyright notices in
+
+* the code are not to be removed.
+
+* If this package is used in a product, Eric Young should be given attribution
+
+* as the author of the parts of the library used.
+
+* This can be in the form of a textual message at program startup or
+
+* in documentation (online or textual) provided with the package.
+
+*
+
+* Redistribution and use in source and binary forms, with or without
+
+* modification, are permitted provided that the following conditions
+
+* are met:
+
+* 1. Redistributions of source code must retain the copyright
+
+* notice, this list of conditions and the following disclaimer.
+
+* 2. Redistributions in binary form must reproduce the above copyright
+
+* notice, this list of conditions and the following disclaimer in the
+
+* documentation and/or other materials provided with the distribution.
+
+* 3. All advertising materials mentioning features or use of this software
+
+* must display the following acknowledgement:
+
+* "This product includes cryptographic software written by
+
+* Eric Young (eay@cryptsoft.com)"
+
+* The word 'cryptographic' can be left out if the rouines from the library
+
+* being used are not cryptographic related :-).
+
+* 4. If you include any Windows specific code (or a derivative thereof) from
+
+* the apps directory (application code) you must include an acknowledgement:
+
+* "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
+
+*
+
+* THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
+
+* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+
+* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+
+* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+
+* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+
+* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
+* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+
+* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+
+* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+
+* SUCH DAMAGE.
+
+*
+
+* The licence and distribution terms for any publically available version or
+
+* derivative of this code cannot be changed. i.e. this code cannot simply be
+
+* copied and put under another distribution licence
+
+* [including the GNU Public Licence.]
+
+*/
+A copy of the product's source code can be obtained from the project page at
+
+https://www.openssl.org/.
+
+
+ + diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_about_page/about_phone.html b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_about_page/about_phone.html new file mode 100644 index 0000000..fb46dae --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_about_page/about_phone.html @@ -0,0 +1,412 @@ + + + + + + + + + + + + +
+


+
+ +

+
+

aFreeRDP
+ Remote + Desktop Client
+

+
+
+

+ + +

+
+
+

+ aFreeRDP ist ein Open Source Programm + mit nativer Unterstützung des Remote Desktop Protocol (RDP)
+ um + einen entfernten Zugriff auf Windows Desktops zu ermöglichen.
+

+
+

+ Versions Information

+
+ + + + + + + + + + + + + +
+

aFreeRDP Version

+
+

%AFREERDP_VERSION%

+
+

System Version

+
+

%SYSTEM_VERSION%

+
+

Model

+
+

%DEVICE_MODEL%

+
+
+
+

+ +
+
+ +

+
+

+ + Credits

+
+

aFreeRDP + ist ein Teil von FreeRDP +

+
+
+

+ +
+
+ +

+
+

+ + Datenschutz

+
+

Details + zu den Daten die aFreeRDP sammelt und verarbeitet sind unter

+

http://www.freerdp.com/privacy + zu finden.

+
+

+ + Lizenzen

+
+
+

aFreeRDP

+
+
This program is free software;
+
+you can redistribute it and/or modify it under the terms
+
+of the Mozilla Public License, v. 2.0.
+
+You can obtain an online version of the License from
+
+http://mozilla.org/MPL/2.0/. 
+This program is distributed in the hope
+that it will be useful, but WITHOUT ANY WARRANTY;
+
+without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
+A copy of the product's source code can be
+obtained from the FreeRDP GitHub repository at
+
+https://github.com/FreeRDP/FreeRDP.
+
+

+ + FreeRDP

+
+
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. 
+A copy of the product's source code can be obtained
+ from the FreeRDP GitHub repository at
+
+https://github.com/FreeRDP/FreeRDP.
+
+

+ + OpenSSL

+
+
LICENSE ISSUES
+
+==============
+
+
+The OpenSSL toolkit stays under a dual license, i.e. both the conditions of
+
+the OpenSSL License and the original SSLeay license apply to the toolkit.
+
+See below for the actual license texts.
+
+
+OpenSSL License
+
+---------------
+
+
+/* ====================================================================
+
+* Copyright (c) 1998-2016 The OpenSSL Project. All rights reserved.
+
+*
+
+* Redistribution and use in source and binary forms, with or without
+
+* modification, are permitted provided that the following conditions
+
+* are met:
+
+*
+
+* 1. Redistributions of source code must retain the above copyright
+
+* notice, this list of conditions and the following disclaimer.
+
+*
+
+* 2. Redistributions in binary form must reproduce the above copyright
+
+* notice, this list of conditions and the following disclaimer in
+
+* the documentation and/or other materials provided with the
+
+* distribution.
+
+*
+
+* 3. All advertising materials mentioning features or use of this
+
+* software must display the following acknowledgment:
+
+* "This product includes software developed by the OpenSSL Project
+
+* for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
+
+*
+
+* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
+
+* endorse or promote products derived from this software without
+
+* prior written permission. For written permission, please contact
+
+* openssl-core@openssl.org.
+
+*
+
+* 5. Products derived from this software may not be called "OpenSSL"
+
+* nor may "OpenSSL" appear in their names without prior written
+
+* permission of the OpenSSL Project.
+
+*
+
+* 6. Redistributions of any form whatsoever must retain the following
+
+* acknowledgment:
+
+* "This product includes software developed by the OpenSSL Project
+
+* for use in the OpenSSL Toolkit (http://www.openssl.org/)"
+
+*
+
+* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
+
+* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+
+* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
+
+* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+
+* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+
+* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+
+* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
+* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+
+* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+
+* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+
+* OF THE POSSIBILITY OF SUCH DAMAGE.
+
+* ====================================================================
+
+*
+
+* This product includes cryptographic software written by Eric Young
+
+* (eay@cryptsoft.com). This product includes software written by Tim
+
+* Hudson (tjh@cryptsoft.com).
+
+*
+
+*/
+
+
+Original SSLeay License
+
+-----------------------
+
+
+/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
+
+* All rights reserved.
+
+*
+
+* This package is an SSL implementation written
+
+* by Eric Young (eay@cryptsoft.com).
+
+* The implementation was written so as to conform with Netscapes SSL.
+
+*
+
+* This library is free for commercial and non-commercial use as long as
+
+* the following conditions are aheared to. The following conditions
+
+* apply to all code found in this distribution, be it the RC4, RSA,
+
+* lhash, DES, etc., code; not just the SSL code. The SSL documentation
+
+* included with this distribution is covered by the same copyright terms
+
+* except that the holder is Tim Hudson (tjh@cryptsoft.com).
+
+*
+
+* Copyright remains Eric Young's, and as such any Copyright notices in
+
+* the code are not to be removed.
+
+* If this package is used in a product, Eric Young should be given attribution
+
+* as the author of the parts of the library used.
+
+* This can be in the form of a textual message at program startup or
+
+* in documentation (online or textual) provided with the package.
+
+*
+
+* Redistribution and use in source and binary forms, with or without
+
+* modification, are permitted provided that the following conditions
+
+* are met:
+
+* 1. Redistributions of source code must retain the copyright
+
+* notice, this list of conditions and the following disclaimer.
+
+* 2. Redistributions in binary form must reproduce the above copyright
+
+* notice, this list of conditions and the following disclaimer in the
+
+* documentation and/or other materials provided with the distribution.
+
+* 3. All advertising materials mentioning features or use of this software
+
+* must display the following acknowledgement:
+
+* "This product includes cryptographic software written by
+
+* Eric Young (eay@cryptsoft.com)"
+
+* The word 'cryptographic' can be left out if the rouines from the library
+
+* being used are not cryptographic related :-).
+
+* 4. If you include any Windows specific code (or a derivative thereof) from
+
+* the apps directory (application code) you must include an acknowledgement:
+
+* "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
+
+*
+
+* THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
+
+* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+
+* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+
+* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+
+* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+
+* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+
+* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+
+* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+
+* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+
+* SUCH DAMAGE.
+
+*
+
+* The licence and distribution terms for any publicly available version or
+
+* derivative of this code cannot be changed. i.e. this code cannot simply be
+
+* copied and put under another distribution licence
+
+* [including the GNU Public Licence.]
+
+*/
+A copy of the product's source code can be obtained from the project page at
+
+https://www.openssl.org/.
+
+
+ + diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/gestures.html b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/gestures.html new file mode 100644 index 0000000..879f2ee --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/gestures.html @@ -0,0 +1,33 @@ + + + + + + + Help + + + + + +
+
+ + +
+

Gesten

+

+ aFreeRDP ist für Touch Geräte entwickelt worden. + Diese Gesten lassen sie die häufigsten Operationen mit ihren Fingern + durchführen.

+

+
+
+
+ + diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/gestures.png b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/gestures.png new file mode 100644 index 0000000..78b3e7b Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/gestures.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/gestures_phone.html b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/gestures_phone.html new file mode 100644 index 0000000..b72dabf --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/gestures_phone.html @@ -0,0 +1,38 @@ + + + + + + + + Help + + + + + +
+
+ + +
+

Gesten

+

+ aFreeRDP ist für Touch Geräte entwickelt worden. + Diese Gesten lassen sie die häufigsten Operationen mit ihren Fingern + durchführen.

+

+ + +
+
+
+ + + diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/gestures_phone.png b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/gestures_phone.png new file mode 100644 index 0000000..4eea33e Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/gestures_phone.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/nav_gestures.png b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/nav_gestures.png new file mode 100644 index 0000000..50bfaa2 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/nav_gestures.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/nav_toolbar.png b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/nav_toolbar.png new file mode 100644 index 0000000..f66b24d Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/nav_toolbar.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/nav_touch_pointer.png b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/nav_touch_pointer.png new file mode 100644 index 0000000..930fc9c Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/nav_touch_pointer.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/toolbar.html b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/toolbar.html new file mode 100644 index 0000000..c40694a --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/toolbar.html @@ -0,0 +1,49 @@ + + + + + + + + Help + + + + + +
+
+ + +
+

Toolbar

+

+ With the toolbar you'll be able to display and hide the main tools in your session. + This allows together with the touch pointer and the gestures an intuitiv workflow + for remote computing on touch sensitive screens. +

+

+ +
+
+

Tastatur

+ Zeige/verstecke die standard und die erweiterte Tastatur mit Funktionstasten +
+
+

Touch Zeiger

+ Zeige/verstecke den gesten gesteuerten Zeiger +
+
+

Beenden

+ Beende die aktuelle Sitzung. Seihen sie sich bewusst, dass das Beenden kein Logout + ist. +
+
+
+
+ + diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/toolbar.png b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/toolbar.png new file mode 100644 index 0000000..42f055b Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/toolbar.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/toolbar_phone.html b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/toolbar_phone.html new file mode 100644 index 0000000..65d0f94 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/toolbar_phone.html @@ -0,0 +1,49 @@ + + + + + + + + Help + + + + + +
+
+ + +
+

Toolbar

+

+ With the toolbar you'll be able to display and hide the main tools in your session. + This allows together with the touch pointer and the gestures an intuitiv workflow + for remote computing on touch sensitive screens. +

+

+ +
+
+

Tastatur

+ Zeige/verstecke die standard und die erweiterte Tastatur mit Funktionstasten +
+
+

Touch Zeiger

+ Zeige/verstecke den gesten gesteuerten Zeiger +
+
+

Beenden

+ Beende die aktuelle Sitzung. Seihen sie sich bewusst, dass das Beenden kein Logout + ist. +
+
+
+
+ + diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/toolbar_phone.png b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/toolbar_phone.png new file mode 100644 index 0000000..278cd3a Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/toolbar_phone.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/touch_pointer.html b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/touch_pointer.html new file mode 100644 index 0000000..3da3ef5 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/touch_pointer.html @@ -0,0 +1,29 @@ + + + + + + + + Help + + + + + +
+
+ + +
+

Touch Pointer

+

+

+
+
+ + diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/touch_pointer.png b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/touch_pointer.png new file mode 100644 index 0000000..af3ebca Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/touch_pointer.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/touch_pointer_phone.html b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/touch_pointer_phone.html new file mode 100644 index 0000000..58e68df --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/touch_pointer_phone.html @@ -0,0 +1,30 @@ + + + + + + + + Help + + + + + +
+
+ + +
+

Touch Pointer

+

+

+
+
+
+ + diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/touch_pointer_phone.png b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/touch_pointer_phone.png new file mode 100644 index 0000000..ab7c598 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/de_help_page/touch_pointer_phone.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help.css b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help.css new file mode 100644 index 0000000..e845acd --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help.css @@ -0,0 +1,100 @@ + \ No newline at end of file diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/gestures.html b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/gestures.html new file mode 100644 index 0000000..a9ae66d --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/gestures.html @@ -0,0 +1,32 @@ + + + + + + + + Help + + + + + +
+
+ + +
+

Gestures

+

+ aFreeRDP is designed for touch sensitive devices. + These gestures let you do the most usual operations with your fingers.

+

+
+
+
+ + diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/gestures.png b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/gestures.png new file mode 100644 index 0000000..78b3e7b Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/gestures.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/gestures_phone.html b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/gestures_phone.html new file mode 100644 index 0000000..8c81048 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/gestures_phone.html @@ -0,0 +1,33 @@ + + + + + + + + Help + + + + + +
+
+ + +
+

Gestures

+

+ aFreeRDP is designed for touch sensitive devices. + These gestures let you do the most usual operations with your fingers.

+

+
+
+
+ + + diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/gestures_phone.png b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/gestures_phone.png new file mode 100644 index 0000000..4eea33e Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/gestures_phone.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/nav_gestures.png b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/nav_gestures.png new file mode 100644 index 0000000..50bfaa2 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/nav_gestures.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/nav_toolbar.png b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/nav_toolbar.png new file mode 100644 index 0000000..f66b24d Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/nav_toolbar.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/nav_touch_pointer.png b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/nav_touch_pointer.png new file mode 100644 index 0000000..930fc9c Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/nav_touch_pointer.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/toolbar.html b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/toolbar.html new file mode 100644 index 0000000..639fba9 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/toolbar.html @@ -0,0 +1,49 @@ + + + + + + + + Help + + + + + +
+
+ + +
+

Toolbar

+

+ With the toolbar you'll be able to display and hide the main tools in your session. + This allows together with the touch pointer and the gestures an intuitiv workflow + for remote computing on touch sensitive screens. +

+

+ +
+
+

Keyboards

+ Display/hide the default keyboard as well as an extended keyboard with function keys +
+
+

Touch Pointer

+ Display/hide the gesture controlled cursor +
+
+

Disconnect

+ Disconnect your current session. Please be aware that a disconnect is not the same + as a log out. +
+
+
+
+ + diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/toolbar.png b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/toolbar.png new file mode 100644 index 0000000..42f055b Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/toolbar.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/toolbar_phone.html b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/toolbar_phone.html new file mode 100644 index 0000000..78f7357 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/toolbar_phone.html @@ -0,0 +1,50 @@ + + + + + + + + Help + + + + + +
+
+ + +
+

Toolbar

+

+ With the toolbar you'll be able to display and hide the main tools in your session. + This allows together with the touch pointer and the gestures an intuitiv workflow + for remote computing on touch sensitive screens. +

+

+ +
+
+

Keyboards

+ Display/hide the default keyboard as well as an extended keyboard with function keys +
+
+

Touch Pointer

+ Display/hide the gesture controlled cursor +
+ +
+

Disconnect

+ Disconnect your current session. Please be aware that a disconnect is not the same + as a log out. +
+
+
+
+ + diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/toolbar_phone.png b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/toolbar_phone.png new file mode 100644 index 0000000..278cd3a Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/toolbar_phone.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/touch_pointer.html b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/touch_pointer.html new file mode 100644 index 0000000..3da3ef5 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/touch_pointer.html @@ -0,0 +1,29 @@ + + + + + + + + Help + + + + + +
+
+ + +
+

Touch Pointer

+

+

+
+
+ + diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/touch_pointer.png b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/touch_pointer.png new file mode 100644 index 0000000..af3ebca Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/touch_pointer.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/touch_pointer_phone.html b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/touch_pointer_phone.html new file mode 100644 index 0000000..58e68df --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/touch_pointer_phone.html @@ -0,0 +1,30 @@ + + + + + + + + Help + + + + + +
+
+ + +
+

Touch Pointer

+

+

+
+
+
+ + diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/touch_pointer_phone.png b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/touch_pointer_phone.png new file mode 100644 index 0000000..ab7c598 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/assets/help_page/touch_pointer_phone.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/java/com/freerdp/afreerdp/application/GlobalApp.java b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/java/com/freerdp/afreerdp/application/GlobalApp.java new file mode 100644 index 0000000..7d44959 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/java/com/freerdp/afreerdp/application/GlobalApp.java @@ -0,0 +1,5 @@ +package com.freerdp.afreerdp.application; + +public class GlobalApp extends com.freerdp.freerdpcore.application.GlobalApp +{ +} diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/drawable-hdpi/icon_launcher_freerdp.png b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/drawable-hdpi/icon_launcher_freerdp.png new file mode 100644 index 0000000..ff31f25 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/drawable-hdpi/icon_launcher_freerdp.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/drawable-ldpi/icon_launcher_freerdp.png b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/drawable-ldpi/icon_launcher_freerdp.png new file mode 100644 index 0000000..49726f4 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/drawable-ldpi/icon_launcher_freerdp.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/drawable-mdpi/icon_launcher_freerdp.png b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/drawable-mdpi/icon_launcher_freerdp.png new file mode 100644 index 0000000..6b18c0a Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/drawable-mdpi/icon_launcher_freerdp.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/drawable/button_background.xml b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/drawable/button_background.xml new file mode 100644 index 0000000..a6aeb24 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/drawable/button_background.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/drawable/icon_launcher_freerdp.png b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/drawable/icon_launcher_freerdp.png new file mode 100644 index 0000000..53c5b36 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/drawable/icon_launcher_freerdp.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/drawable/separator_background.xml b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/drawable/separator_background.xml new file mode 100644 index 0000000..4cd72ac --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/drawable/separator_background.xml @@ -0,0 +1,19 @@ + + + + + + + + + \ No newline at end of file diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/values-de/strings.xml b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/values-de/strings.xml new file mode 100644 index 0000000..16fc2ba --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/values-de/strings.xml @@ -0,0 +1,4 @@ + + + Entfernte Rechner + \ No newline at end of file diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/values-es/strings.xml b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/values-es/strings.xml new file mode 100644 index 0000000..401d0f2 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/values-es/strings.xml @@ -0,0 +1,4 @@ + + + Remote Computers + diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/values-fr/strings.xml b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/values-fr/strings.xml new file mode 100644 index 0000000..054f6c2 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/values-fr/strings.xml @@ -0,0 +1,4 @@ + + + L\'ordinateur distant + diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/values-ko/strings.xml b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/values-ko/strings.xml new file mode 100644 index 0000000..caae132 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/values-ko/strings.xml @@ -0,0 +1,4 @@ + + + 원격 컴퓨터 + diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/values-nl/strings.xml b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/values-nl/strings.xml new file mode 100644 index 0000000..401d0f2 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/values-nl/strings.xml @@ -0,0 +1,4 @@ + + + Remote Computers + diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/values-zh-rTW/strings.xml b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/values-zh-rTW/strings.xml new file mode 100644 index 0000000..6309455 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/values-zh-rTW/strings.xml @@ -0,0 +1,5 @@ + + + + 遠端電腦 + diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/values-zh/strings.xml b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/values-zh/strings.xml new file mode 100644 index 0000000..4171dcb --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/values-zh/strings.xml @@ -0,0 +1,4 @@ + + + 远程计算机 + \ No newline at end of file diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/values/strings.xml b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/values/strings.xml new file mode 100644 index 0000000..1c00d49 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/values/strings.xml @@ -0,0 +1,7 @@ + + + aFreeRDP + + aFreeRDP + Remote Computers + diff --git a/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/xml/searchable.xml b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/xml/searchable.xml new file mode 100644 index 0000000..d8b5f0b --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/aFreeRDP/src/main/res/xml/searchable.xml @@ -0,0 +1,22 @@ + + diff --git a/third_party/FreeRDP/client/Android/Studio/build.gradle b/third_party/FreeRDP/client/Android/Studio/build.gradle new file mode 100644 index 0000000..dde326b --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/build.gradle @@ -0,0 +1,70 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +Properties releaseProperties = new Properties() +File file = new File('release.properties') +if (file.canRead()) { + releaseProperties.load(new FileInputStream(file)) +} + +if (!hasProperty('RELEASE_STORE_FILE')) { + RELEASE_STORE_FILE=releaseProperties.get('RELEASE_STORE_FILE', '~/.android/debug.keystore') +} +if (!hasProperty('RELEASE_KEY_ALIAS')) { + RELEASE_KEY_ALIAS=releaseProperties.get('RELEASE_KEY_ALIAS', 'androiddebugkey') +} +if (!hasProperty('RELEASE_KEY_PASSWORD')) { + RELEASE_KEY_PASSWORD=releaseProperties.get('RELEASE_KEY_PASSWORD', 'android') +} +if (!hasProperty('RELEASE_STORE_PASSWORD')) { + RELEASE_STORE_PASSWORD=releaseProperties.get('RELEASE_STORE_PASSWORD', 'android') +} + +ext { + compileApi = releaseProperties.get('COMPILE_API', 35) + targetApi = releaseProperties.get('TARGET_API', 35) + minApi = releaseProperties.get('MIN_API', 23) + toolsVersion = releaseProperties.get('TOOLS_VERSION', '35.0.0') + + println '----------------- Project configuration -------------------' + println 'RELEASE_STORE_FILE: ' + RELEASE_STORE_FILE + println 'RELEASE_KEY_ALIAS: ' + RELEASE_KEY_ALIAS + println 'compile API: ' + compileApi + println 'target API: ' + targetApi + println 'min API: ' + minApi + println 'tools version: ' + toolsVersion + println '-----------------------------------------------------------' +} + +buildscript { + repositories { + mavenCentral() + google() + maven { + url 'https://maven.google.com' + } + } + dependencies { + classpath 'com.android.tools.build:gradle:8.13.0' + } +} + +allprojects { + repositories { + mavenCentral() + google() + maven { + url 'https://maven.google.com' + } + } + + subprojects { + afterEvaluate { project -> + if (project.hasProperty('android')) { + project.android { + if (namespace == null) { + namespace 'com.freerdp.' + project.name.toLowerCase() + } + } + } + } + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/build.gradle b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/build.gradle new file mode 100644 index 0000000..ae231b4 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/build.gradle @@ -0,0 +1,66 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion = rootProject.ext.compileApi + buildToolsVersion = rootProject.ext.toolsVersion + packagingOptions { + jniLibs { + pickFirsts += ['lib/arm64-v8a/libfreerdp3.so', 'lib/armeabi-v7a/libfreerdp3.so', 'lib/x86/libfreerdp3.so', 'lib/x86_64/libfreerdp3.so', 'lib/arm64-v8a/libfreerdp-client3.so', 'lib/armeabi-v7a/libfreerdp-client3.so', 'lib/x86/libfreerdp-client3.so', 'lib/x86_64/libfreerdp-client3.so', 'lib/arm64-v8a/libwinpr3.so', 'lib/armeabi-v7a/libwinpr3.so', 'lib/x86/libwinpr3.so', 'lib/x86_64/libwinpr3.so'] + } + } + + + defaultConfig { + minSdkVersion rootProject.ext.minApi + targetSdkVersion rootProject.ext.targetApi + vectorDrawables.useSupportLibrary = true + ndkVersion = "29.0.13113456" + + ndk { + File jniLibsDirectory = new File(project.projectDir, "src/main/jniLibs") + ArrayList abiFiltersList = new ArrayList() + if (new File(jniLibsDirectory, "arm64-v8a/libfreerdp3.so").exists()) + abiFiltersList.add("arm64-v8a") + if (new File(jniLibsDirectory, "armeabi-v7a/libfreerdp3.so").exists()) + abiFiltersList.add("armeabi-v7a") + if (new File(jniLibsDirectory, "x86_64/libfreerdp3.so").exists()) + abiFiltersList.add("x86_64") + if (new File(jniLibsDirectory, "x86/libfreerdp3.so").exists()) + abiFiltersList.add("x86") + + //noinspection ChromeOsAbiSupport + abiFilters = abiFiltersList + } + + externalNativeBuild { + cmake { + arguments "-DWITH_CLIENT_CHANNELS=ON" + } + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + debug { + jniDebuggable true + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + externalNativeBuild { + cmake { + path file('src/main/cpp/CMakeLists.txt') + } + } +} + +dependencies { + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.android.support:support-v4:28.0.0' + implementation 'com.android.support:support-vector-drawable:28.0.0' +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/lint.xml b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/lint.xml new file mode 100644 index 0000000..c70207f --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/lint.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/AndroidManifest.xml b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d2d5a16 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/AndroidManifest.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/CMakeLists.txt b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000..ee3f6ae --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/CMakeLists.txt @@ -0,0 +1,83 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# Android Client +# +# Copyright 2012 Marc-Andre Moreau +# Copyright 2013 Bernhard Miklautz +# Copyright 2022 Ely Ronnen +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 3.13) + +if(NOT FREERDP_DEFAULT_PROJECT_VERSION) + set(FREERDP_DEFAULT_PROJECT_VERSION "1.0.0.0") +endif() + +project("freerdp-android" LANGUAGES C VERSION ${FREERDP_DEFAULT_PROJECT_VERSION}) + +message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}") + +set(MODULE_NAME "freerdp-android") +set(MODULE_PREFIX "FREERDP_CLIENT_ANDROID") + +set(FREERDP_IMPORT_DIR_RELATIVE ../jniLibs/${CMAKE_ANDROID_ARCH_ABI}) +get_filename_component(FREERDP_IMPORT_DIR ${FREERDP_IMPORT_DIR_RELATIVE} ABSOLUTE) + +include_directories( + SYSTEM ${FREERDP_IMPORT_DIR}/include/freerdp3 ${FREERDP_IMPORT_DIR}/include/winpr3 + ${FREERDP_IMPORT_DIR}/include/openssl +) + +set(${MODULE_PREFIX}_SRCS + android_event.c + android_event.h + android_freerdp.c + android_freerdp.h + android_jni_utils.c + android_jni_utils.h + android_jni_callback.c + android_jni_callback.h +) + +if(WITH_CLIENT_CHANNELS) + set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} android_cliprdr.c android_cliprdr.h) +endif() + +add_library(${MODULE_NAME} SHARED ${${MODULE_PREFIX}_SRCS}) + +add_library(freerdp3-lib SHARED IMPORTED) +set_property(TARGET freerdp3-lib PROPERTY IMPORTED_LOCATION ${FREERDP_IMPORT_DIR}/libfreerdp3.so) + +add_library(freerdp-client3-lib SHARED IMPORTED) +set_property(TARGET freerdp-client3-lib PROPERTY IMPORTED_LOCATION ${FREERDP_IMPORT_DIR}/libfreerdp-client3.so) + +add_library(winpr3-lib SHARED IMPORTED) +set_property(TARGET winpr3-lib PROPERTY IMPORTED_LOCATION ${FREERDP_IMPORT_DIR}/libwinpr3.so) + +find_library(log-lib log) +find_library(dl-lib dl) +find_library(jnigraphics-lib jnigraphics) + +# Specifies libraries CMake should link to your target library. You +# can link multiple libraries, such as libraries you define in this +# build script, prebuilt third-party libraries, or system libraries. + +target_link_libraries( + ${MODULE_NAME} + freerdp3-lib + freerdp-client3-lib + winpr3-lib + ${log-lib} + ${dl-lib} + ${jnigraphics-lib} +) diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_cliprdr.c b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_cliprdr.c new file mode 100644 index 0000000..b93e189 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_cliprdr.c @@ -0,0 +1,501 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Android Clipboard Redirection + * + * Copyright 2013 Felix Long + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include +#include + +#include +#include + +#include "android_cliprdr.h" +#include "android_jni_utils.h" +#include "android_jni_callback.h" + +UINT android_cliprdr_send_client_format_list(CliprdrClientContext* cliprdr) +{ + UINT rc = ERROR_INTERNAL_ERROR; + UINT32 formatId; + UINT32 numFormats; + UINT32* pFormatIds; + const char* formatName; + CLIPRDR_FORMAT* formats; + CLIPRDR_FORMAT_LIST formatList = WINPR_C_ARRAY_INIT; + + if (!cliprdr) + return ERROR_INVALID_PARAMETER; + + androidContext* afc = (androidContext*)cliprdr->custom; + + if (!afc || !afc->cliprdr) + return ERROR_INVALID_PARAMETER; + + pFormatIds = nullptr; + numFormats = ClipboardGetFormatIds(afc->clipboard, &pFormatIds); + formats = (CLIPRDR_FORMAT*)calloc(numFormats, sizeof(CLIPRDR_FORMAT)); + + if (!formats) + goto fail; + + for (UINT32 index = 0; index < numFormats; index++) + { + formatId = pFormatIds[index]; + formatName = ClipboardGetFormatName(afc->clipboard, formatId); + formats[index].formatId = formatId; + formats[index].formatName = nullptr; + + if ((formatId > CF_MAX) && formatName) + { + formats[index].formatName = _strdup(formatName); + + if (!formats[index].formatName) + goto fail; + } + } + + formatList.common.msgFlags = 0; + formatList.numFormats = numFormats; + formatList.formats = formats; + formatList.common.msgType = CB_FORMAT_LIST; + + if (!afc->cliprdr->ClientFormatList) + goto fail; + + rc = afc->cliprdr->ClientFormatList(afc->cliprdr, &formatList); +fail: + free(pFormatIds); + free(formats); + return rc; +} + +static UINT android_cliprdr_send_client_format_data_request(CliprdrClientContext* cliprdr, + UINT32 formatId) +{ + UINT rc = ERROR_INVALID_PARAMETER; + CLIPRDR_FORMAT_DATA_REQUEST formatDataRequest = WINPR_C_ARRAY_INIT; + androidContext* afc; + + if (!cliprdr) + goto fail; + + afc = (androidContext*)cliprdr->custom; + + if (!afc || !afc->clipboardRequestEvent || !cliprdr->ClientFormatDataRequest) + goto fail; + + formatDataRequest.common.msgType = CB_FORMAT_DATA_REQUEST; + formatDataRequest.common.msgFlags = 0; + formatDataRequest.requestedFormatId = formatId; + afc->requestedFormatId = formatId; + (void)ResetEvent(afc->clipboardRequestEvent); + rc = cliprdr->ClientFormatDataRequest(cliprdr, &formatDataRequest); +fail: + return rc; +} + +static UINT android_cliprdr_send_client_capabilities(CliprdrClientContext* cliprdr) +{ + CLIPRDR_CAPABILITIES capabilities; + CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet; + + if (!cliprdr || !cliprdr->ClientCapabilities) + return ERROR_INVALID_PARAMETER; + + capabilities.cCapabilitiesSets = 1; + capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET*)&(generalCapabilitySet); + generalCapabilitySet.capabilitySetType = CB_CAPSTYPE_GENERAL; + generalCapabilitySet.capabilitySetLength = 12; + generalCapabilitySet.version = CB_CAPS_VERSION_2; + generalCapabilitySet.generalFlags = CB_USE_LONG_FORMAT_NAMES; + return cliprdr->ClientCapabilities(cliprdr, &capabilities); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT android_cliprdr_monitor_ready(CliprdrClientContext* cliprdr, + const CLIPRDR_MONITOR_READY* monitorReady) +{ + UINT rc; + androidContext* afc; + + if (!cliprdr || !monitorReady) + return ERROR_INVALID_PARAMETER; + + afc = (androidContext*)cliprdr->custom; + + if (!afc) + return ERROR_INVALID_PARAMETER; + + if ((rc = android_cliprdr_send_client_capabilities(cliprdr)) != CHANNEL_RC_OK) + return rc; + + if ((rc = android_cliprdr_send_client_format_list(cliprdr)) != CHANNEL_RC_OK) + return rc; + + afc->clipboardSync = TRUE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT android_cliprdr_server_capabilities(CliprdrClientContext* cliprdr, + const CLIPRDR_CAPABILITIES* capabilities) +{ + CLIPRDR_CAPABILITY_SET* capabilitySet; + androidContext* afc; + + if (!cliprdr || !capabilities) + return ERROR_INVALID_PARAMETER; + + afc = (androidContext*)cliprdr->custom; + + if (!afc) + return ERROR_INVALID_PARAMETER; + + for (UINT32 index = 0; index < capabilities->cCapabilitiesSets; index++) + { + capabilitySet = &(capabilities->capabilitySets[index]); + + if ((capabilitySet->capabilitySetType == CB_CAPSTYPE_GENERAL) && + (capabilitySet->capabilitySetLength >= CB_CAPSTYPE_GENERAL_LEN)) + { + CLIPRDR_GENERAL_CAPABILITY_SET* generalCapabilitySet = + (CLIPRDR_GENERAL_CAPABILITY_SET*)capabilitySet; + afc->clipboardCapabilities = generalCapabilitySet->generalFlags; + break; + } + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT android_cliprdr_server_format_list(CliprdrClientContext* cliprdr, + const CLIPRDR_FORMAT_LIST* formatList) +{ + UINT rc; + CLIPRDR_FORMAT* format; + androidContext* afc; + + if (!cliprdr || !formatList) + return ERROR_INVALID_PARAMETER; + + afc = (androidContext*)cliprdr->custom; + + if (!afc) + return ERROR_INVALID_PARAMETER; + + if (afc->serverFormats) + { + for (UINT32 index = 0; index < afc->numServerFormats; index++) + free(afc->serverFormats[index].formatName); + + free(afc->serverFormats); + afc->serverFormats = nullptr; + afc->numServerFormats = 0; + } + + if (formatList->numFormats < 1) + return CHANNEL_RC_OK; + + afc->numServerFormats = formatList->numFormats; + afc->serverFormats = (CLIPRDR_FORMAT*)calloc(afc->numServerFormats, sizeof(CLIPRDR_FORMAT)); + + if (!afc->serverFormats) + return CHANNEL_RC_NO_MEMORY; + + for (UINT32 index = 0; index < afc->numServerFormats; index++) + { + afc->serverFormats[index].formatId = formatList->formats[index].formatId; + afc->serverFormats[index].formatName = nullptr; + + if (formatList->formats[index].formatName) + { + afc->serverFormats[index].formatName = _strdup(formatList->formats[index].formatName); + + if (!afc->serverFormats[index].formatName) + return CHANNEL_RC_NO_MEMORY; + } + } + + for (UINT32 index = 0; index < afc->numServerFormats; index++) + { + format = &(afc->serverFormats[index]); + + if (format->formatId == CF_UNICODETEXT) + { + if ((rc = android_cliprdr_send_client_format_data_request(cliprdr, CF_UNICODETEXT)) != + CHANNEL_RC_OK) + return rc; + + break; + } + else if (format->formatId == CF_TEXT) + { + if ((rc = android_cliprdr_send_client_format_data_request(cliprdr, CF_TEXT)) != + CHANNEL_RC_OK) + return rc; + + break; + } + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +android_cliprdr_server_format_list_response(CliprdrClientContext* cliprdr, + const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse) +{ + if (!cliprdr || !formatListResponse) + return ERROR_INVALID_PARAMETER; + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +android_cliprdr_server_lock_clipboard_data(CliprdrClientContext* cliprdr, + const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData) +{ + if (!cliprdr || !lockClipboardData) + return ERROR_INVALID_PARAMETER; + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT android_cliprdr_server_unlock_clipboard_data( + CliprdrClientContext* cliprdr, const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData) +{ + if (!cliprdr || !unlockClipboardData) + return ERROR_INVALID_PARAMETER; + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +android_cliprdr_server_format_data_request(CliprdrClientContext* cliprdr, + const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest) +{ + UINT rc; + BYTE* data; + UINT32 size; + UINT32 formatId; + CLIPRDR_FORMAT_DATA_RESPONSE response = WINPR_C_ARRAY_INIT; + androidContext* afc; + + if (!cliprdr || !formatDataRequest || !cliprdr->ClientFormatDataResponse) + return ERROR_INVALID_PARAMETER; + + afc = (androidContext*)cliprdr->custom; + + if (!afc) + return ERROR_INVALID_PARAMETER; + + formatId = formatDataRequest->requestedFormatId; + data = (BYTE*)ClipboardGetData(afc->clipboard, formatId, &size); + response.common.msgFlags = CB_RESPONSE_OK; + response.common.dataLen = size; + response.requestedFormatData = data; + + if (!data) + { + response.common.msgFlags = CB_RESPONSE_FAIL; + response.common.dataLen = 0; + response.requestedFormatData = nullptr; + } + + rc = cliprdr->ClientFormatDataResponse(cliprdr, &response); + free(data); + return rc; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +android_cliprdr_server_format_data_response(CliprdrClientContext* cliprdr, + const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse) +{ + CLIPRDR_FORMAT* format = nullptr; + + if (!cliprdr || !formatDataResponse) + return ERROR_INVALID_PARAMETER; + + androidContext* afc = (androidContext*)cliprdr->custom; + + if (!afc) + return ERROR_INVALID_PARAMETER; + + freerdp* instance = ((rdpContext*)afc)->instance; + + if (!instance) + return ERROR_INVALID_PARAMETER; + + for (UINT32 index = 0; index < afc->numServerFormats; index++) + { + if (afc->requestedFormatId == afc->serverFormats[index].formatId) + format = &(afc->serverFormats[index]); + } + + if (!format) + { + (void)SetEvent(afc->clipboardRequestEvent); + return ERROR_INTERNAL_ERROR; + } + + UINT32 formatId = format->formatId; + if (format->formatName) + formatId = ClipboardRegisterFormat(afc->clipboard, format->formatName); + + uint32_t size = formatDataResponse->common.dataLen; + + if (!ClipboardSetData(afc->clipboard, formatId, formatDataResponse->requestedFormatData, size)) + return ERROR_INTERNAL_ERROR; + + (void)SetEvent(afc->clipboardRequestEvent); + + if ((formatId == CF_TEXT) || (formatId == CF_UNICODETEXT)) + { + JNIEnv* env = nullptr; + formatId = ClipboardRegisterFormat(afc->clipboard, "text/plain"); + char* data = (char*)ClipboardGetData(afc->clipboard, formatId, &size); + jboolean attached = jni_attach_thread(&env); + size = strnlen(data, size); + jstring jdata = jniNewStringUTF(env, data, size); + freerdp_callback("OnRemoteClipboardChanged", "(JLjava/lang/String;)V", (jlong)instance, + jdata); + (*env)->DeleteLocalRef(env, jdata); + + if (attached == JNI_TRUE) + jni_detach_thread(); + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT android_cliprdr_server_file_contents_request( + CliprdrClientContext* cliprdr, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + if (!cliprdr || !fileContentsRequest) + return ERROR_INVALID_PARAMETER; + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT android_cliprdr_server_file_contents_response( + CliprdrClientContext* cliprdr, const CLIPRDR_FILE_CONTENTS_RESPONSE* fileContentsResponse) +{ + if (!cliprdr || !fileContentsResponse) + return ERROR_INVALID_PARAMETER; + + return CHANNEL_RC_OK; +} + +BOOL android_cliprdr_init(androidContext* afc, CliprdrClientContext* cliprdr) +{ + wClipboard* clipboard; + HANDLE hevent; + + if (!afc || !cliprdr) + return FALSE; + + if (!(hevent = CreateEvent(nullptr, TRUE, FALSE, nullptr))) + return FALSE; + + if (!(clipboard = ClipboardCreate())) + { + (void)CloseHandle(hevent); + return FALSE; + } + + afc->cliprdr = cliprdr; + afc->clipboard = clipboard; + afc->clipboardRequestEvent = hevent; + cliprdr->custom = (void*)afc; + cliprdr->MonitorReady = android_cliprdr_monitor_ready; + cliprdr->ServerCapabilities = android_cliprdr_server_capabilities; + cliprdr->ServerFormatList = android_cliprdr_server_format_list; + cliprdr->ServerFormatListResponse = android_cliprdr_server_format_list_response; + cliprdr->ServerLockClipboardData = android_cliprdr_server_lock_clipboard_data; + cliprdr->ServerUnlockClipboardData = android_cliprdr_server_unlock_clipboard_data; + cliprdr->ServerFormatDataRequest = android_cliprdr_server_format_data_request; + cliprdr->ServerFormatDataResponse = android_cliprdr_server_format_data_response; + cliprdr->ServerFileContentsRequest = android_cliprdr_server_file_contents_request; + cliprdr->ServerFileContentsResponse = android_cliprdr_server_file_contents_response; + return TRUE; +} + +BOOL android_cliprdr_uninit(androidContext* afc, CliprdrClientContext* cliprdr) +{ + if (!afc || !cliprdr) + return FALSE; + + cliprdr->custom = nullptr; + afc->cliprdr = nullptr; + ClipboardDestroy(afc->clipboard); + (void)CloseHandle(afc->clipboardRequestEvent); + return TRUE; +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_cliprdr.h b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_cliprdr.h new file mode 100644 index 0000000..8404d9a --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_cliprdr.h @@ -0,0 +1,33 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Android Clipboard Redirection + * + * Copyright 2013 Felix Long + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_ANDROID_CLIPRDR_H +#define FREERDP_CLIENT_ANDROID_CLIPRDR_H + +#include +#include + +#include "android_freerdp.h" + +FREERDP_LOCAL UINT android_cliprdr_send_client_format_list(CliprdrClientContext* cliprdr); + +FREERDP_LOCAL BOOL android_cliprdr_init(androidContext* afc, CliprdrClientContext* cliprdr); +FREERDP_LOCAL BOOL android_cliprdr_uninit(androidContext* afc, CliprdrClientContext* cliprdr); + +#endif /* FREERDP_CLIENT_ANDROID_CLIPRDR_H */ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_event.c b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_event.c new file mode 100644 index 0000000..f0a2ea2 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_event.c @@ -0,0 +1,404 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Android Event System + * + * Copyright 2010-2012 Marc-Andre Moreau + * Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include + +#include + +#include +#include + +#define TAG CLIENT_TAG("android") + +#include "android_freerdp.h" +#include "android_cliprdr.h" + +BOOL android_push_event(freerdp* inst, ANDROID_EVENT* event) +{ + androidContext* aCtx = (androidContext*)inst->context; + + if (aCtx->event_queue->count >= aCtx->event_queue->size) + { + size_t new_size = aCtx->event_queue->size; + do + { + if (new_size >= SIZE_MAX - 128ull) + return FALSE; + + new_size += 128ull; + } while (new_size <= aCtx->event_queue->count); + void* new_events = + realloc((void*)aCtx->event_queue->events, sizeof(ANDROID_EVENT*) * new_size); + + if (!new_events) + return FALSE; + + aCtx->event_queue->events = new_events; + aCtx->event_queue->size = new_size; + } + + aCtx->event_queue->events[(aCtx->event_queue->count)++] = event; + return SetEvent(aCtx->event_queue->isSet); +} + +static ANDROID_EVENT* android_peek_event(ANDROID_EVENT_QUEUE* queue) +{ + ANDROID_EVENT* event; + + if (queue->count < 1) + return nullptr; + + event = queue->events[0]; + return event; +} + +static ANDROID_EVENT* android_pop_event(ANDROID_EVENT_QUEUE* queue) +{ + ANDROID_EVENT* event; + + if (queue->count < 1) + return nullptr; + + event = queue->events[0]; + (queue->count)--; + + for (size_t i = 0; i < queue->count; i++) + { + queue->events[i] = queue->events[i + 1]; + } + + return event; +} + +static BOOL android_process_event(ANDROID_EVENT_QUEUE* queue, freerdp* inst) +{ + rdpContext* context; + + WINPR_ASSERT(queue); + WINPR_ASSERT(inst); + + context = inst->context; + WINPR_ASSERT(context); + + while (android_peek_event(queue)) + { + BOOL rc = FALSE; + androidContext* afc = (androidContext*)context; + ANDROID_EVENT* event = android_pop_event(queue); + + WINPR_ASSERT(event); + + switch (event->type) + { + case EVENT_TYPE_KEY: + { + ANDROID_EVENT_KEY* key_event = (ANDROID_EVENT_KEY*)event; + + rc = freerdp_input_send_keyboard_event(context->input, key_event->flags, + key_event->scancode); + } + break; + + case EVENT_TYPE_KEY_UNICODE: + { + ANDROID_EVENT_KEY* key_event = (ANDROID_EVENT_KEY*)event; + + rc = freerdp_input_send_unicode_keyboard_event(context->input, key_event->flags, + key_event->scancode); + } + break; + + case EVENT_TYPE_CURSOR: + { + ANDROID_EVENT_CURSOR* cursor_event = (ANDROID_EVENT_CURSOR*)event; + + rc = freerdp_input_send_mouse_event(context->input, cursor_event->flags, + cursor_event->x, cursor_event->y); + } + break; + + case EVENT_TYPE_CLIPBOARD: + { + ANDROID_EVENT_CLIPBOARD* clipboard_event = (ANDROID_EVENT_CLIPBOARD*)event; + UINT32 formatId = ClipboardRegisterFormat(afc->clipboard, "text/plain"); + UINT32 size = clipboard_event->data_length; + + if (size) + ClipboardSetData(afc->clipboard, formatId, clipboard_event->data, size); + else + ClipboardEmpty(afc->clipboard); + + rc = (android_cliprdr_send_client_format_list(afc->cliprdr) == CHANNEL_RC_OK); + } + break; + + case EVENT_TYPE_DISCONNECT: + default: + break; + } + + android_event_free(event); + + if (!rc) + return FALSE; + } + + return TRUE; +} + +HANDLE android_get_handle(freerdp* inst) +{ + androidContext* aCtx; + + if (!inst || !inst->context) + return nullptr; + + aCtx = (androidContext*)inst->context; + + if (!aCtx->event_queue || !aCtx->event_queue->isSet) + return nullptr; + + return aCtx->event_queue->isSet; +} + +BOOL android_check_handle(freerdp* inst) +{ + androidContext* aCtx; + + if (!inst || !inst->context) + return FALSE; + + aCtx = (androidContext*)inst->context; + + if (!aCtx->event_queue || !aCtx->event_queue->isSet) + return FALSE; + + if (WaitForSingleObject(aCtx->event_queue->isSet, 0) == WAIT_OBJECT_0) + { + if (!ResetEvent(aCtx->event_queue->isSet)) + return FALSE; + + if (!android_process_event(aCtx->event_queue, inst)) + return FALSE; + } + + return TRUE; +} + +ANDROID_EVENT_KEY* android_event_key_new(int flags, UINT16 scancode) +{ + ANDROID_EVENT_KEY* event = (ANDROID_EVENT_KEY*)calloc(1, sizeof(ANDROID_EVENT_KEY)); + + if (!event) + return nullptr; + + event->type = EVENT_TYPE_KEY; + event->flags = flags; + event->scancode = scancode; + return event; +} + +static void android_event_key_free(ANDROID_EVENT_KEY* event) +{ + free(event); +} + +ANDROID_EVENT_KEY* android_event_unicodekey_new(UINT16 flags, UINT16 key) +{ + ANDROID_EVENT_KEY* event; + event = (ANDROID_EVENT_KEY*)calloc(1, sizeof(ANDROID_EVENT_KEY)); + + if (!event) + return nullptr; + + event->type = EVENT_TYPE_KEY_UNICODE; + event->flags = flags; + event->scancode = key; + return event; +} + +static void android_event_unicodekey_free(ANDROID_EVENT_KEY* event) +{ + free(event); +} + +ANDROID_EVENT_CURSOR* android_event_cursor_new(UINT16 flags, UINT16 x, UINT16 y) +{ + ANDROID_EVENT_CURSOR* event; + event = (ANDROID_EVENT_CURSOR*)calloc(1, sizeof(ANDROID_EVENT_CURSOR)); + + if (!event) + return nullptr; + + event->type = EVENT_TYPE_CURSOR; + event->x = x; + event->y = y; + event->flags = flags; + return event; +} + +static void android_event_cursor_free(ANDROID_EVENT_CURSOR* event) +{ + free(event); +} + +ANDROID_EVENT* android_event_disconnect_new(void) +{ + ANDROID_EVENT* event; + event = (ANDROID_EVENT*)calloc(1, sizeof(ANDROID_EVENT)); + + if (!event) + return nullptr; + + event->type = EVENT_TYPE_DISCONNECT; + return event; +} + +static void android_event_disconnect_free(ANDROID_EVENT* event) +{ + free(event); +} + +ANDROID_EVENT_CLIPBOARD* android_event_clipboard_new(const void* data, size_t data_length) +{ + ANDROID_EVENT_CLIPBOARD* event; + event = (ANDROID_EVENT_CLIPBOARD*)calloc(1, sizeof(ANDROID_EVENT_CLIPBOARD)); + + if (!event) + return nullptr; + + event->type = EVENT_TYPE_CLIPBOARD; + + if (data) + { + event->data = calloc(data_length + 1, sizeof(char)); + + if (!event->data) + { + free(event); + return nullptr; + } + + memcpy(event->data, data, data_length); + event->data_length = data_length + 1; + } + + return event; +} + +static void android_event_clipboard_free(ANDROID_EVENT_CLIPBOARD* event) +{ + if (event) + { + free(event->data); + free(event); + } +} + +BOOL android_event_queue_init(freerdp* inst) +{ + androidContext* aCtx = (androidContext*)inst->context; + ANDROID_EVENT_QUEUE* queue; + queue = (ANDROID_EVENT_QUEUE*)calloc(1, sizeof(ANDROID_EVENT_QUEUE)); + + if (!queue) + { + WLog_ERR(TAG, "android_event_queue_init: memory allocation failed"); + return FALSE; + } + + queue->size = 16; + queue->count = 0; + queue->isSet = CreateEventA(nullptr, TRUE, FALSE, nullptr); + + if (!queue->isSet) + { + free(queue); + return FALSE; + } + + queue->events = (ANDROID_EVENT**)calloc(queue->size, sizeof(ANDROID_EVENT*)); + + if (!queue->events) + { + WLog_ERR(TAG, "android_event_queue_init: memory allocation failed"); + (void)CloseHandle(queue->isSet); + free(queue); + return FALSE; + } + + aCtx->event_queue = queue; + return TRUE; +} + +void android_event_queue_uninit(freerdp* inst) +{ + androidContext* aCtx; + ANDROID_EVENT_QUEUE* queue; + + if (!inst || !inst->context) + return; + + aCtx = (androidContext*)inst->context; + queue = aCtx->event_queue; + + if (queue) + { + if (queue->isSet) + { + (void)CloseHandle(queue->isSet); + queue->isSet = nullptr; + } + + if (queue->events) + { + free(queue->events); + queue->events = nullptr; + queue->size = 0; + queue->count = 0; + } + + free(queue); + } +} + +void android_event_free(ANDROID_EVENT* event) +{ + if (!event) + return; + + switch (event->type) + { + case EVENT_TYPE_KEY: + android_event_key_free((ANDROID_EVENT_KEY*)event); + break; + + case EVENT_TYPE_KEY_UNICODE: + android_event_unicodekey_free((ANDROID_EVENT_KEY*)event); + break; + + case EVENT_TYPE_CURSOR: + android_event_cursor_free((ANDROID_EVENT_CURSOR*)event); + break; + + case EVENT_TYPE_DISCONNECT: + android_event_disconnect_free((ANDROID_EVENT*)event); + break; + + case EVENT_TYPE_CLIPBOARD: + android_event_clipboard_free((ANDROID_EVENT_CLIPBOARD*)event); + break; + + default: + break; + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_event.h b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_event.h new file mode 100644 index 0000000..26e9950 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_event.h @@ -0,0 +1,75 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Android Event System + * + * Copyright 2010-2012 Marc-Andre Moreau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef FREERDP_CLIENT_ANDROID_EVENT_H +#define FREERDP_CLIENT_ANDROID_EVENT_H +#include +#include + +#define EVENT_TYPE_KEY 1 +#define EVENT_TYPE_CURSOR 2 +#define EVENT_TYPE_DISCONNECT 3 +#define EVENT_TYPE_KEY_UNICODE 4 +#define EVENT_TYPE_CLIPBOARD 5 + +typedef struct +{ + int type; +} ANDROID_EVENT; + +typedef struct +{ + int type; + int flags; + UINT16 scancode; +} ANDROID_EVENT_KEY; + +typedef struct +{ + int type; + UINT16 flags; + UINT16 x; + UINT16 y; +} ANDROID_EVENT_CURSOR; + +typedef struct +{ + int type; + void* data; + int data_length; +} ANDROID_EVENT_CLIPBOARD; + +typedef struct +{ + int size; + int count; + HANDLE isSet; + ANDROID_EVENT** events; +} ANDROID_EVENT_QUEUE; + +FREERDP_LOCAL BOOL android_push_event(freerdp* inst, ANDROID_EVENT* event); + +FREERDP_LOCAL HANDLE android_get_handle(freerdp* inst); +FREERDP_LOCAL BOOL android_check_handle(freerdp* inst); + +FREERDP_LOCAL ANDROID_EVENT_KEY* android_event_key_new(int flags, UINT16 scancode); +FREERDP_LOCAL ANDROID_EVENT_KEY* android_event_unicodekey_new(UINT16 flags, UINT16 key); +FREERDP_LOCAL ANDROID_EVENT_CURSOR* android_event_cursor_new(UINT16 flags, UINT16 x, UINT16 y); +FREERDP_LOCAL ANDROID_EVENT* android_event_disconnect_new(void); +FREERDP_LOCAL ANDROID_EVENT_CLIPBOARD* android_event_clipboard_new(const void* data, + size_t data_length); + +FREERDP_LOCAL void android_event_free(ANDROID_EVENT* event); + +FREERDP_LOCAL BOOL android_event_queue_init(freerdp* inst); +FREERDP_LOCAL void android_event_queue_uninit(freerdp* inst); + +#endif /* FREERDP_CLIENT_ANDROID_EVENT_H */ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_freerdp.c b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_freerdp.c new file mode 100644 index 0000000..9e06851 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_freerdp.c @@ -0,0 +1,1039 @@ +/* + Android JNI Client Layer + + Copyright 2010-2012 Marc-Andre Moreau + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + Copyright 2013 Thincast Technologies GmbH, Author: Armin Novak + Copyright 2015 Bernhard Miklautz + Copyright 2016 Thincast Technologies GmbH + Copyright 2016 Armin Novak + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +#include + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "android_jni_callback.h" +#include "android_jni_utils.h" +#include "android_cliprdr.h" +#include "android_freerdp_jni.h" + +#if defined(WITH_GPROF) +#include "jni/prof.h" +#endif + +#define TAG CLIENT_TAG("android") + +/* Defines the JNI version supported by this library. */ +#define FREERDP_JNI_VERSION FREERDP_VERSION_FULL +static void android_OnChannelConnectedEventHandler(void* context, + const ChannelConnectedEventArgs* e) +{ + rdpSettings* settings; + androidContext* afc; + + if (!context || !e) + { + WLog_FATAL(TAG, "(context=%p, EventArgs=%p", context, (void*)e); + return; + } + + afc = (androidContext*)context; + settings = afc->common.context.settings; + + if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) + { + android_cliprdr_init(afc, (CliprdrClientContext*)e->pInterface); + } + else + freerdp_client_OnChannelConnectedEventHandler(context, e); +} + +static void android_OnChannelDisconnectedEventHandler(void* context, + const ChannelDisconnectedEventArgs* e) +{ + rdpSettings* settings; + androidContext* afc; + + if (!context || !e) + { + WLog_FATAL(TAG, "(context=%p, EventArgs=%p", context, (void*)e); + return; + } + + afc = (androidContext*)context; + settings = afc->common.context.settings; + + if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) + { + android_cliprdr_uninit(afc, (CliprdrClientContext*)e->pInterface); + } + else + freerdp_client_OnChannelDisconnectedEventHandler(context, e); +} + +static BOOL android_begin_paint(rdpContext* context) +{ + return TRUE; +} + +static BOOL android_end_paint(rdpContext* context) +{ + HGDI_WND hwnd; + int ninvalid; + rdpGdi* gdi; + HGDI_RGN cinvalid; + int x1, y1, x2, y2; + androidContext* ctx = (androidContext*)context; + rdpSettings* settings; + + if (!ctx || !context->instance) + return FALSE; + + settings = context->settings; + + if (!settings) + return FALSE; + + gdi = context->gdi; + + if (!gdi || !gdi->primary || !gdi->primary->hdc) + return FALSE; + + hwnd = ctx->common.context.gdi->primary->hdc->hwnd; + + if (!hwnd) + return FALSE; + + ninvalid = hwnd->ninvalid; + + if (ninvalid < 1) + return TRUE; + + cinvalid = hwnd->cinvalid; + + if (!cinvalid) + return FALSE; + + x1 = cinvalid[0].x; + y1 = cinvalid[0].y; + x2 = cinvalid[0].x + cinvalid[0].w; + y2 = cinvalid[0].y + cinvalid[0].h; + + for (int i = 0; i < ninvalid; i++) + { + x1 = MIN(x1, cinvalid[i].x); + y1 = MIN(y1, cinvalid[i].y); + x2 = MAX(x2, cinvalid[i].x + cinvalid[i].w); + y2 = MAX(y2, cinvalid[i].y + cinvalid[i].h); + } + + freerdp_callback("OnGraphicsUpdate", "(JIIII)V", (jlong)context->instance, x1, y1, x2 - x1, + y2 - y1); + + hwnd->invalid->null = TRUE; + hwnd->ninvalid = 0; + return TRUE; +} + +static BOOL android_desktop_resize(rdpContext* context) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->settings); + WINPR_ASSERT(context->instance); + + freerdp_callback("OnGraphicsResize", "(JIII)V", (jlong)context->instance, + freerdp_settings_get_uint32(context->settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(context->settings, FreeRDP_DesktopHeight), + freerdp_settings_get_uint32(context->settings, FreeRDP_ColorDepth)); + return TRUE; +} + +static BOOL android_pre_connect(freerdp* instance) +{ + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->context); + + rdpSettings* settings = instance->context->settings; + + if (!settings) + return FALSE; + + int rc = PubSub_SubscribeChannelConnected(instance->context->pubSub, + android_OnChannelConnectedEventHandler); + + if (rc != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "Could not subscribe to connect event handler [%08X]", rc); + return FALSE; + } + + rc = PubSub_SubscribeChannelDisconnected(instance->context->pubSub, + android_OnChannelDisconnectedEventHandler); + + if (rc != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "Could not subscribe to disconnect event handler [%08X]", rc); + return FALSE; + } + + freerdp_callback("OnPreConnect", "(J)V", (jlong)instance); + return TRUE; +} + +static BOOL android_Pointer_New(rdpContext* context, rdpPointer* pointer) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(pointer); + WINPR_ASSERT(context->gdi); + + return TRUE; +} + +static void android_Pointer_Free(rdpContext* context, rdpPointer* pointer) +{ + WINPR_ASSERT(context); +} + +static BOOL android_Pointer_Set(rdpContext* context, rdpPointer* pointer) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(pointer); + + return TRUE; +} + +static BOOL android_Pointer_SetPosition(rdpContext* context, UINT32 x, UINT32 y) +{ + WINPR_ASSERT(context); + + return TRUE; +} + +static BOOL android_Pointer_SetNull(rdpContext* context) +{ + WINPR_ASSERT(context); + + return TRUE; +} + +static BOOL android_Pointer_SetDefault(rdpContext* context) +{ + WINPR_ASSERT(context); + + return TRUE; +} + +static BOOL android_register_pointer(rdpGraphics* graphics) +{ + rdpPointer pointer = WINPR_C_ARRAY_INIT; + + if (!graphics) + return FALSE; + + pointer.size = sizeof(pointer); + pointer.New = android_Pointer_New; + pointer.Free = android_Pointer_Free; + pointer.Set = android_Pointer_Set; + pointer.SetNull = android_Pointer_SetNull; + pointer.SetDefault = android_Pointer_SetDefault; + pointer.SetPosition = android_Pointer_SetPosition; + graphics_register_pointer(graphics, &pointer); + return TRUE; +} + +static BOOL android_post_connect(freerdp* instance) +{ + rdpSettings* settings; + rdpUpdate* update; + + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->context); + + update = instance->context->update; + WINPR_ASSERT(update); + + settings = instance->context->settings; + WINPR_ASSERT(settings); + + if (!gdi_init(instance, PIXEL_FORMAT_RGBX32)) + return FALSE; + + if (!android_register_pointer(instance->context->graphics)) + return FALSE; + + update->BeginPaint = android_begin_paint; + update->EndPaint = android_end_paint; + update->DesktopResize = android_desktop_resize; + freerdp_callback("OnSettingsChanged", "(JIII)V", (jlong)instance, + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight), + freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth)); + freerdp_callback("OnConnectionSuccess", "(J)V", (jlong)instance); + return TRUE; +} + +static void android_post_disconnect(freerdp* instance) +{ + freerdp_callback("OnDisconnecting", "(J)V", (jlong)instance); + gdi_free(instance); +} + +static BOOL android_authenticate_int(freerdp* instance, char** username, char** password, + char** domain, const char* cb_name) +{ + JNIEnv* env; + jboolean attached = jni_attach_thread(&env); + jobject jstr1 = create_string_builder(env, *username); + jobject jstr2 = create_string_builder(env, *domain); + jobject jstr3 = create_string_builder(env, *password); + jboolean res; + res = freerdp_callback_bool_result(cb_name, + "(JLjava/lang/StringBuilder;" + "Ljava/lang/StringBuilder;" + "Ljava/lang/StringBuilder;)Z", + (jlong)instance, jstr1, jstr2, jstr3); + + if (res == JNI_TRUE) + { + // read back string values + free(*username); + *username = get_string_from_string_builder(env, jstr1); + free(*domain); + *domain = get_string_from_string_builder(env, jstr2); + free(*password); + *password = get_string_from_string_builder(env, jstr3); + } + + if (attached == JNI_TRUE) + jni_detach_thread(); + + return ((res == JNI_TRUE) ? TRUE : FALSE); +} + +static BOOL android_authenticate(freerdp* instance, char** username, char** password, char** domain) +{ + return android_authenticate_int(instance, username, password, domain, "OnAuthenticate"); +} + +static BOOL android_gw_authenticate(freerdp* instance, char** username, char** password, + char** domain) +{ + return android_authenticate_int(instance, username, password, domain, "OnGatewayAuthenticate"); +} + +static DWORD android_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) +{ + WLog_DBG(TAG, "Certificate details [%s:%" PRIu16 ":", host, port); + WLog_DBG(TAG, "\tSubject: %s", subject); + WLog_DBG(TAG, "\tIssuer: %s", issuer); + WLog_DBG(TAG, "\tThumbprint: %s", fingerprint); + WLog_DBG(TAG, + "The above X.509 certificate could not be verified, possibly because you do not have " + "the CA certificate in your certificate store, or the certificate has expired." + "Please look at the OpenSSL documentation on how to add a private CA to the store.\n"); + JNIEnv* env; + jboolean attached = jni_attach_thread(&env); + jstring jstr0 = (*env)->NewStringUTF(env, host); + jstring jstr1 = (*env)->NewStringUTF(env, common_name); + jstring jstr2 = (*env)->NewStringUTF(env, subject); + jstring jstr3 = (*env)->NewStringUTF(env, issuer); + jstring jstr4 = (*env)->NewStringUTF(env, fingerprint); + jint res = freerdp_callback_int_result("OnVerifyCertificateEx", + "(JLjava/lang/String;JLjava/lang/String;Ljava/lang/" + "String;Ljava/lang/String;Ljava/lang/String;J)I", + (jlong)instance, jstr0, (jlong)port, jstr1, jstr2, jstr3, + jstr4, (jlong)flags); + + if (attached == JNI_TRUE) + jni_detach_thread(); + + return res; +} + +static DWORD android_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) +{ + JNIEnv* env; + jboolean attached = jni_attach_thread(&env); + jstring jhost = (*env)->NewStringUTF(env, host); + jstring jstr0 = (*env)->NewStringUTF(env, common_name); + jstring jstr1 = (*env)->NewStringUTF(env, subject); + jstring jstr2 = (*env)->NewStringUTF(env, issuer); + jstring jstr3 = (*env)->NewStringUTF(env, new_fingerprint); + jstring jstr4 = (*env)->NewStringUTF(env, old_subject); + jstring jstr5 = (*env)->NewStringUTF(env, old_issuer); + jstring jstr6 = (*env)->NewStringUTF(env, old_fingerprint); + jint res = + freerdp_callback_int_result("OnVerifyChangedCertificateEx", + "(JLjava/lang/String;JLjava/lang/String;Ljava/lang/" + "String;Ljava/lang/String;Ljava/lang/String;" + "Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;J)I", + (jlong)instance, jhost, (jlong)port, jstr0, jstr1, jstr2, jstr3, + jstr4, jstr5, jstr6, (jlong)flags); + + if (attached == JNI_TRUE) + jni_detach_thread(); + + return res; +} + +static int android_freerdp_run(freerdp* instance) +{ + DWORD count; + DWORD status = WAIT_FAILED; + HANDLE handles[MAXIMUM_WAIT_OBJECTS]; + HANDLE inputEvent = nullptr; + const rdpSettings* settings = instance->context->settings; + rdpContext* context = instance->context; + + inputEvent = android_get_handle(instance); + + while (!freerdp_shall_disconnect_context(instance->context)) + { + DWORD tmp; + count = 0; + + handles[count++] = inputEvent; + + tmp = freerdp_get_event_handles(context, &handles[count], 64 - count); + + if (tmp == 0) + { + WLog_ERR(TAG, "freerdp_get_event_handles failed"); + break; + } + + count += tmp; + status = WaitForMultipleObjects(count, handles, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + WLog_ERR(TAG, "WaitForMultipleObjects failed with %u [%08X]", status, + (unsigned)GetLastError()); + break; + } + + if (!freerdp_check_event_handles(context)) + { + /* TODO: Auto reconnect + if (xf_auto_reconnect(instance)) + continue; + */ + WLog_ERR(TAG, "Failed to check FreeRDP file descriptor"); + status = GetLastError(); + break; + } + + if (freerdp_shall_disconnect_context(instance->context)) + break; + + if (android_check_handle(instance) != TRUE) + { + WLog_ERR(TAG, "Failed to check android file descriptor"); + status = GetLastError(); + break; + } + } + +disconnect: + WLog_INFO(TAG, "Prepare shutdown..."); + + return status; +} + +static DWORD WINAPI android_thread_func(LPVOID param) +{ + DWORD status = ERROR_BAD_ARGUMENTS; + freerdp* instance = param; + WLog_DBG(TAG, "Start..."); + + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->context); + + if (freerdp_client_start(instance->context) != CHANNEL_RC_OK) + goto fail; + + WLog_DBG(TAG, "Connect..."); + + if (!freerdp_connect(instance)) + status = GetLastError(); + else + { + status = android_freerdp_run(instance); + WLog_DBG(TAG, "Disconnect..."); + + if (!freerdp_disconnect(instance)) + status = GetLastError(); + } + + WLog_DBG(TAG, "Stop..."); + + if (freerdp_client_stop(instance->context) != CHANNEL_RC_OK) + goto fail; + +fail: + WLog_DBG(TAG, "Session ended with %08" PRIX32 "", status); + + if (status == CHANNEL_RC_OK) + freerdp_callback("OnDisconnected", "(J)V", (jlong)instance); + else + freerdp_callback("OnConnectionFailure", "(J)V", (jlong)instance); + + WLog_DBG(TAG, "Quit."); + ExitThread(status); + return status; +} + +static BOOL android_client_new(freerdp* instance, rdpContext* context) +{ + WINPR_ASSERT(instance); + WINPR_ASSERT(context); + + if (!android_event_queue_init(instance)) + return FALSE; + + instance->PreConnect = android_pre_connect; + instance->PostConnect = android_post_connect; + instance->PostDisconnect = android_post_disconnect; + instance->Authenticate = android_authenticate; + instance->GatewayAuthenticate = android_gw_authenticate; + instance->VerifyCertificateEx = android_verify_certificate_ex; + instance->VerifyChangedCertificateEx = android_verify_changed_certificate_ex; + instance->LogonErrorInfo = nullptr; + return TRUE; +} + +static void android_client_free(freerdp* instance, rdpContext* context) +{ + if (!context) + return; + + android_event_queue_uninit(instance); +} + +static int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints) +{ + WINPR_ASSERT(pEntryPoints); + + ZeroMemory(pEntryPoints, sizeof(RDP_CLIENT_ENTRY_POINTS)); + + pEntryPoints->Version = RDP_CLIENT_INTERFACE_VERSION; + pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1); + pEntryPoints->GlobalInit = nullptr; + pEntryPoints->GlobalUninit = nullptr; + pEntryPoints->ContextSize = sizeof(androidContext); + pEntryPoints->ClientNew = android_client_new; + pEntryPoints->ClientFree = android_client_free; + pEntryPoints->ClientStart = nullptr; + pEntryPoints->ClientStop = nullptr; + return 0; +} + +JNIEXPORT jlong JNICALL Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1new( + JNIEnv* env, jclass cls, jobject context) +{ + jclass contextClass; + jclass fileClass; + jobject filesDirObj; + jmethodID getFilesDirID; + jmethodID getAbsolutePathID; + jstring path; + const char* raw; + char* envStr; + RDP_CLIENT_ENTRY_POINTS clientEntryPoints; + rdpContext* ctx; +#if defined(WITH_GPROF) + setenv("CPUPROFILE_FREQUENCY", "200", 1); + monstartup("libfreerdp-android.so"); +#endif + contextClass = (*env)->FindClass(env, JAVA_CONTEXT_CLASS); + fileClass = (*env)->FindClass(env, JAVA_FILE_CLASS); + + if (!contextClass || !fileClass) + { + WLog_FATAL(TAG, "Failed to load class references %s=%p, %s=%p", JAVA_CONTEXT_CLASS, + (void*)contextClass, JAVA_FILE_CLASS, (void*)fileClass); + return (jlong) nullptr; + } + + getFilesDirID = + (*env)->GetMethodID(env, contextClass, "getFilesDir", "()L" JAVA_FILE_CLASS ";"); + + if (!getFilesDirID) + { + WLog_FATAL(TAG, "Failed to find method ID getFilesDir ()L" JAVA_FILE_CLASS ";"); + return (jlong) nullptr; + } + + getAbsolutePathID = + (*env)->GetMethodID(env, fileClass, "getAbsolutePath", "()Ljava/lang/String;"); + + if (!getAbsolutePathID) + { + WLog_FATAL(TAG, "Failed to find method ID getAbsolutePath ()Ljava/lang/String;"); + return (jlong) nullptr; + } + + filesDirObj = (*env)->CallObjectMethod(env, context, getFilesDirID); + + if (!filesDirObj) + { + WLog_FATAL(TAG, "Failed to call getFilesDir"); + return (jlong) nullptr; + } + + path = (*env)->CallObjectMethod(env, filesDirObj, getAbsolutePathID); + + if (!path) + { + WLog_FATAL(TAG, "Failed to call getAbsolutePath"); + return (jlong) nullptr; + } + + raw = (*env)->GetStringUTFChars(env, path, 0); + + if (!raw) + { + WLog_FATAL(TAG, "Failed to get C string from java string"); + return (jlong) nullptr; + } + + envStr = _strdup(raw); + (*env)->ReleaseStringUTFChars(env, path, raw); + + if (!envStr) + { + WLog_FATAL(TAG, "_strdup(%s) failed", raw); + return (jlong) nullptr; + } + + if (setenv("HOME", _strdup(envStr), 1) != 0) + { + char ebuffer[256] = WINPR_C_ARRAY_INIT; + WLog_FATAL(TAG, "Failed to set environment HOME=%s %s [%d]", envStr, + winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno); + return (jlong) nullptr; + } + + RdpClientEntry(&clientEntryPoints); + ctx = freerdp_client_context_new(&clientEntryPoints); + + if (!ctx) + return (jlong) nullptr; + + return (jlong)ctx->instance; +} + +JNIEXPORT void JNICALL Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1free( + JNIEnv* env, jclass cls, jlong instance) +{ + freerdp* inst = (freerdp*)instance; + + if (inst) + freerdp_client_context_free(inst->context); + +#if defined(WITH_GPROF) + moncleanup(); +#endif +} + +JNIEXPORT jstring JNICALL +Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1get_1last_1error_1string(JNIEnv* env, + jclass cls, + jlong instance) +{ + freerdp* inst = (freerdp*)instance; + + if (!inst || !inst->context) + return (*env)->NewStringUTF(env, ""); + + return (*env)->NewStringUTF( + env, freerdp_get_last_error_string(freerdp_get_last_error(inst->context))); +} + +JNIEXPORT jboolean JNICALL +Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1parse_1arguments(JNIEnv* env, jclass cls, + jlong instance, + jobjectArray arguments) +{ + freerdp* inst = (freerdp*)instance; + int count; + char** argv; + DWORD status; + + if (!inst || !inst->context) + return JNI_FALSE; + + count = (*env)->GetArrayLength(env, arguments); + argv = calloc(count, sizeof(char*)); + + if (!argv) + return JNI_TRUE; + + for (int i = 0; i < count; i++) + { + jstring str = (jstring)(*env)->GetObjectArrayElement(env, arguments, i); + const char* raw = (*env)->GetStringUTFChars(env, str, 0); + argv[i] = _strdup(raw); + (*env)->ReleaseStringUTFChars(env, str, raw); + } + + status = + freerdp_client_settings_parse_command_line(inst->context->settings, count, argv, FALSE); + + for (int i = 0; i < count; i++) + free(argv[i]); + + free(argv); + return (status == 0) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jboolean JNICALL Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1connect( + JNIEnv* env, jclass cls, jlong instance) +{ + freerdp* inst = (freerdp*)instance; + + if (!inst || !inst->context) + { + WLog_FATAL(TAG, "(env=%p, cls=%p, instance=%" PRId64, (void*)env, (void*)cls, + (int64_t)instance); + return JNI_FALSE; + } + + androidContext* ctx = (androidContext*)inst->context; + + if (!(ctx->thread = CreateThread(nullptr, 0, android_thread_func, inst, 0, nullptr))) + { + return JNI_FALSE; + } + + return JNI_TRUE; +} + +JNIEXPORT jboolean JNICALL Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1disconnect( + JNIEnv* env, jclass cls, jlong instance) +{ + freerdp* inst = (freerdp*)instance; + + if (!inst || !inst->context || !cls || !env) + { + WLog_FATAL(TAG, "(env=%p, cls=%p, instance=%" PRId64, (void*)env, (void*)cls, + (int64_t)instance); + return JNI_FALSE; + } + + androidContext* ctx = (androidContext*)inst->context; + ANDROID_EVENT* event = (ANDROID_EVENT*)android_event_disconnect_new(); + + if (!event) + return JNI_FALSE; + + if (!android_push_event(inst, event)) + { + android_event_free((ANDROID_EVENT*)event); + return JNI_FALSE; + } + + if (!freerdp_abort_connect_context(inst->context)) + return JNI_FALSE; + + return JNI_TRUE; +} + +JNIEXPORT jboolean JNICALL +Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1update_1graphics(JNIEnv* env, jclass cls, + jlong instance, + jobject bitmap, jint x, + jint y, jint width, + jint height) +{ + UINT32 DstFormat; + jboolean rc; + int ret; + void* pixels; + AndroidBitmapInfo info; + freerdp* inst = (freerdp*)instance; + rdpGdi* gdi; + + if (!env || !cls || !inst) + { + WLog_FATAL(TAG, "(env=%p, cls=%p, instance=%" PRId64, (void*)env, (void*)cls, + (int64_t)instance); + return JNI_FALSE; + } + + gdi = inst->context->gdi; + + if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) + { + WLog_FATAL(TAG, "AndroidBitmap_getInfo() failed ! error=%d", ret); + return JNI_FALSE; + } + + if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixels)) < 0) + { + WLog_FATAL(TAG, "AndroidBitmap_lockPixels() failed ! error=%d", ret); + return JNI_FALSE; + } + + rc = JNI_TRUE; + + switch (info.format) + { + case ANDROID_BITMAP_FORMAT_RGBA_8888: + DstFormat = PIXEL_FORMAT_RGBX32; + break; + + case ANDROID_BITMAP_FORMAT_RGB_565: + DstFormat = PIXEL_FORMAT_RGB16; + break; + + case ANDROID_BITMAP_FORMAT_RGBA_4444: + case ANDROID_BITMAP_FORMAT_A_8: + case ANDROID_BITMAP_FORMAT_NONE: + default: + rc = JNI_FALSE; + break; + } + + if (rc) + { + rc = freerdp_image_copy(pixels, DstFormat, info.stride, x, y, width, height, + gdi->primary_buffer, gdi->dstFormat, gdi->stride, x, y, + &gdi->palette, FREERDP_FLIP_NONE); + } + + if ((ret = AndroidBitmap_unlockPixels(env, bitmap)) < 0) + { + WLog_FATAL(TAG, "AndroidBitmap_unlockPixels() failed ! error=%d", ret); + return JNI_FALSE; + } + + return rc; +} + +JNIEXPORT jboolean JNICALL +Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1send_1key_1event(JNIEnv* env, jclass cls, + jlong instance, + jint keycode, + jboolean down) +{ + DWORD scancode; + ANDROID_EVENT* event; + freerdp* inst = (freerdp*)instance; + scancode = GetVirtualScanCodeFromVirtualKeyCode(keycode, 4); + int flags = (down == JNI_TRUE) ? KBD_FLAGS_DOWN : KBD_FLAGS_RELEASE; + flags |= (scancode & KBDEXT) ? KBD_FLAGS_EXTENDED : 0; + event = (ANDROID_EVENT*)android_event_key_new(flags, scancode & 0xFF); + + if (!event) + return JNI_FALSE; + + if (!android_push_event(inst, event)) + { + android_event_free(event); + return JNI_FALSE; + } + + WLog_DBG(TAG, "send_key_event: %" PRIu32 ", %d", scancode, flags); + return JNI_TRUE; +} + +JNIEXPORT jboolean JNICALL +Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1send_1unicodekey_1event( + JNIEnv* env, jclass cls, jlong instance, jint keycode, jboolean down) +{ + ANDROID_EVENT* event; + freerdp* inst = (freerdp*)instance; + UINT16 flags = (down == JNI_TRUE) ? 0 : KBD_FLAGS_RELEASE; + event = (ANDROID_EVENT*)android_event_unicodekey_new(flags, keycode); + + if (!event) + return JNI_FALSE; + + if (!android_push_event(inst, event)) + { + android_event_free(event); + return JNI_FALSE; + } + + WLog_DBG(TAG, "send_unicodekey_event: %d", keycode); + return JNI_TRUE; +} + +JNIEXPORT jboolean JNICALL +Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1send_1cursor_1event( + JNIEnv* env, jclass cls, jlong instance, jint x, jint y, jint flags) +{ + ANDROID_EVENT* event; + freerdp* inst = (freerdp*)instance; + event = (ANDROID_EVENT*)android_event_cursor_new(flags, x, y); + + if (!event) + return JNI_FALSE; + + if (!android_push_event(inst, event)) + { + android_event_free(event); + return JNI_FALSE; + } + + WLog_DBG(TAG, "send_cursor_event: (%d, %d), %d", x, y, flags); + return JNI_TRUE; +} + +JNIEXPORT jboolean JNICALL +Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1send_1clipboard_1data(JNIEnv* env, + jclass cls, + jlong instance, + jstring jdata) +{ + ANDROID_EVENT* event; + freerdp* inst = (freerdp*)instance; + const char* data = jdata != nullptr ? (*env)->GetStringUTFChars(env, jdata, nullptr) : nullptr; + const size_t data_length = data ? (*env)->GetStringUTFLength(env, jdata) : 0; + jboolean ret = JNI_FALSE; + event = (ANDROID_EVENT*)android_event_clipboard_new((void*)data, data_length); + + if (!event) + goto out_fail; + + if (!android_push_event(inst, event)) + { + android_event_free(event); + goto out_fail; + } + + WLog_DBG(TAG, "send_clipboard_data: (%s)", data); + ret = JNI_TRUE; +out_fail: + + if (data) + (*env)->ReleaseStringUTFChars(env, jdata, data); + + return ret; +} + +JNIEXPORT jstring JNICALL +Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1get_1jni_1version(JNIEnv* env, jclass cls) +{ + return (*env)->NewStringUTF(env, FREERDP_JNI_VERSION); +} + +JNIEXPORT jboolean JNICALL +Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1has_1h264(JNIEnv* env, jclass cls) +{ + H264_CONTEXT* ctx = h264_context_new(FALSE); + if (!ctx) + return JNI_FALSE; + h264_context_free(ctx); + return JNI_TRUE; +} + +JNIEXPORT jstring JNICALL +Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1get_1version(JNIEnv* env, jclass cls) +{ + return (*env)->NewStringUTF(env, freerdp_get_version_string()); +} + +JNIEXPORT jstring JNICALL +Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1get_1build_1revision(JNIEnv* env, + jclass cls) +{ + return (*env)->NewStringUTF(env, freerdp_get_build_revision()); +} + +JNIEXPORT jstring JNICALL +Java_com_freerdp_freerdpcore_services_LibFreeRDP_freerdp_1get_1build_1config(JNIEnv* env, + jclass cls) +{ + return (*env)->NewStringUTF(env, freerdp_get_build_config()); +} + +static jclass gJavaActivityClass = nullptr; + +jint JNI_OnLoad(JavaVM* vm, void* reserved) +{ + JNIEnv* env; + setlocale(LC_ALL, ""); + WLog_DBG(TAG, "Setting up JNI environment..."); + + /* + if (freerdp_handle_signals() != 0) + { + WLog_FATAL(TAG, "Failed to register signal handler"); + return -1; + } + */ + if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) + { + WLog_FATAL(TAG, "Failed to get the environment"); + return -1; + } + + // Get SBCEngine activity class + jclass activityClass = (*env)->FindClass(env, JAVA_LIBFREERDP_CLASS); + + if (!activityClass) + { + WLog_FATAL(TAG, "failed to get %s class reference", JAVA_LIBFREERDP_CLASS); + return -1; + } + + /* create global reference for class */ + gJavaActivityClass = (*env)->NewGlobalRef(env, activityClass); + g_JavaVm = vm; + return init_callback_environment(vm, env); +} + +void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) +{ + JNIEnv* env; + WLog_DBG(TAG, "Tearing down JNI environment..."); + + if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) + { + WLog_FATAL(TAG, "Failed to get the environment"); + return; + } + + if (gJavaActivityClass) + (*env)->DeleteGlobalRef(env, gJavaActivityClass); +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_freerdp.h b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_freerdp.h new file mode 100644 index 0000000..9f5157a --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_freerdp.h @@ -0,0 +1,43 @@ +/* + Android JNI Client Layer + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +#ifndef FREERDP_CLIENT_ANDROID_FREERDP_H +#define FREERDP_CLIENT_ANDROID_FREERDP_H + +#include + +#include +#include + +#include +#include + +#include "android_event.h" + +typedef struct +{ + rdpClientContext common; + + ANDROID_EVENT_QUEUE* event_queue; + HANDLE thread; + + BOOL is_connected; + + BOOL clipboardSync; + wClipboard* clipboard; + UINT32 numServerFormats; + UINT32 requestedFormatId; + HANDLE clipboardRequestEvent; + CLIPRDR_FORMAT* serverFormats; + CliprdrClientContext* cliprdr; + UINT32 clipboardCapabilities; +} androidContext; + +#endif /* FREERDP_CLIENT_ANDROID_FREERDP_H */ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_freerdp_jni.h b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_freerdp_jni.h new file mode 100644 index 0000000..c0055f5 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_freerdp_jni.h @@ -0,0 +1,27 @@ +/* + FreeRDP: A Remote Desktop Protocol client. + Android FreeRDP JNI Definitions + + Copyright 2010 Marc-Andre Moreau + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifndef FREERDP_CLIENT_ANDROID_FREERDP_JNI_H +#define FREERDP_CLIENT_ANDROID_FREERDP_JNI_H + +#define JAVA_LIBFREERDP_CLASS "com/freerdp/freerdpcore/services/LibFreeRDP" +#define JAVA_CONTEXT_CLASS "android/content/Context" +#define JAVA_FILE_CLASS "java/io/File" + +#endif /* FREERDP_CLIENT_ANDROID_FREERDP_JNI_H */ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_jni_callback.c b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_jni_callback.c new file mode 100644 index 0000000..9d964fc --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_jni_callback.c @@ -0,0 +1,226 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Android JNI Callback Helpers + * + * Copyright 2011-2013 Thincast Technologies GmbH, Author: Martin Fleisz + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include + +#include + +#include "android_jni_callback.h" +#include "android_freerdp_jni.h" + +#include +#define TAG CLIENT_TAG("android.callback") + +static JavaVM* jVM; +static jobject jLibFreeRDPObject; + +static const char* jLibFreeRDPPath = JAVA_LIBFREERDP_CLASS; + +static void jni_load_class(JNIEnv* env, const char* path, jobject* objptr) +{ + jclass class; + jmethodID method; + jobject object; + WLog_DBG(TAG, "jni_load_class: %s", path); + class = (*env)->FindClass(env, path); + + if (!class) + { + WLog_ERR(TAG, "jni_load_class: failed to find class %s", path); + goto finish; + } + + method = (*env)->GetMethodID(env, class, "", "()V"); + + if (!method) + { + WLog_ERR(TAG, "jni_load_class: failed to find class constructor of %s", path); + goto finish; + } + + object = (*env)->NewObject(env, class, method); + + if (!object) + { + WLog_ERR(TAG, "jni_load_class: failed create new object of %s", path); + goto finish; + } + + (*objptr) = (*env)->NewGlobalRef(env, object); +finish: + + while (0) + ; +} + +jint init_callback_environment(JavaVM* vm, JNIEnv* env) +{ + jVM = vm; + jni_load_class(env, jLibFreeRDPPath, &jLibFreeRDPObject); + return JNI_VERSION_1_6; +} + +/* attach current thread to jvm */ +jboolean jni_attach_thread(JNIEnv** env) +{ + if ((*jVM)->GetEnv(jVM, (void**)env, JNI_VERSION_1_4) != JNI_OK) + { + WLog_DBG(TAG, "android_java_callback: attaching current thread"); + (*jVM)->AttachCurrentThread(jVM, env, nullptr); + + if ((*jVM)->GetEnv(jVM, (void**)env, JNI_VERSION_1_4) != JNI_OK) + { + WLog_ERR(TAG, "android_java_callback: failed to obtain current JNI environment"); + } + + return JNI_TRUE; + } + + return JNI_FALSE; +} + +/* attach current thread to JVM */ +void jni_detach_thread() +{ + (*jVM)->DetachCurrentThread(jVM); +} + +/* callback with void result */ +static void java_callback_void(jobject obj, const char* callback, const char* signature, + va_list args) +{ + jclass jObjClass; + jmethodID jCallback; + jboolean attached; + JNIEnv* env; + WLog_DBG(TAG, "java_callback: %s (%s)", callback, signature); + attached = jni_attach_thread(&env); + jObjClass = (*env)->GetObjectClass(env, obj); + + if (!jObjClass) + { + WLog_ERR(TAG, "android_java_callback: failed to get class reference"); + goto finish; + } + + jCallback = (*env)->GetStaticMethodID(env, jObjClass, callback, signature); + + if (!jCallback) + { + WLog_ERR(TAG, "android_java_callback: failed to get method id"); + goto finish; + } + + (*env)->CallStaticVoidMethodV(env, jObjClass, jCallback, args); +finish: + + if (attached == JNI_TRUE) + jni_detach_thread(); +} + +/* callback with bool result */ +static jboolean java_callback_bool(jobject obj, const char* callback, const char* signature, + va_list args) +{ + jclass jObjClass; + jmethodID jCallback; + jboolean attached; + jboolean res = JNI_FALSE; + JNIEnv* env; + WLog_DBG(TAG, "java_callback: %s (%s)", callback, signature); + attached = jni_attach_thread(&env); + jObjClass = (*env)->GetObjectClass(env, obj); + + if (!jObjClass) + { + WLog_ERR(TAG, "android_java_callback: failed to get class reference"); + goto finish; + } + + jCallback = (*env)->GetStaticMethodID(env, jObjClass, callback, signature); + + if (!jCallback) + { + WLog_ERR(TAG, "android_java_callback: failed to get method id"); + goto finish; + } + + res = (*env)->CallStaticBooleanMethodV(env, jObjClass, jCallback, args); +finish: + + if (attached == JNI_TRUE) + jni_detach_thread(); + + return res; +} + +/* callback with int result */ +static jint java_callback_int(jobject obj, const char* callback, const char* signature, + va_list args) +{ + jclass jObjClass; + jmethodID jCallback; + jboolean attached; + jint res = -1; + JNIEnv* env; + WLog_DBG(TAG, "java_callback: %s (%s)", callback, signature); + attached = jni_attach_thread(&env); + jObjClass = (*env)->GetObjectClass(env, obj); + + if (!jObjClass) + { + WLog_ERR(TAG, "android_java_callback: failed to get class reference"); + goto finish; + } + + jCallback = (*env)->GetStaticMethodID(env, jObjClass, callback, signature); + + if (!jCallback) + { + WLog_ERR(TAG, "android_java_callback: failed to get method id"); + goto finish; + } + + res = (*env)->CallStaticIntMethodV(env, jObjClass, jCallback, args); +finish: + + if (attached == JNI_TRUE) + jni_detach_thread(); + + return res; +} + +/* callback to freerdp class */ +void freerdp_callback(const char* callback, const char* signature, ...) +{ + va_list vl = WINPR_C_ARRAY_INIT; + va_start(vl, signature); + java_callback_void(jLibFreeRDPObject, callback, signature, vl); + va_end(vl); +} + +jboolean freerdp_callback_bool_result(const char* callback, const char* signature, ...) +{ + va_list vl = WINPR_C_ARRAY_INIT; + va_start(vl, signature); + jboolean res = java_callback_bool(jLibFreeRDPObject, callback, signature, vl); + va_end(vl); + return res; +} + +jint freerdp_callback_int_result(const char* callback, const char* signature, ...) +{ + va_list vl = WINPR_C_ARRAY_INIT; + va_start(vl, signature); + jint res = java_callback_int(jLibFreeRDPObject, callback, signature, vl); + va_end(vl); + return res; +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_jni_callback.h b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_jni_callback.h new file mode 100644 index 0000000..861e40d --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_jni_callback.h @@ -0,0 +1,28 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Android JNI Callback Helpers + * + * Copyright 2010-2012 Marc-Andre Moreau + * Copyright 2011-2013 Thincast Technologies GmbH, Author: Martin Fleisz + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef FREERDP_CLIENT_ANDROID_JNI_CALLBACK_H +#define FREERDP_CLIENT_ANDROID_JNI_CALLBACK_H + +#include +#include +#include + +FREERDP_LOCAL jint init_callback_environment(JavaVM* vm, JNIEnv* env); +FREERDP_LOCAL jboolean jni_attach_thread(JNIEnv** env); +FREERDP_LOCAL void jni_detach_thread(void); +FREERDP_LOCAL void freerdp_callback(const char* callback, const char* signature, ...); +FREERDP_LOCAL jboolean freerdp_callback_bool_result(const char* callback, const char* signature, + ...); +FREERDP_LOCAL jint freerdp_callback_int_result(const char* callback, const char* signature, ...); + +#endif /* FREERDP_CLIENT_ANDROID_JNI_CALLBACK_H */ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_jni_utils.c b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_jni_utils.c new file mode 100644 index 0000000..0a21510 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_jni_utils.c @@ -0,0 +1,191 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Android Event System + * + * Copyright 2013 Felix Long + * Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "android_jni_utils.h" + +#include +#include +#include + +#include "android_jni_callback.h" + +#define TAG CLIENT_TAG("android.utils") + +JavaVM* g_JavaVm; + +JavaVM* getJavaVM() +{ + return g_JavaVm; +} + +JNIEnv* getJNIEnv() +{ + JNIEnv* env = nullptr; + if ((*g_JavaVm)->GetEnv(g_JavaVm, (void**)&env, JNI_VERSION_1_4) != JNI_OK) + { + WLog_FATAL(TAG, "Failed to obtain JNIEnv"); + return nullptr; + } + return env; +} + +jobject create_string_builder(JNIEnv* env, char* initialStr) +{ + jclass cls; + jmethodID methodId; + jobject obj; + + // get class + cls = (*env)->FindClass(env, "java/lang/StringBuilder"); + if (!cls) + return nullptr; + + if (initialStr) + { + // get method id for constructor + methodId = (*env)->GetMethodID(env, cls, "", "(Ljava/lang/String;)V"); + if (!methodId) + return nullptr; + + // create string that holds our initial string + jstring jstr = (*env)->NewStringUTF(env, initialStr); + + // construct new StringBuilder + obj = (*env)->NewObject(env, cls, methodId, jstr); + } + else + { + // get method id for constructor + methodId = (*env)->GetMethodID(env, cls, "", "()V"); + if (!methodId) + return nullptr; + + // construct new StringBuilder + obj = (*env)->NewObject(env, cls, methodId); + } + + return obj; +} + +char* get_string_from_string_builder(JNIEnv* env, jobject strBuilder) +{ + jclass cls; + jmethodID methodId; + jstring strObj; + const char* native_str; + char* result; + + // get class + cls = (*env)->FindClass(env, "java/lang/StringBuilder"); + if (!cls) + return nullptr; + + // get method id for constructor + methodId = (*env)->GetMethodID(env, cls, "toString", "()Ljava/lang/String;"); + if (!methodId) + return nullptr; + + // get jstring representation of our buffer + strObj = (*env)->CallObjectMethod(env, strBuilder, methodId); + + // read string + native_str = (*env)->GetStringUTFChars(env, strObj, nullptr); + if (!native_str) + return nullptr; + result = _strdup(native_str); + (*env)->ReleaseStringUTFChars(env, strObj, native_str); + + return result; +} + +jstring jniNewStringUTF(JNIEnv* env, const char* in, int len) +{ + jstring out = nullptr; + jchar* unicode = nullptr; + jint result_size = 0; + unsigned char* utf8 = (unsigned char*)in; + + if (!in) + { + return nullptr; + } + if (len < 0) + len = strlen(in); + + unicode = (jchar*)malloc(sizeof(jchar) * (len + 1)); + if (!unicode) + { + return nullptr; + } + + for (jint i = 0; i < len; i++) + { + unsigned char one = utf8[i]; + switch (one >> 4) + { + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + unicode[result_size++] = one; + break; + case 0x08: + case 0x09: + case 0x0a: + case 0x0b: + // case 0x0f: + /* + * Bit pattern 10xx or 1111, which are illegal start bytes. + * Note: 1111 is valid for normal UTF-8, but not the + * modified UTF-8 used here. + */ + break; + case 0x0f: + case 0x0e: + // Bit pattern 111x, so there are two additional bytes. + if (i < (len - 2)) + { + unsigned char two = utf8[i + 1]; + unsigned char three = utf8[i + 2]; + if ((two & 0xc0) == 0x80 && (three & 0xc0) == 0x80) + { + i += 2; + unicode[result_size++] = + ((one & 0x0f) << 12) | ((two & 0x3f) << 6) | (three & 0x3f); + } + } + break; + case 0x0c: + case 0x0d: + // Bit pattern 110x, so there is one additional byte. + if (i < (len - 1)) + { + unsigned char two = utf8[i + 1]; + if ((two & 0xc0) == 0x80) + { + i += 1; + unicode[result_size++] = ((one & 0x1f) << 6) | (two & 0x3f); + } + } + break; + } + } + + out = (*env)->NewString(env, unicode, result_size); + free(unicode); + + return out; +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_jni_utils.h b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_jni_utils.h new file mode 100644 index 0000000..0ac804d --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/cpp/android_jni_utils.h @@ -0,0 +1,36 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Android Event System + * + * Copyright 2013 Felix Long + * Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#ifndef FREERDP_CLIENT_ANDROID_JNI_UTILS_H +#define FREERDP_CLIENT_ANDROID_JNI_UTILS_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + + FREERDP_LOCAL JNIEnv* getJNIEnv(); + FREERDP_LOCAL JavaVM* getJavaVM(); + + FREERDP_LOCAL char* get_string_from_string_builder(JNIEnv* env, jobject strBuilder); + FREERDP_LOCAL jobject create_string_builder(JNIEnv* env, char* initialStr); + FREERDP_LOCAL jstring jniNewStringUTF(JNIEnv* env, const char* in, int len); + + FREERDP_LOCAL extern JavaVM* g_JavaVm; + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_CLIENT_ANDROID_JNI_UTILS_H */ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/GlobalApp.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/GlobalApp.java new file mode 100644 index 0000000..14b1156 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/GlobalApp.java @@ -0,0 +1,211 @@ +/* + Android Main Application + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.application; + +import android.app.Application; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Uri; +import android.util.Log; + +import com.freerdp.freerdpcore.domain.BookmarkBase; +import com.freerdp.freerdpcore.presentation.ApplicationSettingsActivity; +import com.freerdp.freerdpcore.services.BookmarkDB; +import com.freerdp.freerdpcore.services.HistoryDB; +import com.freerdp.freerdpcore.services.LibFreeRDP; +import com.freerdp.freerdpcore.services.ManualBookmarkGateway; +import com.freerdp.freerdpcore.services.QuickConnectHistoryGateway; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; + +public class GlobalApp extends Application implements LibFreeRDP.EventListener +{ + // event notification defines + public static final String EVENT_TYPE = "EVENT_TYPE"; + public static final String EVENT_PARAM = "EVENT_PARAM"; + public static final String EVENT_STATUS = "EVENT_STATUS"; + public static final String EVENT_ERROR = "EVENT_ERROR"; + public static final String ACTION_EVENT_FREERDP = "com.freerdp.freerdp.event.freerdp"; + public static final int FREERDP_EVENT_CONNECTION_SUCCESS = 1; + public static final int FREERDP_EVENT_CONNECTION_FAILURE = 2; + public static final int FREERDP_EVENT_DISCONNECTED = 3; + private static final String TAG = "GlobalApp"; + public static boolean ConnectedTo3G = false; + private static Map sessionMap; + private static BookmarkDB bookmarkDB; + private static ManualBookmarkGateway manualBookmarkGateway; + + private static HistoryDB historyDB; + private static QuickConnectHistoryGateway quickConnectHistoryGateway; + + // timer for disconnecting sessions after the screen was turned off + private static Timer disconnectTimer = null; + + public static ManualBookmarkGateway getManualBookmarkGateway() + { + return manualBookmarkGateway; + } + + public static QuickConnectHistoryGateway getQuickConnectHistoryGateway() + { + return quickConnectHistoryGateway; + } + + // Disconnect handling for Screen on/off events + public void startDisconnectTimer() + { + final int timeoutMinutes = ApplicationSettingsActivity.getDisconnectTimeout(this); + if (timeoutMinutes > 0) + { + // start disconnect timeout... + disconnectTimer = new Timer(); + disconnectTimer.schedule(new DisconnectTask(), (long)timeoutMinutes * 60 * 1000); + } + } + + static public void cancelDisconnectTimer() + { + // cancel any pending timer events + if (disconnectTimer != null) + { + disconnectTimer.cancel(); + disconnectTimer.purge(); + disconnectTimer = null; + } + } + + // RDP session handling + static public SessionState createSession(BookmarkBase bookmark, Context context) + { + SessionState session = new SessionState(LibFreeRDP.newInstance(context), bookmark); + sessionMap.put(session.getInstance(), session); + return session; + } + + static public SessionState createSession(Uri openUri, Context context) + { + SessionState session = new SessionState(LibFreeRDP.newInstance(context), openUri); + sessionMap.put(session.getInstance(), session); + return session; + } + + static public SessionState getSession(long instance) + { + return sessionMap.get(instance); + } + + static public Collection getSessions() + { + // return a copy of the session items + return new ArrayList<>(sessionMap.values()); + } + + static public void freeSession(long instance) + { + if (GlobalApp.sessionMap.containsKey(instance)) + { + GlobalApp.sessionMap.remove(instance); + LibFreeRDP.freeInstance(instance); + } + } + + @Override public void onCreate() + { + super.onCreate(); + + /* Initialize preferences. */ + ApplicationSettingsActivity.get(this); + + sessionMap = Collections.synchronizedMap(new HashMap()); + + LibFreeRDP.setEventListener(this); + + bookmarkDB = new BookmarkDB(this); + + manualBookmarkGateway = new ManualBookmarkGateway(bookmarkDB); + + historyDB = new HistoryDB(this); + quickConnectHistoryGateway = new QuickConnectHistoryGateway(historyDB); + + ConnectedTo3G = NetworkStateReceiver.isConnectedTo3G(this); + + // init screen receiver here (this can't be declared in AndroidManifest - refer to: + // http://thinkandroid.wordpress.com/2010/01/24/handling-screen-off-and-screen-on-intents/ + IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON); + filter.addAction(Intent.ACTION_SCREEN_OFF); + registerReceiver(new ScreenReceiver(), filter, RECEIVER_EXPORTED); + } + + // helper to send FreeRDP notifications + private void sendRDPNotification(int type, long param) + { + // send broadcast + Intent intent = new Intent(ACTION_EVENT_FREERDP); + intent.putExtra(EVENT_TYPE, type); + intent.putExtra(EVENT_PARAM, param); + sendBroadcast(intent); + } + + @Override public void OnPreConnect(long instance) + { + Log.v(TAG, "OnPreConnect"); + } + + // ////////////////////////////////////////////////////////////////////// + // Implementation of LibFreeRDP.EventListener + public void OnConnectionSuccess(long instance) + { + Log.v(TAG, "OnConnectionSuccess"); + sendRDPNotification(FREERDP_EVENT_CONNECTION_SUCCESS, instance); + } + + public void OnConnectionFailure(long instance) + { + Log.v(TAG, "OnConnectionFailure"); + + // send notification to session activity + sendRDPNotification(FREERDP_EVENT_CONNECTION_FAILURE, instance); + } + + public void OnDisconnecting(long instance) + { + Log.v(TAG, "OnDisconnecting"); + } + + public void OnDisconnected(long instance) + { + Log.v(TAG, "OnDisconnected"); + sendRDPNotification(FREERDP_EVENT_DISCONNECTED, instance); + } + + // TimerTask for disconnecting sessions after screen was turned off + private static class DisconnectTask extends TimerTask + { + @Override public void run() + { + Log.v("DisconnectTask", "Doing action"); + + // disconnect any running rdp session + Collection sessions = GlobalApp.getSessions(); + for (SessionState session : sessions) + { + LibFreeRDP.disconnect(session.getInstance()); + } + } + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/NetworkStateReceiver.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/NetworkStateReceiver.java new file mode 100644 index 0000000..ba3bbec --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/NetworkStateReceiver.java @@ -0,0 +1,68 @@ +/* + Network State Receiver + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.application; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.util.Log; + +import androidx.annotation.NonNull; + +public class NetworkStateReceiver extends BroadcastReceiver +{ + + public static boolean isConnectedTo3G(Context context) + { + ConnectivityManager connectivity = + (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo info = connectivity.getActiveNetworkInfo(); + + // no connection or background data disabled + if (info == null || !info.isConnected()) + return false; + + return (info.getType() != ConnectivityManager.TYPE_WIFI && + info.getType() != ConnectivityManager.TYPE_WIMAX); + } + + @Override public void onReceive(@NonNull Context context, @NonNull Intent intent) + { + String action = intent.getAction(); + if (!action.equals("android.net.conn.CONNECTIVITY_CHANGE")) + { + return; + } + + // check if we are connected via 3g or wlan + if (intent.getExtras() != null) + { + NetworkInfo info = + (NetworkInfo)intent.getExtras().get(ConnectivityManager.EXTRA_NETWORK_INFO); + + // are we connected at all? + if (info != null) + { + if (info.isConnected()) + { + // see if we are connected through 3G or WiFi + Log.d("app", "Connected via type " + info.getTypeName()); + GlobalApp.ConnectedTo3G = (info.getType() != ConnectivityManager.TYPE_WIFI && + info.getType() != ConnectivityManager.TYPE_WIMAX); + } + + Log.v("NetworkState", info.toString()); + } + } + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/ScreenReceiver.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/ScreenReceiver.java new file mode 100644 index 0000000..58b788f --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/ScreenReceiver.java @@ -0,0 +1,30 @@ +/* + Helper class to receive notifications when the screen is turned on/off + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.application; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +public class ScreenReceiver extends BroadcastReceiver +{ + + @Override public void onReceive(Context context, Intent intent) + { + GlobalApp app = (GlobalApp)context.getApplicationContext(); + Log.v("ScreenReceiver", "Received action: " + intent.getAction()); + if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) + app.startDisconnectTimer(); + else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) + GlobalApp.cancelDisconnectTimer(); + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/SessionState.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/SessionState.java new file mode 100644 index 0000000..3b03991 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/SessionState.java @@ -0,0 +1,129 @@ +/* + Session State class + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.application; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +import com.freerdp.freerdpcore.domain.BookmarkBase; +import com.freerdp.freerdpcore.services.LibFreeRDP; + +public class SessionState implements Parcelable +{ + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public SessionState createFromParcel(Parcel in) + { + return new SessionState(in); + } + + @Override public SessionState[] newArray(int size) + { + return new SessionState[size]; + } + }; + private final long instance; + private final BookmarkBase bookmark; + private final Uri openUri; + private BitmapDrawable surface; + private LibFreeRDP.UIEventListener uiEventListener; + + public SessionState(Parcel parcel) + { + instance = parcel.readLong(); + bookmark = parcel.readParcelable(null); + openUri = parcel.readParcelable(null); + + Bitmap bitmap = parcel.readParcelable(null); + surface = new BitmapDrawable(bitmap); + } + + public SessionState(long instance, BookmarkBase bookmark) + { + this.instance = instance; + this.bookmark = bookmark; + this.openUri = null; + this.uiEventListener = null; + } + + public SessionState(long instance, Uri openUri) + { + this.instance = instance; + this.bookmark = null; + this.openUri = openUri; + this.uiEventListener = null; + } + + public void connect(Context context) + { + if (bookmark != null) + { + LibFreeRDP.setConnectionInfo(context, instance, bookmark); + } + else + { + LibFreeRDP.setConnectionInfo(context, instance, openUri); + } + LibFreeRDP.connect(instance); + } + + public long getInstance() + { + return instance; + } + + public BookmarkBase getBookmark() + { + return bookmark; + } + + public Uri getOpenUri() + { + return openUri; + } + + public LibFreeRDP.UIEventListener getUIEventListener() + { + return uiEventListener; + } + + public void setUIEventListener(LibFreeRDP.UIEventListener uiEventListener) + { + this.uiEventListener = uiEventListener; + } + + public BitmapDrawable getSurface() + { + return surface; + } + + public void setSurface(BitmapDrawable surface) + { + this.surface = surface; + } + + @Override public int describeContents() + { + return 0; + } + + @Override public void writeToParcel(Parcel out, int flags) + { + out.writeLong(instance); + out.writeParcelable(bookmark, flags); + out.writeParcelable(openUri, flags); + out.writeParcelable(surface.getBitmap(), flags); + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/BookmarkBase.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/BookmarkBase.java new file mode 100644 index 0000000..3a85e33 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/BookmarkBase.java @@ -0,0 +1,1047 @@ +/* + Defines base attributes of a bookmark object + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.domain; + +import android.content.SharedPreferences; +import android.os.Parcel; +import android.os.Parcelable; + +import com.freerdp.freerdpcore.application.GlobalApp; + +import java.util.Locale; + +public class BookmarkBase implements Parcelable, Cloneable +{ + public static final int TYPE_INVALID = -1; + public static final int TYPE_MANUAL = 1; + public static final int TYPE_QUICKCONNECT = 2; + public static final int TYPE_PLACEHOLDER = 3; + public static final int TYPE_CUSTOM_BASE = 1000; + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public BookmarkBase createFromParcel(Parcel in) + { + return new BookmarkBase(in); + } + + @Override public BookmarkBase[] newArray(int size) + { + return new BookmarkBase[size]; + } + }; + protected int type; + private long id; + private String label; + private String username; + private String password; + private String domain; + private ScreenSettings screenSettings; + private PerformanceFlags performanceFlags; + private AdvancedSettings advancedSettings; + private DebugSettings debugSettings; + + public BookmarkBase(Parcel parcel) + { + type = parcel.readInt(); + id = parcel.readLong(); + label = parcel.readString(); + username = parcel.readString(); + password = parcel.readString(); + domain = parcel.readString(); + + screenSettings = parcel.readParcelable(ScreenSettings.class.getClassLoader()); + performanceFlags = parcel.readParcelable(PerformanceFlags.class.getClassLoader()); + advancedSettings = parcel.readParcelable(AdvancedSettings.class.getClassLoader()); + debugSettings = parcel.readParcelable(DebugSettings.class.getClassLoader()); + } + + public BookmarkBase() + { + init(); + } + + private void init() + { + type = TYPE_INVALID; + id = -1; + label = ""; + username = ""; + password = ""; + domain = ""; + + screenSettings = new ScreenSettings(); + performanceFlags = new PerformanceFlags(); + advancedSettings = new AdvancedSettings(); + debugSettings = new DebugSettings(); + } + + @SuppressWarnings("unchecked") public T get() + { + return (T)this; + } + + public int getType() + { + return type; + } + + public long getId() + { + return id; + } + + public void setId(long id) + { + this.id = id; + } + + public String getLabel() + { + return label; + } + + public void setLabel(String label) + { + this.label = label; + } + + public String getUsername() + { + return username; + } + + public void setUsername(String username) + { + this.username = username; + } + + public String getPassword() + { + return password; + } + + public void setPassword(String password) + { + this.password = password; + } + + public String getDomain() + { + return domain; + } + + public void setDomain(String domain) + { + this.domain = domain; + } + + public ScreenSettings getScreenSettings() + { + return screenSettings; + } + + public void setScreenSettings(ScreenSettings screenSettings) + { + this.screenSettings = screenSettings; + } + + public PerformanceFlags getPerformanceFlags() + { + return performanceFlags; + } + + public void setPerformanceFlags(PerformanceFlags performanceFlags) + { + this.performanceFlags = performanceFlags; + } + + public AdvancedSettings getAdvancedSettings() + { + return advancedSettings; + } + + public void setAdvancedSettings(AdvancedSettings advancedSettings) + { + this.advancedSettings = advancedSettings; + } + + public DebugSettings getDebugSettings() + { + return debugSettings; + } + + public void setDebugSettings(DebugSettings debugSettings) + { + this.debugSettings = debugSettings; + } + + public ScreenSettings getActiveScreenSettings() + { + return (GlobalApp.ConnectedTo3G && advancedSettings.getEnable3GSettings()) + ? advancedSettings.getScreen3G() + : screenSettings; + } + + public PerformanceFlags getActivePerformanceFlags() + { + return (GlobalApp.ConnectedTo3G && advancedSettings.getEnable3GSettings()) + ? advancedSettings.getPerformance3G() + : performanceFlags; + } + + @Override public int describeContents() + { + return 0; + } + + @Override public void writeToParcel(Parcel out, int flags) + { + out.writeInt(type); + out.writeLong(id); + out.writeString(label); + out.writeString(username); + out.writeString(password); + out.writeString(domain); + + out.writeParcelable(screenSettings, flags); + out.writeParcelable(performanceFlags, flags); + out.writeParcelable(advancedSettings, flags); + out.writeParcelable(debugSettings, flags); + } + + // write to shared preferences + public void writeToSharedPreferences(SharedPreferences sharedPrefs) + { + + Locale locale = Locale.ENGLISH; + + SharedPreferences.Editor editor = sharedPrefs.edit(); + editor.clear(); + editor.putString("bookmark.label", label); + editor.putString("bookmark.username", username); + editor.putString("bookmark.password", password); + editor.putString("bookmark.domain", domain); + + editor.putInt("bookmark.colors", screenSettings.getColors()); + editor.putString("bookmark.resolution", + screenSettings.getResolutionString().toLowerCase(locale)); + editor.putInt("bookmark.width", screenSettings.getWidth()); + editor.putInt("bookmark.height", screenSettings.getHeight()); + + editor.putBoolean("bookmark.perf_remotefx", performanceFlags.getRemoteFX()); + editor.putBoolean("bookmark.perf_gfx", performanceFlags.getGfx()); + editor.putBoolean("bookmark.perf_gfx_h264", performanceFlags.getH264()); + editor.putBoolean("bookmark.perf_wallpaper", performanceFlags.getWallpaper()); + editor.putBoolean("bookmark.perf_font_smoothing", performanceFlags.getFontSmoothing()); + editor.putBoolean("bookmark.perf_desktop_composition", + performanceFlags.getDesktopComposition()); + editor.putBoolean("bookmark.perf_window_dragging", performanceFlags.getFullWindowDrag()); + editor.putBoolean("bookmark.perf_menu_animation", performanceFlags.getMenuAnimations()); + editor.putBoolean("bookmark.perf_themes", performanceFlags.getTheming()); + + editor.putBoolean("bookmark.enable_3g_settings", advancedSettings.getEnable3GSettings()); + + editor.putInt("bookmark.colors_3g", advancedSettings.getScreen3G().getColors()); + editor.putString("bookmark.resolution_3g", + advancedSettings.getScreen3G().getResolutionString().toLowerCase(locale)); + editor.putInt("bookmark.width_3g", advancedSettings.getScreen3G().getWidth()); + editor.putInt("bookmark.height_3g", advancedSettings.getScreen3G().getHeight()); + + editor.putBoolean("bookmark.perf_remotefx_3g", + advancedSettings.getPerformance3G().getRemoteFX()); + editor.putBoolean("bookmark.perf_gfx_3g", advancedSettings.getPerformance3G().getGfx()); + editor.putBoolean("bookmark.perf_gfx_h264_3g", + advancedSettings.getPerformance3G().getH264()); + editor.putBoolean("bookmark.perf_wallpaper_3g", + advancedSettings.getPerformance3G().getWallpaper()); + editor.putBoolean("bookmark.perf_font_smoothing_3g", + advancedSettings.getPerformance3G().getFontSmoothing()); + editor.putBoolean("bookmark.perf_desktop_composition_3g", + advancedSettings.getPerformance3G().getDesktopComposition()); + editor.putBoolean("bookmark.perf_window_dragging_3g", + advancedSettings.getPerformance3G().getFullWindowDrag()); + editor.putBoolean("bookmark.perf_menu_animation_3g", + advancedSettings.getPerformance3G().getMenuAnimations()); + editor.putBoolean("bookmark.perf_themes_3g", + advancedSettings.getPerformance3G().getTheming()); + + editor.putBoolean("bookmark.redirect_sdcard", advancedSettings.getRedirectSDCard()); + editor.putInt("bookmark.redirect_sound", advancedSettings.getRedirectSound()); + editor.putBoolean("bookmark.redirect_microphone", advancedSettings.getRedirectMicrophone()); + editor.putInt("bookmark.security", advancedSettings.getSecurity()); + editor.putString("bookmark.remote_program", advancedSettings.getRemoteProgram()); + editor.putString("bookmark.work_dir", advancedSettings.getWorkDir()); + editor.putBoolean("bookmark.console_mode", advancedSettings.getConsoleMode()); + + editor.putBoolean("bookmark.async_channel", debugSettings.getAsyncChannel()); + editor.putBoolean("bookmark.async_update", debugSettings.getAsyncUpdate()); + editor.putString("bookmark.debug_level", debugSettings.getDebugLevel()); + + editor.apply(); + } + + // read from shared preferences + public void readFromSharedPreferences(SharedPreferences sharedPrefs) + { + label = sharedPrefs.getString("bookmark.label", ""); + username = sharedPrefs.getString("bookmark.username", ""); + password = sharedPrefs.getString("bookmark.password", ""); + domain = sharedPrefs.getString("bookmark.domain", ""); + + screenSettings.setColors(sharedPrefs.getInt("bookmark.colors", 16)); + screenSettings.setResolution(sharedPrefs.getString("bookmark.resolution", "automatic"), + sharedPrefs.getInt("bookmark.width", 800), + sharedPrefs.getInt("bookmark.height", 600)); + + performanceFlags.setRemoteFX(sharedPrefs.getBoolean("bookmark.perf_remotefx", false)); + performanceFlags.setGfx(sharedPrefs.getBoolean("bookmark.perf_gfx", true)); + performanceFlags.setH264(sharedPrefs.getBoolean("bookmark.perf_gfx_h264", true)); + performanceFlags.setWallpaper(sharedPrefs.getBoolean("bookmark.perf_wallpaper", false)); + performanceFlags.setFontSmoothing( + sharedPrefs.getBoolean("bookmark.perf_font_smoothing", false)); + performanceFlags.setDesktopComposition( + sharedPrefs.getBoolean("bookmark.perf_desktop_composition", false)); + performanceFlags.setFullWindowDrag( + sharedPrefs.getBoolean("bookmark.perf_window_dragging", false)); + performanceFlags.setMenuAnimations( + sharedPrefs.getBoolean("bookmark.perf_menu_animation", false)); + performanceFlags.setTheming(sharedPrefs.getBoolean("bookmark.perf_themes", false)); + + advancedSettings.setEnable3GSettings( + sharedPrefs.getBoolean("bookmark.enable_3g_settings", false)); + + advancedSettings.getScreen3G().setColors(sharedPrefs.getInt("bookmark.colors_3g", 16)); + advancedSettings.getScreen3G().setResolution( + sharedPrefs.getString("bookmark.resolution_3g", "automatic"), + sharedPrefs.getInt("bookmark.width_3g", 800), + sharedPrefs.getInt("bookmark.height_3g", 600)); + + advancedSettings.getPerformance3G().setRemoteFX( + sharedPrefs.getBoolean("bookmark.perf_remotefx_3g", false)); + advancedSettings.getPerformance3G().setGfx( + sharedPrefs.getBoolean("bookmark.perf_gfx_3g", false)); + advancedSettings.getPerformance3G().setH264( + sharedPrefs.getBoolean("bookmark.perf_gfx_h264_3g", false)); + advancedSettings.getPerformance3G().setWallpaper( + sharedPrefs.getBoolean("bookmark.perf_wallpaper_3g", false)); + advancedSettings.getPerformance3G().setFontSmoothing( + sharedPrefs.getBoolean("bookmark.perf_font_smoothing_3g", false)); + advancedSettings.getPerformance3G().setDesktopComposition( + sharedPrefs.getBoolean("bookmark.perf_desktop_composition_3g", false)); + advancedSettings.getPerformance3G().setFullWindowDrag( + sharedPrefs.getBoolean("bookmark.perf_window_dragging_3g", false)); + advancedSettings.getPerformance3G().setMenuAnimations( + sharedPrefs.getBoolean("bookmark.perf_menu_animation_3g", false)); + advancedSettings.getPerformance3G().setTheming( + sharedPrefs.getBoolean("bookmark.perf_themes_3g", false)); + + advancedSettings.setRedirectSDCard( + sharedPrefs.getBoolean("bookmark.redirect_sdcard", false)); + advancedSettings.setRedirectSound(sharedPrefs.getInt("bookmark.redirect_sound", 0)); + advancedSettings.setRedirectMicrophone( + sharedPrefs.getBoolean("bookmark.redirect_microphone", false)); + advancedSettings.setSecurity(sharedPrefs.getInt("bookmark.security", 0)); + advancedSettings.setRemoteProgram(sharedPrefs.getString("bookmark.remote_program", "")); + advancedSettings.setWorkDir(sharedPrefs.getString("bookmark.work_dir", "")); + advancedSettings.setConsoleMode(sharedPrefs.getBoolean("bookmark.console_mode", false)); + + debugSettings.setAsyncChannel(sharedPrefs.getBoolean("bookmark.async_channel", true)); + debugSettings.setAsyncUpdate(sharedPrefs.getBoolean("bookmark.async_update", true)); + debugSettings.setDebugLevel(sharedPrefs.getString("bookmark.debug_level", "INFO")); + } + + // Cloneable + public Object clone() + { + try + { + return super.clone(); + } + catch (CloneNotSupportedException e) + { + return null; + } + } + + // performance flags + public static class PerformanceFlags implements Parcelable + { + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public PerformanceFlags createFromParcel(Parcel in) + { + return new PerformanceFlags(in); + } + + @Override public PerformanceFlags[] newArray(int size) + { + return new PerformanceFlags[size]; + } + }; + private boolean remotefx; + private boolean gfx; + private boolean h264; + private boolean wallpaper; + private boolean theming; + private boolean fullWindowDrag; + private boolean menuAnimations; + private boolean fontSmoothing; + private boolean desktopComposition; + + public PerformanceFlags() + { + remotefx = false; + gfx = true; + h264 = true; + wallpaper = false; + theming = false; + fullWindowDrag = false; + menuAnimations = false; + fontSmoothing = false; + desktopComposition = false; + } + + public PerformanceFlags(Parcel parcel) + { + remotefx = parcel.readInt() == 1; + gfx = parcel.readInt() == 1; + h264 = parcel.readInt() == 1; + wallpaper = parcel.readInt() == 1; + theming = parcel.readInt() == 1; + fullWindowDrag = (parcel.readInt() == 1); + menuAnimations = parcel.readInt() == 1; + fontSmoothing = parcel.readInt() == 1; + desktopComposition = parcel.readInt() == 1; + } + + public boolean getRemoteFX() + { + return remotefx; + } + + public void setRemoteFX(boolean remotefx) + { + this.remotefx = remotefx; + } + + public boolean getGfx() + { + return gfx; + } + + public void setGfx(boolean gfx) + { + this.gfx = gfx; + } + + public boolean getH264() + { + return h264; + } + + public void setH264(boolean h264) + { + this.h264 = h264; + } + + public boolean getWallpaper() + { + return wallpaper; + } + + public void setWallpaper(boolean wallpaper) + { + this.wallpaper = wallpaper; + } + + public boolean getTheming() + { + return theming; + } + + public void setTheming(boolean theming) + { + this.theming = theming; + } + + public boolean getFullWindowDrag() + { + return fullWindowDrag; + } + + public void setFullWindowDrag(boolean fullWindowDrag) + { + this.fullWindowDrag = fullWindowDrag; + } + + public boolean getMenuAnimations() + { + return menuAnimations; + } + + public void setMenuAnimations(boolean menuAnimations) + { + this.menuAnimations = menuAnimations; + } + + public boolean getFontSmoothing() + { + return fontSmoothing; + } + + public void setFontSmoothing(boolean fontSmoothing) + { + this.fontSmoothing = fontSmoothing; + } + + public boolean getDesktopComposition() + { + return desktopComposition; + } + + public void setDesktopComposition(boolean desktopComposition) + { + this.desktopComposition = desktopComposition; + } + + @Override public int describeContents() + { + return 0; + } + + @Override public void writeToParcel(Parcel out, int flags) + { + out.writeInt(remotefx ? 1 : 0); + out.writeInt(gfx ? 1 : 0); + out.writeInt(h264 ? 1 : 0); + out.writeInt(wallpaper ? 1 : 0); + out.writeInt(theming ? 1 : 0); + out.writeInt(fullWindowDrag ? 1 : 0); + out.writeInt(menuAnimations ? 1 : 0); + out.writeInt(fontSmoothing ? 1 : 0); + out.writeInt(desktopComposition ? 1 : 0); + } + } + + // Screen Settings class + public static class ScreenSettings implements Parcelable + { + public static final int FITSCREEN = -2; + public static final int AUTOMATIC = -1; + public static final int CUSTOM = 0; + public static final int PREDEFINED = 1; + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public ScreenSettings createFromParcel(Parcel in) + { + return new ScreenSettings(in); + } + + @Override public ScreenSettings[] newArray(int size) + { + return new ScreenSettings[size]; + } + }; + private int resolution; + private int colors; + private int width; + private int height; + + public ScreenSettings() + { + init(); + } + + public ScreenSettings(Parcel parcel) + { + resolution = parcel.readInt(); + colors = parcel.readInt(); + width = parcel.readInt(); + height = parcel.readInt(); + } + + private void validate() + { + switch (colors) + { + case 32: + case 24: + case 16: + case 15: + case 8: + break; + default: + colors = 32; + break; + } + + if ((width <= 0) || (width > 65536)) + { + width = 1024; + } + + if ((height <= 0) || (height > 65536)) + { + height = 768; + } + + switch (resolution) + { + case FITSCREEN: + case AUTOMATIC: + case CUSTOM: + case PREDEFINED: + break; + default: + resolution = AUTOMATIC; + break; + } + } + + private void init() + { + resolution = AUTOMATIC; + colors = 16; + width = 0; + height = 0; + } + + public void setResolution(String resolution, int width, int height) + { + if (resolution.contains("x")) + { + String[] dimensions = resolution.split("x"); + this.width = Integer.parseInt(dimensions[0]); + this.height = Integer.parseInt(dimensions[1]); + this.resolution = PREDEFINED; + } + else if (resolution.equalsIgnoreCase("custom")) + { + this.width = width; + this.height = height; + this.resolution = CUSTOM; + } + else if (resolution.equalsIgnoreCase("fitscreen")) + { + this.width = this.height = 0; + this.resolution = FITSCREEN; + } + else + { + this.width = this.height = 0; + this.resolution = AUTOMATIC; + } + } + + public int getResolution() + { + return resolution; + } + + public void setResolution(int resolution) + { + this.resolution = resolution; + + if (resolution == AUTOMATIC || resolution == FITSCREEN) + { + width = 0; + height = 0; + } + } + + public String getResolutionString() + { + if (isPredefined()) + return (width + "x" + height); + + return (isFitScreen() ? "fitscreen" : isAutomatic() ? "automatic" : "custom"); + } + + public boolean isPredefined() + { + validate(); + return (resolution == PREDEFINED); + } + + public boolean isAutomatic() + { + validate(); + return (resolution == AUTOMATIC); + } + + public boolean isFitScreen() + { + validate(); + return (resolution == FITSCREEN); + } + + public boolean isCustom() + { + validate(); + return (resolution == CUSTOM); + } + + public int getWidth() + { + validate(); + return width; + } + + public void setWidth(int width) + { + this.width = width; + } + + public int getHeight() + { + validate(); + return height; + } + + public void setHeight(int height) + { + this.height = height; + } + + public int getColors() + { + validate(); + return colors; + } + + public void setColors(int colors) + { + this.colors = colors; + } + + @Override public int describeContents() + { + return 0; + } + + @Override public void writeToParcel(Parcel out, int flags) + { + out.writeInt(resolution); + out.writeInt(colors); + out.writeInt(width); + out.writeInt(height); + } + } + + public static class DebugSettings implements Parcelable + { + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public DebugSettings createFromParcel(Parcel in) + { + return new DebugSettings(in); + } + + @Override public DebugSettings[] newArray(int size) + { + return new DebugSettings[size]; + } + }; + private String debug; + private boolean asyncChannel; + private boolean asyncTransport; + private boolean asyncUpdate; + + public DebugSettings() + { + init(); + } + + // Session Settings + public DebugSettings(Parcel parcel) + { + asyncChannel = parcel.readInt() == 1; + asyncTransport = parcel.readInt() == 1; + asyncUpdate = parcel.readInt() == 1; + debug = parcel.readString(); + } + + private void init() + { + debug = "INFO"; + asyncChannel = true; + asyncTransport = false; + asyncUpdate = true; + } + + private void validate() + { + final String[] levels = { "OFF", "FATAL", "ERROR", "WARN", "INFO", "DEBUG", "TRACE" }; + + for (String level : levels) + { + if (level.equalsIgnoreCase(this.debug)) + { + return; + } + } + + this.debug = "INFO"; + } + + public String getDebugLevel() + { + validate(); + return debug; + } + + public void setDebugLevel(String debug) + { + this.debug = debug; + } + + public boolean getAsyncUpdate() + { + return asyncUpdate; + } + + public void setAsyncUpdate(boolean enabled) + { + asyncUpdate = enabled; + } + + public boolean getAsyncChannel() + { + return asyncChannel; + } + + public void setAsyncChannel(boolean enabled) + { + asyncChannel = enabled; + } + + @Override public int describeContents() + { + return 0; + } + + @Override public void writeToParcel(Parcel out, int flags) + { + out.writeInt(asyncChannel ? 1 : 0); + out.writeInt(asyncTransport ? 1 : 0); + out.writeInt(asyncUpdate ? 1 : 0); + out.writeString(debug); + } + } + + // Session Settings + public static class AdvancedSettings implements Parcelable + { + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public AdvancedSettings createFromParcel(Parcel in) + { + return new AdvancedSettings(in); + } + + @Override public AdvancedSettings[] newArray(int size) + { + return new AdvancedSettings[size]; + } + }; + private boolean enable3GSettings; + private ScreenSettings screen3G; + private PerformanceFlags performance3G; + private boolean redirectSDCard; + private int redirectSound; + private boolean redirectMicrophone; + private int security; + private boolean consoleMode; + private String remoteProgram; + private String workDir; + + public AdvancedSettings() + { + init(); + } + + public AdvancedSettings(Parcel parcel) + { + enable3GSettings = parcel.readInt() == 1; + screen3G = parcel.readParcelable(ScreenSettings.class.getClassLoader()); + performance3G = parcel.readParcelable(PerformanceFlags.class.getClassLoader()); + redirectSDCard = parcel.readInt() == 1; + redirectSound = parcel.readInt(); + redirectMicrophone = parcel.readInt() == 1; + security = parcel.readInt(); + consoleMode = parcel.readInt() == 1; + remoteProgram = parcel.readString(); + workDir = parcel.readString(); + } + + private void init() + { + enable3GSettings = false; + screen3G = new ScreenSettings(); + performance3G = new PerformanceFlags(); + redirectSDCard = false; + redirectSound = 0; + redirectMicrophone = false; + security = 0; + consoleMode = false; + remoteProgram = ""; + workDir = ""; + } + + private void validate() + { + switch (redirectSound) + { + case 0: + case 1: + case 2: + break; + default: + redirectSound = 0; + break; + } + + switch (security) + { + case 0: + case 1: + case 2: + case 3: + break; + default: + security = 0; + break; + } + } + + public boolean getEnable3GSettings() + { + return enable3GSettings; + } + + public void setEnable3GSettings(boolean enable3GSettings) + { + this.enable3GSettings = enable3GSettings; + } + + public ScreenSettings getScreen3G() + { + return screen3G; + } + + public void setScreen3G(ScreenSettings screen3G) + { + this.screen3G = screen3G; + } + + public PerformanceFlags getPerformance3G() + { + return performance3G; + } + + public void setPerformance3G(PerformanceFlags performance3G) + { + this.performance3G = performance3G; + } + + public boolean getRedirectSDCard() + { + return redirectSDCard; + } + + public void setRedirectSDCard(boolean redirectSDCard) + { + this.redirectSDCard = redirectSDCard; + } + + public int getRedirectSound() + { + validate(); + return redirectSound; + } + + public void setRedirectSound(int redirect) + { + this.redirectSound = redirect; + } + + public boolean getRedirectMicrophone() + { + return redirectMicrophone; + } + + public void setRedirectMicrophone(boolean redirect) + { + this.redirectMicrophone = redirect; + } + + public int getSecurity() + { + validate(); + return security; + } + + public void setSecurity(int security) + { + this.security = security; + } + + public boolean getConsoleMode() + { + return consoleMode; + } + + public void setConsoleMode(boolean consoleMode) + { + this.consoleMode = consoleMode; + } + + public String getRemoteProgram() + { + return remoteProgram; + } + + public void setRemoteProgram(String remoteProgram) + { + this.remoteProgram = remoteProgram; + } + + public String getWorkDir() + { + return workDir; + } + + public void setWorkDir(String workDir) + { + this.workDir = workDir; + } + + @Override public int describeContents() + { + return 0; + } + + @Override public void writeToParcel(Parcel out, int flags) + { + out.writeInt(enable3GSettings ? 1 : 0); + out.writeParcelable(screen3G, flags); + out.writeParcelable(performance3G, flags); + out.writeInt(redirectSDCard ? 1 : 0); + out.writeInt(redirectSound); + out.writeInt(redirectMicrophone ? 1 : 0); + out.writeInt(security); + out.writeInt(consoleMode ? 1 : 0); + out.writeString(remoteProgram); + out.writeString(workDir); + } + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/ConnectionReference.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/ConnectionReference.java new file mode 100644 index 0000000..3e68776 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/ConnectionReference.java @@ -0,0 +1,85 @@ +/* + A RDP connection reference. References can use bookmark ids or hostnames to connect to a RDP + server. + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.domain; + +public class ConnectionReference +{ + public static final String PATH_MANUAL_BOOKMARK_ID = "MBMID/"; + public static final String PATH_HOSTNAME = "HOST/"; + public static final String PATH_PLACEHOLDER = "PLCHLD/"; + public static final String PATH_FILE = "FILE/"; + + public static String getManualBookmarkReference(long bookmarkId) + { + return (PATH_MANUAL_BOOKMARK_ID + bookmarkId); + } + + public static String getHostnameReference(String hostname) + { + return (PATH_HOSTNAME + hostname); + } + + public static String getPlaceholderReference(String name) + { + return (PATH_PLACEHOLDER + name); + } + + public static String getFileReference(String uri) + { + return (PATH_FILE + uri); + } + + public static boolean isBookmarkReference(String refStr) + { + return refStr.startsWith(PATH_MANUAL_BOOKMARK_ID); + } + + public static boolean isManualBookmarkReference(String refStr) + { + return refStr.startsWith(PATH_MANUAL_BOOKMARK_ID); + } + + public static boolean isHostnameReference(String refStr) + { + return refStr.startsWith(PATH_HOSTNAME); + } + + public static boolean isPlaceholderReference(String refStr) + { + return refStr.startsWith(PATH_PLACEHOLDER); + } + + public static boolean isFileReference(String refStr) + { + return refStr.startsWith(PATH_FILE); + } + + public static long getManualBookmarkId(String refStr) + { + return Integer.parseInt(refStr.substring(PATH_MANUAL_BOOKMARK_ID.length())); + } + + public static String getHostname(String refStr) + { + return refStr.substring(PATH_HOSTNAME.length()); + } + + public static String getPlaceholder(String refStr) + { + return refStr.substring(PATH_PLACEHOLDER.length()); + } + + public static String getFile(String refStr) + { + return refStr.substring(PATH_FILE.length()); + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/ManualBookmark.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/ManualBookmark.java new file mode 100644 index 0000000..1e1a828 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/ManualBookmark.java @@ -0,0 +1,255 @@ +/* + Manual Bookmark implementation + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.domain; + +import android.content.SharedPreferences; +import android.os.Parcel; +import android.os.Parcelable; + +public class ManualBookmark extends BookmarkBase +{ + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public ManualBookmark createFromParcel(Parcel in) + { + return new ManualBookmark(in); + } + + @Override public ManualBookmark[] newArray(int size) + { + return new ManualBookmark[size]; + } + }; + private String hostname; + private int port; + private boolean enableGatewaySettings; + private GatewaySettings gatewaySettings; + + public ManualBookmark(Parcel parcel) + { + super(parcel); + type = TYPE_MANUAL; + hostname = parcel.readString(); + port = parcel.readInt(); + + enableGatewaySettings = (parcel.readInt() == 1); + gatewaySettings = parcel.readParcelable(GatewaySettings.class.getClassLoader()); + } + + public ManualBookmark() + { + super(); + init(); + } + + private void init() + { + type = TYPE_MANUAL; + hostname = ""; + port = 3389; + enableGatewaySettings = false; + gatewaySettings = new GatewaySettings(); + } + + public String getHostname() + { + return hostname; + } + + public void setHostname(String hostname) + { + this.hostname = hostname; + } + + public int getPort() + { + return port; + } + + public void setPort(int port) + { + this.port = port; + } + + public boolean getEnableGatewaySettings() + { + return enableGatewaySettings; + } + + public void setEnableGatewaySettings(boolean enableGatewaySettings) + { + this.enableGatewaySettings = enableGatewaySettings; + } + + public GatewaySettings getGatewaySettings() + { + return gatewaySettings; + } + + public void setGatewaySettings(GatewaySettings gatewaySettings) + { + this.gatewaySettings = gatewaySettings; + } + + @Override public int describeContents() + { + return 0; + } + + @Override public void writeToParcel(Parcel out, int flags) + { + super.writeToParcel(out, flags); + out.writeString(hostname); + out.writeInt(port); + out.writeInt(enableGatewaySettings ? 1 : 0); + out.writeParcelable(gatewaySettings, flags); + } + + @Override public void writeToSharedPreferences(SharedPreferences sharedPrefs) + { + super.writeToSharedPreferences(sharedPrefs); + + SharedPreferences.Editor editor = sharedPrefs.edit(); + editor.putString("bookmark.hostname", hostname); + editor.putInt("bookmark.port", port); + editor.putBoolean("bookmark.enable_gateway_settings", enableGatewaySettings); + editor.putString("bookmark.gateway_hostname", gatewaySettings.getHostname()); + editor.putInt("bookmark.gateway_port", gatewaySettings.getPort()); + editor.putString("bookmark.gateway_username", gatewaySettings.getUsername()); + editor.putString("bookmark.gateway_password", gatewaySettings.getPassword()); + editor.putString("bookmark.gateway_domain", gatewaySettings.getDomain()); + editor.commit(); + } + + @Override public void readFromSharedPreferences(SharedPreferences sharedPrefs) + { + super.readFromSharedPreferences(sharedPrefs); + + hostname = sharedPrefs.getString("bookmark.hostname", ""); + port = sharedPrefs.getInt("bookmark.port", 3389); + enableGatewaySettings = sharedPrefs.getBoolean("bookmark.enable_gateway_settings", false); + gatewaySettings.setHostname(sharedPrefs.getString("bookmark.gateway_hostname", "")); + gatewaySettings.setPort(sharedPrefs.getInt("bookmark.gateway_port", 443)); + gatewaySettings.setUsername(sharedPrefs.getString("bookmark.gateway_username", "")); + gatewaySettings.setPassword(sharedPrefs.getString("bookmark.gateway_password", "")); + gatewaySettings.setDomain(sharedPrefs.getString("bookmark.gateway_domain", "")); + } + + // Cloneable + public Object clone() + { + return super.clone(); + } + + // Gateway Settings class + public static class GatewaySettings implements Parcelable + { + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public GatewaySettings createFromParcel(Parcel in) + { + return new GatewaySettings(in); + } + + @Override public GatewaySettings[] newArray(int size) + { + return new GatewaySettings[size]; + } + }; + private String hostname; + private int port; + private String username; + private String password; + private String domain; + + public GatewaySettings() + { + hostname = ""; + port = 443; + username = ""; + password = ""; + domain = ""; + } + + public GatewaySettings(Parcel parcel) + { + hostname = parcel.readString(); + port = parcel.readInt(); + username = parcel.readString(); + password = parcel.readString(); + domain = parcel.readString(); + } + + public String getHostname() + { + return hostname; + } + + public void setHostname(String hostname) + { + this.hostname = hostname; + } + + public int getPort() + { + return port; + } + + public void setPort(int port) + { + this.port = port; + } + + public String getUsername() + { + return username; + } + + public void setUsername(String username) + { + this.username = username; + } + + public String getPassword() + { + return password; + } + + public void setPassword(String password) + { + this.password = password; + } + + public String getDomain() + { + return domain; + } + + public void setDomain(String domain) + { + this.domain = domain; + } + + @Override public int describeContents() + { + return 0; + } + + @Override public void writeToParcel(Parcel out, int flags) + { + out.writeString(hostname); + out.writeInt(port); + out.writeString(username); + out.writeString(password); + out.writeString(domain); + } + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/PlaceholderBookmark.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/PlaceholderBookmark.java new file mode 100644 index 0000000..d15aaf7 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/PlaceholderBookmark.java @@ -0,0 +1,84 @@ +/* + Placeholder for bookmark items with a special purpose (i.e. just displaying some text) + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.domain; + +import android.content.SharedPreferences; +import android.os.Parcel; +import android.os.Parcelable; + +public class PlaceholderBookmark extends BookmarkBase +{ + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public PlaceholderBookmark createFromParcel(Parcel in) + { + return new PlaceholderBookmark(in); + } + + @Override public PlaceholderBookmark[] newArray(int size) + { + return new PlaceholderBookmark[size]; + } + }; + private String name; + + public PlaceholderBookmark(Parcel parcel) + { + super(parcel); + type = TYPE_PLACEHOLDER; + name = parcel.readString(); + } + + public PlaceholderBookmark() + { + super(); + type = TYPE_PLACEHOLDER; + name = ""; + } + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + @Override public int describeContents() + { + return 0; + } + + @Override public void writeToParcel(Parcel out, int flags) + { + super.writeToParcel(out, flags); + out.writeString(name); + } + + @Override public void writeToSharedPreferences(SharedPreferences sharedPrefs) + { + super.writeToSharedPreferences(sharedPrefs); + } + + @Override public void readFromSharedPreferences(SharedPreferences sharedPrefs) + { + super.readFromSharedPreferences(sharedPrefs); + } + + // Cloneable + public Object clone() + { + return super.clone(); + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/QuickConnectBookmark.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/QuickConnectBookmark.java new file mode 100644 index 0000000..3367b54 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/domain/QuickConnectBookmark.java @@ -0,0 +1,70 @@ +/* + Quick Connect bookmark (used for quick connects using just a hostname) + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.domain; + +import android.content.SharedPreferences; +import android.os.Parcel; +import android.os.Parcelable; + +public class QuickConnectBookmark extends ManualBookmark +{ + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public QuickConnectBookmark createFromParcel(Parcel in) + { + return new QuickConnectBookmark(in); + } + + @Override public QuickConnectBookmark[] newArray(int size) + { + return new QuickConnectBookmark[size]; + } + }; + + public QuickConnectBookmark(Parcel parcel) + { + super(parcel); + type = TYPE_QUICKCONNECT; + } + + public QuickConnectBookmark() + { + super(); + type = TYPE_QUICKCONNECT; + } + + @Override public int describeContents() + { + return 0; + } + + @Override public void writeToParcel(Parcel out, int flags) + { + super.writeToParcel(out, flags); + } + + @Override public void writeToSharedPreferences(SharedPreferences sharedPrefs) + { + super.writeToSharedPreferences(sharedPrefs); + } + + @Override public void readFromSharedPreferences(SharedPreferences sharedPrefs) + { + super.readFromSharedPreferences(sharedPrefs); + } + + // Cloneable + public Object clone() + { + return super.clone(); + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/AboutActivity.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/AboutActivity.java new file mode 100644 index 0000000..edcbb96 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/AboutActivity.java @@ -0,0 +1,117 @@ +package com.freerdp.freerdpcore.presentation; + +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.nfc.FormatException; +import android.os.Build; +import android.os.Bundle; +import androidx.core.text.TextUtilsCompat; +import androidx.appcompat.app.AppCompatActivity; +import android.util.Log; +import android.webkit.WebSettings; +import android.webkit.WebView; + +import com.freerdp.freerdpcore.R; +import com.freerdp.freerdpcore.services.LibFreeRDP; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Formatter; +import java.util.IllegalFormatException; +import java.util.Locale; + +public class AboutActivity extends AppCompatActivity +{ + private static final String TAG = AboutActivity.class.toString(); + private WebView mWebView; + + @Override protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_about); + mWebView = findViewById(R.id.activity_about_webview); + } + + @Override protected void onResume() + { + populate(); + super.onResume(); + } + + private void populate() + { + StringBuilder total = new StringBuilder(); + + String filename = "about_phone.html"; + if ((getResources().getConfiguration().screenLayout & + Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE) + { + filename = "about.html"; + } + Locale def = Locale.getDefault(); + String prefix = def.getLanguage().toLowerCase(def); + + String dir = prefix + "_about_page/"; + String file = dir + filename; + InputStream is; + try + { + is = getAssets().open(file); + is.close(); + } + catch (IOException e) + { + Log.e(TAG, "Missing localized asset " + file, e); + dir = "about_page/"; + file = dir + filename; + } + + try + { + try (BufferedReader r = + new BufferedReader(new InputStreamReader(getAssets().open(file)))) + { + String line; + while ((line = r.readLine()) != null) + { + total.append(line); + total.append("\n"); + } + } + } + catch (IOException e) + { + Log.e(TAG, "Could not read about page " + file, e); + } + + // append FreeRDP core version to app version + // get app version + String version; + try + { + version = getPackageManager().getPackageInfo(getPackageName(), 0).versionName; + } + catch (PackageManager.NameNotFoundException e) + { + version = "unknown"; + } + version = version + " (" + LibFreeRDP.getVersion() + ")"; + + WebSettings settings = mWebView.getSettings(); + settings.setDomStorageEnabled(true); + settings.setUseWideViewPort(true); + settings.setLoadWithOverviewMode(true); + settings.setSupportZoom(true); + + final String base = "file:///android_asset/" + dir; + + final String rawHtml = total.toString(); + final String html = rawHtml.replaceAll("%AFREERDP_VERSION%", version) + .replaceAll("%SYSTEM_VERSION%", Build.VERSION.RELEASE) + .replaceAll("%DEVICE_MODEL%", Build.MODEL); + + mWebView.loadDataWithBaseURL(base, html, "text/html", null, "about:blank"); + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/ApplicationSettingsActivity.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/ApplicationSettingsActivity.java new file mode 100644 index 0000000..f090478 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/ApplicationSettingsActivity.java @@ -0,0 +1,307 @@ +/* + Application Settings Activity + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.presentation; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.os.Build; +import android.os.Bundle; +import android.preference.EditTextPreference; +import android.preference.Preference; +import android.preference.PreferenceFragment; +import android.preference.PreferenceManager; +import android.preference.PreferenceScreen; +import androidx.appcompat.app.AlertDialog; +import android.widget.Toast; + +import com.freerdp.freerdpcore.R; +import com.freerdp.freerdpcore.utils.AppCompatPreferenceActivity; + +import java.io.File; +import java.util.List; +import java.util.UUID; + +public class ApplicationSettingsActivity extends AppCompatPreferenceActivity +{ + private static boolean isXLargeTablet(Context context) + { + return (context.getResources().getConfiguration().screenLayout & + Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE; + } + + @Override protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setupActionBar(); + } + + private void setupActionBar() + { + android.app.ActionBar actionBar = getActionBar(); + if (actionBar != null) + { + actionBar.setDisplayHomeAsUpEnabled(true); + } + } + + @Override public boolean onIsMultiPane() + { + return isXLargeTablet(this); + } + + @Override + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public void onBuildHeaders(List
target) + { + loadHeadersFromResource(R.xml.settings_app_headers, target); + } + + protected boolean isValidFragment(String fragmentName) + { + return PreferenceFragment.class.getName().equals(fragmentName) || + ClientPreferenceFragment.class.getName().equals(fragmentName) || + UiPreferenceFragment.class.getName().equals(fragmentName) || + PowerPreferenceFragment.class.getName().equals(fragmentName) || + SecurityPreferenceFragment.class.getName().equals(fragmentName); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static class ClientPreferenceFragment + extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener + { + @Override public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.settings_app_client); + SharedPreferences preferences = get(getActivity()); + preferences.registerOnSharedPreferenceChangeListener(this); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) + { + if (isAdded()) + { + final String clientNameKey = getString(R.string.preference_key_client_name); + + get(getActivity()); + if (key.equals(clientNameKey)) + { + final String clientNameValue = sharedPreferences.getString(clientNameKey, ""); + EditTextPreference pref = (EditTextPreference)findPreference(clientNameKey); + pref.setText(clientNameValue); + } + } + } + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static class UiPreferenceFragment extends PreferenceFragment + { + @Override public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.settings_app_ui); + } + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static class PowerPreferenceFragment extends PreferenceFragment + { + @Override public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.settings_app_power); + } + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static class SecurityPreferenceFragment extends PreferenceFragment + { + @Override public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.settings_app_security); + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, + Preference preference) + { + final String clear = + getString(R.string.preference_key_security_clear_certificate_cache); + if (preference.getKey().equals(clear)) + { + showDialog(); + return true; + } + else + { + return super.onPreferenceTreeClick(preferenceScreen, preference); + } + } + + private void showDialog() + { + new AlertDialog.Builder(getActivity()) + .setTitle(R.string.dlg_title_clear_cert_cache) + .setMessage(R.string.dlg_msg_clear_cert_cache) + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) + { + clearCertificateCache(); + dialog.dismiss(); + } + }) + .setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) + { + dialog.dismiss(); + } + }) + .setIcon(android.R.drawable.ic_delete) + .show(); + } + + private boolean deleteDirectory(File dir) + { + if (dir.isDirectory()) + { + String[] children = dir.list(); + for (String file : children) + { + if (!deleteDirectory(new File(dir, file))) + return false; + } + } + return dir.delete(); + } + + private void clearCertificateCache() + { + Context context = getActivity(); + if ((new File(context.getFilesDir() + "/.freerdp")).exists()) + { + if (deleteDirectory(new File(context.getFilesDir() + "/.freerdp"))) + Toast.makeText(context, R.string.info_reset_success, Toast.LENGTH_LONG).show(); + else + Toast.makeText(context, R.string.info_reset_failed, Toast.LENGTH_LONG).show(); + } + else + Toast.makeText(context, R.string.info_reset_success, Toast.LENGTH_LONG).show(); + } + } + + public static SharedPreferences get(Context context) + { + Context appContext = context.getApplicationContext(); + PreferenceManager.setDefaultValues(appContext, R.xml.settings_app_client, false); + PreferenceManager.setDefaultValues(appContext, R.xml.settings_app_power, false); + PreferenceManager.setDefaultValues(appContext, R.xml.settings_app_security, false); + PreferenceManager.setDefaultValues(appContext, R.xml.settings_app_ui, false); + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(appContext); + + final String key = context.getString(R.string.preference_key_client_name); + final String value = preferences.getString(key, ""); + if (value.isEmpty()) + { + final String android_id = UUID.randomUUID().toString(); + final String defaultValue = context.getString(R.string.preference_default_client_name); + final String name = defaultValue + "-" + android_id; + preferences.edit().putString(key, name.substring(0, 31)).apply(); + } + + return preferences; + } + + public static int getDisconnectTimeout(Context context) + { + SharedPreferences preferences = get(context); + return preferences.getInt( + context.getString(R.string.preference_key_power_disconnect_timeout), 0); + } + + public static boolean getHideStatusBar(Context context) + { + SharedPreferences preferences = get(context); + return preferences.getBoolean(context.getString(R.string.preference_key_ui_hide_status_bar), + false); + } + + public static boolean getHideActionBar(Context context) + { + SharedPreferences preferences = get(context); + return preferences.getBoolean(context.getString(R.string.preference_key_ui_hide_action_bar), + false); + } + + public static boolean getUseBackAsAltf4(Context context) + { + SharedPreferences preferences = get(context); + return preferences.getBoolean( + context.getString(R.string.preference_key_ui_use_back_as_altf4), true); + } + + public static boolean getAcceptAllCertificates(Context context) + { + SharedPreferences preferences = get(context); + return preferences.getBoolean( + context.getString(R.string.preference_key_accept_certificates), false); + } + + public static boolean getHideZoomControls(Context context) + { + SharedPreferences preferences = get(context); + return preferences.getBoolean( + context.getString(R.string.preference_key_ui_hide_zoom_controls), false); + } + + public static boolean getSwapMouseButtons(Context context) + { + SharedPreferences preferences = get(context); + return preferences.getBoolean( + context.getString(R.string.preference_key_ui_swap_mouse_buttons), false); + } + + public static boolean getInvertScrolling(Context context) + { + SharedPreferences preferences = get(context); + return preferences.getBoolean( + context.getString(R.string.preference_key_ui_invert_scrolling), true); + } + + public static boolean getAskOnExit(Context context) + { + SharedPreferences preferences = get(context); + return preferences.getBoolean(context.getString(R.string.preference_key_ui_ask_on_exit), + false); + } + + public static boolean getAutoScrollTouchPointer(Context context) + { + SharedPreferences preferences = get(context); + return preferences.getBoolean( + context.getString(R.string.preference_key_ui_auto_scroll_touchpointer), false); + } + + public static String getClientName(Context context) + { + SharedPreferences preferences = get(context); + return preferences.getString(context.getString(R.string.preference_key_client_name), ""); + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/BookmarkActivity.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/BookmarkActivity.java new file mode 100644 index 0000000..c0c9f93 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/BookmarkActivity.java @@ -0,0 +1,732 @@ +/* + Bookmark editing activity + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. + */ + +package com.freerdp.freerdpcore.presentation; + +import android.app.AlertDialog; +import android.content.ComponentName; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.os.Bundle; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceManager; +import android.preference.PreferenceScreen; +import android.util.Log; +import android.view.View; + +import com.freerdp.freerdpcore.R; +import com.freerdp.freerdpcore.application.GlobalApp; +import com.freerdp.freerdpcore.domain.BookmarkBase; +import com.freerdp.freerdpcore.domain.ConnectionReference; +import com.freerdp.freerdpcore.domain.ManualBookmark; +import com.freerdp.freerdpcore.services.BookmarkBaseGateway; +import com.freerdp.freerdpcore.services.LibFreeRDP; +import com.freerdp.freerdpcore.utils.RDPFileParser; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; + +public class BookmarkActivity extends PreferenceActivity implements OnSharedPreferenceChangeListener +{ + public static final String PARAM_CONNECTION_REFERENCE = "conRef"; + + private static final String TAG = "BookmarkActivity"; + private static final int PREFERENCES_BOOKMARK = 1; + private static final int PREFERENCES_CREDENTIALS = 2; + private static final int PREFERENCES_SCREEN = 3; + private static final int PREFERENCES_PERFORMANCE = 4; + private static final int PREFERENCES_ADVANCED = 5; + private static final int PREFERENCES_SCREEN3G = 6; + private static final int PREFERENCES_PERFORMANCE3G = 7; + private static final int PREFERENCES_GATEWAY = 8; + private static final int PREFERENCES_DEBUG = 9; + // bookmark needs to be static because the activity is started for each + // subview + // (we have to do this because Android has a bug where the style for + // Preferences + // is only applied to the first PreferenceScreen but not to subsequent ones) + private static BookmarkBase bookmark = null; + private static boolean settings_changed = false; + private static boolean new_bookmark = false; + private int current_preferences; + + @Override public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + PreferenceManager mgr = getPreferenceManager(); + // init shared preferences for activity + mgr.setSharedPreferencesName("TEMP"); + mgr.setSharedPreferencesMode(MODE_PRIVATE); + + if (bookmark == null) + { + // if we have a bookmark id set in the extras we are in edit mode + Bundle bundle = getIntent().getExtras(); + if (bundle != null) + { + // See if we got a connection reference to a bookmark + if (bundle.containsKey(PARAM_CONNECTION_REFERENCE)) + { + String refStr = bundle.getString(PARAM_CONNECTION_REFERENCE); + if (ConnectionReference.isManualBookmarkReference(refStr)) + { + bookmark = GlobalApp.getManualBookmarkGateway().findById( + ConnectionReference.getManualBookmarkId(refStr)); + new_bookmark = false; + } + else if (ConnectionReference.isHostnameReference(refStr)) + { + bookmark = new ManualBookmark(); + bookmark.get().setLabel( + ConnectionReference.getHostname(refStr)); + bookmark.get().setHostname( + ConnectionReference.getHostname(refStr)); + new_bookmark = true; + } + else if (ConnectionReference.isFileReference(refStr)) + { + String file = ConnectionReference.getFile(refStr); + + bookmark = new ManualBookmark(); + bookmark.setLabel(file); + + try + { + RDPFileParser rdpFile = new RDPFileParser(file); + updateBookmarkFromFile((ManualBookmark)bookmark, rdpFile); + + bookmark.setLabel(new File(file).getName()); + new_bookmark = true; + } + catch (IOException e) + { + Log.e(TAG, "Failed reading RDP file", e); + } + } + } + } + + // last chance - ensure we really have a valid bookmark + if (bookmark == null) + bookmark = new ManualBookmark(); + + // hide gateway settings if we edit a non-manual bookmark + if (current_preferences == PREFERENCES_ADVANCED && + bookmark.getType() != ManualBookmark.TYPE_MANUAL) + { + PreferenceScreen screen = getPreferenceScreen(); + screen.removePreference(findPreference("bookmark.enable_gateway")); + screen.removePreference(findPreference("bookmark.gateway")); + } + + updateH264Preferences(); + + // update preferences from bookmark + bookmark.writeToSharedPreferences(mgr.getSharedPreferences()); + + // no settings changed yet + settings_changed = false; + } + + // load the requested settings resource + if (getIntent() == null || getIntent().getData() == null) + { + addPreferencesFromResource(R.xml.bookmark_settings); + current_preferences = PREFERENCES_BOOKMARK; + } + else if (getIntent().getData().toString().equals("preferences://screen_settings")) + { + addPreferencesFromResource(R.xml.screen_settings); + current_preferences = PREFERENCES_SCREEN; + } + else if (getIntent().getData().toString().equals("preferences://performance_flags")) + { + addPreferencesFromResource(R.xml.performance_flags); + current_preferences = PREFERENCES_PERFORMANCE; + } + else if (getIntent().getData().toString().equals("preferences://screen_settings_3g")) + { + addPreferencesFromResource(R.xml.screen_settings_3g); + current_preferences = PREFERENCES_SCREEN3G; + } + else if (getIntent().getData().toString().equals("preferences://performance_flags_3g")) + { + addPreferencesFromResource(R.xml.performance_flags_3g); + current_preferences = PREFERENCES_PERFORMANCE3G; + } + else if (getIntent().getData().toString().equals("preferences://advanced_settings")) + { + addPreferencesFromResource(R.xml.advanced_settings); + current_preferences = PREFERENCES_ADVANCED; + } + else if (getIntent().getData().toString().equals("preferences://credentials_settings")) + { + addPreferencesFromResource(R.xml.credentials_settings); + current_preferences = PREFERENCES_CREDENTIALS; + } + else if (getIntent().getData().toString().equals("preferences://gateway_settings")) + { + addPreferencesFromResource(R.xml.gateway_settings); + current_preferences = PREFERENCES_GATEWAY; + } + else if (getIntent().getData().toString().equals("preferences://debug_settings")) + { + addPreferencesFromResource(R.xml.debug_settings); + current_preferences = PREFERENCES_DEBUG; + } + else + { + addPreferencesFromResource(R.xml.bookmark_settings); + current_preferences = PREFERENCES_BOOKMARK; + } + + // update UI with bookmark data + SharedPreferences spref = mgr.getSharedPreferences(); + initSettings(spref); + + // register for preferences changed notification + mgr.getSharedPreferences().registerOnSharedPreferenceChangeListener(this); + + // set the correct component names in our preferencescreen settings + setIntentComponentNames(); + + updateH264Preferences(); + } + + private void updateH264Preferences() + { + if (!LibFreeRDP.hasH264Support()) + { + final int[] preferenceIdList = { R.string.preference_key_h264, + R.string.preference_key_h264_3g }; + + PreferenceManager mgr = getPreferenceManager(); + for (int id : preferenceIdList) + { + final String key = getString(id); + Preference preference = mgr.findPreference(key); + if (preference != null) + { + preference.setEnabled(false); + } + } + } + } + + private void updateBookmarkFromFile(ManualBookmark bookmark, RDPFileParser rdpFile) + { + String s; + Integer i; + + s = rdpFile.getString("full address"); + if (s != null) + { + // this gets complicated as it can include port + if (s.lastIndexOf(":") > s.lastIndexOf("]")) + { + try + { + String port = s.substring(s.lastIndexOf(":") + 1); + bookmark.setPort(Integer.parseInt(port)); + } + catch (NumberFormatException e) + { + Log.e(TAG, "Malformed address"); + } + + s = s.substring(0, s.lastIndexOf(":")); + } + + // or even be an ipv6 address + if (s.startsWith("[") && s.endsWith("]")) + s = s.substring(1, s.length() - 1); + + bookmark.setHostname(s); + } + + i = rdpFile.getInteger("server port"); + if (i != null) + bookmark.setPort(i); + + s = rdpFile.getString("username"); + if (s != null) + bookmark.setUsername(s); + + s = rdpFile.getString("domain"); + if (s != null) + bookmark.setDomain(s); + + i = rdpFile.getInteger("connect to console"); + if (i != null) + bookmark.getAdvancedSettings().setConsoleMode(i == 1); + } + + private void setIntentComponentNames() + { + // we set the component name for our sub-activity calls here because we + // don't know the package + // name of the main app in our library project. + ComponentName compName = + new ComponentName(getPackageName(), BookmarkActivity.class.getName()); + ArrayList prefKeys = new ArrayList<>(); + + prefKeys.add("bookmark.credentials"); + prefKeys.add("bookmark.screen"); + prefKeys.add("bookmark.performance"); + prefKeys.add("bookmark.advanced"); + prefKeys.add("bookmark.screen_3g"); + prefKeys.add("bookmark.performance_3g"); + prefKeys.add("bookmark.gateway_settings"); + prefKeys.add("bookmark.debug"); + + for (String p : prefKeys) + { + Preference pref = findPreference(p); + if (pref != null) + pref.getIntent().setComponent(compName); + } + } + + @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) + { + settings_changed = true; + switch (current_preferences) + { + case PREFERENCES_DEBUG: + debugSettingsChanged(sharedPreferences, key); + break; + + case PREFERENCES_BOOKMARK: + bookmarkSettingsChanged(sharedPreferences, key); + break; + + case PREFERENCES_ADVANCED: + advancedSettingsChanged(sharedPreferences, key); + break; + + case PREFERENCES_CREDENTIALS: + credentialsSettingsChanged(sharedPreferences, key); + break; + + case PREFERENCES_SCREEN: + case PREFERENCES_SCREEN3G: + screenSettingsChanged(sharedPreferences, key); + break; + + case PREFERENCES_GATEWAY: + gatewaySettingsChanged(sharedPreferences, key); + break; + + default: + break; + } + } + + private void initSettings(SharedPreferences sharedPreferences) + { + switch (current_preferences) + { + case PREFERENCES_BOOKMARK: + initBookmarkSettings(sharedPreferences); + break; + + case PREFERENCES_ADVANCED: + initAdvancedSettings(sharedPreferences); + break; + + case PREFERENCES_CREDENTIALS: + initCredentialsSettings(sharedPreferences); + break; + + case PREFERENCES_SCREEN: + initScreenSettings(sharedPreferences); + break; + + case PREFERENCES_SCREEN3G: + initScreenSettings3G(sharedPreferences); + break; + + case PREFERENCES_GATEWAY: + initGatewaySettings(sharedPreferences); + break; + + case PREFERENCES_DEBUG: + initDebugSettings(sharedPreferences); + break; + + default: + break; + } + } + + private void initBookmarkSettings(SharedPreferences sharedPreferences) + { + bookmarkSettingsChanged(sharedPreferences, "bookmark.label"); + bookmarkSettingsChanged(sharedPreferences, "bookmark.hostname"); + bookmarkSettingsChanged(sharedPreferences, "bookmark.port"); + bookmarkSettingsChanged(sharedPreferences, "bookmark.username"); + bookmarkSettingsChanged(sharedPreferences, "bookmark.resolution"); + } + + private void bookmarkSettingsChanged(SharedPreferences sharedPreferences, String key) + { + if (key.equals("bookmark.label") && findPreference(key) != null) + findPreference(key).setSummary(sharedPreferences.getString(key, "")); + else if (key.equals("bookmark.hostname") && findPreference(key) != null) + findPreference(key).setSummary(sharedPreferences.getString(key, "")); + else if (key.equals("bookmark.port") && findPreference(key) != null) + findPreference(key).setSummary(String.valueOf(sharedPreferences.getInt(key, -1))); + else if (key.equals("bookmark.username")) + { + String username = sharedPreferences.getString(key, ""); + if (username.length() == 0) + username = ""; + findPreference("bookmark.credentials").setSummary(username); + } + else if (key.equals("bookmark.resolution") || key.equals("bookmark.colors") || + key.equals("bookmark.width") || key.equals("bookmark.height")) + { + String resolution = sharedPreferences.getString("bookmark.resolution", "800x600"); + // compare english string from resolutions_values_array array, + // decode to localized + // text for display + if (resolution.equals("automatic")) + { + resolution = getResources().getString(R.string.resolution_automatic); + } + if (resolution.equals("custom")) + { + resolution = getResources().getString(R.string.resolution_custom); + } + if (resolution.equals("fitscreen")) + { + resolution = getResources().getString(R.string.resolution_fit); + } + resolution += "@" + sharedPreferences.getInt("bookmark.colors", 16); + findPreference("bookmark.screen").setSummary(resolution); + } + } + + private void initAdvancedSettings(SharedPreferences sharedPreferences) + { + advancedSettingsChanged(sharedPreferences, "bookmark.enable_gateway_settings"); + advancedSettingsChanged(sharedPreferences, "bookmark.enable_3g_settings"); + advancedSettingsChanged(sharedPreferences, "bookmark.security"); + advancedSettingsChanged(sharedPreferences, "bookmark.resolution_3g"); + advancedSettingsChanged(sharedPreferences, "bookmark.remote_program"); + advancedSettingsChanged(sharedPreferences, "bookmark.work_dir"); + } + + private void advancedSettingsChanged(SharedPreferences sharedPreferences, String key) + { + if (key.equals("bookmark.enable_gateway_settings")) + { + boolean enabled = sharedPreferences.getBoolean(key, false); + findPreference("bookmark.gateway_settings").setEnabled(enabled); + } + else if (key.equals("bookmark.enable_3g_settings")) + { + boolean enabled = sharedPreferences.getBoolean(key, false); + findPreference("bookmark.screen_3g").setEnabled(enabled); + findPreference("bookmark.performance_3g").setEnabled(enabled); + } + else if (key.equals("bookmark.security")) + { + ListPreference listPreference = (ListPreference)findPreference(key); + CharSequence security = listPreference.getEntries()[sharedPreferences.getInt(key, 0)]; + listPreference.setSummary(security); + } + else if (key.equals("bookmark.resolution_3g") || key.equals("bookmark.colors_3g") || + key.equals("bookmark.width_3g") || key.equals("bookmark.height_3g")) + { + String resolution = sharedPreferences.getString("bookmark.resolution_3g", "800x600"); + if (resolution.equals("automatic")) + resolution = getResources().getString(R.string.resolution_automatic); + else if (resolution.equals("custom")) + resolution = getResources().getString(R.string.resolution_custom); + resolution += "@" + sharedPreferences.getInt("bookmark.colors_3g", 16); + findPreference("bookmark.screen_3g").setSummary(resolution); + } + else if (key.equals("bookmark.remote_program")) + findPreference(key).setSummary(sharedPreferences.getString(key, "")); + else if (key.equals("bookmark.work_dir")) + findPreference(key).setSummary(sharedPreferences.getString(key, "")); + } + + private void initCredentialsSettings(SharedPreferences sharedPreferences) + { + credentialsSettingsChanged(sharedPreferences, "bookmark.username"); + credentialsSettingsChanged(sharedPreferences, "bookmark.password"); + credentialsSettingsChanged(sharedPreferences, "bookmark.domain"); + } + + private void credentialsSettingsChanged(SharedPreferences sharedPreferences, String key) + { + if (key.equals("bookmark.username")) + findPreference(key).setSummary(sharedPreferences.getString(key, "")); + else if (key.equals("bookmark.password")) + { + if (sharedPreferences.getString(key, "").length() == 0) + findPreference(key).setSummary( + getResources().getString(R.string.settings_password_empty)); + else + findPreference(key).setSummary( + getResources().getString(R.string.settings_password_present)); + } + else if (key.equals("bookmark.domain")) + findPreference(key).setSummary(sharedPreferences.getString(key, "")); + } + + private void initScreenSettings(SharedPreferences sharedPreferences) + { + screenSettingsChanged(sharedPreferences, "bookmark.colors"); + screenSettingsChanged(sharedPreferences, "bookmark.resolution"); + screenSettingsChanged(sharedPreferences, "bookmark.width"); + screenSettingsChanged(sharedPreferences, "bookmark.height"); + } + + private void initScreenSettings3G(SharedPreferences sharedPreferences) + { + screenSettingsChanged(sharedPreferences, "bookmark.colors_3g"); + screenSettingsChanged(sharedPreferences, "bookmark.resolution_3g"); + screenSettingsChanged(sharedPreferences, "bookmark.width_3g"); + screenSettingsChanged(sharedPreferences, "bookmark.height_3g"); + } + + private void screenSettingsChanged(SharedPreferences sharedPreferences, String key) + { + // could happen during initialization because 3g and non-3g settings + // share this routine - just skip + if (findPreference(key) == null) + return; + + if (key.equals("bookmark.colors") || key.equals("bookmark.colors_3g")) + { + ListPreference listPreference = (ListPreference)findPreference(key); + listPreference.setSummary(listPreference.getEntry()); + } + else if (key.equals("bookmark.resolution") || key.equals("bookmark.resolution_3g")) + { + ListPreference listPreference = (ListPreference)findPreference(key); + listPreference.setSummary(listPreference.getEntry()); + + String value = listPreference.getValue(); + boolean enabled = value.equalsIgnoreCase("custom"); + if (key.equals("bookmark.resolution")) + { + findPreference("bookmark.width").setEnabled(enabled); + findPreference("bookmark.height").setEnabled(enabled); + } + else + { + findPreference("bookmark.width_3g").setEnabled(enabled); + findPreference("bookmark.height_3g").setEnabled(enabled); + } + } + else if (key.equals("bookmark.width") || key.equals("bookmark.width_3g")) + findPreference(key).setSummary(String.valueOf(sharedPreferences.getInt(key, 800))); + else if (key.equals("bookmark.height") || key.equals("bookmark.height_3g")) + findPreference(key).setSummary(String.valueOf(sharedPreferences.getInt(key, 600))); + } + + private void initDebugSettings(SharedPreferences sharedPreferences) + { + debugSettingsChanged(sharedPreferences, "bookmark.debug_level"); + debugSettingsChanged(sharedPreferences, "bookmark.async_channel"); + debugSettingsChanged(sharedPreferences, "bookmark.async_update"); + } + + private void initGatewaySettings(SharedPreferences sharedPreferences) + { + gatewaySettingsChanged(sharedPreferences, "bookmark.gateway_hostname"); + gatewaySettingsChanged(sharedPreferences, "bookmark.gateway_port"); + gatewaySettingsChanged(sharedPreferences, "bookmark.gateway_username"); + gatewaySettingsChanged(sharedPreferences, "bookmark.gateway_password"); + gatewaySettingsChanged(sharedPreferences, "bookmark.gateway_domain"); + } + + private void debugSettingsChanged(SharedPreferences sharedPreferences, String key) + { + if (key.equals("bookmark.debug_level")) + { + String level = sharedPreferences.getString(key, "INFO"); + Preference pref = findPreference("bookmark.debug_level"); + pref.setDefaultValue(level); + } + else if (key.equals("bookmark.async_channel")) + { + boolean enabled = sharedPreferences.getBoolean(key, false); + Preference pref = findPreference("bookmark.async_channel"); + pref.setDefaultValue(enabled); + } + else if (key.equals("bookmark.async_update")) + { + boolean enabled = sharedPreferences.getBoolean(key, false); + Preference pref = findPreference("bookmark.async_update"); + pref.setDefaultValue(enabled); + } + } + + private void gatewaySettingsChanged(SharedPreferences sharedPreferences, String key) + { + if (key.equals("bookmark.gateway_hostname")) + { + findPreference(key).setSummary(sharedPreferences.getString(key, "")); + } + else if (key.equals("bookmark.gateway_port")) + { + findPreference(key).setSummary(String.valueOf(sharedPreferences.getInt(key, 443))); + } + else if (key.equals("bookmark.gateway_username")) + { + findPreference(key).setSummary(sharedPreferences.getString(key, "")); + } + else if (key.equals("bookmark.gateway_password")) + { + if (sharedPreferences.getString(key, "").length() == 0) + findPreference(key).setSummary( + getResources().getString(R.string.settings_password_empty)); + else + findPreference(key).setSummary( + getResources().getString(R.string.settings_password_present)); + } + else if (key.equals("bookmark.gateway_domain")) + findPreference(key).setSummary(sharedPreferences.getString(key, "")); + } + + private boolean verifySettings(SharedPreferences sharedPreferences) + { + + boolean verifyFailed = sharedPreferences.getString("bookmark.label", "").length() == 0; + // perform sanity checks on settings + // Label set + + // Server and port specified + if (!verifyFailed && sharedPreferences.getString("bookmark.hostname", "").length() == 0) + verifyFailed = true; + + // Server and port specified + if (!verifyFailed && sharedPreferences.getInt("bookmark.port", -1) <= 0) + verifyFailed = true; + + // if an error occurred - display toast and return false + return (!verifyFailed); + } + + private void finishAndResetBookmark() + { + bookmark = null; + getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener( + this); + finish(); + } + + @Override public void onBackPressed() + { + // only proceed if we are in the main preferences screen + if (current_preferences != PREFERENCES_BOOKMARK) + { + super.onBackPressed(); + getPreferenceManager() + .getSharedPreferences() + .unregisterOnSharedPreferenceChangeListener(this); + return; + } + + SharedPreferences sharedPreferences = getPreferenceManager().getSharedPreferences(); + if (!verifySettings(sharedPreferences)) + { + // ask the user if he wants to cancel or continue editing + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.error_bookmark_incomplete_title) + .setMessage(R.string.error_bookmark_incomplete) + .setPositiveButton(R.string.cancel, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) + { + finishAndResetBookmark(); + } + }) + .setNegativeButton(R.string.cont, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) + { + dialog.cancel(); + } + }) + .show(); + } + else + { + // ask the user if he wants to save or cancel editing if a setting + // has changed + if (new_bookmark || settings_changed) + { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.dlg_title_save_bookmark) + .setMessage(R.string.dlg_save_bookmark) + .setPositiveButton( + R.string.yes, + new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int which) + { + // read shared prefs back to bookmark + bookmark.readFromSharedPreferences( + getPreferenceManager().getSharedPreferences()); + + BookmarkBaseGateway bookmarkGateway; + if (bookmark.getType() == BookmarkBase.TYPE_MANUAL) + { + bookmarkGateway = GlobalApp.getManualBookmarkGateway(); + // remove any history entry for this + // bookmark + GlobalApp.getQuickConnectHistoryGateway().removeHistoryItem( + bookmark.get().getHostname()); + } + else + { + assert false; + return; + } + + // insert or update bookmark and leave + // activity + if (bookmark.getId() > 0) + bookmarkGateway.update(bookmark); + else + bookmarkGateway.insert(bookmark); + + finishAndResetBookmark(); + } + }) + .setNegativeButton(R.string.no, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) + { + finishAndResetBookmark(); + } + }) + .show(); + } + else + { + finishAndResetBookmark(); + } + } + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/HelpActivity.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/HelpActivity.java new file mode 100644 index 0000000..8772c9e --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/HelpActivity.java @@ -0,0 +1,77 @@ +/* + Activity that displays the help pages + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.presentation; + +import android.content.res.Configuration; +import android.os.Bundle; +import androidx.appcompat.app.AppCompatActivity; +import android.util.Log; +import android.webkit.WebSettings; +import android.webkit.WebView; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Locale; + +public class HelpActivity extends AppCompatActivity +{ + + private static final String TAG = HelpActivity.class.toString(); + + @Override public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + WebView webview = new WebView(this); + setContentView(webview); + + String filename; + if ((getResources().getConfiguration().screenLayout & + Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE) + filename = "gestures.html"; + else + filename = "gestures_phone.html"; + + WebSettings settings = webview.getSettings(); + settings.setDomStorageEnabled(true); + settings.setUseWideViewPort(true); + settings.setLoadWithOverviewMode(true); + settings.setSupportZoom(true); + settings.setJavaScriptEnabled(true); + + settings.setAllowContentAccess(true); + settings.setAllowFileAccess(true); + + final Locale def = Locale.getDefault(); + final String prefix = def.getLanguage().toLowerCase(def); + + final String base = "file:///android_asset/"; + final String baseName = "help_page"; + String dir = prefix + "_" + baseName + "/"; + String file = dir + filename; + InputStream is; + try + { + is = getAssets().open(file); + is.close(); + } + catch (IOException e) + { + Log.e(TAG, "Missing localized asset " + file, e); + dir = baseName + "/"; + file = dir + filename; + } + + webview.loadUrl(base + file); + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/HomeActivity.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/HomeActivity.java new file mode 100644 index 0000000..9547ff1 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/HomeActivity.java @@ -0,0 +1,399 @@ +/* + Main/Home Activity + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.presentation; + +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Configuration; +import android.net.Uri; +import android.os.Bundle; +import androidx.appcompat.app.AppCompatActivity; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Log; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnCreateContextMenuListener; +import android.widget.AdapterView; +import android.widget.AdapterView.AdapterContextMenuInfo; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.ListView; + +import com.freerdp.freerdpcore.R; +import com.freerdp.freerdpcore.application.GlobalApp; +import com.freerdp.freerdpcore.domain.BookmarkBase; +import com.freerdp.freerdpcore.domain.ConnectionReference; +import com.freerdp.freerdpcore.domain.PlaceholderBookmark; +import com.freerdp.freerdpcore.domain.QuickConnectBookmark; +import com.freerdp.freerdpcore.utils.BookmarkArrayAdapter; +import com.freerdp.freerdpcore.utils.SeparatedListAdapter; + +import java.util.ArrayList; + +public class HomeActivity extends AppCompatActivity +{ + private final static String ADD_BOOKMARK_PLACEHOLDER = "add_bookmark"; + private static final String TAG = "HomeActivity"; + private static final String PARAM_SUPERBAR_TEXT = "superbar_text"; + private ListView listViewBookmarks; + private Button clearTextButton; + private EditText superBarEditText; + private BookmarkArrayAdapter manualBookmarkAdapter; + private SeparatedListAdapter separatedListAdapter; + private PlaceholderBookmark addBookmarkPlaceholder; + private String sectionLabelBookmarks; + + View mDecor; + + @Override public void onCreate(Bundle savedInstanceState) + { + setTitle(R.string.title_home); + super.onCreate(savedInstanceState); + setContentView(R.layout.home); + + mDecor = getWindow().getDecorView(); + mDecor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + + long heapSize = Runtime.getRuntime().maxMemory(); + Log.i(TAG, "Max HeapSize: " + heapSize); + Log.i(TAG, "App data folder: " + getFilesDir().toString()); + + // load strings + sectionLabelBookmarks = getResources().getString(R.string.section_bookmarks); + + // create add bookmark/quick connect bookmark placeholder + addBookmarkPlaceholder = new PlaceholderBookmark(); + addBookmarkPlaceholder.setName(ADD_BOOKMARK_PLACEHOLDER); + addBookmarkPlaceholder.setLabel( + getResources().getString(R.string.list_placeholder_add_bookmark)); + + // check for passed .rdp file and open it in a new bookmark + Intent caller = getIntent(); + Uri callParameter = caller.getData(); + + if (Intent.ACTION_VIEW.equals(caller.getAction()) && callParameter != null) + { + String refStr = ConnectionReference.getFileReference(callParameter.getPath()); + Bundle bundle = new Bundle(); + bundle.putString(BookmarkActivity.PARAM_CONNECTION_REFERENCE, refStr); + + Intent bookmarkIntent = + new Intent(this.getApplicationContext(), BookmarkActivity.class); + bookmarkIntent.putExtras(bundle); + startActivity(bookmarkIntent); + } + + // load views + clearTextButton = findViewById(R.id.clear_search_btn); + superBarEditText = findViewById(R.id.superBarEditText); + + listViewBookmarks = findViewById(R.id.listViewBookmarks); + + // set listeners for the list view + listViewBookmarks.setOnItemClickListener(new AdapterView.OnItemClickListener() { + public void onItemClick(AdapterView parent, View view, int position, long id) + { + String curSection = separatedListAdapter.getSectionForPosition(position); + Log.v(TAG, "Clicked on item id " + separatedListAdapter.getItemId(position) + + " in section " + curSection); + if (curSection.equals(sectionLabelBookmarks)) + { + String refStr = view.getTag().toString(); + if (ConnectionReference.isManualBookmarkReference(refStr) || + ConnectionReference.isHostnameReference(refStr)) + { + Bundle bundle = new Bundle(); + bundle.putString(SessionActivity.PARAM_CONNECTION_REFERENCE, refStr); + + Intent sessionIntent = new Intent(view.getContext(), SessionActivity.class); + sessionIntent.putExtras(bundle); + startActivity(sessionIntent); + + // clear any search text + superBarEditText.setText(""); + superBarEditText.clearFocus(); + } + else if (ConnectionReference.isPlaceholderReference(refStr)) + { + // is this the add bookmark placeholder? + if (ConnectionReference.getPlaceholder(refStr).equals( + ADD_BOOKMARK_PLACEHOLDER)) + { + Intent bookmarkIntent = + new Intent(view.getContext(), BookmarkActivity.class); + startActivity(bookmarkIntent); + } + } + } + } + }); + + listViewBookmarks.setOnCreateContextMenuListener(new OnCreateContextMenuListener() { + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) + { + // if the selected item is not a session item (tag == null) and not a quick connect + // entry (not a hostname connection reference) inflate the context menu + View itemView = ((AdapterContextMenuInfo)menuInfo).targetView; + String refStr = itemView.getTag() != null ? itemView.getTag().toString() : null; + if (refStr != null && !ConnectionReference.isHostnameReference(refStr) && + !ConnectionReference.isPlaceholderReference(refStr)) + { + getMenuInflater().inflate(R.menu.bookmark_context_menu, menu); + menu.setHeaderTitle(getResources().getString(R.string.menu_title_bookmark)); + } + } + }); + + superBarEditText.addTextChangedListener(new SuperBarTextWatcher()); + + clearTextButton.setOnClickListener(new OnClickListener() { + @Override public void onClick(View v) + { + superBarEditText.setText(""); + } + }); + } + + @Override public void onConfigurationChanged(Configuration newConfig) + { + // ignore orientation/keyboard change + super.onConfigurationChanged(newConfig); + mDecor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + } + + @Override public boolean onSearchRequested() + { + superBarEditText.requestFocus(); + return true; + } + + @Override public boolean onContextItemSelected(MenuItem aItem) + { + + // get connection reference + AdapterContextMenuInfo menuInfo = (AdapterContextMenuInfo)aItem.getMenuInfo(); + String refStr = menuInfo.targetView.getTag().toString(); + + // refer to http://tools.android.com/tips/non-constant-fields why we can't use switch/case + // here .. + int itemId = aItem.getItemId(); + if (itemId == R.id.bookmark_connect) + { + Bundle bundle = new Bundle(); + bundle.putString(SessionActivity.PARAM_CONNECTION_REFERENCE, refStr); + Intent sessionIntent = new Intent(this, SessionActivity.class); + sessionIntent.putExtras(bundle); + + startActivity(sessionIntent); + return true; + } + else if (itemId == R.id.bookmark_edit) + { + Bundle bundle = new Bundle(); + bundle.putString(BookmarkActivity.PARAM_CONNECTION_REFERENCE, refStr); + + Intent bookmarkIntent = + new Intent(this.getApplicationContext(), BookmarkActivity.class); + bookmarkIntent.putExtras(bundle); + startActivity(bookmarkIntent); + return true; + } + else if (itemId == R.id.bookmark_delete) + { + if (ConnectionReference.isManualBookmarkReference(refStr)) + { + long id = ConnectionReference.getManualBookmarkId(refStr); + GlobalApp.getManualBookmarkGateway().delete(id); + manualBookmarkAdapter.remove(id); + separatedListAdapter.notifyDataSetChanged(); + } + else + { + assert false; + } + + // clear super bar text + superBarEditText.setText(""); + return true; + } + + return false; + } + + @Override protected void onResume() + { + super.onResume(); + Log.v(TAG, "HomeActivity.onResume"); + + // create bookmark cursor adapter + manualBookmarkAdapter = new BookmarkArrayAdapter( + this, R.layout.bookmark_list_item, GlobalApp.getManualBookmarkGateway().findAll()); + + // add add bookmark item to manual adapter + manualBookmarkAdapter.insert(addBookmarkPlaceholder, 0); + + // attach all adapters to the separatedListView adapter and assign it to the list view + separatedListAdapter = new SeparatedListAdapter(this); + separatedListAdapter.addSection(sectionLabelBookmarks, manualBookmarkAdapter); + listViewBookmarks.setAdapter(separatedListAdapter); + + // if we have a filter text entered cause an update to be caused here + String filter = superBarEditText.getText().toString(); + if (filter.length() > 0) + superBarEditText.setText(filter); + } + + @Override protected void onPause() + { + super.onPause(); + Log.v(TAG, "HomeActivity.onPause"); + + // reset adapters + listViewBookmarks.setAdapter(null); + separatedListAdapter = null; + manualBookmarkAdapter = null; + } + + @Override public void onBackPressed() + { + // if back was pressed - ask the user if he really wants to exit + if (ApplicationSettingsActivity.getAskOnExit(this)) + { + final CheckBox cb = new CheckBox(this); + cb.setChecked(!ApplicationSettingsActivity.getAskOnExit(this)); + cb.setText(R.string.dlg_dont_show_again); + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.dlg_title_exit) + .setMessage(R.string.dlg_msg_exit) + .setView(cb) + .setPositiveButton(R.string.yes, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) + { + finish(); + } + }) + .setNegativeButton(R.string.no, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) + { + dialog.dismiss(); + } + }) + .create() + .show(); + } + else + { + super.onBackPressed(); + } + } + + @Override protected void onSaveInstanceState(Bundle outState) + { + super.onSaveInstanceState(outState); + outState.putString(PARAM_SUPERBAR_TEXT, superBarEditText.getText().toString()); + } + + @Override protected void onRestoreInstanceState(Bundle inState) + { + super.onRestoreInstanceState(inState); + superBarEditText.setText(inState.getString(PARAM_SUPERBAR_TEXT)); + } + + @Override public boolean onCreateOptionsMenu(Menu menu) + { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.home_menu, menu); + return true; + } + + @Override public boolean onOptionsItemSelected(MenuItem item) + { + + // refer to http://tools.android.com/tips/non-constant-fields why we can't use switch/case + // here .. + int itemId = item.getItemId(); + if (itemId == R.id.newBookmark) + { + Intent bookmarkIntent = new Intent(this, BookmarkActivity.class); + startActivity(bookmarkIntent); + } + else if (itemId == R.id.appSettings) + { + Intent settingsIntent = new Intent(this, ApplicationSettingsActivity.class); + startActivity(settingsIntent); + } + else if (itemId == R.id.help) + { + Intent helpIntent = new Intent(this, HelpActivity.class); + startActivity(helpIntent); + } + else if (itemId == R.id.about) + { + Intent aboutIntent = new Intent(this, AboutActivity.class); + startActivity(aboutIntent); + } + + return true; + } + + private class SuperBarTextWatcher implements TextWatcher + { + @Override public void afterTextChanged(Editable s) + { + if (separatedListAdapter != null) + { + String text = s.toString(); + if (text.length() > 0) + { + ArrayList computers_list = + GlobalApp.getQuickConnectHistoryGateway().findHistory(text); + computers_list.addAll( + GlobalApp.getManualBookmarkGateway().findByLabelOrHostnameLike(text)); + manualBookmarkAdapter.replaceItems(computers_list); + QuickConnectBookmark qcBm = new QuickConnectBookmark(); + qcBm.setLabel(text); + qcBm.setHostname(text); + manualBookmarkAdapter.insert(qcBm, 0); + } + else + { + manualBookmarkAdapter.replaceItems( + GlobalApp.getManualBookmarkGateway().findAll()); + manualBookmarkAdapter.insert(addBookmarkPlaceholder, 0); + } + + separatedListAdapter.notifyDataSetChanged(); + } + } + + @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) + { + } + + @Override public void onTextChanged(CharSequence s, int start, int before, int count) + { + } + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/ScrollView2D.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/ScrollView2D.java new file mode 100644 index 0000000..db79496 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/ScrollView2D.java @@ -0,0 +1,1349 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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. + */ +/* + * Revised 5/19/2010 by GORGES + * Now supports two-dimensional view scrolling + * http://GORGES.us + */ + +package com.freerdp.freerdpcore.presentation; + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.FocusFinder; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.animation.AnimationUtils; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.Scroller; +import android.widget.TextView; + +import java.util.List; + +/** + * Layout container for a view hierarchy that can be scrolled by the user, + * allowing it to be larger than the physical display. A TwoDScrollView + * is a {@link FrameLayout}, meaning you should place one child in it + * containing the entire contents to scroll; this child may itself be a layout + * manager with a complex hierarchy of objects. A child that is often used + * is a {@link LinearLayout} in a vertical orientation, presenting a vertical + * array of top-level items that the user can scroll through. + *

+ *

The {@link TextView} class also + * takes care of its own scrolling, so does not require a TwoDScrollView, but + * using the two together is possible to achieve the effect of a text view + * within a larger container. + */ +public class ScrollView2D extends FrameLayout +{ + + static final int ANIMATED_SCROLL_GAP = 250; + static final float MAX_SCROLL_FACTOR = 0.5f; + private final Rect mTempRect = new Rect(); + private ScrollView2DListener scrollView2DListener = null; + private long mLastScroll; + private Scroller mScroller; + private boolean scrollEnabled = true; + /** + * Flag to indicate that we are moving focus ourselves. This is so the + * code that watches for focus changes initiated outside this TwoDScrollView + * knows that it does not have to do anything. + */ + private boolean mTwoDScrollViewMovedFocus; + /** + * Position of the last motion event. + */ + private float mLastMotionY; + private float mLastMotionX; + /** + * True when the layout has changed but the traversal has not come through yet. + * Ideally the view hierarchy would keep track of this for us. + */ + private boolean mIsLayoutDirty = true; + /** + * The child to give focus to in the event that a child has requested focus while the + * layout is dirty. This prevents the scroll from being wrong if the child has not been + * laid out before requesting focus. + */ + private View mChildToScrollTo = null; + /** + * True if the user is currently dragging this TwoDScrollView around. This is + * not the same as 'is being flinged', which can be checked by + * mScroller.isFinished() (flinging begins when the user lifts his finger). + */ + private boolean mIsBeingDragged = false; + /** + * Determines speed during touch scrolling + */ + private VelocityTracker mVelocityTracker; + /** + * Whether arrow scrolling is animated. + */ + private int mTouchSlop; + private int mMinimumVelocity; + private int mMaximumVelocity; + public ScrollView2D(Context context) + { + super(context); + initTwoDScrollView(); + } + + public ScrollView2D(Context context, AttributeSet attrs) + { + super(context, attrs); + initTwoDScrollView(); + } + + public ScrollView2D(Context context, AttributeSet attrs, int defStyle) + { + super(context, attrs, defStyle); + initTwoDScrollView(); + } + + @Override protected float getTopFadingEdgeStrength() + { + if (getChildCount() == 0) + { + return 0.0f; + } + final int length = getVerticalFadingEdgeLength(); + if (getScrollY() < length) + { + return getScrollY() / (float)length; + } + return 1.0f; + } + + @Override protected float getBottomFadingEdgeStrength() + { + if (getChildCount() == 0) + { + return 0.0f; + } + final int length = getVerticalFadingEdgeLength(); + final int bottomEdge = getHeight() - getPaddingBottom(); + final int span = getChildAt(0).getBottom() - getScrollY() - bottomEdge; + if (span < length) + { + return span / (float)length; + } + return 1.0f; + } + + @Override protected float getLeftFadingEdgeStrength() + { + if (getChildCount() == 0) + { + return 0.0f; + } + final int length = getHorizontalFadingEdgeLength(); + if (getScrollX() < length) + { + return getScrollX() / (float)length; + } + return 1.0f; + } + + @Override protected float getRightFadingEdgeStrength() + { + if (getChildCount() == 0) + { + return 0.0f; + } + final int length = getHorizontalFadingEdgeLength(); + final int rightEdge = getWidth() - getPaddingRight(); + final int span = getChildAt(0).getRight() - getScrollX() - rightEdge; + if (span < length) + { + return span / (float)length; + } + return 1.0f; + } + + /** + * Disable/Enable scrolling + */ + public void setScrollEnabled(boolean enable) + { + scrollEnabled = enable; + } + + /** + * @return The maximum amount this scroll view will scroll in response to + * an arrow event. + */ + public int getMaxScrollAmountVertical() + { + return (int)(MAX_SCROLL_FACTOR * getHeight()); + } + + public int getMaxScrollAmountHorizontal() + { + return (int)(MAX_SCROLL_FACTOR * getWidth()); + } + + private void initTwoDScrollView() + { + mScroller = new Scroller(getContext()); + setFocusable(true); + setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); + setWillNotDraw(false); + final ViewConfiguration configuration = ViewConfiguration.get(getContext()); + mTouchSlop = configuration.getScaledTouchSlop(); + mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); + mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); + } + + @Override public void addView(View child) + { + if (getChildCount() > 0) + { + throw new IllegalStateException("TwoDScrollView can host only one direct child"); + } + super.addView(child); + } + + @Override public void addView(View child, int index) + { + if (getChildCount() > 0) + { + throw new IllegalStateException("TwoDScrollView can host only one direct child"); + } + super.addView(child, index); + } + + @Override public void addView(View child, ViewGroup.LayoutParams params) + { + if (getChildCount() > 0) + { + throw new IllegalStateException("TwoDScrollView can host only one direct child"); + } + super.addView(child, params); + } + + @Override public void addView(View child, int index, ViewGroup.LayoutParams params) + { + if (getChildCount() > 0) + { + throw new IllegalStateException("TwoDScrollView can host only one direct child"); + } + super.addView(child, index, params); + } + + /** + * @return Returns true this TwoDScrollView can be scrolled + */ + private boolean canScroll() + { + if (!scrollEnabled) + return false; + View child = getChildAt(0); + if (child != null) + { + int childHeight = child.getHeight(); + int childWidth = child.getWidth(); + return (getHeight() < childHeight + getPaddingTop() + getPaddingBottom()) || + (getWidth() < childWidth + getPaddingLeft() + getPaddingRight()); + } + return false; + } + + @Override public boolean dispatchKeyEvent(KeyEvent event) + { + // Let the focused view and/or our descendants get the key first + boolean handled = super.dispatchKeyEvent(event); + if (handled) + { + return true; + } + return executeKeyEvent(event); + } + + /** + * You can call this function yourself to have the scroll view perform + * scrolling from a key event, just as if the event had been dispatched to + * it by the view hierarchy. + * + * @param event The key event to execute. + * @return Return true if the event was handled, else false. + */ + public boolean executeKeyEvent(KeyEvent event) + { + mTempRect.setEmpty(); + if (!canScroll()) + { + if (isFocused()) + { + View currentFocused = findFocus(); + if (currentFocused == this) + currentFocused = null; + View nextFocused = + FocusFinder.getInstance().findNextFocus(this, currentFocused, View.FOCUS_DOWN); + return nextFocused != null && nextFocused != this && + nextFocused.requestFocus(View.FOCUS_DOWN); + } + return false; + } + boolean handled = false; + if (event.getAction() == KeyEvent.ACTION_DOWN) + { + switch (event.getKeyCode()) + { + case KeyEvent.KEYCODE_DPAD_UP: + if (!event.isAltPressed()) + { + handled = arrowScroll(View.FOCUS_UP, false); + } + else + { + handled = fullScroll(View.FOCUS_UP, false); + } + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + if (!event.isAltPressed()) + { + handled = arrowScroll(View.FOCUS_DOWN, false); + } + else + { + handled = fullScroll(View.FOCUS_DOWN, false); + } + break; + case KeyEvent.KEYCODE_DPAD_LEFT: + if (!event.isAltPressed()) + { + handled = arrowScroll(View.FOCUS_LEFT, true); + } + else + { + handled = fullScroll(View.FOCUS_LEFT, true); + } + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (!event.isAltPressed()) + { + handled = arrowScroll(View.FOCUS_RIGHT, true); + } + else + { + handled = fullScroll(View.FOCUS_RIGHT, true); + } + break; + } + } + return handled; + } + + @Override public boolean onInterceptTouchEvent(MotionEvent ev) + { + /* + * This method JUST determines whether we want to intercept the motion. + * If we return true, onMotionEvent will be called and we do the actual + * scrolling there. + * + * Shortcut the most recurring case: the user is in the dragging + * state and he is moving his finger. We want to intercept this + * motion. + */ + final int action = ev.getAction(); + if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) + { + return true; + } + if (!canScroll()) + { + mIsBeingDragged = false; + return false; + } + final float y = ev.getY(); + final float x = ev.getX(); + switch (action) + { + case MotionEvent.ACTION_MOVE: + /* + * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check + * whether the user has moved far enough from his original down touch. + */ + /* + * Locally do absolute value. mLastMotionY is set to the y value + * of the down event. + */ + final int yDiff = (int)Math.abs(y - mLastMotionY); + final int xDiff = (int)Math.abs(x - mLastMotionX); + if (yDiff > mTouchSlop || xDiff > mTouchSlop) + { + mIsBeingDragged = true; + } + break; + + case MotionEvent.ACTION_DOWN: + /* Remember location of down touch */ + mLastMotionY = y; + mLastMotionX = x; + + /* + * If being flinged and user touches the screen, initiate drag; + * otherwise don't. mScroller.isFinished should be false when + * being flinged. + */ + mIsBeingDragged = !mScroller.isFinished(); + break; + + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + /* Release the drag */ + mIsBeingDragged = false; + break; + } + + /* + * The only time we want to intercept motion events is if we are in the + * drag mode. + */ + return mIsBeingDragged; + } + + @Override public boolean onTouchEvent(MotionEvent ev) + { + + if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) + { + // Don't handle edge touches immediately -- they may actually belong to one of our + // descendants. + return false; + } + + if (!canScroll()) + { + return false; + } + + if (mVelocityTracker == null) + { + mVelocityTracker = VelocityTracker.obtain(); + } + mVelocityTracker.addMovement(ev); + + final int action = ev.getAction(); + final float y = ev.getY(); + final float x = ev.getX(); + + switch (action) + { + case MotionEvent.ACTION_DOWN: + /* + * If being flinged and user touches, stop the fling. isFinished + * will be false if being flinged. + */ + if (!mScroller.isFinished()) + { + mScroller.abortAnimation(); + } + + // Remember where the motion event started + mLastMotionY = y; + mLastMotionX = x; + break; + case MotionEvent.ACTION_MOVE: + // Scroll to follow the motion event + int deltaX = (int)(mLastMotionX - x); + int deltaY = (int)(mLastMotionY - y); + mLastMotionX = x; + mLastMotionY = y; + + if (deltaX < 0) + { + if (getScrollX() < 0) + { + deltaX = 0; + } + } + else if (deltaX > 0) + { + final int rightEdge = getWidth() - getPaddingRight(); + final int availableToScroll = + getChildAt(0).getRight() - getScrollX() - rightEdge; + if (availableToScroll > 0) + { + deltaX = Math.min(availableToScroll, deltaX); + } + else + { + deltaX = 0; + } + } + if (deltaY < 0) + { + if (getScrollY() < 0) + { + deltaY = 0; + } + } + else if (deltaY > 0) + { + final int bottomEdge = getHeight() - getPaddingBottom(); + final int availableToScroll = + getChildAt(0).getBottom() - getScrollY() - bottomEdge; + if (availableToScroll > 0) + { + deltaY = Math.min(availableToScroll, deltaY); + } + else + { + deltaY = 0; + } + } + if (deltaY != 0 || deltaX != 0) + scrollBy(deltaX, deltaY); + break; + case MotionEvent.ACTION_UP: + final VelocityTracker velocityTracker = mVelocityTracker; + velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); + int initialXVelocity = (int)velocityTracker.getXVelocity(); + int initialYVelocity = (int)velocityTracker.getYVelocity(); + if ((Math.abs(initialXVelocity) + Math.abs(initialYVelocity) > mMinimumVelocity) && + getChildCount() > 0) + { + fling(-initialXVelocity, -initialYVelocity); + } + if (mVelocityTracker != null) + { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + } + return true; + } + + /** + * Finds the next focusable component that fits in this View's bounds + * (excluding fading edges) pretending that this View's top is located at + * the parameter top. + * + * @param topFocus look for a candidate is the one at the top of the bounds + * if topFocus is true, or at the bottom of the bounds if topFocus is + * false + * @param top the top offset of the bounds in which a focusable must be + * found (the fading edge is assumed to start at this position) + * @param preferredFocusable the View that has highest priority and will be + * returned if it is within my bounds (null is valid) + * @return the next focusable component in the bounds or null if none can be + * found + */ + private View findFocusableViewInMyBounds(final boolean topFocus, final int top, + final boolean leftFocus, final int left, + View preferredFocusable) + { + /* + * The fading edge's transparent side should be considered for focus + * since it's mostly visible, so we divide the actual fading edge length + * by 2. + */ + final int verticalFadingEdgeLength = getVerticalFadingEdgeLength() / 2; + final int topWithoutFadingEdge = top + verticalFadingEdgeLength; + final int bottomWithoutFadingEdge = top + getHeight() - verticalFadingEdgeLength; + final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength() / 2; + final int leftWithoutFadingEdge = left + horizontalFadingEdgeLength; + final int rightWithoutFadingEdge = left + getWidth() - horizontalFadingEdgeLength; + + if ((preferredFocusable != null) && + (preferredFocusable.getTop() < bottomWithoutFadingEdge) && + (preferredFocusable.getBottom() > topWithoutFadingEdge) && + (preferredFocusable.getLeft() < rightWithoutFadingEdge) && + (preferredFocusable.getRight() > leftWithoutFadingEdge)) + { + return preferredFocusable; + } + return findFocusableViewInBounds(topFocus, topWithoutFadingEdge, bottomWithoutFadingEdge, + leftFocus, leftWithoutFadingEdge, rightWithoutFadingEdge); + } + + /** + * Finds the next focusable component that fits in the specified bounds. + *

+ * + * @param topFocus look for a candidate is the one at the top of the bounds + * if topFocus is true, or at the bottom of the bounds if topFocus is + * false + * @param top the top offset of the bounds in which a focusable must be + * found + * @param bottom the bottom offset of the bounds in which a focusable must + * be found + * @return the next focusable component in the bounds or null if none can + * be found + */ + private View findFocusableViewInBounds(boolean topFocus, int top, int bottom, boolean leftFocus, + int left, int right) + { + List focusables = getFocusables(View.FOCUS_FORWARD); + View focusCandidate = null; + + /* + * A fully contained focusable is one where its top is below the bound's + * top, and its bottom is above the bound's bottom. A partially + * contained focusable is one where some part of it is within the + * bounds, but it also has some part that is not within bounds. A fully contained + * focusable is preferred to a partially contained focusable. + */ + boolean foundFullyContainedFocusable = false; + + int count = focusables.size(); + for (int i = 0; i < count; i++) + { + View view = focusables.get(i); + int viewTop = view.getTop(); + int viewBottom = view.getBottom(); + int viewLeft = view.getLeft(); + int viewRight = view.getRight(); + + if (top < viewBottom && viewTop < bottom && left < viewRight && viewLeft < right) + { + /* + * the focusable is in the target area, it is a candidate for + * focusing + */ + final boolean viewIsFullyContained = (top < viewTop) && (viewBottom < bottom) && + (left < viewLeft) && (viewRight < right); + if (focusCandidate == null) + { + /* No candidate, take this one */ + focusCandidate = view; + foundFullyContainedFocusable = viewIsFullyContained; + } + else + { + final boolean viewIsCloserToVerticalBoundary = + (topFocus && viewTop < focusCandidate.getTop()) || + (!topFocus && viewBottom > focusCandidate.getBottom()); + final boolean viewIsCloserToHorizontalBoundary = + (leftFocus && viewLeft < focusCandidate.getLeft()) || + (!leftFocus && viewRight > focusCandidate.getRight()); + if (foundFullyContainedFocusable) + { + if (viewIsFullyContained && viewIsCloserToVerticalBoundary && + viewIsCloserToHorizontalBoundary) + { + /* + * We're dealing with only fully contained views, so + * it has to be closer to the boundary to beat our + * candidate + */ + focusCandidate = view; + } + } + else + { + if (viewIsFullyContained) + { + /* Any fully contained view beats a partially contained view */ + focusCandidate = view; + foundFullyContainedFocusable = true; + } + else if (viewIsCloserToVerticalBoundary && viewIsCloserToHorizontalBoundary) + { + /* + * Partially contained view beats another partially + * contained view if it's closer + */ + focusCandidate = view; + } + } + } + } + } + return focusCandidate; + } + + /** + *

Handles scrolling in response to a "home/end" shortcut press. This + * method will scroll the view to the top or bottom and give the focus + * to the topmost/bottommost component in the new visible area. If no + * component is a good candidate for focus, this scrollview reclaims the + * focus.

+ * + * @param direction the scroll direction: {@link android.view.View#FOCUS_UP} + * to go the top of the view or + * {@link android.view.View#FOCUS_DOWN} to go the bottom + * @return true if the key event is consumed by this method, false otherwise + */ + public boolean fullScroll(int direction, boolean horizontal) + { + if (!horizontal) + { + boolean down = direction == View.FOCUS_DOWN; + int height = getHeight(); + mTempRect.top = 0; + mTempRect.bottom = height; + if (down) + { + int count = getChildCount(); + if (count > 0) + { + View view = getChildAt(count - 1); + mTempRect.bottom = view.getBottom(); + mTempRect.top = mTempRect.bottom - height; + } + } + return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom, 0, 0, 0); + } + else + { + boolean right = direction == View.FOCUS_DOWN; + int width = getWidth(); + mTempRect.left = 0; + mTempRect.right = width; + if (right) + { + int count = getChildCount(); + if (count > 0) + { + View view = getChildAt(count - 1); + mTempRect.right = view.getBottom(); + mTempRect.left = mTempRect.right - width; + } + } + return scrollAndFocus(0, 0, 0, direction, mTempRect.top, mTempRect.bottom); + } + } + + /** + *

Scrolls the view to make the area defined by top and + * bottom visible. This method attempts to give the focus + * to a component visible in this area. If no component can be focused in + * the new visible area, the focus is reclaimed by this scrollview.

+ * + * @param direction the scroll direction: {@link android.view.View#FOCUS_UP} + * to go upward + * {@link android.view.View#FOCUS_DOWN} to downward + * @param top the top offset of the new area to be made visible + * @param bottom the bottom offset of the new area to be made visible + * @return true if the key event is consumed by this method, false otherwise + */ + private boolean scrollAndFocus(int directionY, int top, int bottom, int directionX, int left, + int right) + { + boolean handled = true; + int height = getHeight(); + int containerTop = getScrollY(); + int containerBottom = containerTop + height; + boolean up = directionY == View.FOCUS_UP; + int width = getWidth(); + int containerLeft = getScrollX(); + int containerRight = containerLeft + width; + boolean leftwards = directionX == View.FOCUS_UP; + View newFocused = findFocusableViewInBounds(up, top, bottom, leftwards, left, right); + if (newFocused == null) + { + newFocused = this; + } + if ((top >= containerTop && bottom <= containerBottom) || + (left >= containerLeft && right <= containerRight)) + { + handled = false; + } + else + { + int deltaY = up ? (top - containerTop) : (bottom - containerBottom); + int deltaX = leftwards ? (left - containerLeft) : (right - containerRight); + doScroll(deltaX, deltaY); + } + if (newFocused != findFocus() && newFocused.requestFocus(directionY)) + { + mTwoDScrollViewMovedFocus = true; + mTwoDScrollViewMovedFocus = false; + } + return handled; + } + + /** + * Handle scrolling in response to an up or down arrow click. + * + * @param direction The direction corresponding to the arrow key that was + * pressed + * @return True if we consumed the event, false otherwise + */ + public boolean arrowScroll(int direction, boolean horizontal) + { + View currentFocused = findFocus(); + if (currentFocused == this) + currentFocused = null; + View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction); + final int maxJump = + horizontal ? getMaxScrollAmountHorizontal() : getMaxScrollAmountVertical(); + + if (!horizontal) + { + if (nextFocused != null) + { + nextFocused.getDrawingRect(mTempRect); + offsetDescendantRectToMyCoords(nextFocused, mTempRect); + int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect); + doScroll(0, scrollDelta); + nextFocused.requestFocus(direction); + } + else + { + // no new focus + int scrollDelta = maxJump; + if (direction == View.FOCUS_UP && getScrollY() < scrollDelta) + { + scrollDelta = getScrollY(); + } + else if (direction == View.FOCUS_DOWN) + { + if (getChildCount() > 0) + { + int daBottom = getChildAt(0).getBottom(); + int screenBottom = getScrollY() + getHeight(); + if (daBottom - screenBottom < maxJump) + { + scrollDelta = daBottom - screenBottom; + } + } + } + if (scrollDelta == 0) + { + return false; + } + doScroll(0, direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta); + } + } + else + { + if (nextFocused != null) + { + nextFocused.getDrawingRect(mTempRect); + offsetDescendantRectToMyCoords(nextFocused, mTempRect); + int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect); + doScroll(scrollDelta, 0); + nextFocused.requestFocus(direction); + } + else + { + // no new focus + int scrollDelta = maxJump; + if (direction == View.FOCUS_UP && getScrollY() < scrollDelta) + { + scrollDelta = getScrollY(); + } + else if (direction == View.FOCUS_DOWN) + { + if (getChildCount() > 0) + { + int daBottom = getChildAt(0).getBottom(); + int screenBottom = getScrollY() + getHeight(); + if (daBottom - screenBottom < maxJump) + { + scrollDelta = daBottom - screenBottom; + } + } + } + if (scrollDelta == 0) + { + return false; + } + doScroll(direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta, 0); + } + } + return true; + } + + /** + * Smooth scroll by a Y delta + * + * @param delta the number of pixels to scroll by on the Y axis + */ + private void doScroll(int deltaX, int deltaY) + { + if (deltaX != 0 || deltaY != 0) + { + smoothScrollBy(deltaX, deltaY); + } + } + + /** + * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. + * + * @param dx the number of pixels to scroll by on the X axis + * @param dy the number of pixels to scroll by on the Y axis + */ + public final void smoothScrollBy(int dx, int dy) + { + long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll; + if (duration > ANIMATED_SCROLL_GAP) + { + mScroller.startScroll(getScrollX(), getScrollY(), dx, dy); + awakenScrollBars(mScroller.getDuration()); + invalidate(); + } + else + { + if (!mScroller.isFinished()) + { + mScroller.abortAnimation(); + } + scrollBy(dx, dy); + } + mLastScroll = AnimationUtils.currentAnimationTimeMillis(); + } + + /** + * Like {@link #scrollTo}, but scroll smoothly instead of immediately. + * + * @param x the position where to scroll on the X axis + * @param y the position where to scroll on the Y axis + */ + public final void smoothScrollTo(int x, int y) + { + smoothScrollBy(x - getScrollX(), y - getScrollY()); + } + + /** + *

The scroll range of a scroll view is the overall height of all of its + * children.

+ */ + @Override protected int computeVerticalScrollRange() + { + int count = getChildCount(); + return count == 0 ? getHeight() : (getChildAt(0)).getBottom(); + } + + @Override protected int computeHorizontalScrollRange() + { + int count = getChildCount(); + return count == 0 ? getWidth() : (getChildAt(0)).getRight(); + } + + @Override + protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) + { + ViewGroup.LayoutParams lp = child.getLayoutParams(); + int childWidthMeasureSpec; + int childHeightMeasureSpec; + + childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, + getPaddingLeft() + getPaddingRight(), lp.width); + childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + } + + @Override + protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, + int parentHeightMeasureSpec, int heightUsed) + { + final MarginLayoutParams lp = (MarginLayoutParams)child.getLayoutParams(); + final int childWidthMeasureSpec = + MeasureSpec.makeMeasureSpec(lp.leftMargin + lp.rightMargin, MeasureSpec.UNSPECIFIED); + final int childHeightMeasureSpec = + MeasureSpec.makeMeasureSpec(lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED); + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + } + + @Override public void computeScroll() + { + if (mScroller.computeScrollOffset()) + { + // This is called at drawing time by ViewGroup. We don't want to + // re-show the scrollbars at this point, which scrollTo will do, + // so we replicate most of scrollTo here. + // + // It's a little odd to call onScrollChanged from inside the drawing. + // + // It is, except when you remember that computeScroll() is used to + // animate scrolling. So unless we want to defer the onScrollChanged() + // until the end of the animated scrolling, we don't really have a + // choice here. + // + // I agree. The alternative, which I think would be worse, is to post + // something and tell the subclasses later. This is bad because there + // will be a window where mScrollX/Y is different from what the app + // thinks it is. + // + int oldX = getScrollX(); + int oldY = getScrollY(); + int x = mScroller.getCurrX(); + int y = mScroller.getCurrY(); + if (getChildCount() > 0) + { + View child = getChildAt(0); + scrollTo( + clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth()), + clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), + child.getHeight())); + } + else + { + scrollTo(x, y); + } + if (oldX != getScrollX() || oldY != getScrollY()) + { + onScrollChanged(getScrollX(), getScrollY(), oldX, oldY); + } + + // Keep on drawing until the animation has finished. + postInvalidate(); + } + } + + /** + * Scrolls the view to the given child. + * + * @param child the View to scroll to + */ + private void scrollToChild(View child) + { + child.getDrawingRect(mTempRect); + /* Offset from child's local coordinates to TwoDScrollView coordinates */ + offsetDescendantRectToMyCoords(child, mTempRect); + int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect); + if (scrollDelta != 0) + { + scrollBy(0, scrollDelta); + } + } + + /** + * If rect is off screen, scroll just enough to get it (or at least the + * first screen size chunk of it) on screen. + * + * @param rect The rectangle. + * @param immediate True to scroll immediately without animation + * @return true if scrolling was performed + */ + private boolean scrollToChildRect(Rect rect, boolean immediate) + { + final int delta = computeScrollDeltaToGetChildRectOnScreen(rect); + final boolean scroll = delta != 0; + if (scroll) + { + if (immediate) + { + scrollBy(0, delta); + } + else + { + smoothScrollBy(0, delta); + } + } + return scroll; + } + + /** + * Compute the amount to scroll in the Y direction in order to get + * a rectangle completely on the screen (or, if taller than the screen, + * at least the first screen size chunk of it). + * + * @param rect The rect. + * @return The scroll delta. + */ + protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) + { + if (getChildCount() == 0) + return 0; + int height = getHeight(); + int screenTop = getScrollY(); + int screenBottom = screenTop + height; + int fadingEdge = getVerticalFadingEdgeLength(); + // leave room for top fading edge as long as rect isn't at very top + if (rect.top > 0) + { + screenTop += fadingEdge; + } + + // leave room for bottom fading edge as long as rect isn't at very bottom + if (rect.bottom < getChildAt(0).getHeight()) + { + screenBottom -= fadingEdge; + } + int scrollYDelta = 0; + if (rect.bottom > screenBottom && rect.top > screenTop) + { + // need to move down to get it in view: move down just enough so + // that the entire rectangle is in view (or at least the first + // screen size chunk). + if (rect.height() > height) + { + // just enough to get screen size chunk on + scrollYDelta += (rect.top - screenTop); + } + else + { + // get entire rect at bottom of screen + scrollYDelta += (rect.bottom - screenBottom); + } + + // make sure we aren't scrolling beyond the end of our content + int bottom = getChildAt(0).getBottom(); + int distanceToBottom = bottom - screenBottom; + scrollYDelta = Math.min(scrollYDelta, distanceToBottom); + } + else if (rect.top < screenTop && rect.bottom < screenBottom) + { + // need to move up to get it in view: move up just enough so that + // entire rectangle is in view (or at least the first screen + // size chunk of it). + + if (rect.height() > height) + { + // screen size chunk + scrollYDelta -= (screenBottom - rect.bottom); + } + else + { + // entire rect at top + scrollYDelta -= (screenTop - rect.top); + } + + // make sure we aren't scrolling any further than the top our content + scrollYDelta = Math.max(scrollYDelta, -getScrollY()); + } + return scrollYDelta; + } + + @Override public void requestChildFocus(View child, View focused) + { + if (!mTwoDScrollViewMovedFocus) + { + if (!mIsLayoutDirty) + { + scrollToChild(focused); + } + else + { + // The child may not be laid out yet, we can't compute the scroll yet + mChildToScrollTo = focused; + } + } + super.requestChildFocus(child, focused); + } + + /** + * When looking for focus in children of a scroll view, need to be a little + * more careful not to give focus to something that is scrolled off screen. + *

+ * This is more expensive than the default {@link android.view.ViewGroup} + * implementation, otherwise this behavior might have been made the default. + */ + @Override + protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) + { + // convert from forward / backward notation to up / down / left / right + // (ugh). + if (direction == View.FOCUS_FORWARD) + { + direction = View.FOCUS_DOWN; + } + else if (direction == View.FOCUS_BACKWARD) + { + direction = View.FOCUS_UP; + } + + final View nextFocus = previouslyFocusedRect == null + ? FocusFinder.getInstance().findNextFocus(this, null, direction) + : FocusFinder.getInstance().findNextFocusFromRect( + this, previouslyFocusedRect, direction); + + if (nextFocus == null) + { + return false; + } + + return nextFocus.requestFocus(direction, previouslyFocusedRect); + } + + @Override + public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) + { + // offset into coordinate space of this scroll view + rectangle.offset(child.getLeft() - child.getScrollX(), child.getTop() - child.getScrollY()); + return scrollToChildRect(rectangle, immediate); + } + + @Override public void requestLayout() + { + mIsLayoutDirty = true; + super.requestLayout(); + } + + @Override protected void onLayout(boolean changed, int l, int t, int r, int b) + { + super.onLayout(changed, l, t, r, b); + mIsLayoutDirty = false; + // Give a child focus if it needs it + if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) + { + scrollToChild(mChildToScrollTo); + } + mChildToScrollTo = null; + + // Calling this with the present values causes it to re-clam them + scrollTo(getScrollX(), getScrollY()); + } + + @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) + { + super.onSizeChanged(w, h, oldw, oldh); + + View currentFocused = findFocus(); + if (null == currentFocused || this == currentFocused) + return; + + // If the currently-focused view was visible on the screen when the + // screen was at the old height, then scroll the screen to make that + // view visible with the new screen height. + currentFocused.getDrawingRect(mTempRect); + offsetDescendantRectToMyCoords(currentFocused, mTempRect); + int scrollDeltaX = computeScrollDeltaToGetChildRectOnScreen(mTempRect); + int scrollDeltaY = computeScrollDeltaToGetChildRectOnScreen(mTempRect); + doScroll(scrollDeltaX, scrollDeltaY); + } + + /** + * Return true if child is an descendant of parent, (or equal to the parent). + */ + private boolean isViewDescendantOf(View child, View parent) + { + if (child == parent) + { + return true; + } + + final ViewParent theParent = child.getParent(); + return (theParent instanceof ViewGroup) && isViewDescendantOf((View)theParent, parent); + } + + /** + * Fling the scroll view + * + * @param velocityY The initial velocity in the Y direction. Positive + * numbers mean that the finger/curor is moving down the screen, + * which means we want to scroll towards the top. + */ + public void fling(int velocityX, int velocityY) + { + if (getChildCount() > 0) + { + int height = getHeight() - getPaddingBottom() - getPaddingTop(); + int bottom = getChildAt(0).getHeight(); + int width = getWidth() - getPaddingRight() - getPaddingLeft(); + int right = getChildAt(0).getWidth(); + + mScroller.fling(getScrollX(), getScrollY(), velocityX, velocityY, 0, right - width, 0, + bottom - height); + + final boolean movingDown = velocityY > 0; + final boolean movingRight = velocityX > 0; + + View newFocused = findFocusableViewInMyBounds( + movingRight, mScroller.getFinalX(), movingDown, mScroller.getFinalY(), findFocus()); + if (newFocused == null) + { + newFocused = this; + } + + if (newFocused != findFocus() && + newFocused.requestFocus(movingDown ? View.FOCUS_DOWN : View.FOCUS_UP)) + { + mTwoDScrollViewMovedFocus = true; + mTwoDScrollViewMovedFocus = false; + } + + awakenScrollBars(mScroller.getDuration()); + invalidate(); + } + } + + /** + * {@inheritDoc} + *

+ *

This version also clamps the scrolling to the bounds of our child. + */ + public void scrollTo(int x, int y) + { + // we rely on the fact the View.scrollBy calls scrollTo. + if (getChildCount() > 0) + { + View child = getChildAt(0); + x = clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth()); + y = clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), child.getHeight()); + if (x != getScrollX() || y != getScrollY()) + { + super.scrollTo(x, y); + } + } + } + + private int clamp(int n, int my, int child) + { + if (my >= child || n < 0) + { + /* my >= child is this case: + * |--------------- me ---------------| + * |------ child ------| + * or + * |--------------- me ---------------| + * |------ child ------| + * or + * |--------------- me ---------------| + * |------ child ------| + * + * n < 0 is this case: + * |------ me ------| + * |-------- child --------| + * |-- mScrollX --| + */ + return 0; + } + if ((my + n) > child) + { + /* this case: + * |------ me ------| + * |------ child ------| + * |-- mScrollX --| + */ + return child - my; + } + return n; + } + + public void setScrollViewListener(ScrollView2DListener scrollViewListener) + { + this.scrollView2DListener = scrollViewListener; + } + + @Override protected void onScrollChanged(int x, int y, int oldx, int oldy) + { + super.onScrollChanged(x, y, oldx, oldy); + if (scrollView2DListener != null) + { + scrollView2DListener.onScrollChanged(this, x, y, oldx, oldy); + } + } + + // interface to receive notifications when the view is scrolled + public interface ScrollView2DListener { + void onScrollChanged(ScrollView2D scrollView, int x, int y, int oldx, int oldy); + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/SessionActivity.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/SessionActivity.java new file mode 100644 index 0000000..461585b --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/SessionActivity.java @@ -0,0 +1,1500 @@ +/* + Android Session Activity + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. + */ + +package com.freerdp.freerdpcore.presentation; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.app.UiModeManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Point; +import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.inputmethodservice.Keyboard; +import android.inputmethodservice.KeyboardView; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; + +import android.text.InputType; +import android.util.Log; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewTreeObserver.OnGlobalLayoutListener; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.InputMethodSubtype; +import android.widget.EditText; +import android.widget.Toast; +import android.widget.ZoomControls; + +import com.freerdp.freerdpcore.R; +import com.freerdp.freerdpcore.application.GlobalApp; +import com.freerdp.freerdpcore.application.SessionState; +import com.freerdp.freerdpcore.domain.BookmarkBase; +import com.freerdp.freerdpcore.domain.ConnectionReference; +import com.freerdp.freerdpcore.domain.ManualBookmark; +import com.freerdp.freerdpcore.services.LibFreeRDP; +import com.freerdp.freerdpcore.utils.ClipboardManagerProxy; +import com.freerdp.freerdpcore.utils.KeyboardMapper; +import com.freerdp.freerdpcore.utils.Mouse; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +public class SessionActivity extends AppCompatActivity + implements LibFreeRDP.UIEventListener, KeyboardView.OnKeyboardActionListener, + ScrollView2D.ScrollView2DListener, KeyboardMapper.KeyProcessingListener, + SessionView.SessionViewListener, TouchPointerView.TouchPointerListener, + ClipboardManagerProxy.OnClipboardChangedListener +{ + public static final String PARAM_CONNECTION_REFERENCE = "conRef"; + public static final String PARAM_INSTANCE = "instance"; + private static final float ZOOMING_STEP = 0.5f; + private static final int ZOOMCONTROLS_AUTOHIDE_TIMEOUT = 4000; + // timeout between subsequent scrolling requests when the touch-pointer is + // at the edge of the session view + private static final int SCROLLING_TIMEOUT = 50; + private static final int SCROLLING_DISTANCE = 20; + private static final String TAG = "FreeRDP.SessionActivity"; + // variables for delayed move event sending + private static final int MAX_DISCARDED_MOVE_EVENTS = 3; + private static final int SEND_MOVE_EVENT_TIMEOUT = 150; + private Bitmap bitmap; + private SessionState session; + private SessionView sessionView; + private TouchPointerView touchPointerView; + private ProgressDialog progressDialog; + private KeyboardView keyboardView; + private KeyboardView modifiersKeyboardView; + private ZoomControls zoomControls; + private KeyboardMapper keyboardMapper; + + private Keyboard specialkeysKeyboard; + private Keyboard numpadKeyboard; + private Keyboard cursorKeyboard; + private Keyboard modifiersKeyboard; + + private AlertDialog dlgVerifyCertificate; + private AlertDialog dlgUserCredentials; + private View userCredView; + + private UIHandler uiHandler; + + private int screen_width; + private int screen_height; + + private boolean connectCancelledByUser = false; + private boolean sessionRunning = false; + private boolean toggleMouseButtons = false; + + private LibFreeRDPBroadcastReceiver libFreeRDPBroadcastReceiver; + private ScrollView2D scrollView; + // keyboard visibility flags + private boolean sysKeyboardVisible = false; + private boolean extKeyboardVisible = false; + private int discardedMoveEvents = 0; + private ClipboardManagerProxy mClipboardManager; + private boolean callbackDialogResult; + View mDecor; + + private void createDialogs() + { + // build verify certificate dialog + dlgVerifyCertificate = + new AlertDialog.Builder(this) + .setTitle(R.string.dlg_title_verify_certificate) + .setPositiveButton(android.R.string.yes, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) + { + callbackDialogResult = true; + synchronized (dialog) + { + dialog.notify(); + } + } + }) + .setNegativeButton(android.R.string.no, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) + { + callbackDialogResult = false; + connectCancelledByUser = true; + synchronized (dialog) + { + dialog.notify(); + } + } + }) + .setCancelable(false) + .create(); + + // build the dialog + userCredView = getLayoutInflater().inflate(R.layout.credentials, null, true); + dlgUserCredentials = + new AlertDialog.Builder(this) + .setView(userCredView) + .setTitle(R.string.dlg_title_credentials) + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) + { + callbackDialogResult = true; + synchronized (dialog) + { + dialog.notify(); + } + } + }) + .setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) + { + callbackDialogResult = false; + connectCancelledByUser = true; + synchronized (dialog) + { + dialog.notify(); + } + } + }) + .setCancelable(false) + .create(); + } + + private boolean hasHardwareMenuButton() + { + if (Build.VERSION.SDK_INT <= 10) + return true; + + if (Build.VERSION.SDK_INT >= 14) + { + boolean rc = false; + final ViewConfiguration cfg = ViewConfiguration.get(this); + + return cfg.hasPermanentMenuKey(); + } + + return false; + } + + @Override public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + // show status bar or make fullscreen? + if (ApplicationSettingsActivity.getHideStatusBar(this)) + { + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + } + + this.setContentView(R.layout.session); + if (hasHardwareMenuButton() || ApplicationSettingsActivity.getHideActionBar(this)) + { + this.getSupportActionBar().hide(); + } + else + this.getSupportActionBar().show(); + + Log.v(TAG, "Session.onCreate"); + + // ATTENTION: We use the onGlobalLayout notification to start our + // session. + // This is because only then we can know the exact size of our session + // when using fit screen + // accounting for any status bars etc. that Android might throws on us. + // A bit weird looking + // but this is the only way ... + final View activityRootView = findViewById(R.id.session_root_view); + activityRootView.getViewTreeObserver().addOnGlobalLayoutListener( + new OnGlobalLayoutListener() { + @Override public void onGlobalLayout() + { + screen_width = activityRootView.getWidth(); + screen_height = activityRootView.getHeight(); + + // start session + if (!sessionRunning && getIntent() != null) + { + processIntent(getIntent()); + sessionRunning = true; + } + } + }); + + sessionView = findViewById(R.id.sessionView); + sessionView.setScaleGestureDetector( + new ScaleGestureDetector(this, new PinchZoomListener())); + sessionView.setSessionViewListener(this); + sessionView.requestFocus(); + + touchPointerView = findViewById(R.id.touchPointerView); + touchPointerView.setTouchPointerListener(this); + + keyboardMapper = new KeyboardMapper(); + keyboardMapper.init(this); + keyboardMapper.reset(this); + + modifiersKeyboard = new Keyboard(getApplicationContext(), R.xml.modifiers_keyboard); + specialkeysKeyboard = new Keyboard(getApplicationContext(), R.xml.specialkeys_keyboard); + numpadKeyboard = new Keyboard(getApplicationContext(), R.xml.numpad_keyboard); + cursorKeyboard = new Keyboard(getApplicationContext(), R.xml.cursor_keyboard); + + // hide keyboard below the sessionView + keyboardView = findViewById(R.id.extended_keyboard); + keyboardView.setKeyboard(specialkeysKeyboard); + keyboardView.setOnKeyboardActionListener(this); + + modifiersKeyboardView = findViewById(R.id.extended_keyboard_header); + modifiersKeyboardView.setKeyboard(modifiersKeyboard); + modifiersKeyboardView.setOnKeyboardActionListener(this); + + scrollView = findViewById(R.id.sessionScrollView); + scrollView.setScrollViewListener(this); + uiHandler = new UIHandler(); + libFreeRDPBroadcastReceiver = new LibFreeRDPBroadcastReceiver(); + + zoomControls = findViewById(R.id.zoomControls); + zoomControls.hide(); + zoomControls.setOnZoomInClickListener(new View.OnClickListener() { + @Override public void onClick(View v) + { + resetZoomControlsAutoHideTimeout(); + zoomControls.setIsZoomInEnabled(sessionView.zoomIn(ZOOMING_STEP)); + zoomControls.setIsZoomOutEnabled(true); + } + }); + zoomControls.setOnZoomOutClickListener(new View.OnClickListener() { + @Override public void onClick(View v) + { + resetZoomControlsAutoHideTimeout(); + zoomControls.setIsZoomOutEnabled(sessionView.zoomOut(ZOOMING_STEP)); + zoomControls.setIsZoomInEnabled(true); + } + }); + + toggleMouseButtons = false; + + createDialogs(); + + // register freerdp events broadcast receiver + IntentFilter filter = new IntentFilter(); + filter.addAction(GlobalApp.ACTION_EVENT_FREERDP); + registerReceiver(libFreeRDPBroadcastReceiver, filter, RECEIVER_EXPORTED); + + mClipboardManager = ClipboardManagerProxy.getClipboardManager(this); + mClipboardManager.addClipboardChangedListener(this); + + mDecor = getWindow().getDecorView(); + mDecor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + } + + @Override public void onWindowFocusChanged(boolean hasFocus) + { + super.onWindowFocusChanged(hasFocus); + mClipboardManager.getPrimaryClipManually(); + } + + @Override protected void onStart() + { + super.onStart(); + Log.v(TAG, "Session.onStart"); + } + + @Override protected void onRestart() + { + super.onRestart(); + Log.v(TAG, "Session.onRestart"); + } + + @Override protected void onResume() + { + super.onResume(); + Log.v(TAG, "Session.onResume"); + } + + @Override protected void onPause() + { + super.onPause(); + Log.v(TAG, "Session.onPause"); + + // hide any visible keyboards + showKeyboard(false, false); + } + + @Override protected void onStop() + { + super.onStop(); + Log.v(TAG, "Session.onStop"); + } + + @Override protected void onDestroy() + { + if (connectThread != null) + { + connectThread.interrupt(); + } + super.onDestroy(); + Log.v(TAG, "Session.onDestroy"); + + // Cancel running disconnect timers. + GlobalApp.cancelDisconnectTimer(); + + // Disconnect all remaining sessions. + Collection sessions = GlobalApp.getSessions(); + for (SessionState session : sessions) + LibFreeRDP.disconnect(session.getInstance()); + + // unregister freerdp events broadcast receiver + unregisterReceiver(libFreeRDPBroadcastReceiver); + + // remove clipboard listener + mClipboardManager.removeClipboardboardChangedListener(this); + + // free session + GlobalApp.freeSession(session.getInstance()); + + session = null; + } + + @Override public void onConfigurationChanged(Configuration newConfig) + { + super.onConfigurationChanged(newConfig); + + // reload keyboard resources (changed from landscape) + modifiersKeyboard = new Keyboard(getApplicationContext(), R.xml.modifiers_keyboard); + specialkeysKeyboard = new Keyboard(getApplicationContext(), R.xml.specialkeys_keyboard); + numpadKeyboard = new Keyboard(getApplicationContext(), R.xml.numpad_keyboard); + cursorKeyboard = new Keyboard(getApplicationContext(), R.xml.cursor_keyboard); + + // apply loaded keyboards + keyboardView.setKeyboard(specialkeysKeyboard); + modifiersKeyboardView.setKeyboard(modifiersKeyboard); + + mDecor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + } + + private void processIntent(Intent intent) + { + // get either session instance or create one from a bookmark/uri + Bundle bundle = intent.getExtras(); + Uri openUri = intent.getData(); + if (openUri != null) + { + // Launched from URI, e.g: + // freerdp://user@ip:port/connect?sound=&rfx=&p=password&clipboard=%2b&themes=- + connect(openUri); + } + else if (bundle.containsKey(PARAM_INSTANCE)) + { + int inst = bundle.getInt(PARAM_INSTANCE); + session = GlobalApp.getSession(inst); + bitmap = session.getSurface().getBitmap(); + bindSession(); + } + else if (bundle.containsKey(PARAM_CONNECTION_REFERENCE)) + { + BookmarkBase bookmark = null; + String refStr = bundle.getString(PARAM_CONNECTION_REFERENCE); + if (ConnectionReference.isHostnameReference(refStr)) + { + bookmark = new ManualBookmark(); + bookmark.get().setHostname(ConnectionReference.getHostname(refStr)); + } + else if (ConnectionReference.isBookmarkReference(refStr)) + { + if (ConnectionReference.isManualBookmarkReference(refStr)) + bookmark = GlobalApp.getManualBookmarkGateway().findById( + ConnectionReference.getManualBookmarkId(refStr)); + else + assert false; + } + + if (bookmark != null) + connect(bookmark); + else + closeSessionActivity(RESULT_CANCELED); + } + else + { + // no session found - exit + closeSessionActivity(RESULT_CANCELED); + } + } + + private void connect(BookmarkBase bookmark) + { + session = GlobalApp.createSession(bookmark, getApplicationContext()); + + BookmarkBase.ScreenSettings screenSettings = + session.getBookmark().getActiveScreenSettings(); + Log.v(TAG, "Screen Resolution: " + screenSettings.getResolutionString()); + if (screenSettings.isAutomatic()) + { + if ((getResources().getConfiguration().screenLayout & + Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE) + { + // large screen device i.e. tablet: simply use screen info + screenSettings.setHeight(screen_height); + screenSettings.setWidth(screen_width); + } + else + { + // small screen device i.e. phone: + // Automatic uses the largest side length of the screen and + // makes a 16:10 resolution setting out of it + int screenMax = Math.max(screen_width, screen_height); + screenSettings.setHeight(screenMax); + screenSettings.setWidth((int)((float)screenMax * 1.6f)); + } + } + if (screenSettings.isFitScreen()) + { + screenSettings.setHeight(screen_height); + screenSettings.setWidth(screen_width); + } + + connectWithTitle(bookmark.getLabel()); + } + + private void connect(Uri openUri) + { + session = GlobalApp.createSession(openUri, getApplicationContext()); + + connectWithTitle(openUri.getAuthority()); + } + + static class ConnectThread extends Thread + { + private final SessionState runnableSession; + private final Context context; + + public ConnectThread(@NonNull Context context, @NonNull SessionState session) + { + this.context = context; + runnableSession = session; + } + + public void run() + { + runnableSession.connect(context.getApplicationContext()); + } + } + + private ConnectThread connectThread = null; + + private void connectWithTitle(String title) + { + session.setUIEventListener(this); + + progressDialog = new ProgressDialog(this); + progressDialog.setTitle(title); + progressDialog.setMessage(getResources().getText(R.string.dlg_msg_connecting)); + progressDialog.setButton( + ProgressDialog.BUTTON_NEGATIVE, "Cancel", new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int which) + { + connectCancelledByUser = true; + LibFreeRDP.cancelConnection(session.getInstance()); + } + }); + progressDialog.setCancelable(false); + progressDialog.show(); + + connectThread = new ConnectThread(getApplicationContext(), session); + connectThread.start(); + } + + // binds the current session to the activity by wiring it up with the + // sessionView and updating all internal objects accordingly + private void bindSession() + { + Log.v(TAG, "bindSession called"); + session.setUIEventListener(this); + sessionView.onSurfaceChange(session); + scrollView.requestLayout(); + keyboardMapper.reset(this); + mDecor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + } + + private void setSoftInputState(boolean state) + { + InputMethodManager mgr = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); + + if (state) + { + mgr.showSoftInput(sessionView, InputMethodManager.SHOW_FORCED); + } + else + { + mgr.hideSoftInputFromWindow(sessionView.getWindowToken(), 0); + } + } + + // displays either the system or the extended keyboard or non of them + private void showKeyboard(final boolean showSystemKeyboard, final boolean showExtendedKeyboard) + { + // no matter what we are doing ... hide the zoom controls + // onScrollChange notification showing the control again ... + // i think check for "preference_key_ui_hide_zoom_controls" preference should be there + uiHandler.removeMessages(UIHandler.SHOW_ZOOMCONTROLS); + uiHandler.sendEmptyMessage(UIHandler.HIDE_ZOOMCONTROLS); + + InputMethodManager mgr = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); + + if (showSystemKeyboard) + { + // hide extended keyboard + keyboardView.setVisibility(View.GONE); + // show system keyboard + setSoftInputState(true); + + // show modifiers keyboard + modifiersKeyboardView.setVisibility(View.VISIBLE); + } + else if (showExtendedKeyboard) + { + // hide system keyboard + setSoftInputState(false); + + // show extended keyboard + keyboardView.setKeyboard(specialkeysKeyboard); + keyboardView.setVisibility(View.VISIBLE); + modifiersKeyboardView.setVisibility(View.VISIBLE); + } + else + { + // hide both + setSoftInputState(false); + keyboardView.setVisibility(View.GONE); + modifiersKeyboardView.setVisibility(View.GONE); + + // clear any active key modifiers) + keyboardMapper.clearlAllModifiers(); + } + + sysKeyboardVisible = showSystemKeyboard; + extKeyboardVisible = showExtendedKeyboard; + } + + private void closeSessionActivity(int resultCode) + { + // Go back to home activity (and send intent data back to home) + setResult(resultCode, getIntent()); + finish(); + } + + // update the state of our modifier keys + private void updateModifierKeyStates() + { + // check if any key is in the keycodes list + + List keys = modifiersKeyboard.getKeys(); + for (Keyboard.Key curKey : keys) + { + // if the key is a sticky key - just set it to off + if (curKey.sticky) + { + switch (keyboardMapper.getModifierState(curKey.codes[0])) + { + case KeyboardMapper.KEYSTATE_ON: + curKey.on = true; + curKey.pressed = false; + break; + + case KeyboardMapper.KEYSTATE_OFF: + curKey.on = false; + curKey.pressed = false; + break; + + case KeyboardMapper.KEYSTATE_LOCKED: + curKey.on = true; + curKey.pressed = true; + break; + } + } + } + + // refresh image + modifiersKeyboardView.invalidateAllKeys(); + } + + private void sendDelayedMoveEvent(int x, int y) + { + if (uiHandler.hasMessages(UIHandler.SEND_MOVE_EVENT)) + { + uiHandler.removeMessages(UIHandler.SEND_MOVE_EVENT); + discardedMoveEvents++; + } + else + discardedMoveEvents = 0; + + if (discardedMoveEvents > MAX_DISCARDED_MOVE_EVENTS) + LibFreeRDP.sendCursorEvent(session.getInstance(), x, y, Mouse.getMoveEvent()); + else + uiHandler.sendMessageDelayed(Message.obtain(null, UIHandler.SEND_MOVE_EVENT, x, y), + SEND_MOVE_EVENT_TIMEOUT); + } + + private void cancelDelayedMoveEvent() + { + uiHandler.removeMessages(UIHandler.SEND_MOVE_EVENT); + } + + @Override public boolean onCreateOptionsMenu(Menu menu) + { + getMenuInflater().inflate(R.menu.session_menu, menu); + return true; + } + + @Override public boolean onOptionsItemSelected(MenuItem item) + { + // refer to http://tools.android.com/tips/non-constant-fields why we + // can't use switch/case here .. + int itemId = item.getItemId(); + + if (itemId == R.id.session_touch_pointer) + { + // toggle touch pointer + if (touchPointerView.getVisibility() == View.VISIBLE) + { + touchPointerView.setVisibility(View.INVISIBLE); + sessionView.setTouchPointerPadding(0, 0); + } + else + { + touchPointerView.setVisibility(View.VISIBLE); + sessionView.setTouchPointerPadding(touchPointerView.getPointerWidth(), + touchPointerView.getPointerHeight()); + } + } + else if (itemId == R.id.session_sys_keyboard) + { + showKeyboard(!sysKeyboardVisible, false); + } + else if (itemId == R.id.session_ext_keyboard) + { + showKeyboard(false, !extKeyboardVisible); + } + else if (itemId == R.id.session_disconnect) + { + showKeyboard(false, false); + LibFreeRDP.disconnect(session.getInstance()); + } + + return true; + } + + @Override public void onBackPressed() + { + // hide keyboards (if any visible) or send alt+f4 to the session + if (sysKeyboardVisible || extKeyboardVisible) + showKeyboard(false, false); + else if (ApplicationSettingsActivity.getUseBackAsAltf4(this)) + { + keyboardMapper.sendAltF4(); + } + } + + @Override public boolean onKeyLongPress(int keyCode, KeyEvent event) + { + if (keyCode == KeyEvent.KEYCODE_BACK) + { + LibFreeRDP.disconnect(session.getInstance()); + return true; + } + return super.onKeyLongPress(keyCode, event); + } + + // android keyboard input handling + // We always use the unicode value to process input from the android + // keyboard except if key modifiers + // (like Win, Alt, Ctrl) are activated. In this case we will send the + // virtual key code to allow key + // combinations (like Win + E to open the explorer). + @Override public boolean onKeyDown(int keycode, KeyEvent event) + { + return keyboardMapper.processAndroidKeyEvent(event); + } + + @Override public boolean onKeyUp(int keycode, KeyEvent event) + { + return keyboardMapper.processAndroidKeyEvent(event); + } + + // onKeyMultiple is called for input of some special characters like umlauts + // and some symbol characters + @Override public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) + { + return keyboardMapper.processAndroidKeyEvent(event); + } + + // **************************************************************************** + // KeyboardView.KeyboardActionEventListener + @Override public void onKey(int primaryCode, int[] keyCodes) + { + keyboardMapper.processCustomKeyEvent(primaryCode); + } + + @Override public void onText(CharSequence text) + { + } + + @Override public void swipeRight() + { + } + + @Override public void swipeLeft() + { + } + + @Override public void swipeDown() + { + } + + @Override public void swipeUp() + { + } + + @Override public void onPress(int primaryCode) + { + } + + @Override public void onRelease(int primaryCode) + { + } + + // **************************************************************************** + // KeyboardMapper.KeyProcessingListener implementation + @Override public void processVirtualKey(int virtualKeyCode, boolean down) + { + LibFreeRDP.sendKeyEvent(session.getInstance(), virtualKeyCode, down); + } + + @Override public void processUnicodeKey(int unicodeKey) + { + LibFreeRDP.sendUnicodeKeyEvent(session.getInstance(), unicodeKey, true); + LibFreeRDP.sendUnicodeKeyEvent(session.getInstance(), unicodeKey, false); + } + + @Override public void switchKeyboard(int keyboardType) + { + switch (keyboardType) + { + case KeyboardMapper.KEYBOARD_TYPE_FUNCTIONKEYS: + keyboardView.setKeyboard(specialkeysKeyboard); + break; + + case KeyboardMapper.KEYBOARD_TYPE_NUMPAD: + keyboardView.setKeyboard(numpadKeyboard); + break; + + case KeyboardMapper.KEYBOARD_TYPE_CURSOR: + keyboardView.setKeyboard(cursorKeyboard); + break; + + default: + break; + } + } + + @Override public void modifiersChanged() + { + updateModifierKeyStates(); + } + + // **************************************************************************** + // LibFreeRDP UI event listener implementation + @Override public void OnSettingsChanged(int width, int height, int bpp) + { + + if (bpp > 16) + bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888); + else + bitmap = Bitmap.createBitmap(width, height, Config.RGB_565); + + session.setSurface(new BitmapDrawable(getResources(), bitmap)); + + if (session.getBookmark() == null) + { + // Return immediately if we launch from URI + return; + } + + // check this settings and initial settings - if they are not equal the + // server doesn't support our settings + // FIXME: the additional check (settings.getWidth() != width + 1) is for + // the RDVH bug fix to avoid accidental notifications + // (refer to android_freerdp.c for more info on this problem) + BookmarkBase.ScreenSettings settings = session.getBookmark().getActiveScreenSettings(); + if ((settings.getWidth() != width && settings.getWidth() != width + 1) || + settings.getHeight() != height || settings.getColors() != bpp) + uiHandler.sendMessage( + Message.obtain(null, UIHandler.DISPLAY_TOAST, + getResources().getText(R.string.info_capabilities_changed))); + } + + @Override public void OnGraphicsUpdate(int x, int y, int width, int height) + { + LibFreeRDP.updateGraphics(session.getInstance(), bitmap, x, y, width, height); + + sessionView.addInvalidRegion(new Rect(x, y, x + width, y + height)); + + /* + * since sessionView can only be modified from the UI thread any + * modifications to it need to be scheduled + */ + + uiHandler.sendEmptyMessage(UIHandler.REFRESH_SESSIONVIEW); + } + + @Override public void OnGraphicsResize(int width, int height, int bpp) + { + // replace bitmap + if (bpp > 16) + bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888); + else + bitmap = Bitmap.createBitmap(width, height, Config.RGB_565); + session.setSurface(new BitmapDrawable(getResources(), bitmap)); + + /* + * since sessionView can only be modified from the UI thread any + * modifications to it need to be scheduled + */ + uiHandler.sendEmptyMessage(UIHandler.GRAPHICS_CHANGED); + } + + @Override + public boolean OnAuthenticate(StringBuilder username, StringBuilder domain, + StringBuilder password) + { + // this is where the return code of our dialog will be stored + callbackDialogResult = false; + + // set text fields + ((EditText)userCredView.findViewById(R.id.editTextUsername)).setText(username); + ((EditText)userCredView.findViewById(R.id.editTextDomain)).setText(domain); + ((EditText)userCredView.findViewById(R.id.editTextPassword)).setText(password); + + // start dialog in UI thread + uiHandler.sendMessage(Message.obtain(null, UIHandler.SHOW_DIALOG, dlgUserCredentials)); + + // wait for result + try + { + synchronized (dlgUserCredentials) + { + dlgUserCredentials.wait(); + } + } + catch (InterruptedException e) + { + } + + // clear buffers + username.setLength(0); + domain.setLength(0); + password.setLength(0); + + // read back user credentials + username.append( + ((EditText)userCredView.findViewById(R.id.editTextUsername)).getText().toString()); + domain.append( + ((EditText)userCredView.findViewById(R.id.editTextDomain)).getText().toString()); + password.append( + ((EditText)userCredView.findViewById(R.id.editTextPassword)).getText().toString()); + + return callbackDialogResult; + } + + @Override + public boolean OnGatewayAuthenticate(StringBuilder username, StringBuilder domain, + StringBuilder password) + { + // this is where the return code of our dialog will be stored + callbackDialogResult = false; + + // set text fields + ((EditText)userCredView.findViewById(R.id.editTextUsername)).setText(username); + ((EditText)userCredView.findViewById(R.id.editTextDomain)).setText(domain); + ((EditText)userCredView.findViewById(R.id.editTextPassword)).setText(password); + + // start dialog in UI thread + uiHandler.sendMessage(Message.obtain(null, UIHandler.SHOW_DIALOG, dlgUserCredentials)); + + // wait for result + try + { + synchronized (dlgUserCredentials) + { + dlgUserCredentials.wait(); + } + } + catch (InterruptedException e) + { + } + + // clear buffers + username.setLength(0); + domain.setLength(0); + password.setLength(0); + + // read back user credentials + username.append( + ((EditText)userCredView.findViewById(R.id.editTextUsername)).getText().toString()); + domain.append( + ((EditText)userCredView.findViewById(R.id.editTextDomain)).getText().toString()); + password.append( + ((EditText)userCredView.findViewById(R.id.editTextPassword)).getText().toString()); + + return callbackDialogResult; + } + + @Override + public int OnVerifiyCertificateEx(String host, long port, String commonName, String subject, + String issuer, String fingerprint, long flags) + { + // see if global settings says accept all + if (ApplicationSettingsActivity.getAcceptAllCertificates(this)) + return 0; + + // this is where the return code of our dialog will be stored + callbackDialogResult = false; + + // set message + String msg = getResources().getString(R.string.dlg_msg_verify_certificate); + String type = "RDP-Server"; + if ((flags & LibFreeRDP.VERIFY_CERT_FLAG_GATEWAY) != 0) + type = "RDP-Gateway"; + if ((flags & LibFreeRDP.VERIFY_CERT_FLAG_REDIRECT) != 0) + type = "RDP-Redirect"; + msg += "\n\n" + type + ": " + host + ":" + port; + + msg += "\n\nSubject: " + subject + "\nIssuer: " + issuer; + + if ((flags & LibFreeRDP.VERIFY_CERT_FLAG_FP_IS_PEM) != 0) + msg += "\nCertificate: " + fingerprint; + else + msg += "\nFingerprint: " + fingerprint; + dlgVerifyCertificate.setMessage(msg); + + // start dialog in UI thread + uiHandler.sendMessage(Message.obtain(null, UIHandler.SHOW_DIALOG, dlgVerifyCertificate)); + + // wait for result + try + { + synchronized (dlgVerifyCertificate) + { + dlgVerifyCertificate.wait(); + } + } + catch (InterruptedException e) + { + } + + return callbackDialogResult ? 1 : 0; + } + + @Override + public int OnVerifyChangedCertificateEx(String host, long port, String commonName, + String subject, String issuer, String fingerprint, + String oldSubject, String oldIssuer, + String oldFingerprint, long flags) + { + // see if global settings says accept all + if (ApplicationSettingsActivity.getAcceptAllCertificates(this)) + return 0; + + // this is where the return code of our dialog will be stored + callbackDialogResult = false; + + // set message + String msg = getResources().getString(R.string.dlg_msg_verify_certificate); + String type = "RDP-Server"; + if ((flags & LibFreeRDP.VERIFY_CERT_FLAG_GATEWAY) != 0) + type = "RDP-Gateway"; + if ((flags & LibFreeRDP.VERIFY_CERT_FLAG_REDIRECT) != 0) + type = "RDP-Redirect"; + msg += "\n\n" + type + ": " + host + ":" + port; + msg += "\n\nSubject: " + subject + "\nIssuer: " + issuer; + if ((flags & LibFreeRDP.VERIFY_CERT_FLAG_FP_IS_PEM) != 0) + msg += "\nCertificate: " + fingerprint; + else + msg += "\nFingerprint: " + fingerprint; + dlgVerifyCertificate.setMessage(msg); + + // start dialog in UI thread + uiHandler.sendMessage(Message.obtain(null, UIHandler.SHOW_DIALOG, dlgVerifyCertificate)); + + // wait for result + try + { + synchronized (dlgVerifyCertificate) + { + dlgVerifyCertificate.wait(); + } + } + catch (InterruptedException e) + { + } + + return callbackDialogResult ? 1 : 0; + } + + @Override public void OnRemoteClipboardChanged(String data) + { + Log.v(TAG, "OnRemoteClipboardChanged: " + data); + mClipboardManager.setClipboardData(data); + } + + // **************************************************************************** + // ScrollView2DListener implementation + private void resetZoomControlsAutoHideTimeout() + { + uiHandler.removeMessages(UIHandler.HIDE_ZOOMCONTROLS); + uiHandler.sendEmptyMessageDelayed(UIHandler.HIDE_ZOOMCONTROLS, + ZOOMCONTROLS_AUTOHIDE_TIMEOUT); + } + + @Override public void onScrollChanged(ScrollView2D scrollView, int x, int y, int oldx, int oldy) + { + zoomControls.setIsZoomInEnabled(!sessionView.isAtMaxZoom()); + zoomControls.setIsZoomOutEnabled(!sessionView.isAtMinZoom()); + + if (sysKeyboardVisible || extKeyboardVisible) + return; + + if (!ApplicationSettingsActivity.getHideZoomControls(this)) + { + uiHandler.sendEmptyMessage(UIHandler.SHOW_ZOOMCONTROLS); + resetZoomControlsAutoHideTimeout(); + } + } + + // **************************************************************************** + // SessionView.SessionViewListener + @Override public void onSessionViewBeginTouch() + { + scrollView.setScrollEnabled(false); + } + + @Override public void onSessionViewEndTouch() + { + scrollView.setScrollEnabled(true); + } + + @Override public void onSessionViewLeftTouch(int x, int y, boolean down) + { + if (!down) + cancelDelayedMoveEvent(); + + LibFreeRDP.sendCursorEvent(session.getInstance(), x, y, + toggleMouseButtons ? Mouse.getRightButtonEvent(this, down) + : Mouse.getLeftButtonEvent(this, down)); + + if (!down) + toggleMouseButtons = false; + } + + public void onSessionViewRightTouch(int x, int y, boolean down) + { + if (!down) + toggleMouseButtons = !toggleMouseButtons; + } + + @Override public void onSessionViewMove(int x, int y) + { + sendDelayedMoveEvent(x, y); + } + + @Override public void onSessionViewScroll(boolean down) + { + LibFreeRDP.sendCursorEvent(session.getInstance(), 0, 0, Mouse.getScrollEvent(this, down)); + } + + // **************************************************************************** + // TouchPointerView.TouchPointerListener + @Override public void onTouchPointerClose() + { + touchPointerView.setVisibility(View.INVISIBLE); + sessionView.setTouchPointerPadding(0, 0); + } + + private Point mapScreenCoordToSessionCoord(int x, int y) + { + int mappedX = (int)((float)(x + scrollView.getScrollX()) / sessionView.getZoom()); + int mappedY = (int)((float)(y + scrollView.getScrollY()) / sessionView.getZoom()); + if (bitmap != null) + { + if (mappedX > bitmap.getWidth()) + mappedX = bitmap.getWidth(); + if (mappedY > bitmap.getHeight()) + mappedY = bitmap.getHeight(); + } + return new Point(mappedX, mappedY); + } + + @Override public void onTouchPointerLeftClick(int x, int y, boolean down) + { + Point p = mapScreenCoordToSessionCoord(x, y); + LibFreeRDP.sendCursorEvent(session.getInstance(), p.x, p.y, + Mouse.getLeftButtonEvent(this, down)); + } + + @Override public void onTouchPointerRightClick(int x, int y, boolean down) + { + Point p = mapScreenCoordToSessionCoord(x, y); + LibFreeRDP.sendCursorEvent(session.getInstance(), p.x, p.y, + Mouse.getRightButtonEvent(this, down)); + } + + @Override public void onTouchPointerMove(int x, int y) + { + Point p = mapScreenCoordToSessionCoord(x, y); + LibFreeRDP.sendCursorEvent(session.getInstance(), p.x, p.y, Mouse.getMoveEvent()); + + if (ApplicationSettingsActivity.getAutoScrollTouchPointer(this) && + !uiHandler.hasMessages(UIHandler.SCROLLING_REQUESTED)) + { + Log.v(TAG, "Starting auto-scroll"); + uiHandler.sendEmptyMessageDelayed(UIHandler.SCROLLING_REQUESTED, SCROLLING_TIMEOUT); + } + } + + @Override public void onTouchPointerScroll(boolean down) + { + LibFreeRDP.sendCursorEvent(session.getInstance(), 0, 0, Mouse.getScrollEvent(this, down)); + } + + @Override public void onTouchPointerToggleKeyboard() + { + showKeyboard(!sysKeyboardVisible, false); + } + + @Override public void onTouchPointerToggleExtKeyboard() + { + showKeyboard(false, !extKeyboardVisible); + } + + @Override public void onTouchPointerResetScrollZoom() + { + sessionView.setZoom(1.0f); + scrollView.scrollTo(0, 0); + } + + @Override public boolean onGenericMotionEvent(MotionEvent e) + { + super.onGenericMotionEvent(e); + switch (e.getAction()) + { + case MotionEvent.ACTION_SCROLL: + final float vScroll = e.getAxisValue(MotionEvent.AXIS_VSCROLL); + if (vScroll < 0) + { + LibFreeRDP.sendCursorEvent(session.getInstance(), 0, 0, + Mouse.getScrollEvent(this, false)); + } + if (vScroll > 0) + { + LibFreeRDP.sendCursorEvent(session.getInstance(), 0, 0, + Mouse.getScrollEvent(this, true)); + } + break; + } + return true; + } + + // **************************************************************************** + // ClipboardManagerProxy.OnClipboardChangedListener + @Override public void onClipboardChanged(String data) + { + Log.v(TAG, "onClipboardChanged: " + data); + LibFreeRDP.sendClipboardData(session.getInstance(), data); + } + + private class UIHandler extends Handler + { + + public static final int REFRESH_SESSIONVIEW = 1; + public static final int DISPLAY_TOAST = 2; + public static final int HIDE_ZOOMCONTROLS = 3; + public static final int SEND_MOVE_EVENT = 4; + public static final int SHOW_DIALOG = 5; + public static final int GRAPHICS_CHANGED = 6; + public static final int SCROLLING_REQUESTED = 7; + public static final int SHOW_ZOOMCONTROLS = 8; + + UIHandler() + { + super(); + } + + @Override public void handleMessage(Message msg) + { + switch (msg.what) + { + case GRAPHICS_CHANGED: + { + sessionView.onSurfaceChange(session); + scrollView.requestLayout(); + break; + } + case REFRESH_SESSIONVIEW: + { + sessionView.invalidateRegion(); + break; + } + case DISPLAY_TOAST: + { + Toast errorToast = Toast.makeText(getApplicationContext(), msg.obj.toString(), + Toast.LENGTH_LONG); + errorToast.show(); + break; + } + case HIDE_ZOOMCONTROLS: + { + if (zoomControls.isShown()) + zoomControls.hide(); + break; + } + case SHOW_ZOOMCONTROLS: + { + if (!zoomControls.isShown()) + zoomControls.show(); + + break; + } + case SEND_MOVE_EVENT: + { + LibFreeRDP.sendCursorEvent(session.getInstance(), msg.arg1, msg.arg2, + Mouse.getMoveEvent()); + break; + } + case SHOW_DIALOG: + { + // create and show the dialog + ((Dialog)msg.obj).show(); + break; + } + case SCROLLING_REQUESTED: + { + int scrollX = 0; + int scrollY = 0; + float[] pointerPos = touchPointerView.getPointerPosition(); + + if (pointerPos[0] > (screen_width - touchPointerView.getPointerWidth())) + scrollX = SCROLLING_DISTANCE; + else if (pointerPos[0] < 0) + scrollX = -SCROLLING_DISTANCE; + + if (pointerPos[1] > (screen_height - touchPointerView.getPointerHeight())) + scrollY = SCROLLING_DISTANCE; + else if (pointerPos[1] < 0) + scrollY = -SCROLLING_DISTANCE; + + scrollView.scrollBy(scrollX, scrollY); + + // see if we reached the min/max scroll positions + if (scrollView.getScrollX() == 0 || + scrollView.getScrollX() == (sessionView.getWidth() - scrollView.getWidth())) + scrollX = 0; + if (scrollView.getScrollY() == 0 || + scrollView.getScrollY() == + (sessionView.getHeight() - scrollView.getHeight())) + scrollY = 0; + + if (scrollX != 0 || scrollY != 0) + uiHandler.sendEmptyMessageDelayed(SCROLLING_REQUESTED, SCROLLING_TIMEOUT); + else + Log.v(TAG, "Stopping auto-scroll"); + break; + } + } + } + } + + private class PinchZoomListener extends ScaleGestureDetector.SimpleOnScaleGestureListener + { + private float scaleFactor = 1.0f; + + @Override public boolean onScaleBegin(ScaleGestureDetector detector) + { + scrollView.setScrollEnabled(false); + return true; + } + + @Override public boolean onScale(ScaleGestureDetector detector) + { + + // calc scale factor + scaleFactor *= detector.getScaleFactor(); + scaleFactor = Math.max(SessionView.MIN_SCALE_FACTOR, + Math.min(scaleFactor, SessionView.MAX_SCALE_FACTOR)); + sessionView.setZoom(scaleFactor); + + if (!sessionView.isAtMinZoom() && !sessionView.isAtMaxZoom()) + { + // transform scroll origin to the new zoom space + float transOriginX = scrollView.getScrollX() * detector.getScaleFactor(); + float transOriginY = scrollView.getScrollY() * detector.getScaleFactor(); + + // transform center point to the zoomed space + float transCenterX = + (scrollView.getScrollX() + detector.getFocusX()) * detector.getScaleFactor(); + float transCenterY = + (scrollView.getScrollY() + detector.getFocusY()) * detector.getScaleFactor(); + + // scroll by the difference between the distance of the + // transformed center/origin point and their old distance + // (focusX/Y) + scrollView.scrollBy((int)((transCenterX - transOriginX) - detector.getFocusX()), + (int)((transCenterY - transOriginY) - detector.getFocusY())); + } + + return true; + } + + @Override public void onScaleEnd(ScaleGestureDetector de) + { + scrollView.setScrollEnabled(true); + } + } + + private class LibFreeRDPBroadcastReceiver extends BroadcastReceiver + { + @Override public void onReceive(Context context, Intent intent) + { + // still got a valid session? + if (session == null) + return; + + // is this event for the current session? + if (session.getInstance() != intent.getExtras().getLong(GlobalApp.EVENT_PARAM, -1)) + return; + + switch (intent.getExtras().getInt(GlobalApp.EVENT_TYPE, -1)) + { + case GlobalApp.FREERDP_EVENT_CONNECTION_SUCCESS: + OnConnectionSuccess(context); + break; + + case GlobalApp.FREERDP_EVENT_CONNECTION_FAILURE: + OnConnectionFailure(context); + break; + case GlobalApp.FREERDP_EVENT_DISCONNECTED: + OnDisconnected(context); + break; + } + } + + private void OnConnectionSuccess(Context context) + { + Log.v(TAG, "OnConnectionSuccess"); + + // bind session + bindSession(); + + if (progressDialog != null) + { + progressDialog.dismiss(); + progressDialog = null; + } + + if (session.getBookmark() == null) + { + // Return immediately if we launch from URI + return; + } + + // add hostname to history if quick connect was used + Bundle bundle = getIntent().getExtras(); + if (bundle != null && bundle.containsKey(PARAM_CONNECTION_REFERENCE)) + { + if (ConnectionReference.isHostnameReference( + bundle.getString(PARAM_CONNECTION_REFERENCE))) + { + assert session.getBookmark().getType() == BookmarkBase.TYPE_MANUAL; + String item = session.getBookmark().get().getHostname(); + if (!GlobalApp.getQuickConnectHistoryGateway().historyItemExists(item)) + GlobalApp.getQuickConnectHistoryGateway().addHistoryItem(item); + } + } + } + + private void OnConnectionFailure(Context context) + { + Log.v(TAG, "OnConnectionFailure"); + + // remove pending move events + uiHandler.removeMessages(UIHandler.SEND_MOVE_EVENT); + + if (progressDialog != null) + { + progressDialog.dismiss(); + progressDialog = null; + } + + // post error message on UI thread + if (!connectCancelledByUser) + uiHandler.sendMessage( + Message.obtain(null, UIHandler.DISPLAY_TOAST, + getResources().getText(R.string.error_connection_failure))); + + closeSessionActivity(RESULT_CANCELED); + } + + private void OnDisconnected(Context context) + { + Log.v(TAG, "OnDisconnected"); + + // remove pending move events + uiHandler.removeMessages(UIHandler.SEND_MOVE_EVENT); + + if (progressDialog != null) + { + progressDialog.dismiss(); + progressDialog = null; + } + + session.setUIEventListener(null); + closeSessionActivity(RESULT_OK); + } + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/SessionView.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/SessionView.java new file mode 100644 index 0000000..0ee1d88 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/SessionView.java @@ -0,0 +1,445 @@ +/* + Android Session view + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.presentation; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.BitmapDrawable; +import android.text.InputType; +import android.util.AttributeSet; +import android.util.Log; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; + +import androidx.annotation.NonNull; + +import com.freerdp.freerdpcore.application.SessionState; +import com.freerdp.freerdpcore.services.LibFreeRDP; +import com.freerdp.freerdpcore.utils.DoubleGestureDetector; +import com.freerdp.freerdpcore.utils.GestureDetector; +import com.freerdp.freerdpcore.utils.Mouse; + +import java.util.Stack; + +public class SessionView extends View +{ + public static final float MAX_SCALE_FACTOR = 3.0f; + public static final float MIN_SCALE_FACTOR = 1.0f; + private static final String TAG = "SessionView"; + private static final float SCALE_FACTOR_DELTA = 0.0001f; + private static final float TOUCH_SCROLL_DELTA = 10.0f; + private int width; + private int height; + private BitmapDrawable surface; + private Stack invalidRegions; + private int touchPointerPaddingWidth = 0; + private int touchPointerPaddingHeight = 0; + private SessionViewListener sessionViewListener = null; + // helpers for scaling gesture handling + private float scaleFactor = 1.0f; + private Matrix scaleMatrix; + private Matrix invScaleMatrix; + private RectF invalidRegionF; + private GestureDetector gestureDetector; + private SessionState currentSession; + + // private static final String TAG = "FreeRDP.SessionView"; + private DoubleGestureDetector doubleGestureDetector; + public SessionView(Context context) + { + super(context); + initSessionView(context); + } + + public SessionView(Context context, AttributeSet attrs) + { + super(context, attrs); + initSessionView(context); + } + + public SessionView(Context context, AttributeSet attrs, int defStyle) + { + super(context, attrs, defStyle); + initSessionView(context); + } + + private void initSessionView(Context context) + { + invalidRegions = new Stack<>(); + gestureDetector = new GestureDetector(context, new SessionGestureListener(), null, true); + doubleGestureDetector = + new DoubleGestureDetector(context, null, new SessionDoubleGestureListener()); + + scaleFactor = 1.0f; + scaleMatrix = new Matrix(); + invScaleMatrix = new Matrix(); + invalidRegionF = new RectF(); + + setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + } + + /* External Mouse Hover */ + @Override public boolean onHoverEvent(MotionEvent event) + { + if (event.getAction() == MotionEvent.ACTION_HOVER_MOVE) + { + // Handle hover move event + float x = event.getX(); + float y = event.getY(); + // Perform actions based on the hover position (x, y) + MotionEvent mappedEvent = mapTouchEvent(event); + LibFreeRDP.sendCursorEvent(currentSession.getInstance(), (int)mappedEvent.getX(), + (int)mappedEvent.getY(), Mouse.getMoveEvent()); + } + // Return true to indicate that you've handled the event + return true; + } + + public void setScaleGestureDetector(ScaleGestureDetector scaleGestureDetector) + { + doubleGestureDetector.setScaleGestureDetector(scaleGestureDetector); + } + + public void setSessionViewListener(SessionViewListener sessionViewListener) + { + this.sessionViewListener = sessionViewListener; + } + + public void addInvalidRegion(Rect invalidRegion) + { + // correctly transform invalid region depending on current scaling + invalidRegionF.set(invalidRegion); + scaleMatrix.mapRect(invalidRegionF); + invalidRegionF.roundOut(invalidRegion); + + invalidRegions.add(invalidRegion); + } + + public void invalidateRegion() + { + invalidate(invalidRegions.pop()); + } + + public void onSurfaceChange(SessionState session) + { + surface = session.getSurface(); + Bitmap bitmap = surface.getBitmap(); + width = bitmap.getWidth(); + height = bitmap.getHeight(); + surface.setBounds(0, 0, width, height); + + setMinimumWidth(width); + setMinimumHeight(height); + + requestLayout(); + currentSession = session; + } + + public float getZoom() + { + return scaleFactor; + } + + public void setZoom(float factor) + { + // calc scale matrix and inverse scale matrix (to correctly transform the view and moues + // coordinates) + scaleFactor = factor; + scaleMatrix.setScale(scaleFactor, scaleFactor); + invScaleMatrix.setScale(1.0f / scaleFactor, 1.0f / scaleFactor); + + // update layout + requestLayout(); + } + + public boolean isAtMaxZoom() + { + return (scaleFactor > (MAX_SCALE_FACTOR - SCALE_FACTOR_DELTA)); + } + + public boolean isAtMinZoom() + { + return (scaleFactor < (MIN_SCALE_FACTOR + SCALE_FACTOR_DELTA)); + } + + public boolean zoomIn(float factor) + { + boolean res = true; + scaleFactor += factor; + if (scaleFactor > (MAX_SCALE_FACTOR - SCALE_FACTOR_DELTA)) + { + scaleFactor = MAX_SCALE_FACTOR; + res = false; + } + setZoom(scaleFactor); + return res; + } + + public boolean zoomOut(float factor) + { + boolean res = true; + scaleFactor -= factor; + if (scaleFactor < (MIN_SCALE_FACTOR + SCALE_FACTOR_DELTA)) + { + scaleFactor = MIN_SCALE_FACTOR; + res = false; + } + setZoom(scaleFactor); + return res; + } + + public void setTouchPointerPadding(int width, int height) + { + touchPointerPaddingWidth = width; + touchPointerPaddingHeight = height; + requestLayout(); + } + + public int getTouchPointerPaddingWidth() + { + return touchPointerPaddingWidth; + } + + public int getTouchPointerPaddingHeight() + { + return touchPointerPaddingHeight; + } + + @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) + { + Log.v(TAG, width + "x" + height); + this.setMeasuredDimension((int)(width * scaleFactor) + touchPointerPaddingWidth, + (int)(height * scaleFactor) + touchPointerPaddingHeight); + } + + @Override public void onDraw(@NonNull Canvas canvas) + { + super.onDraw(canvas); + + canvas.save(); + canvas.concat(scaleMatrix); + canvas.drawColor(Color.BLACK); + if (surface != null) + { + surface.draw(canvas); + } + canvas.restore(); + } + + // dirty hack: we call back to our activity and call onBackPressed as this doesn't reach us when + // the soft keyboard is shown ... + @Override public boolean dispatchKeyEventPreIme(KeyEvent event) + { + if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && + event.getAction() == KeyEvent.ACTION_DOWN) + ((SessionActivity)this.getContext()).onBackPressed(); + return super.dispatchKeyEventPreIme(event); + } + + // perform mapping on the touch event's coordinates according to the current scaling + private MotionEvent mapTouchEvent(MotionEvent event) + { + MotionEvent mappedEvent = MotionEvent.obtain(event); + float[] coordinates = { mappedEvent.getX(), mappedEvent.getY() }; + invScaleMatrix.mapPoints(coordinates); + mappedEvent.setLocation(coordinates[0], coordinates[1]); + return mappedEvent; + } + + // perform mapping on the double touch event's coordinates according to the current scaling + private MotionEvent mapDoubleTouchEvent(MotionEvent event) + { + MotionEvent mappedEvent = MotionEvent.obtain(event); + float[] coordinates = { (mappedEvent.getX(0) + mappedEvent.getX(1)) / 2, + (mappedEvent.getY(0) + mappedEvent.getY(1)) / 2 }; + invScaleMatrix.mapPoints(coordinates); + mappedEvent.setLocation(coordinates[0], coordinates[1]); + return mappedEvent; + } + + @Override public boolean onTouchEvent(MotionEvent event) + { + boolean res = gestureDetector.onTouchEvent(event); + res |= doubleGestureDetector.onTouchEvent(event); + return res; + } + + public interface SessionViewListener { + void onSessionViewBeginTouch(); + + void onSessionViewEndTouch(); + + void onSessionViewLeftTouch(int x, int y, boolean down); + + void onSessionViewRightTouch(int x, int y, boolean down); + + void onSessionViewMove(int x, int y); + + void onSessionViewScroll(boolean down); + } + + private class SessionGestureListener extends GestureDetector.SimpleOnGestureListener + { + boolean longPressInProgress = false; + + public boolean onDown(MotionEvent e) + { + return true; + } + + public boolean onUp(MotionEvent e) + { + sessionViewListener.onSessionViewEndTouch(); + return true; + } + + public void onLongPress(MotionEvent e) + { + MotionEvent mappedEvent = mapTouchEvent(e); + sessionViewListener.onSessionViewBeginTouch(); + sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(), + (int)mappedEvent.getY(), true); + longPressInProgress = true; + } + + public void onLongPressUp(MotionEvent e) + { + MotionEvent mappedEvent = mapTouchEvent(e); + sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(), + (int)mappedEvent.getY(), false); + longPressInProgress = false; + sessionViewListener.onSessionViewEndTouch(); + } + + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) + { + if (longPressInProgress) + { + MotionEvent mappedEvent = mapTouchEvent(e2); + sessionViewListener.onSessionViewMove((int)mappedEvent.getX(), + (int)mappedEvent.getY()); + return true; + } + + return false; + } + + public boolean onDoubleTap(MotionEvent e) + { + // send 2nd click for double click + MotionEvent mappedEvent = mapTouchEvent(e); + sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(), + (int)mappedEvent.getY(), true); + sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(), + (int)mappedEvent.getY(), false); + return true; + } + + public boolean onSingleTapUp(MotionEvent e) + { + // send single click + MotionEvent mappedEvent = mapTouchEvent(e); + sessionViewListener.onSessionViewBeginTouch(); + switch (e.getButtonState()) + { + case MotionEvent.BUTTON_PRIMARY: + sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(), + (int)mappedEvent.getY(), true); + sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(), + (int)mappedEvent.getY(), false); + break; + case MotionEvent.BUTTON_SECONDARY: + sessionViewListener.onSessionViewRightTouch((int)mappedEvent.getX(), + (int)mappedEvent.getY(), true); + sessionViewListener.onSessionViewRightTouch((int)mappedEvent.getX(), + (int)mappedEvent.getY(), false); + sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(), + (int)mappedEvent.getY(), true); + sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(), + (int)mappedEvent.getY(), false); + break; + } + sessionViewListener.onSessionViewEndTouch(); + return true; + } + } + + private class SessionDoubleGestureListener + implements DoubleGestureDetector.OnDoubleGestureListener + { + private MotionEvent prevEvent = null; + + public boolean onDoubleTouchDown(MotionEvent e) + { + sessionViewListener.onSessionViewBeginTouch(); + prevEvent = MotionEvent.obtain(e); + return true; + } + + public boolean onDoubleTouchUp(MotionEvent e) + { + if (prevEvent != null) + { + prevEvent.recycle(); + prevEvent = null; + } + sessionViewListener.onSessionViewEndTouch(); + return true; + } + + public boolean onDoubleTouchScroll(MotionEvent e1, MotionEvent e2) + { + // calc if user scrolled up or down (or if any scrolling happened at all) + float deltaY = e2.getY() - prevEvent.getY(); + if (deltaY > TOUCH_SCROLL_DELTA) + { + sessionViewListener.onSessionViewScroll(true); + prevEvent.recycle(); + prevEvent = MotionEvent.obtain(e2); + } + else if (deltaY < -TOUCH_SCROLL_DELTA) + { + sessionViewListener.onSessionViewScroll(false); + prevEvent.recycle(); + prevEvent = MotionEvent.obtain(e2); + } + return true; + } + + public boolean onDoubleTouchSingleTap(MotionEvent e) + { + // send single click + MotionEvent mappedEvent = mapDoubleTouchEvent(e); + sessionViewListener.onSessionViewRightTouch((int)mappedEvent.getX(), + (int)mappedEvent.getY(), true); + sessionViewListener.onSessionViewRightTouch((int)mappedEvent.getX(), + (int)mappedEvent.getY(), false); + return true; + } + } + + @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) + { + super.onCreateInputConnection(outAttrs); + outAttrs.inputType = InputType.TYPE_CLASS_TEXT; + return null; + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/ShortcutsActivity.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/ShortcutsActivity.java new file mode 100644 index 0000000..2121c6e --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/ShortcutsActivity.java @@ -0,0 +1,160 @@ +/* + Android Shortcut activity + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.presentation; + +import android.app.AlertDialog; +import android.app.ListActivity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.os.Parcelable; +import android.view.View; +import android.widget.AdapterView; +import android.widget.EditText; +import android.widget.TextView; + +import com.freerdp.freerdpcore.R; +import com.freerdp.freerdpcore.application.GlobalApp; +import com.freerdp.freerdpcore.domain.BookmarkBase; +import com.freerdp.freerdpcore.services.SessionRequestHandlerActivity; +import com.freerdp.freerdpcore.utils.BookmarkArrayAdapter; + +import java.util.ArrayList; + +public class ShortcutsActivity extends ListActivity +{ + + public static final String TAG = "ShortcutsActivity"; + + @Override public void onCreate(Bundle savedInstanceState) + { + + super.onCreate(savedInstanceState); + + Intent intent = getIntent(); + if (Intent.ACTION_CREATE_SHORTCUT.equals(intent.getAction())) + { + // set listeners for the list view + getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() { + public void onItemClick(AdapterView parent, View view, int position, long id) + { + String refStr = view.getTag().toString(); + String defLabel = + ((TextView)(view.findViewById(R.id.bookmark_text1))).getText().toString(); + setupShortcut(refStr, defLabel); + } + }); + } + else + { + // just exit + finish(); + } + } + + @Override public void onResume() + { + super.onResume(); + // create bookmark cursor adapter + ArrayList bookmarks = GlobalApp.getManualBookmarkGateway().findAll(); + BookmarkArrayAdapter bookmarkAdapter = + new BookmarkArrayAdapter(this, android.R.layout.simple_list_item_2, bookmarks); + getListView().setAdapter(bookmarkAdapter); + } + + public void onPause() + { + super.onPause(); + getListView().setAdapter(null); + } + + /** + * This function creates a shortcut and returns it to the caller. There are actually two + * intents that you will send back. + *

+ * The first intent serves as a container for the shortcut and is returned to the launcher by + * setResult(). This intent must contain three fields: + *

+ *

    + *
  • {@link android.content.Intent#EXTRA_SHORTCUT_INTENT} The shortcut intent.
  • + *
  • {@link android.content.Intent#EXTRA_SHORTCUT_NAME} The text that will be displayed with + * the shortcut.
  • + *
  • {@link android.content.Intent#EXTRA_SHORTCUT_ICON} The shortcut's icon, if provided as a + * bitmap, or {@link android.content.Intent#EXTRA_SHORTCUT_ICON_RESOURCE} if provided as + * a drawable resource.
  • + *
+ *

+ * If you use a simple drawable resource, note that you must wrapper it using + * {@link android.content.Intent.ShortcutIconResource}, as shown below. This is required so + * that the launcher can access resources that are stored in your application's .apk file. If + * you return a bitmap, such as a thumbnail, you can simply put the bitmap into the extras + * bundle using {@link android.content.Intent#EXTRA_SHORTCUT_ICON}. + *

+ * The shortcut intent can be any intent that you wish the launcher to send, when the user + * clicks on the shortcut. Typically this will be {@link android.content.Intent#ACTION_VIEW} + * with an appropriate Uri for your content, but any Intent will work here as long as it + * triggers the desired action within your Activity. + */ + + private void setupShortcut(String strRef, String defaultLabel) + { + final String paramStrRef = strRef; + final String paramDefaultLabel = defaultLabel; + final Context paramContext = this; + + // display edit dialog to the user so he can specify the shortcut name + final EditText input = new EditText(this); + input.setText(defaultLabel); + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.dlg_title_create_shortcut) + .setMessage(R.string.dlg_msg_create_shortcut) + .setView(input) + .setPositiveButton( + android.R.string.ok, + new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int which) + { + String label = input.getText().toString(); + if (label.length() == 0) + label = paramDefaultLabel; + + Intent shortcutIntent = new Intent(Intent.ACTION_VIEW); + shortcutIntent.setClassName(paramContext, + SessionRequestHandlerActivity.class.getName()); + shortcutIntent.setData(Uri.parse(paramStrRef)); + + // Then, set up the container intent (the response to the caller) + Intent intent = new Intent(); + intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); + intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, label); + Parcelable iconResource = Intent.ShortcutIconResource.fromContext( + paramContext, R.drawable.icon_launcher_freerdp); + intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource); + + // Now, return the result to the launcher + setResult(RESULT_OK, intent); + finish(); + } + }) + .setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int which) + { + dialog.dismiss(); + } + }) + .create() + .show(); + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/TouchPointerView.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/TouchPointerView.java new file mode 100644 index 0000000..523cc52 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/TouchPointerView.java @@ -0,0 +1,381 @@ +/* + Android Touch Pointer view + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.presentation; + +import android.content.Context; +import android.graphics.Matrix; +import android.graphics.RectF; +import android.os.Handler; +import android.os.Message; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.ImageView; + +import com.freerdp.freerdpcore.R; +import com.freerdp.freerdpcore.utils.GestureDetector; + +public class TouchPointerView extends ImageView +{ + + private static final int POINTER_ACTION_CURSOR = 0; + private static final int POINTER_ACTION_CLOSE = 3; + + // the touch pointer consists of 9 quadrants with the following functionality: + // + // ------------- + // | 0 | 1 | 2 | + // ------------- + // | 3 | 4 | 5 | + // ------------- + // | 6 | 7 | 8 | + // ------------- + // + // 0 ... contains the actual pointer (the tip must be centered in the quadrant) + // 1 ... is left empty + // 2, 3, 5, 6, 7, 8 ... function quadrants that issue a callback + // 4 ... pointer center used for left clicks and to drag the pointer + private static final int POINTER_ACTION_RCLICK = 2; + private static final int POINTER_ACTION_LCLICK = 4; + private static final int POINTER_ACTION_MOVE = 4; + private static final int POINTER_ACTION_SCROLL = 5; + private static final int POINTER_ACTION_RESET = 6; + private static final int POINTER_ACTION_KEYBOARD = 7; + private static final int POINTER_ACTION_EXTKEYBOARD = 8; + private static final float SCROLL_DELTA = 10.0f; + private static final int DEFAULT_TOUCH_POINTER_RESTORE_DELAY = 150; + private RectF pointerRect; + private final RectF[] pointerAreaRects = new RectF[9]; + private Matrix translationMatrix; + private boolean pointerMoving = false; + private boolean pointerScrolling = false; + private TouchPointerListener listener = null; + private final UIHandler uiHandler = new UIHandler(); + // gesture detection + private GestureDetector gestureDetector; + public TouchPointerView(Context context) + { + super(context); + initTouchPointer(context); + } + + public TouchPointerView(Context context, AttributeSet attrs) + { + super(context, attrs); + initTouchPointer(context); + } + + public TouchPointerView(Context context, AttributeSet attrs, int defStyle) + { + super(context, attrs, defStyle); + initTouchPointer(context); + } + + private void initTouchPointer(Context context) + { + gestureDetector = + new GestureDetector(context, new TouchPointerGestureListener(), null, true); + gestureDetector.setLongPressTimeout(500); + translationMatrix = new Matrix(); + setScaleType(ScaleType.MATRIX); + setImageMatrix(translationMatrix); + + // init rects + final float rectSizeWidth = (float)getDrawable().getIntrinsicWidth() / 3.0f; + final float rectSizeHeight = (float)getDrawable().getIntrinsicWidth() / 3.0f; + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + { + int left = (int)(j * rectSizeWidth); + int top = (int)(i * rectSizeHeight); + int right = left + (int)rectSizeWidth; + int bottom = top + (int)rectSizeHeight; + pointerAreaRects[i * 3 + j] = new RectF(left, top, right, bottom); + } + } + pointerRect = + new RectF(0, 0, getDrawable().getIntrinsicWidth(), getDrawable().getIntrinsicHeight()); + } + + public void setTouchPointerListener(TouchPointerListener listener) + { + this.listener = listener; + } + + public int getPointerWidth() + { + return getDrawable().getIntrinsicWidth(); + } + + public int getPointerHeight() + { + return getDrawable().getIntrinsicHeight(); + } + + public float[] getPointerPosition() + { + float[] curPos = new float[2]; + translationMatrix.mapPoints(curPos); + return curPos; + } + + private void movePointer(float deltaX, float deltaY) + { + translationMatrix.postTranslate(deltaX, deltaY); + setImageMatrix(translationMatrix); + } + + private void ensureVisibility(int screen_width, int screen_height) + { + float[] curPos = new float[2]; + translationMatrix.mapPoints(curPos); + + if (curPos[0] > (screen_width - pointerRect.width())) + curPos[0] = screen_width - pointerRect.width(); + if (curPos[0] < 0) + curPos[0] = 0; + if (curPos[1] > (screen_height - pointerRect.height())) + curPos[1] = screen_height - pointerRect.height(); + if (curPos[1] < 0) + curPos[1] = 0; + + translationMatrix.setTranslate(curPos[0], curPos[1]); + setImageMatrix(translationMatrix); + } + + private void displayPointerImageAction(int resId) + { + setPointerImage(resId); + uiHandler.sendEmptyMessageDelayed(0, DEFAULT_TOUCH_POINTER_RESTORE_DELAY); + } + + private void setPointerImage(int resId) + { + setImageResource(resId); + } + + // returns the pointer area with the current translation matrix applied + private RectF getCurrentPointerArea(int area) + { + RectF transRect = new RectF(pointerAreaRects[area]); + translationMatrix.mapRect(transRect); + return transRect; + } + + private boolean pointerAreaTouched(MotionEvent event, int area) + { + RectF transRect = new RectF(pointerAreaRects[area]); + translationMatrix.mapRect(transRect); + return transRect.contains(event.getX(), event.getY()); + } + + private boolean pointerTouched(MotionEvent event) + { + RectF transRect = new RectF(pointerRect); + translationMatrix.mapRect(transRect); + return transRect.contains(event.getX(), event.getY()); + } + + @Override public boolean onTouchEvent(MotionEvent event) + { + // check if pointer is being moved or if we are in scroll mode or if the pointer is touched + if (!pointerMoving && !pointerScrolling && !pointerTouched(event)) + return false; + return gestureDetector.onTouchEvent(event); + } + + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) + { + // ensure touch pointer is visible + if (changed) + ensureVisibility(right - left, bottom - top); + } + + // touch pointer listener - is triggered if an action field is + public interface TouchPointerListener { + void onTouchPointerClose(); + + void onTouchPointerLeftClick(int x, int y, boolean down); + + void onTouchPointerRightClick(int x, int y, boolean down); + + void onTouchPointerMove(int x, int y); + + void onTouchPointerScroll(boolean down); + + void onTouchPointerToggleKeyboard(); + + void onTouchPointerToggleExtKeyboard(); + + void onTouchPointerResetScrollZoom(); + } + + private class UIHandler extends Handler + { + + UIHandler() + { + super(); + } + + @Override public void handleMessage(Message msg) + { + setPointerImage(R.drawable.touch_pointer_default); + } + } + + private class TouchPointerGestureListener extends GestureDetector.SimpleOnGestureListener + { + + private MotionEvent prevEvent = null; + + public boolean onDown(MotionEvent e) + { + if (pointerAreaTouched(e, POINTER_ACTION_MOVE)) + { + prevEvent = MotionEvent.obtain(e); + pointerMoving = true; + } + else if (pointerAreaTouched(e, POINTER_ACTION_SCROLL)) + { + prevEvent = MotionEvent.obtain(e); + pointerScrolling = true; + setPointerImage(R.drawable.touch_pointer_scroll); + } + + return true; + } + + public boolean onUp(MotionEvent e) + { + if (prevEvent != null) + { + prevEvent.recycle(); + prevEvent = null; + } + + if (pointerScrolling) + setPointerImage(R.drawable.touch_pointer_default); + + pointerMoving = false; + pointerScrolling = false; + return true; + } + + public void onLongPress(MotionEvent e) + { + if (pointerAreaTouched(e, POINTER_ACTION_LCLICK)) + { + setPointerImage(R.drawable.touch_pointer_active); + pointerMoving = true; + RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR); + listener.onTouchPointerLeftClick((int)rect.centerX(), (int)rect.centerY(), true); + } + } + + public void onLongPressUp(MotionEvent e) + { + if (pointerMoving) + { + setPointerImage(R.drawable.touch_pointer_default); + pointerMoving = false; + RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR); + listener.onTouchPointerLeftClick((int)rect.centerX(), (int)rect.centerY(), false); + } + } + + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) + { + if (pointerMoving) + { + // move pointer graphics + movePointer((int)(e2.getX() - prevEvent.getX()), + (int)(e2.getY() - prevEvent.getY())); + prevEvent.recycle(); + prevEvent = MotionEvent.obtain(e2); + + // send move notification + RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR); + listener.onTouchPointerMove((int)rect.centerX(), (int)rect.centerY()); + return true; + } + else if (pointerScrolling) + { + // calc if user scrolled up or down (or if any scrolling happened at all) + float deltaY = e2.getY() - prevEvent.getY(); + if (deltaY > SCROLL_DELTA) + { + listener.onTouchPointerScroll(true); + prevEvent.recycle(); + prevEvent = MotionEvent.obtain(e2); + } + else if (deltaY < -SCROLL_DELTA) + { + listener.onTouchPointerScroll(false); + prevEvent.recycle(); + prevEvent = MotionEvent.obtain(e2); + } + return true; + } + return false; + } + + public boolean onSingleTapUp(MotionEvent e) + { + // look what area got touched and fire actions accordingly + if (pointerAreaTouched(e, POINTER_ACTION_CLOSE)) + listener.onTouchPointerClose(); + else if (pointerAreaTouched(e, POINTER_ACTION_LCLICK)) + { + displayPointerImageAction(R.drawable.touch_pointer_lclick); + RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR); + listener.onTouchPointerLeftClick((int)rect.centerX(), (int)rect.centerY(), true); + listener.onTouchPointerLeftClick((int)rect.centerX(), (int)rect.centerY(), false); + } + else if (pointerAreaTouched(e, POINTER_ACTION_RCLICK)) + { + displayPointerImageAction(R.drawable.touch_pointer_rclick); + RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR); + listener.onTouchPointerRightClick((int)rect.centerX(), (int)rect.centerY(), true); + listener.onTouchPointerRightClick((int)rect.centerX(), (int)rect.centerY(), false); + } + else if (pointerAreaTouched(e, POINTER_ACTION_KEYBOARD)) + { + displayPointerImageAction(R.drawable.touch_pointer_keyboard); + listener.onTouchPointerToggleKeyboard(); + } + else if (pointerAreaTouched(e, POINTER_ACTION_EXTKEYBOARD)) + { + displayPointerImageAction(R.drawable.touch_pointer_extkeyboard); + listener.onTouchPointerToggleExtKeyboard(); + } + else if (pointerAreaTouched(e, POINTER_ACTION_RESET)) + { + displayPointerImageAction(R.drawable.touch_pointer_reset); + listener.onTouchPointerResetScrollZoom(); + } + + return true; + } + + public boolean onDoubleTap(MotionEvent e) + { + // issue a double click notification if performed in center quadrant + if (pointerAreaTouched(e, POINTER_ACTION_LCLICK)) + { + RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR); + listener.onTouchPointerLeftClick((int)rect.centerX(), (int)rect.centerY(), true); + listener.onTouchPointerLeftClick((int)rect.centerX(), (int)rect.centerY(), false); + } + return true; + } + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/BookmarkBaseGateway.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/BookmarkBaseGateway.java new file mode 100644 index 0000000..38bc466 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/BookmarkBaseGateway.java @@ -0,0 +1,610 @@ +/* + Helper class to access bookmark database + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.services; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteQueryBuilder; +import android.util.Log; + +import com.freerdp.freerdpcore.domain.BookmarkBase; + +import java.util.ArrayList; + +public abstract class BookmarkBaseGateway +{ + private final static String TAG = "BookmarkBaseGateway"; + private final SQLiteOpenHelper bookmarkDB; + + private static final String JOIN_PREFIX = "join_"; + private static final String KEY_BOOKMARK_ID = "bookmarkId"; + private static final String KEY_SCREEN_COLORS = "screenColors"; + private static final String KEY_SCREEN_COLORS_3G = "screenColors3G"; + private static final String KEY_SCREEN_RESOLUTION = "screenResolution"; + private static final String KEY_SCREEN_RESOLUTION_3G = "screenResolution3G"; + private static final String KEY_SCREEN_WIDTH = "screenWidth"; + private static final String KEY_SCREEN_WIDTH_3G = "screenWidth3G"; + private static final String KEY_SCREEN_HEIGHT = "screenHeight"; + private static final String KEY_SCREEN_HEIGHT_3G = "screenHeight3G"; + + private static final String KEY_PERFORMANCE_RFX = "performanceRemoteFX"; + private static final String KEY_PERFORMANCE_RFX_3G = "performanceRemoteFX3G"; + private static final String KEY_PERFORMANCE_GFX = "performanceGfx"; + private static final String KEY_PERFORMANCE_GFX_3G = "performanceGfx3G"; + private static final String KEY_PERFORMANCE_H264 = "performanceGfxH264"; + private static final String KEY_PERFORMANCE_H264_3G = "performanceGfxH2643G"; + private static final String KEY_PERFORMANCE_WALLPAPER = "performanceWallpaper"; + private static final String KEY_PERFORMANCE_WALLPAPER_3G = "performanceWallpaper3G"; + private static final String KEY_PERFORMANCE_THEME = "performanceTheming"; + private static final String KEY_PERFORMANCE_THEME_3G = "performanceTheming3G"; + + private static final String KEY_PERFORMANCE_DRAG = "performanceFullWindowDrag"; + private static final String KEY_PERFORMANCE_DRAG_3G = "performanceFullWindowDrag3G"; + private static final String KEY_PERFORMANCE_MENU_ANIMATIONS = "performanceMenuAnimations"; + private static final String KEY_PERFORMANCE_MENU_ANIMATIONS_3G = "performanceMenuAnimations3G"; + private static final String KEY_PERFORMANCE_FONTS = "performanceFontSmoothing"; + private static final String KEY_PERFORMANCE_FONTS_3G = "performanceFontSmoothing3G"; + private static final String KEY_PERFORMANCE_COMPOSITION = "performanceDesktopComposition"; + private static final String KEY_PERFORMANCE_COMPOSITION_3G = "performanceDesktopComposition3G"; + + public BookmarkBaseGateway(SQLiteOpenHelper bookmarkDB) + { + this.bookmarkDB = bookmarkDB; + } + + protected abstract BookmarkBase createBookmark(); + + protected abstract String getBookmarkTableName(); + + protected abstract void addBookmarkSpecificColumns(ArrayList columns); + + protected abstract void addBookmarkSpecificColumns(BookmarkBase bookmark, + ContentValues columns); + + protected abstract void readBookmarkSpecificColumns(BookmarkBase bookmark, Cursor cursor); + + public void insert(BookmarkBase bookmark) + { + // begin transaction + SQLiteDatabase db = getWritableDatabase(); + db.beginTransaction(); + + long rowid; + ContentValues values = new ContentValues(); + values.put(BookmarkDB.DB_KEY_BOOKMARK_LABEL, bookmark.getLabel()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_USERNAME, bookmark.getUsername()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_PASSWORD, bookmark.getPassword()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_DOMAIN, bookmark.getDomain()); + // insert screen and performance settings + rowid = insertScreenSettings(db, bookmark.getScreenSettings()); + values.put(BookmarkDB.DB_KEY_SCREEN_SETTINGS, rowid); + rowid = insertPerformanceFlags(db, bookmark.getPerformanceFlags()); + values.put(BookmarkDB.DB_KEY_PERFORMANCE_FLAGS, rowid); + + // advanced settings + values.put(BookmarkDB.DB_KEY_BOOKMARK_3G_ENABLE, + bookmark.getAdvancedSettings().getEnable3GSettings()); + // insert 3G screen and 3G performance settings + rowid = insertScreenSettings(db, bookmark.getAdvancedSettings().getScreen3G()); + values.put(BookmarkDB.DB_KEY_SCREEN_SETTINGS_3G, rowid); + rowid = insertPerformanceFlags(db, bookmark.getAdvancedSettings().getPerformance3G()); + values.put(BookmarkDB.DB_KEY_PERFORMANCE_FLAGS_3G, rowid); + values.put(BookmarkDB.DB_KEY_BOOKMARK_REDIRECT_SDCARD, + bookmark.getAdvancedSettings().getRedirectSDCard()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_REDIRECT_SOUND, + bookmark.getAdvancedSettings().getRedirectSound()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_REDIRECT_MICROPHONE, + bookmark.getAdvancedSettings().getRedirectMicrophone()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_SECURITY, + bookmark.getAdvancedSettings().getSecurity()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_CONSOLE_MODE, + bookmark.getAdvancedSettings().getConsoleMode()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_REMOTE_PROGRAM, + bookmark.getAdvancedSettings().getRemoteProgram()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_WORK_DIR, + bookmark.getAdvancedSettings().getWorkDir()); + + values.put(BookmarkDB.DB_KEY_BOOKMARK_ASYNC_CHANNEL, + bookmark.getDebugSettings().getAsyncChannel()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_ASYNC_UPDATE, + bookmark.getDebugSettings().getAsyncUpdate()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_DEBUG_LEVEL, + bookmark.getDebugSettings().getDebugLevel()); + + // add any special columns + addBookmarkSpecificColumns(bookmark, values); + + // insert bookmark and end transaction + db.insertOrThrow(getBookmarkTableName(), null, values); + db.setTransactionSuccessful(); + db.endTransaction(); + } + + public boolean update(BookmarkBase bookmark) + { + // start a transaction + SQLiteDatabase db = getWritableDatabase(); + db.beginTransaction(); + + // bookmark settings + ContentValues values = new ContentValues(); + values.put(BookmarkDB.DB_KEY_BOOKMARK_LABEL, bookmark.getLabel()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_USERNAME, bookmark.getUsername()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_PASSWORD, bookmark.getPassword()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_DOMAIN, bookmark.getDomain()); + // update screen and performance settings settings + updateScreenSettings(db, bookmark); + updatePerformanceFlags(db, bookmark); + + // advanced settings + values.put(BookmarkDB.DB_KEY_BOOKMARK_3G_ENABLE, + bookmark.getAdvancedSettings().getEnable3GSettings()); + // update 3G screen and 3G performance settings settings + updateScreenSettings3G(db, bookmark); + updatePerformanceFlags3G(db, bookmark); + values.put(BookmarkDB.DB_KEY_BOOKMARK_REDIRECT_SDCARD, + bookmark.getAdvancedSettings().getRedirectSDCard()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_REDIRECT_SOUND, + bookmark.getAdvancedSettings().getRedirectSound()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_REDIRECT_MICROPHONE, + bookmark.getAdvancedSettings().getRedirectMicrophone()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_SECURITY, + bookmark.getAdvancedSettings().getSecurity()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_CONSOLE_MODE, + bookmark.getAdvancedSettings().getConsoleMode()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_REMOTE_PROGRAM, + bookmark.getAdvancedSettings().getRemoteProgram()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_WORK_DIR, + bookmark.getAdvancedSettings().getWorkDir()); + + values.put(BookmarkDB.DB_KEY_BOOKMARK_ASYNC_CHANNEL, + bookmark.getDebugSettings().getAsyncChannel()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_ASYNC_UPDATE, + bookmark.getDebugSettings().getAsyncUpdate()); + values.put(BookmarkDB.DB_KEY_BOOKMARK_DEBUG_LEVEL, + bookmark.getDebugSettings().getDebugLevel()); + + addBookmarkSpecificColumns(bookmark, values); + + // update bookmark + boolean res = (db.update(getBookmarkTableName(), values, + BookmarkDB.ID + " = " + bookmark.getId(), null) == 1); + + // commit + db.setTransactionSuccessful(); + db.endTransaction(); + + return res; + } + + public void delete(long id) + { + SQLiteDatabase db = getWritableDatabase(); + db.delete(getBookmarkTableName(), BookmarkDB.ID + " = " + id, null); + } + + public BookmarkBase findById(long id) + { + Cursor cursor = + queryBookmarks(getBookmarkTableName() + "." + BookmarkDB.ID + " = " + id, null); + if (cursor.getCount() == 0) + { + cursor.close(); + return null; + } + + cursor.moveToFirst(); + BookmarkBase bookmark = getBookmarkFromCursor(cursor); + cursor.close(); + return bookmark; + } + + public BookmarkBase findByLabel(String label) + { + Cursor cursor = queryBookmarks(BookmarkDB.DB_KEY_BOOKMARK_LABEL + " = '" + label + "'", + BookmarkDB.DB_KEY_BOOKMARK_LABEL); + if (cursor.getCount() > 1) + Log.e(TAG, "More than one bookmark with the same label found!"); + + BookmarkBase bookmark = null; + if (cursor.moveToFirst() && (cursor.getCount() > 0)) + bookmark = getBookmarkFromCursor(cursor); + + cursor.close(); + return bookmark; + } + + public ArrayList findByLabelLike(String pattern) + { + Cursor cursor = + queryBookmarks(BookmarkDB.DB_KEY_BOOKMARK_LABEL + " LIKE '%" + pattern + "%'", + BookmarkDB.DB_KEY_BOOKMARK_LABEL); + ArrayList bookmarks = new ArrayList<>(cursor.getCount()); + + if (cursor.moveToFirst() && (cursor.getCount() > 0)) + { + do + { + bookmarks.add(getBookmarkFromCursor(cursor)); + } while (cursor.moveToNext()); + } + + cursor.close(); + return bookmarks; + } + + public ArrayList findAll() + { + Cursor cursor = queryBookmarks(null, BookmarkDB.DB_KEY_BOOKMARK_LABEL); + final int count = cursor.getCount(); + ArrayList bookmarks = new ArrayList<>(count); + + if (cursor.moveToFirst() && (count > 0)) + { + do + { + bookmarks.add(getBookmarkFromCursor(cursor)); + } while (cursor.moveToNext()); + } + + cursor.close(); + return bookmarks; + } + + protected Cursor queryBookmarks(String whereClause, String orderBy) + { + // create tables string + final String ID = BookmarkDB.ID; + final String tables = + BookmarkDB.DB_TABLE_BOOKMARK + " INNER JOIN " + BookmarkDB.DB_TABLE_SCREEN + " AS " + + JOIN_PREFIX + BookmarkDB.DB_KEY_SCREEN_SETTINGS + " ON " + JOIN_PREFIX + + BookmarkDB.DB_KEY_SCREEN_SETTINGS + "." + ID + " = " + BookmarkDB.DB_TABLE_BOOKMARK + + "." + BookmarkDB.DB_KEY_SCREEN_SETTINGS + " INNER JOIN " + + BookmarkDB.DB_TABLE_PERFORMANCE + " AS " + JOIN_PREFIX + + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS + " ON " + JOIN_PREFIX + + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS + "." + ID + " = " + BookmarkDB.DB_TABLE_BOOKMARK + + "." + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS + " INNER JOIN " + + BookmarkDB.DB_TABLE_SCREEN + " AS " + JOIN_PREFIX + + BookmarkDB.DB_KEY_SCREEN_SETTINGS_3G + " ON " + JOIN_PREFIX + + BookmarkDB.DB_KEY_SCREEN_SETTINGS_3G + "." + ID + " = " + BookmarkDB.DB_TABLE_BOOKMARK + + "." + BookmarkDB.DB_KEY_SCREEN_SETTINGS_3G + " INNER JOIN " + + BookmarkDB.DB_TABLE_PERFORMANCE + " AS " + JOIN_PREFIX + + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS_3G + " ON " + JOIN_PREFIX + + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS_3G + "." + ID + " = " + + BookmarkDB.DB_TABLE_BOOKMARK + "." + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS_3G; + + // create columns list + ArrayList columns = new ArrayList<>(); + addBookmarkColumns(columns); + addScreenSettingsColumns(columns); + addPerformanceFlagsColumns(columns); + addScreenSettings3GColumns(columns); + addPerformanceFlags3GColumns(columns); + + String[] cols = new String[columns.size()]; + columns.toArray(cols); + + SQLiteDatabase db = getReadableDatabase(); + final String query = SQLiteQueryBuilder.buildQueryString(false, tables, cols, whereClause, + null, null, orderBy, null); + return db.rawQuery(query, null); + } + + private void addBookmarkColumns(ArrayList columns) + { + columns.add(getBookmarkTableName() + "." + BookmarkDB.ID + " " + KEY_BOOKMARK_ID); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_LABEL); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_USERNAME); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_PASSWORD); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_DOMAIN); + + // advanced settings + columns.add(BookmarkDB.DB_KEY_BOOKMARK_3G_ENABLE); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_REDIRECT_SDCARD); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_REDIRECT_SOUND); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_REDIRECT_MICROPHONE); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_SECURITY); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_CONSOLE_MODE); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_REMOTE_PROGRAM); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_WORK_DIR); + + // debug settings + columns.add(BookmarkDB.DB_KEY_BOOKMARK_DEBUG_LEVEL); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_ASYNC_CHANNEL); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_ASYNC_UPDATE); + + addBookmarkSpecificColumns(columns); + } + + private void addScreenSettingsColumns(ArrayList columns) + { + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_SCREEN_SETTINGS + "." + + BookmarkDB.DB_KEY_SCREEN_COLORS + " as " + KEY_SCREEN_COLORS); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_SCREEN_SETTINGS + "." + + BookmarkDB.DB_KEY_SCREEN_RESOLUTION + " as " + KEY_SCREEN_RESOLUTION); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_SCREEN_SETTINGS + "." + + BookmarkDB.DB_KEY_SCREEN_WIDTH + " as " + KEY_SCREEN_WIDTH); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_SCREEN_SETTINGS + "." + + BookmarkDB.DB_KEY_SCREEN_HEIGHT + " as " + KEY_SCREEN_HEIGHT); + } + + private void addPerformanceFlagsColumns(ArrayList columns) + { + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS + "." + + BookmarkDB.DB_KEY_PERFORMANCE_RFX + " as " + KEY_PERFORMANCE_RFX); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS + "." + + BookmarkDB.DB_KEY_PERFORMANCE_GFX + " as " + KEY_PERFORMANCE_GFX); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS + "." + + BookmarkDB.DB_KEY_PERFORMANCE_H264 + " as " + KEY_PERFORMANCE_H264); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS + "." + + BookmarkDB.DB_KEY_PERFORMANCE_WALLPAPER + " as " + KEY_PERFORMANCE_WALLPAPER); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS + "." + + BookmarkDB.DB_KEY_PERFORMANCE_THEME + " as " + KEY_PERFORMANCE_THEME); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS + "." + + BookmarkDB.DB_KEY_PERFORMANCE_DRAG + " as " + KEY_PERFORMANCE_DRAG); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS + "." + + BookmarkDB.DB_KEY_PERFORMANCE_MENU_ANIMATIONS + " as " + + KEY_PERFORMANCE_MENU_ANIMATIONS); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS + "." + + BookmarkDB.DB_KEY_PERFORMANCE_FONTS + " as " + KEY_PERFORMANCE_FONTS); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS + "." + + BookmarkDB.DB_KEY_PERFORMANCE_COMPOSITION + " " + KEY_PERFORMANCE_COMPOSITION); + } + + private void addScreenSettings3GColumns(ArrayList columns) + { + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_SCREEN_SETTINGS_3G + "." + + BookmarkDB.DB_KEY_SCREEN_COLORS + " as " + KEY_SCREEN_COLORS_3G); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_SCREEN_SETTINGS_3G + "." + + BookmarkDB.DB_KEY_SCREEN_RESOLUTION + " as " + KEY_SCREEN_RESOLUTION_3G); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_SCREEN_SETTINGS_3G + "." + + BookmarkDB.DB_KEY_SCREEN_WIDTH + " as " + KEY_SCREEN_WIDTH_3G); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_SCREEN_SETTINGS_3G + "." + + BookmarkDB.DB_KEY_SCREEN_HEIGHT + " as " + KEY_SCREEN_HEIGHT_3G); + } + + private void addPerformanceFlags3GColumns(ArrayList columns) + { + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS_3G + "." + + BookmarkDB.DB_KEY_PERFORMANCE_RFX + " as " + KEY_PERFORMANCE_RFX_3G); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS_3G + "." + + BookmarkDB.DB_KEY_PERFORMANCE_GFX + " as " + KEY_PERFORMANCE_GFX_3G); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS_3G + "." + + BookmarkDB.DB_KEY_PERFORMANCE_H264 + " as " + KEY_PERFORMANCE_H264_3G); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS_3G + "." + + BookmarkDB.DB_KEY_PERFORMANCE_WALLPAPER + " as " + + KEY_PERFORMANCE_WALLPAPER_3G); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS_3G + "." + + BookmarkDB.DB_KEY_PERFORMANCE_THEME + " as " + KEY_PERFORMANCE_THEME_3G); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS_3G + "." + + BookmarkDB.DB_KEY_PERFORMANCE_DRAG + " as " + KEY_PERFORMANCE_DRAG_3G); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS_3G + "." + + BookmarkDB.DB_KEY_PERFORMANCE_MENU_ANIMATIONS + " as " + + KEY_PERFORMANCE_MENU_ANIMATIONS_3G); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS_3G + "." + + BookmarkDB.DB_KEY_PERFORMANCE_FONTS + " as " + KEY_PERFORMANCE_FONTS_3G); + columns.add(JOIN_PREFIX + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS_3G + "." + + BookmarkDB.DB_KEY_PERFORMANCE_COMPOSITION + " " + + KEY_PERFORMANCE_COMPOSITION_3G); + } + + protected BookmarkBase getBookmarkFromCursor(Cursor cursor) + { + BookmarkBase bookmark = createBookmark(); + bookmark.setId(cursor.getLong(cursor.getColumnIndex(KEY_BOOKMARK_ID))); + bookmark.setLabel( + cursor.getString(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_LABEL))); + bookmark.setUsername( + cursor.getString(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_USERNAME))); + bookmark.setPassword( + cursor.getString(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_PASSWORD))); + bookmark.setDomain( + cursor.getString(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_DOMAIN))); + readScreenSettings(bookmark, cursor); + readPerformanceFlags(bookmark, cursor); + + // advanced settings + bookmark.getAdvancedSettings().setEnable3GSettings( + cursor.getInt(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_3G_ENABLE)) != 0); + readScreenSettings3G(bookmark, cursor); + readPerformanceFlags3G(bookmark, cursor); + bookmark.getAdvancedSettings().setRedirectSDCard( + cursor.getInt(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_REDIRECT_SDCARD)) != 0); + bookmark.getAdvancedSettings().setRedirectSound( + cursor.getInt(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_REDIRECT_SOUND))); + bookmark.getAdvancedSettings().setRedirectMicrophone( + cursor.getInt(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_REDIRECT_MICROPHONE)) != + 0); + bookmark.getAdvancedSettings().setSecurity( + cursor.getInt(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_SECURITY))); + bookmark.getAdvancedSettings().setConsoleMode( + cursor.getInt(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_CONSOLE_MODE)) != 0); + bookmark.getAdvancedSettings().setRemoteProgram( + cursor.getString(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_REMOTE_PROGRAM))); + bookmark.getAdvancedSettings().setWorkDir( + cursor.getString(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_WORK_DIR))); + + bookmark.getDebugSettings().setAsyncChannel( + cursor.getInt(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_ASYNC_CHANNEL)) == 1); + bookmark.getDebugSettings().setAsyncUpdate( + cursor.getInt(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_ASYNC_UPDATE)) == 1); + bookmark.getDebugSettings().setDebugLevel( + cursor.getString(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_DEBUG_LEVEL))); + + readBookmarkSpecificColumns(bookmark, cursor); + + return bookmark; + } + + private void readScreenSettings(BookmarkBase bookmark, Cursor cursor) + { + BookmarkBase.ScreenSettings screenSettings = bookmark.getScreenSettings(); + screenSettings.setColors(cursor.getInt(cursor.getColumnIndex(KEY_SCREEN_COLORS))); + screenSettings.setResolution(cursor.getInt(cursor.getColumnIndex(KEY_SCREEN_RESOLUTION))); + screenSettings.setWidth(cursor.getInt(cursor.getColumnIndex(KEY_SCREEN_WIDTH))); + screenSettings.setHeight(cursor.getInt(cursor.getColumnIndex(KEY_SCREEN_HEIGHT))); + } + + private void readPerformanceFlags(BookmarkBase bookmark, Cursor cursor) + { + BookmarkBase.PerformanceFlags perfFlags = bookmark.getPerformanceFlags(); + perfFlags.setRemoteFX(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_RFX)) != 0); + perfFlags.setGfx(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_GFX)) != 0); + perfFlags.setH264(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_H264)) != 0); + perfFlags.setWallpaper(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_WALLPAPER)) != + 0); + perfFlags.setTheming(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_THEME)) != 0); + perfFlags.setFullWindowDrag(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_DRAG)) != + 0); + perfFlags.setMenuAnimations( + cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_MENU_ANIMATIONS)) != 0); + perfFlags.setFontSmoothing(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_FONTS)) != + 0); + perfFlags.setDesktopComposition( + cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_COMPOSITION)) != 0); + } + + private void readScreenSettings3G(BookmarkBase bookmark, Cursor cursor) + { + BookmarkBase.ScreenSettings screenSettings = bookmark.getAdvancedSettings().getScreen3G(); + screenSettings.setColors(cursor.getInt(cursor.getColumnIndex(KEY_SCREEN_COLORS_3G))); + screenSettings.setResolution( + cursor.getInt(cursor.getColumnIndex(KEY_SCREEN_RESOLUTION_3G))); + screenSettings.setWidth(cursor.getInt(cursor.getColumnIndex(KEY_SCREEN_WIDTH_3G))); + screenSettings.setHeight(cursor.getInt(cursor.getColumnIndex(KEY_SCREEN_HEIGHT_3G))); + } + + private void readPerformanceFlags3G(BookmarkBase bookmark, Cursor cursor) + { + BookmarkBase.PerformanceFlags perfFlags = bookmark.getAdvancedSettings().getPerformance3G(); + perfFlags.setRemoteFX(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_RFX_3G)) != 0); + perfFlags.setGfx(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_GFX_3G)) != 0); + perfFlags.setH264(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_H264_3G)) != 0); + perfFlags.setWallpaper(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_WALLPAPER_3G)) != + 0); + perfFlags.setTheming(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_THEME_3G)) != 0); + perfFlags.setFullWindowDrag(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_DRAG_3G)) != + 0); + perfFlags.setMenuAnimations( + cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_MENU_ANIMATIONS_3G)) != 0); + perfFlags.setFontSmoothing(cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_FONTS_3G)) != + 0); + perfFlags.setDesktopComposition( + cursor.getInt(cursor.getColumnIndex(KEY_PERFORMANCE_COMPOSITION_3G)) != 0); + } + + private void fillScreenSettingsContentValues(BookmarkBase.ScreenSettings settings, + ContentValues values) + { + values.put(BookmarkDB.DB_KEY_SCREEN_COLORS, settings.getColors()); + values.put(BookmarkDB.DB_KEY_SCREEN_RESOLUTION, settings.getResolution()); + values.put(BookmarkDB.DB_KEY_SCREEN_WIDTH, settings.getWidth()); + values.put(BookmarkDB.DB_KEY_SCREEN_HEIGHT, settings.getHeight()); + } + + private void fillPerformanceFlagsContentValues(BookmarkBase.PerformanceFlags perfFlags, + ContentValues values) + { + values.put(BookmarkDB.DB_KEY_PERFORMANCE_RFX, perfFlags.getRemoteFX()); + values.put(BookmarkDB.DB_KEY_PERFORMANCE_GFX, perfFlags.getGfx()); + values.put(BookmarkDB.DB_KEY_PERFORMANCE_H264, perfFlags.getH264()); + values.put(BookmarkDB.DB_KEY_PERFORMANCE_WALLPAPER, perfFlags.getWallpaper()); + values.put(BookmarkDB.DB_KEY_PERFORMANCE_THEME, perfFlags.getTheming()); + values.put(BookmarkDB.DB_KEY_PERFORMANCE_DRAG, perfFlags.getFullWindowDrag()); + values.put(BookmarkDB.DB_KEY_PERFORMANCE_MENU_ANIMATIONS, perfFlags.getMenuAnimations()); + values.put(BookmarkDB.DB_KEY_PERFORMANCE_FONTS, perfFlags.getFontSmoothing()); + values.put(BookmarkDB.DB_KEY_PERFORMANCE_COMPOSITION, perfFlags.getDesktopComposition()); + } + + private long insertScreenSettings(SQLiteDatabase db, BookmarkBase.ScreenSettings settings) + { + ContentValues values = new ContentValues(); + fillScreenSettingsContentValues(settings, values); + return db.insertOrThrow(BookmarkDB.DB_TABLE_SCREEN, null, values); + } + + private boolean updateScreenSettings(SQLiteDatabase db, BookmarkBase bookmark) + { + ContentValues values = new ContentValues(); + fillScreenSettingsContentValues(bookmark.getScreenSettings(), values); + String whereClause = BookmarkDB.ID + " IN " + + "(SELECT " + BookmarkDB.DB_KEY_SCREEN_SETTINGS + " FROM " + + getBookmarkTableName() + " WHERE " + BookmarkDB.ID + " = " + + bookmark.getId() + ");"; + return (db.update(BookmarkDB.DB_TABLE_SCREEN, values, whereClause, null) == 1); + } + + private boolean updateScreenSettings3G(SQLiteDatabase db, BookmarkBase bookmark) + { + ContentValues values = new ContentValues(); + fillScreenSettingsContentValues(bookmark.getAdvancedSettings().getScreen3G(), values); + String whereClause = BookmarkDB.ID + " IN " + + "(SELECT " + BookmarkDB.DB_KEY_SCREEN_SETTINGS_3G + " FROM " + + getBookmarkTableName() + " WHERE " + BookmarkDB.ID + " = " + + bookmark.getId() + ");"; + return (db.update(BookmarkDB.DB_TABLE_SCREEN, values, whereClause, null) == 1); + } + + private long insertPerformanceFlags(SQLiteDatabase db, BookmarkBase.PerformanceFlags perfFlags) + { + ContentValues values = new ContentValues(); + fillPerformanceFlagsContentValues(perfFlags, values); + return db.insertOrThrow(BookmarkDB.DB_TABLE_PERFORMANCE, null, values); + } + + private boolean updatePerformanceFlags(SQLiteDatabase db, BookmarkBase bookmark) + { + ContentValues values = new ContentValues(); + fillPerformanceFlagsContentValues(bookmark.getPerformanceFlags(), values); + String whereClause = BookmarkDB.ID + " IN " + + "(SELECT " + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS + " FROM " + + getBookmarkTableName() + " WHERE " + BookmarkDB.ID + " = " + + bookmark.getId() + ");"; + return (db.update(BookmarkDB.DB_TABLE_PERFORMANCE, values, whereClause, null) == 1); + } + + private boolean updatePerformanceFlags3G(SQLiteDatabase db, BookmarkBase bookmark) + { + ContentValues values = new ContentValues(); + fillPerformanceFlagsContentValues(bookmark.getAdvancedSettings().getPerformance3G(), + values); + String whereClause = BookmarkDB.ID + " IN " + + "(SELECT " + BookmarkDB.DB_KEY_PERFORMANCE_FLAGS_3G + " FROM " + + getBookmarkTableName() + " WHERE " + BookmarkDB.ID + " = " + + bookmark.getId() + ");"; + return (db.update(BookmarkDB.DB_TABLE_PERFORMANCE, values, whereClause, null) == 1); + } + + // safety wrappers + // in case of getReadableDatabase it could happen that upgradeDB gets called which is + // a problem if the DB is only readable + private SQLiteDatabase getWritableDatabase() + { + return bookmarkDB.getWritableDatabase(); + } + + private SQLiteDatabase getReadableDatabase() + { + SQLiteDatabase db; + try + { + db = bookmarkDB.getReadableDatabase(); + } + catch (SQLiteException e) + { + db = bookmarkDB.getWritableDatabase(); + } + return db; + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/BookmarkDB.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/BookmarkDB.java new file mode 100644 index 0000000..8f95ec1 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/BookmarkDB.java @@ -0,0 +1,412 @@ +/* + Android Bookmark Database + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.services; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.provider.BaseColumns; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class BookmarkDB extends SQLiteOpenHelper +{ + public static final String ID = BaseColumns._ID; + private static final int DB_VERSION = 10; + private static final String DB_BACKUP_PREFIX = "temp_"; + private static final String DB_NAME = "bookmarks.db"; + static final String DB_TABLE_BOOKMARK = "tbl_manual_bookmarks"; + static final String DB_TABLE_SCREEN = "tbl_screen_settings"; + static final String DB_TABLE_PERFORMANCE = "tbl_performance_flags"; + private static final String[] DB_TABLES = { DB_TABLE_BOOKMARK, DB_TABLE_SCREEN, + DB_TABLE_PERFORMANCE }; + + static final String DB_KEY_SCREEN_COLORS = "colors"; + static final String DB_KEY_SCREEN_RESOLUTION = "resolution"; + static final String DB_KEY_SCREEN_WIDTH = "width"; + static final String DB_KEY_SCREEN_HEIGHT = "height"; + + static final String DB_KEY_SCREEN_SETTINGS = "screen_settings"; + static final String DB_KEY_SCREEN_SETTINGS_3G = "screen_3g"; + static final String DB_KEY_PERFORMANCE_FLAGS = "performance_flags"; + static final String DB_KEY_PERFORMANCE_FLAGS_3G = "performance_3g"; + + static final String DB_KEY_PERFORMANCE_RFX = "perf_remotefx"; + static final String DB_KEY_PERFORMANCE_GFX = "perf_gfx"; + static final String DB_KEY_PERFORMANCE_H264 = "perf_gfx_h264"; + static final String DB_KEY_PERFORMANCE_WALLPAPER = "perf_wallpaper"; + static final String DB_KEY_PERFORMANCE_THEME = "perf_theming"; + static final String DB_KEY_PERFORMANCE_DRAG = "perf_full_window_drag"; + static final String DB_KEY_PERFORMANCE_MENU_ANIMATIONS = "perf_menu_animations"; + static final String DB_KEY_PERFORMANCE_FONTS = "perf_font_smoothing"; + static final String DB_KEY_PERFORMANCE_COMPOSITION = "perf_desktop_composition"; + + static final String DB_KEY_BOOKMARK_LABEL = "label"; + static final String DB_KEY_BOOKMARK_HOSTNAME = "hostname"; + static final String DB_KEY_BOOKMARK_USERNAME = "username"; + static final String DB_KEY_BOOKMARK_PASSWORD = "password"; + static final String DB_KEY_BOOKMARK_DOMAIN = "domain"; + static final String DB_KEY_BOOKMARK_PORT = "port"; + + static final String DB_KEY_BOOKMARK_REDIRECT_SDCARD = "redirect_sdcard"; + static final String DB_KEY_BOOKMARK_REDIRECT_SOUND = "redirect_sound"; + static final String DB_KEY_BOOKMARK_REDIRECT_MICROPHONE = "redirect_microphone"; + static final String DB_KEY_BOOKMARK_SECURITY = "security"; + static final String DB_KEY_BOOKMARK_REMOTE_PROGRAM = "remote_program"; + static final String DB_KEY_BOOKMARK_WORK_DIR = "work_dir"; + static final String DB_KEY_BOOKMARK_ASYNC_CHANNEL = "async_channel"; + static final String DB_KEY_BOOKMARK_ASYNC_UPDATE = "async_update"; + static final String DB_KEY_BOOKMARK_CONSOLE_MODE = "console_mode"; + static final String DB_KEY_BOOKMARK_DEBUG_LEVEL = "debug_level"; + + static final String DB_KEY_BOOKMARK_GW_ENABLE = "enable_gateway_settings"; + static final String DB_KEY_BOOKMARK_GW_HOSTNAME = "gateway_hostname"; + static final String DB_KEY_BOOKMARK_GW_PORT = "gateway_port"; + static final String DB_KEY_BOOKMARK_GW_USERNAME = "gateway_username"; + static final String DB_KEY_BOOKMARK_GW_PASSWORD = "gateway_password"; + static final String DB_KEY_BOOKMARK_GW_DOMAIN = "gateway_domain"; + static final String DB_KEY_BOOKMARK_3G_ENABLE = "enable_3g_settings"; + + public BookmarkDB(Context context) + { + super(context, DB_NAME, null, DB_VERSION); + } + + private static List GetColumns(SQLiteDatabase db, String tableName) + { + List ar = null; + try (Cursor c = db.rawQuery("SELECT * FROM " + tableName + " LIMIT 1", null)) + { + if (c != null) + { + ar = new ArrayList<>(Arrays.asList(c.getColumnNames())); + } + } + catch (Exception e) + { + Log.v(tableName, e.getMessage(), e); + e.printStackTrace(); + } + return ar; + } + + private static String joinStrings(List list, String delim) + { + StringBuilder buf = new StringBuilder(); + int num = list.size(); + for (int i = 0; i < num; i++) + { + if (i != 0) + buf.append(delim); + buf.append(list.get(i)); + } + return buf.toString(); + } + + private void backupTables(SQLiteDatabase db) + { + for (String table : DB_TABLES) + { + final String tmpTable = DB_BACKUP_PREFIX + table; + final String query = "ALTER TABLE '" + table + "' RENAME TO '" + tmpTable + "'"; + try + { + db.execSQL(query); + } + catch (Exception e) + { + /* Ignore errors if table does not exist. */ + } + } + } + + private void dropOldTables(SQLiteDatabase db) + { + for (String table : DB_TABLES) + { + final String tmpTable = DB_BACKUP_PREFIX + table; + final String query = "DROP TABLE IF EXISTS '" + tmpTable + "'"; + db.execSQL(query); + } + } + + private void createDB(SQLiteDatabase db) + { + final String sqlScreenSettings = + "CREATE TABLE IF NOT EXISTS " + DB_TABLE_SCREEN + " (" + ID + " INTEGER PRIMARY KEY, " + + DB_KEY_SCREEN_COLORS + " INTEGER DEFAULT 16, " + DB_KEY_SCREEN_RESOLUTION + + " INTEGER DEFAULT 0, " + DB_KEY_SCREEN_WIDTH + ", " + DB_KEY_SCREEN_HEIGHT + ");"; + + db.execSQL(sqlScreenSettings); + + final String sqlPerformanceFlags = + "CREATE TABLE IF NOT EXISTS " + DB_TABLE_PERFORMANCE + " (" + ID + + " INTEGER PRIMARY KEY, " + DB_KEY_PERFORMANCE_RFX + " INTEGER, " + + DB_KEY_PERFORMANCE_GFX + " INTEGER, " + DB_KEY_PERFORMANCE_H264 + " INTEGER, " + + DB_KEY_PERFORMANCE_WALLPAPER + " INTEGER, " + DB_KEY_PERFORMANCE_THEME + " INTEGER, " + + DB_KEY_PERFORMANCE_DRAG + " INTEGER, " + DB_KEY_PERFORMANCE_MENU_ANIMATIONS + + " INTEGER, " + DB_KEY_PERFORMANCE_FONTS + " INTEGER, " + + DB_KEY_PERFORMANCE_COMPOSITION + " INTEGER);"; + + db.execSQL(sqlPerformanceFlags); + + final String sqlManualBookmarks = getManualBookmarksCreationString(); + db.execSQL(sqlManualBookmarks); + } + + private void upgradeTables(SQLiteDatabase db) + { + for (String table : DB_TABLES) + { + final String tmpTable = DB_BACKUP_PREFIX + table; + + final List newColumns = GetColumns(db, table); + List columns = GetColumns(db, tmpTable); + + if (columns != null) + { + columns.retainAll(newColumns); + + // restore data + final String cols = joinStrings(columns, ","); + final String query = String.format("INSERT INTO %s (%s) SELECT %s from '%s'", table, + cols, cols, tmpTable); + db.execSQL(query); + } + } + } + + private void downgradeTables(SQLiteDatabase db) + { + for (String table : DB_TABLES) + { + final String tmpTable = DB_BACKUP_PREFIX + table; + + List oldColumns = GetColumns(db, table); + final List columns = GetColumns(db, tmpTable); + + if (oldColumns != null) + { + oldColumns.retainAll(columns); + + // restore data + final String cols = joinStrings(oldColumns, ","); + final String query = String.format("INSERT INTO %s (%s) SELECT %s from '%s'", table, + cols, cols, tmpTable); + db.execSQL(query); + } + } + } + + private List getTableNames(SQLiteDatabase db) + { + final String query = "SELECT name FROM sqlite_master WHERE type='table'"; + Cursor cursor = db.rawQuery(query, null); + List list = new ArrayList<>(); + try + { + if (cursor.moveToFirst() && (cursor.getCount() > 0)) + { + while (!cursor.isAfterLast()) + { + final String name = cursor.getString(cursor.getColumnIndex("name")); + list.add(name); + cursor.moveToNext(); + } + } + } + finally + { + cursor.close(); + } + + return list; + } + + private void insertDefault(SQLiteDatabase db) + { + ContentValues screenValues = new ContentValues(); + screenValues.put(DB_KEY_SCREEN_COLORS, 32); + screenValues.put(DB_KEY_SCREEN_RESOLUTION, 1); + screenValues.put(DB_KEY_SCREEN_WIDTH, 1024); + screenValues.put(DB_KEY_SCREEN_HEIGHT, 768); + + final long idScreen = db.insert(DB_TABLE_SCREEN, null, screenValues); + final long idScreen3g = db.insert(DB_TABLE_SCREEN, null, screenValues); + + ContentValues performanceValues = new ContentValues(); + performanceValues.put(DB_KEY_PERFORMANCE_RFX, 1); + performanceValues.put(DB_KEY_PERFORMANCE_GFX, 1); + performanceValues.put(DB_KEY_PERFORMANCE_H264, 0); + performanceValues.put(DB_KEY_PERFORMANCE_WALLPAPER, 0); + performanceValues.put(DB_KEY_PERFORMANCE_THEME, 0); + performanceValues.put(DB_KEY_PERFORMANCE_DRAG, 0); + performanceValues.put(DB_KEY_PERFORMANCE_MENU_ANIMATIONS, 0); + performanceValues.put(DB_KEY_PERFORMANCE_FONTS, 0); + performanceValues.put(DB_KEY_PERFORMANCE_COMPOSITION, 0); + + final long idPerformance = db.insert(DB_TABLE_PERFORMANCE, null, performanceValues); + final long idPerformance3g = db.insert(DB_TABLE_PERFORMANCE, null, performanceValues); + + ContentValues bookmarkValues = new ContentValues(); + bookmarkValues.put(DB_KEY_BOOKMARK_LABEL, "Test Server"); + bookmarkValues.put(DB_KEY_BOOKMARK_HOSTNAME, "testservice.afreerdp.com"); + bookmarkValues.put(DB_KEY_BOOKMARK_USERNAME, ""); + bookmarkValues.put(DB_KEY_BOOKMARK_PASSWORD, ""); + bookmarkValues.put(DB_KEY_BOOKMARK_DOMAIN, ""); + bookmarkValues.put(DB_KEY_BOOKMARK_PORT, "3389"); + + bookmarkValues.put(DB_KEY_SCREEN_SETTINGS, idScreen); + bookmarkValues.put(DB_KEY_SCREEN_SETTINGS_3G, idScreen3g); + bookmarkValues.put(DB_KEY_PERFORMANCE_FLAGS, idPerformance); + bookmarkValues.put(DB_KEY_PERFORMANCE_FLAGS_3G, idPerformance3g); + + bookmarkValues.put(DB_KEY_BOOKMARK_REDIRECT_SDCARD, 0); + bookmarkValues.put(DB_KEY_BOOKMARK_REDIRECT_SOUND, 0); + bookmarkValues.put(DB_KEY_BOOKMARK_REDIRECT_MICROPHONE, 0); + bookmarkValues.put(DB_KEY_BOOKMARK_SECURITY, 0); + bookmarkValues.put(DB_KEY_BOOKMARK_REMOTE_PROGRAM, ""); + bookmarkValues.put(DB_KEY_BOOKMARK_WORK_DIR, ""); + bookmarkValues.put(DB_KEY_BOOKMARK_ASYNC_CHANNEL, 1); + bookmarkValues.put(DB_KEY_BOOKMARK_ASYNC_UPDATE, 1); + bookmarkValues.put(DB_KEY_BOOKMARK_CONSOLE_MODE, 0); + bookmarkValues.put(DB_KEY_BOOKMARK_DEBUG_LEVEL, "INFO"); + + db.insert(DB_TABLE_BOOKMARK, null, bookmarkValues); + } + + @Override public void onCreate(SQLiteDatabase db) + { + createDB(db); + insertDefault(db); + } + + private String getManualBookmarksCreationString() + { + return ("CREATE TABLE IF NOT EXISTS " + DB_TABLE_BOOKMARK + " (" + ID + + " INTEGER PRIMARY KEY, " + DB_KEY_BOOKMARK_LABEL + " TEXT NOT NULL, " + + DB_KEY_BOOKMARK_HOSTNAME + " TEXT NOT NULL, " + DB_KEY_BOOKMARK_USERNAME + + " TEXT NOT NULL, " + DB_KEY_BOOKMARK_PASSWORD + " TEXT, " + DB_KEY_BOOKMARK_DOMAIN + + " TEXT, " + DB_KEY_BOOKMARK_PORT + " TEXT, " + DB_KEY_SCREEN_SETTINGS + + " INTEGER NOT NULL, " + DB_KEY_PERFORMANCE_FLAGS + " INTEGER NOT NULL, " + + + DB_KEY_BOOKMARK_GW_ENABLE + " INTEGER DEFAULT 0, " + DB_KEY_BOOKMARK_GW_HOSTNAME + + " TEXT, " + DB_KEY_BOOKMARK_GW_PORT + " INTEGER DEFAULT 443, " + + DB_KEY_BOOKMARK_GW_USERNAME + " TEXT, " + DB_KEY_BOOKMARK_GW_PASSWORD + " TEXT, " + + DB_KEY_BOOKMARK_GW_DOMAIN + " TEXT, " + + + DB_KEY_BOOKMARK_3G_ENABLE + " INTEGER DEFAULT 0, " + DB_KEY_SCREEN_SETTINGS_3G + + " INTEGER NOT NULL, " + DB_KEY_PERFORMANCE_FLAGS_3G + " INTEGER NOT NULL, " + + DB_KEY_BOOKMARK_REDIRECT_SDCARD + " INTEGER DEFAULT 0, " + + DB_KEY_BOOKMARK_REDIRECT_SOUND + " INTEGER DEFAULT 0, " + + DB_KEY_BOOKMARK_REDIRECT_MICROPHONE + " INTEGER DEFAULT 0, " + + DB_KEY_BOOKMARK_SECURITY + " INTEGER, " + DB_KEY_BOOKMARK_REMOTE_PROGRAM + + " TEXT, " + DB_KEY_BOOKMARK_WORK_DIR + " TEXT, " + DB_KEY_BOOKMARK_ASYNC_CHANNEL + + " INTEGER DEFAULT 0, " + DB_KEY_BOOKMARK_ASYNC_UPDATE + " INTEGER DEFAULT 0, " + + DB_KEY_BOOKMARK_CONSOLE_MODE + " INTEGER, " + DB_KEY_BOOKMARK_DEBUG_LEVEL + + " TEXT DEFAULT 'INFO', " + + + "FOREIGN KEY(" + DB_KEY_SCREEN_SETTINGS + ") REFERENCES " + DB_TABLE_SCREEN + + "(" + ID + "), " + + "FOREIGN KEY(" + DB_KEY_PERFORMANCE_FLAGS + ") REFERENCES " + + DB_TABLE_PERFORMANCE + "(" + ID + "), " + + "FOREIGN KEY(" + DB_KEY_SCREEN_SETTINGS_3G + ") REFERENCES " + DB_TABLE_SCREEN + + "(" + ID + "), " + + "FOREIGN KEY(" + DB_KEY_PERFORMANCE_FLAGS_3G + ") REFERENCES " + + DB_TABLE_PERFORMANCE + "(" + ID + ") " + + + ");"); + } + + private void recreateDB(SQLiteDatabase db) + { + for (String table : DB_TABLES) + { + final String query = "DROP TABLE IF EXISTS '" + table + "'"; + db.execSQL(query); + } + onCreate(db); + } + + private void upgradeDB(SQLiteDatabase db) + { + db.beginTransaction(); + try + { + /* Back up old tables. */ + dropOldTables(db); + backupTables(db); + createDB(db); + upgradeTables(db); + + db.setTransactionSuccessful(); + } + finally + { + db.endTransaction(); + dropOldTables(db); + } + } + + private void downgradeDB(SQLiteDatabase db) + { + db.beginTransaction(); + try + { + /* Back up old tables. */ + dropOldTables(db); + backupTables(db); + createDB(db); + downgradeTables(db); + + db.setTransactionSuccessful(); + } + finally + { + db.endTransaction(); + dropOldTables(db); + } + } + + // from + // http://stackoverflow.com/questions/3424156/upgrade-sqlite-database-from-one-version-to-another + @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) + { + switch (oldVersion) + { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + upgradeDB(db); + break; + default: + recreateDB(db); + break; + } + } + + @Override public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) + { + downgradeDB(db); + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/FreeRDPSuggestionProvider.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/FreeRDPSuggestionProvider.java new file mode 100644 index 0000000..5fb931c --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/FreeRDPSuggestionProvider.java @@ -0,0 +1,134 @@ +/* + Suggestion Provider for RDP bookmarks + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.services; + +import android.app.SearchManager; +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.net.Uri; + +import com.freerdp.freerdpcore.R; +import com.freerdp.freerdpcore.application.GlobalApp; +import com.freerdp.freerdpcore.domain.BookmarkBase; +import com.freerdp.freerdpcore.domain.ConnectionReference; +import com.freerdp.freerdpcore.domain.ManualBookmark; + +import java.util.ArrayList; + +public class FreeRDPSuggestionProvider extends ContentProvider +{ + + public static final Uri CONTENT_URI = + Uri.parse("content://com.freerdp.afreerdp.services.freerdpsuggestionprovider"); + + @Override public int delete(Uri uri, String selection, String[] selectionArgs) + { + // TODO Auto-generated method stub + return 0; + } + + @Override public String getType(Uri uri) + { + return "vnd.android.cursor.item/vnd.freerdp.remote"; + } + + @Override public Uri insert(Uri uri, ContentValues values) + { + // TODO Auto-generated method stub + return null; + } + + @Override public boolean onCreate() + { + return true; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) + { + + String query = (selectionArgs != null && selectionArgs.length > 0) ? selectionArgs[0] : ""; + + // search history + ArrayList history = + GlobalApp.getQuickConnectHistoryGateway().findHistory(query); + + // search bookmarks + ArrayList manualBookmarks; + if (query.length() > 0) + manualBookmarks = GlobalApp.getManualBookmarkGateway().findByLabelOrHostnameLike(query); + else + manualBookmarks = GlobalApp.getManualBookmarkGateway().findAll(); + + return createResultCursor(history, manualBookmarks); + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) + { + // TODO Auto-generated method stub + return 0; + } + + private void addBookmarksToCursor(ArrayList bookmarks, MatrixCursor resultCursor) + { + Object[] row = new Object[5]; + for (BookmarkBase bookmark : bookmarks) + { + row[0] = bookmark.getId(); + row[1] = bookmark.getLabel(); + row[2] = bookmark.get().getHostname(); + row[3] = ConnectionReference.getManualBookmarkReference(bookmark.getId()); + row[4] = "android.resource://" + getContext().getPackageName() + "/" + + R.drawable.icon_star_on; + resultCursor.addRow(row); + } + } + + private void addHistoryToCursor(ArrayList history, MatrixCursor resultCursor) + { + Object[] row = new Object[5]; + for (BookmarkBase bookmark : history) + { + row[0] = 1; + row[1] = bookmark.getLabel(); + row[2] = bookmark.getLabel(); + row[3] = ConnectionReference.getHostnameReference(bookmark.getLabel()); + row[4] = "android.resource://" + getContext().getPackageName() + "/" + + R.drawable.icon_star_off; + resultCursor.addRow(row); + } + } + + private Cursor createResultCursor(ArrayList history, + ArrayList manualBookmarks) + { + + // create result matrix cursor + int totalCount = history.size() + manualBookmarks.size(); + String[] columns = { android.provider.BaseColumns._ID, SearchManager.SUGGEST_COLUMN_TEXT_1, + SearchManager.SUGGEST_COLUMN_TEXT_2, + SearchManager.SUGGEST_COLUMN_INTENT_DATA, + SearchManager.SUGGEST_COLUMN_ICON_2 }; + MatrixCursor matrixCursor = new MatrixCursor(columns, totalCount); + + // populate result matrix + if (totalCount > 0) + { + addHistoryToCursor(history, matrixCursor); + addBookmarksToCursor(manualBookmarks, matrixCursor); + } + return matrixCursor; + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/HistoryDB.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/HistoryDB.java new file mode 100644 index 0000000..b483aac --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/HistoryDB.java @@ -0,0 +1,46 @@ +/* + Quick Connect History Database + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.services; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +public class HistoryDB extends SQLiteOpenHelper +{ + + public static final String QUICK_CONNECT_TABLE_NAME = "quick_connect_history"; + public static final String QUICK_CONNECT_TABLE_COL_ITEM = "item"; + public static final String QUICK_CONNECT_TABLE_COL_TIMESTAMP = "timestamp"; + private static final int DB_VERSION = 1; + private static final String DB_NAME = "history.db"; + + public HistoryDB(Context context) + { + super(context, DB_NAME, null, DB_VERSION); + } + + @Override public void onCreate(SQLiteDatabase db) + { + + String sqlQuickConnectHistory = "CREATE TABLE " + QUICK_CONNECT_TABLE_NAME + " (" + + QUICK_CONNECT_TABLE_COL_ITEM + " TEXT PRIMARY KEY, " + + QUICK_CONNECT_TABLE_COL_TIMESTAMP + " INTEGER" + + ");"; + + db.execSQL(sqlQuickConnectHistory); + } + + @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) + { + // TODO Auto-generated method stub + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/LibFreeRDP.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/LibFreeRDP.java new file mode 100644 index 0000000..e9386b9 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/LibFreeRDP.java @@ -0,0 +1,681 @@ +/* + Android FreeRDP JNI Wrapper + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.services; + +import android.content.Context; +import android.graphics.Bitmap; +import android.net.Uri; +import android.util.Log; + +import androidx.collection.LongSparseArray; + +import com.freerdp.freerdpcore.application.GlobalApp; +import com.freerdp.freerdpcore.application.SessionState; +import com.freerdp.freerdpcore.domain.BookmarkBase; +import com.freerdp.freerdpcore.domain.ManualBookmark; +import com.freerdp.freerdpcore.presentation.ApplicationSettingsActivity; + +import java.util.ArrayList; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class LibFreeRDP +{ + private static final String TAG = "LibFreeRDP"; + private static EventListener listener; + private static boolean mHasH264 = false; + + private static final LongSparseArray mInstanceState = new LongSparseArray<>(); + + public static final long VERIFY_CERT_FLAG_NONE = 0x00; + public static final long VERIFY_CERT_FLAG_LEGACY = 0x02; + public static final long VERIFY_CERT_FLAG_REDIRECT = 0x10; + public static final long VERIFY_CERT_FLAG_GATEWAY = 0x20; + public static final long VERIFY_CERT_FLAG_CHANGED = 0x40; + public static final long VERIFY_CERT_FLAG_MISMATCH = 0x80; + public static final long VERIFY_CERT_FLAG_MATCH_LEGACY_SHA1 = 0x100; + public static final long VERIFY_CERT_FLAG_FP_IS_PEM = 0x200; + + private static boolean tryLoad(String[] libraries) + { + boolean success = false; + final String LD_PATH = System.getProperty("java.library.path"); + for (String lib : libraries) + { + try + { + Log.v(TAG, "Trying to load library " + lib + " from LD_PATH: " + LD_PATH); + System.loadLibrary(lib); + success = true; + } + catch (UnsatisfiedLinkError e) + { + Log.e(TAG, "Failed to load library " + lib + ": " + e); + success = false; + break; + } + } + + return success; + } + + private static boolean tryLoad(String library) + { + return tryLoad(new String[] { library }); + } + + static + { + try + { + System.loadLibrary("freerdp-android"); + + /* Load dependent libraries too to trigger JNI_OnLoad calls */ + String version = freerdp_get_jni_version(); + String[] versions = version.split("[\\.-]"); + if (versions.length > 0) + { + System.loadLibrary("freerdp-client" + versions[0]); + System.loadLibrary("freerdp" + versions[0]); + System.loadLibrary("winpr" + versions[0]); + } + Pattern pattern = Pattern.compile("^(\\d+)\\.(\\d+)\\.(\\d+).*"); + Matcher matcher = pattern.matcher(version); + if (!matcher.matches() || (matcher.groupCount() < 3)) + throw new RuntimeException("APK broken: native library version " + version + + " does not meet requirements!"); + int major = Integer.parseInt(Objects.requireNonNull(matcher.group(1))); + int minor = Integer.parseInt(Objects.requireNonNull(matcher.group(2))); + int patch = Integer.parseInt(Objects.requireNonNull(matcher.group(3))); + + if (major > 2) + mHasH264 = freerdp_has_h264(); + else if (minor > 5) + mHasH264 = freerdp_has_h264(); + else if ((minor == 5) && (patch >= 1)) + mHasH264 = freerdp_has_h264(); + else + throw new RuntimeException("APK broken: native library version " + version + + " does not meet requirements!"); + Log.i(TAG, "Successfully loaded native library. H264 is " + + (mHasH264 ? "supported" : "not available")); + } + catch (UnsatisfiedLinkError e) + { + Log.e(TAG, "Failed to load library: " + e); + throw e; + } + } + + public static boolean hasH264Support() + { + return mHasH264; + } + + private static native boolean freerdp_has_h264(); + + private static native String freerdp_get_jni_version(); + + private static native String freerdp_get_version(); + + private static native String freerdp_get_build_revision(); + + private static native String freerdp_get_build_config(); + + private static native long freerdp_new(Context context); + + private static native void freerdp_free(long inst); + + private static native boolean freerdp_parse_arguments(long inst, String[] args); + + private static native boolean freerdp_connect(long inst); + + private static native boolean freerdp_disconnect(long inst); + + private static native boolean freerdp_update_graphics(long inst, Bitmap bitmap, int x, int y, + int width, int height); + + private static native boolean freerdp_send_cursor_event(long inst, int x, int y, int flags); + + private static native boolean freerdp_send_key_event(long inst, int keycode, boolean down); + + private static native boolean freerdp_send_unicodekey_event(long inst, int keycode, + boolean down); + + private static native boolean freerdp_send_clipboard_data(long inst, String data); + + private static native String freerdp_get_last_error_string(long inst); + + public static void setEventListener(EventListener l) + { + listener = l; + } + + public static long newInstance(Context context) + { + return freerdp_new(context); + } + + public static void freeInstance(long inst) + { + synchronized (mInstanceState) + { + if (mInstanceState.get(inst, false)) + { + freerdp_disconnect(inst); + } + while (mInstanceState.get(inst, false)) + { + try + { + mInstanceState.wait(); + } + catch (InterruptedException e) + { + throw new RuntimeException(); + } + } + } + freerdp_free(inst); + } + + public static boolean connect(long inst) + { + synchronized (mInstanceState) + { + if (mInstanceState.get(inst, false)) + { + throw new RuntimeException("instance already connected"); + } + } + return freerdp_connect(inst); + } + + public static boolean disconnect(long inst) + { + synchronized (mInstanceState) + { + if (mInstanceState.get(inst, false)) + { + return freerdp_disconnect(inst); + } + return true; + } + } + + public static boolean cancelConnection(long inst) + { + synchronized (mInstanceState) + { + if (mInstanceState.get(inst, false)) + { + return freerdp_disconnect(inst); + } + return true; + } + } + + private static String addFlag(String name, boolean enabled) + { + if (enabled) + { + return "+" + name; + } + return "-" + name; + } + + public static boolean setConnectionInfo(Context context, long inst, BookmarkBase bookmark) + { + BookmarkBase.ScreenSettings screenSettings = bookmark.getActiveScreenSettings(); + BookmarkBase.AdvancedSettings advanced = bookmark.getAdvancedSettings(); + BookmarkBase.DebugSettings debug = bookmark.getDebugSettings(); + + String arg; + ArrayList args = new ArrayList<>(); + + args.add(TAG); + args.add("/gdi:sw"); + + final String clientName = ApplicationSettingsActivity.getClientName(context); + if (!clientName.isEmpty()) + { + args.add("/client-hostname:" + clientName); + } + String certName = ""; + if (bookmark.getType() != BookmarkBase.TYPE_MANUAL) + { + return false; + } + + int port = bookmark.get().getPort(); + String hostname = bookmark.get().getHostname(); + + args.add("/v:" + hostname); + args.add("/port:" + port); + + arg = bookmark.getUsername(); + if (!arg.isEmpty()) + { + args.add("/u:" + arg); + } + arg = bookmark.getDomain(); + if (!arg.isEmpty()) + { + args.add("/d:" + arg); + } + arg = bookmark.getPassword(); + if (!arg.isEmpty()) + { + args.add("/p:" + arg); + } + + args.add( + String.format("/size:%dx%d", screenSettings.getWidth(), screenSettings.getHeight())); + args.add("/bpp:" + screenSettings.getColors()); + + if (advanced.getConsoleMode()) + { + args.add("/admin"); + } + + switch (advanced.getSecurity()) + { + case 3: // NLA + args.add("/sec:nla"); + break; + case 2: // TLS + args.add("/sec:tls"); + break; + case 1: // RDP + args.add("/sec:rdp"); + break; + default: + break; + } + + if (!certName.isEmpty()) + { + args.add("/cert-name:" + certName); + } + + BookmarkBase.PerformanceFlags flags = bookmark.getActivePerformanceFlags(); + if (flags.getRemoteFX()) + { + args.add("/rfx"); + args.add("/network:auto"); + } + + if (flags.getGfx()) + { + args.add("/gfx"); + args.add("/network:auto"); + } + + if (flags.getH264() && mHasH264) + { + args.add("/gfx:AVC444"); + args.add("/network:auto"); + } + + args.add(addFlag("wallpaper", flags.getWallpaper())); + args.add(addFlag("window-drag", flags.getFullWindowDrag())); + args.add(addFlag("menu-anims", flags.getMenuAnimations())); + args.add(addFlag("themes", flags.getTheming())); + args.add(addFlag("fonts", flags.getFontSmoothing())); + args.add(addFlag("aero", flags.getDesktopComposition())); + + if (!advanced.getRemoteProgram().isEmpty()) + { + args.add("/shell:" + advanced.getRemoteProgram()); + } + + if (!advanced.getWorkDir().isEmpty()) + { + args.add("/shell-dir:" + advanced.getWorkDir()); + } + + args.add(addFlag("async-channels", debug.getAsyncChannel())); + args.add(addFlag("async-update", debug.getAsyncUpdate())); + + if (advanced.getRedirectSDCard()) + { + String path = android.os.Environment.getExternalStorageDirectory().getPath(); + args.add("/drive:sdcard," + path); + } + + args.add("/clipboard"); + + // Gateway enabled? + if (bookmark.getType() == BookmarkBase.TYPE_MANUAL && + bookmark.get().getEnableGatewaySettings()) + { + ManualBookmark.GatewaySettings gateway = + bookmark.get().getGatewaySettings(); + + StringBuilder carg = new StringBuilder(); + carg.append( + String.format("/gateway:g:%s:%d", gateway.getHostname(), gateway.getPort())); + + arg = gateway.getUsername(); + if (!arg.isEmpty()) + { + carg.append(",u:" + arg); + } + arg = gateway.getDomain(); + if (!arg.isEmpty()) + { + carg.append(",d:" + arg); + } + arg = gateway.getPassword(); + if (!arg.isEmpty()) + { + carg.append(",p:" + arg); + } + args.add(carg.toString()); + } + + /* 0 ... local + 1 ... remote + 2 ... disable */ + args.add("/audio-mode:" + advanced.getRedirectSound()); + if (advanced.getRedirectSound() == 0) + { + args.add("/sound"); + } + + if (advanced.getRedirectMicrophone()) + { + args.add("/microphone"); + } + + args.add("/kbd:unicode:on"); + args.add("/cert:ignore"); + args.add("/log-level:" + debug.getDebugLevel()); + String[] arrayArgs = args.toArray(new String[0]); + return freerdp_parse_arguments(inst, arrayArgs); + } + + public static boolean setConnectionInfo(Context context, long inst, Uri openUri) + { + ArrayList args = new ArrayList<>(); + + // Parse URI from query string. Same key overwrite previous one + // freerdp://user@ip:port/connect?sound=&rfx=&p=password&clipboard=%2b&themes=- + + // Now we only support Software GDI + args.add(TAG); + args.add("/gdi:sw"); + + final String clientName = ApplicationSettingsActivity.getClientName(context); + if (!clientName.isEmpty()) + { + args.add("/client-hostname:" + clientName); + } + + // Parse hostname and port. Set to 'v' argument + String hostname = openUri.getHost(); + int port = openUri.getPort(); + if (hostname != null) + { + hostname = hostname + ((port == -1) ? "" : (":" + port)); + args.add("/v:" + hostname); + } + + String user = openUri.getUserInfo(); + if (user != null) + { + args.add("/u:" + user); + } + + for (String key : openUri.getQueryParameterNames()) + { + String value = openUri.getQueryParameter(key); + + if (value.isEmpty()) + { + // Query: key= + // To freerdp argument: /key + args.add("/" + key); + } + else if (value.equals("-") || value.equals("+")) + { + // Query: key=- or key=+ + // To freerdp argument: -key or +key + args.add(value + key); + } + else + { + // Query: key=value + // To freerdp argument: /key:value + if (key.equals("drive") && value.equals("sdcard")) + { + // Special for sdcard redirect + String path = android.os.Environment.getExternalStorageDirectory().getPath(); + value = "sdcard," + path; + } + + args.add("/" + key + ":" + value); + } + } + + String[] arrayArgs = args.toArray(new String[0]); + return freerdp_parse_arguments(inst, arrayArgs); + } + + public static boolean updateGraphics(long inst, Bitmap bitmap, int x, int y, int width, + int height) + { + return freerdp_update_graphics(inst, bitmap, x, y, width, height); + } + + public static boolean sendCursorEvent(long inst, int x, int y, int flags) + { + return freerdp_send_cursor_event(inst, x, y, flags); + } + + public static boolean sendKeyEvent(long inst, int keycode, boolean down) + { + return freerdp_send_key_event(inst, keycode, down); + } + + public static boolean sendUnicodeKeyEvent(long inst, int keycode, boolean down) + { + return freerdp_send_unicodekey_event(inst, keycode, down); + } + + public static boolean sendClipboardData(long inst, String data) + { + return freerdp_send_clipboard_data(inst, data); + } + + private static void OnConnectionSuccess(long inst) + { + if (listener != null) + listener.OnConnectionSuccess(inst); + synchronized (mInstanceState) + { + mInstanceState.append(inst, true); + mInstanceState.notifyAll(); + } + } + + private static void OnConnectionFailure(long inst) + { + if (listener != null) + listener.OnConnectionFailure(inst); + synchronized (mInstanceState) + { + mInstanceState.remove(inst); + mInstanceState.notifyAll(); + } + } + + private static void OnPreConnect(long inst) + { + if (listener != null) + listener.OnPreConnect(inst); + } + + private static void OnDisconnecting(long inst) + { + if (listener != null) + listener.OnDisconnecting(inst); + } + + private static void OnDisconnected(long inst) + { + if (listener != null) + listener.OnDisconnected(inst); + synchronized (mInstanceState) + { + mInstanceState.remove(inst); + mInstanceState.notifyAll(); + } + } + + private static void OnSettingsChanged(long inst, int width, int height, int bpp) + { + SessionState s = GlobalApp.getSession(inst); + if (s == null) + return; + UIEventListener uiEventListener = s.getUIEventListener(); + if (uiEventListener != null) + uiEventListener.OnSettingsChanged(width, height, bpp); + } + + private static boolean OnAuthenticate(long inst, StringBuilder username, StringBuilder domain, + StringBuilder password) + { + SessionState s = GlobalApp.getSession(inst); + if (s == null) + return false; + UIEventListener uiEventListener = s.getUIEventListener(); + if (uiEventListener != null) + return uiEventListener.OnAuthenticate(username, domain, password); + return false; + } + + private static boolean OnGatewayAuthenticate(long inst, StringBuilder username, + StringBuilder domain, StringBuilder password) + { + SessionState s = GlobalApp.getSession(inst); + if (s == null) + return false; + UIEventListener uiEventListener = s.getUIEventListener(); + if (uiEventListener != null) + return uiEventListener.OnGatewayAuthenticate(username, domain, password); + return false; + } + + private static int OnVerifyCertificateEx(long inst, String host, long port, String commonName, + String subject, String issuer, String fingerprint, + long flags) + { + SessionState s = GlobalApp.getSession(inst); + if (s == null) + return 0; + UIEventListener uiEventListener = s.getUIEventListener(); + if (uiEventListener != null) + return uiEventListener.OnVerifiyCertificateEx(host, port, commonName, subject, issuer, + fingerprint, flags); + return 0; + } + + private static int OnVerifyChangedCertificateEx(long inst, String host, long port, + String commonName, String subject, + String issuer, String fingerprint, + String oldSubject, String oldIssuer, + String oldFingerprint, long flags) + { + SessionState s = GlobalApp.getSession(inst); + if (s == null) + return 0; + UIEventListener uiEventListener = s.getUIEventListener(); + if (uiEventListener != null) + return uiEventListener.OnVerifyChangedCertificateEx(host, port, commonName, subject, + issuer, fingerprint, oldSubject, + oldIssuer, oldFingerprint, flags); + return 0; + } + + private static void OnGraphicsUpdate(long inst, int x, int y, int width, int height) + { + SessionState s = GlobalApp.getSession(inst); + if (s == null) + return; + UIEventListener uiEventListener = s.getUIEventListener(); + if (uiEventListener != null) + uiEventListener.OnGraphicsUpdate(x, y, width, height); + } + + private static void OnGraphicsResize(long inst, int width, int height, int bpp) + { + SessionState s = GlobalApp.getSession(inst); + if (s == null) + return; + UIEventListener uiEventListener = s.getUIEventListener(); + if (uiEventListener != null) + uiEventListener.OnGraphicsResize(width, height, bpp); + } + + private static void OnRemoteClipboardChanged(long inst, String data) + { + SessionState s = GlobalApp.getSession(inst); + if (s == null) + return; + UIEventListener uiEventListener = s.getUIEventListener(); + if (uiEventListener != null) + uiEventListener.OnRemoteClipboardChanged(data); + } + + public static String getVersion() + { + return freerdp_get_version(); + } + + public interface EventListener + { + void OnPreConnect(long instance); + + void OnConnectionSuccess(long instance); + + void OnConnectionFailure(long instance); + + void OnDisconnecting(long instance); + + void OnDisconnected(long instance); + } + + public interface UIEventListener + { + void OnSettingsChanged(int width, int height, int bpp); + + boolean OnAuthenticate(StringBuilder username, StringBuilder domain, + StringBuilder password); + + boolean OnGatewayAuthenticate(StringBuilder username, StringBuilder domain, + StringBuilder password); + + int OnVerifiyCertificateEx(String host, long port, String commonName, String subject, String issuer, + String fingerprint, long flags); + + int OnVerifyChangedCertificateEx(String host, long port, String commonName, String subject, String issuer, + String fingerprint, String oldSubject, String oldIssuer, + String oldFingerprint, long flags); + + void OnGraphicsUpdate(int x, int y, int width, int height); + + void OnGraphicsResize(int width, int height, int bpp); + + void OnRemoteClipboardChanged(String data); + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/ManualBookmarkGateway.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/ManualBookmarkGateway.java new file mode 100644 index 0000000..b472b94 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/ManualBookmarkGateway.java @@ -0,0 +1,131 @@ +/* + Manual bookmarks database gateway + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.services; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteOpenHelper; + +import com.freerdp.freerdpcore.domain.BookmarkBase; +import com.freerdp.freerdpcore.domain.ManualBookmark; + +import java.util.ArrayList; + +public class ManualBookmarkGateway extends BookmarkBaseGateway +{ + + public ManualBookmarkGateway(SQLiteOpenHelper bookmarkDB) + { + super(bookmarkDB); + } + + @Override protected BookmarkBase createBookmark() + { + return new ManualBookmark(); + } + + @Override protected String getBookmarkTableName() + { + return BookmarkDB.DB_TABLE_BOOKMARK; + } + + @Override + protected void addBookmarkSpecificColumns(BookmarkBase bookmark, ContentValues columns) + { + ManualBookmark bm = (ManualBookmark)bookmark; + columns.put(BookmarkDB.DB_KEY_BOOKMARK_HOSTNAME, bm.getHostname()); + columns.put(BookmarkDB.DB_KEY_BOOKMARK_PORT, bm.getPort()); + + // gateway settings + columns.put(BookmarkDB.DB_KEY_BOOKMARK_GW_ENABLE, bm.getEnableGatewaySettings()); + columns.put(BookmarkDB.DB_KEY_BOOKMARK_GW_HOSTNAME, bm.getGatewaySettings().getHostname()); + columns.put(BookmarkDB.DB_KEY_BOOKMARK_GW_PORT, bm.getGatewaySettings().getPort()); + columns.put(BookmarkDB.DB_KEY_BOOKMARK_GW_USERNAME, bm.getGatewaySettings().getUsername()); + columns.put(BookmarkDB.DB_KEY_BOOKMARK_GW_PASSWORD, bm.getGatewaySettings().getPassword()); + columns.put(BookmarkDB.DB_KEY_BOOKMARK_GW_DOMAIN, bm.getGatewaySettings().getDomain()); + } + + @Override protected void addBookmarkSpecificColumns(ArrayList columns) + { + columns.add(BookmarkDB.DB_KEY_BOOKMARK_HOSTNAME); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_PORT); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_GW_ENABLE); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_GW_HOSTNAME); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_GW_PORT); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_GW_USERNAME); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_GW_PASSWORD); + columns.add(BookmarkDB.DB_KEY_BOOKMARK_GW_DOMAIN); + } + + @Override protected void readBookmarkSpecificColumns(BookmarkBase bookmark, Cursor cursor) + { + ManualBookmark bm = (ManualBookmark)bookmark; + bm.setHostname( + cursor.getString(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_HOSTNAME))); + bm.setPort(cursor.getInt(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_PORT))); + + bm.setEnableGatewaySettings( + cursor.getInt(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_GW_ENABLE)) != 0); + readGatewaySettings(bm, cursor); + } + + public BookmarkBase findByLabelOrHostname(String pattern) + { + if (pattern.length() == 0) + return null; + + Cursor cursor = + queryBookmarks(BookmarkDB.DB_KEY_BOOKMARK_LABEL + " = '" + pattern + "' OR " + + BookmarkDB.DB_KEY_BOOKMARK_HOSTNAME + " = '" + pattern + "'", + BookmarkDB.DB_KEY_BOOKMARK_LABEL); + BookmarkBase bookmark = null; + if (cursor.moveToFirst() && (cursor.getCount() > 0)) + bookmark = getBookmarkFromCursor(cursor); + + cursor.close(); + return bookmark; + } + + public ArrayList findByLabelOrHostnameLike(String pattern) + { + Cursor cursor = + queryBookmarks(BookmarkDB.DB_KEY_BOOKMARK_LABEL + " LIKE '%" + pattern + "%' OR " + + BookmarkDB.DB_KEY_BOOKMARK_HOSTNAME + " LIKE '%" + pattern + "%'", + BookmarkDB.DB_KEY_BOOKMARK_LABEL); + ArrayList bookmarks = new ArrayList<>(cursor.getCount()); + + if (cursor.moveToFirst() && (cursor.getCount() > 0)) + { + do + { + bookmarks.add(getBookmarkFromCursor(cursor)); + } while (cursor.moveToNext()); + } + + cursor.close(); + return bookmarks; + } + + private void readGatewaySettings(ManualBookmark bookmark, Cursor cursor) + { + ManualBookmark.GatewaySettings gatewaySettings = bookmark.getGatewaySettings(); + gatewaySettings.setHostname( + cursor.getString(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_GW_HOSTNAME))); + gatewaySettings.setPort( + cursor.getInt(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_GW_PORT))); + gatewaySettings.setUsername( + cursor.getString(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_GW_USERNAME))); + gatewaySettings.setPassword( + cursor.getString(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_GW_PASSWORD))); + gatewaySettings.setDomain( + cursor.getString(cursor.getColumnIndex(BookmarkDB.DB_KEY_BOOKMARK_GW_DOMAIN))); + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/QuickConnectHistoryGateway.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/QuickConnectHistoryGateway.java new file mode 100644 index 0000000..a4f2321 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/QuickConnectHistoryGateway.java @@ -0,0 +1,121 @@ +/* + Quick connect history gateway + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.services; + +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +import com.freerdp.freerdpcore.domain.BookmarkBase; +import com.freerdp.freerdpcore.domain.QuickConnectBookmark; + +import java.util.ArrayList; + +public class QuickConnectHistoryGateway +{ + private final static String TAG = "QuickConnectHistoryGateway"; + private final SQLiteOpenHelper historyDB; + + public QuickConnectHistoryGateway(SQLiteOpenHelper historyDB) + { + this.historyDB = historyDB; + } + + public ArrayList findHistory(String filter) + { + String[] column = { HistoryDB.QUICK_CONNECT_TABLE_COL_ITEM }; + + SQLiteDatabase db = getReadableDatabase(); + String selection = + (filter.length() > 0) + ? (HistoryDB.QUICK_CONNECT_TABLE_COL_ITEM + " LIKE '%" + filter + "%'") + : null; + Cursor cursor = db.query(HistoryDB.QUICK_CONNECT_TABLE_NAME, column, selection, null, null, + null, HistoryDB.QUICK_CONNECT_TABLE_COL_TIMESTAMP); + + ArrayList result = new ArrayList<>(cursor.getCount()); + if (cursor.moveToFirst()) + { + do + { + String hostname = + cursor.getString(cursor.getColumnIndex(HistoryDB.QUICK_CONNECT_TABLE_COL_ITEM)); + QuickConnectBookmark bookmark = new QuickConnectBookmark(); + bookmark.setLabel(hostname); + bookmark.setHostname(hostname); + result.add(bookmark); + } while (cursor.moveToNext()); + } + cursor.close(); + return result; + } + + public void addHistoryItem(String item) + { + String insertHistoryItem = "INSERT OR REPLACE INTO " + HistoryDB.QUICK_CONNECT_TABLE_NAME + + " (" + HistoryDB.QUICK_CONNECT_TABLE_COL_ITEM + ", " + + HistoryDB.QUICK_CONNECT_TABLE_COL_TIMESTAMP + ") VALUES('" + + item + "', datetime('now'))"; + SQLiteDatabase db = getWritableDatabase(); + try + { + db.execSQL(insertHistoryItem); + } + catch (SQLException e) + { + Log.v(TAG, e.toString()); + } + } + + public boolean historyItemExists(String item) + { + String[] column = { HistoryDB.QUICK_CONNECT_TABLE_COL_ITEM }; + SQLiteDatabase db = getReadableDatabase(); + Cursor cursor = db.query(HistoryDB.QUICK_CONNECT_TABLE_NAME, column, + HistoryDB.QUICK_CONNECT_TABLE_COL_ITEM + " = '" + item + "'", null, + null, null, null); + boolean exists = (cursor.getCount() == 1); + cursor.close(); + return exists; + } + + public void removeHistoryItem(String hostname) + { + SQLiteDatabase db = getWritableDatabase(); + db.delete(HistoryDB.QUICK_CONNECT_TABLE_NAME, + HistoryDB.QUICK_CONNECT_TABLE_COL_ITEM + " = '" + hostname + "'", null); + } + + // safety wrappers + // in case of getReadableDatabase it could happen that upgradeDB gets called which is + // a problem if the DB is only readable + private SQLiteDatabase getWritableDatabase() + { + return historyDB.getWritableDatabase(); + } + + private SQLiteDatabase getReadableDatabase() + { + SQLiteDatabase db; + try + { + db = historyDB.getReadableDatabase(); + } + catch (SQLiteException e) + { + db = historyDB.getWritableDatabase(); + } + return db; + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/SessionRequestHandlerActivity.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/SessionRequestHandlerActivity.java new file mode 100644 index 0000000..9436210 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/SessionRequestHandlerActivity.java @@ -0,0 +1,77 @@ +/* + Activity for handling connection requests + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.services; + +import android.app.Activity; +import android.app.SearchManager; +import android.content.Intent; +import android.os.Bundle; +import androidx.appcompat.app.AppCompatActivity; + +import com.freerdp.freerdpcore.domain.ConnectionReference; +import com.freerdp.freerdpcore.presentation.BookmarkActivity; +import com.freerdp.freerdpcore.presentation.SessionActivity; + +public class SessionRequestHandlerActivity extends AppCompatActivity +{ + + @Override public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + handleIntent(getIntent()); + } + + @Override protected void onNewIntent(Intent intent) + { + setIntent(intent); + handleIntent(intent); + } + + private void startSessionWithConnectionReference(String refStr) + { + + Bundle bundle = new Bundle(); + bundle.putString(SessionActivity.PARAM_CONNECTION_REFERENCE, refStr); + Intent sessionIntent = new Intent(this, SessionActivity.class); + sessionIntent.putExtras(bundle); + + startActivityForResult(sessionIntent, 0); + } + + private void editBookmarkWithConnectionReference(String refStr) + { + Bundle bundle = new Bundle(); + bundle.putString(BookmarkActivity.PARAM_CONNECTION_REFERENCE, refStr); + Intent bookmarkIntent = new Intent(this.getApplicationContext(), BookmarkActivity.class); + bookmarkIntent.putExtras(bundle); + startActivityForResult(bookmarkIntent, 0); + } + + private void handleIntent(Intent intent) + { + + String action = intent.getAction(); + if (Intent.ACTION_SEARCH.equals(action)) + startSessionWithConnectionReference(ConnectionReference.getHostnameReference( + intent.getStringExtra(SearchManager.QUERY))); + else if (Intent.ACTION_VIEW.equals(action)) + startSessionWithConnectionReference(intent.getDataString()); + else if (Intent.ACTION_EDIT.equals(action)) + editBookmarkWithConnectionReference(intent.getDataString()); + } + + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + super.onActivityResult(requestCode, resultCode, data); + this.setResult(resultCode); + this.finish(); + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/AppCompatPreferenceActivity.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/AppCompatPreferenceActivity.java new file mode 100644 index 0000000..d321d7b --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/AppCompatPreferenceActivity.java @@ -0,0 +1,112 @@ +package com.freerdp.freerdpcore.utils; + +import android.content.res.Configuration; +import android.os.Bundle; +import android.preference.PreferenceActivity; +import androidx.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.LayoutRes; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatDelegate; +import androidx.appcompat.widget.Toolbar; +import android.view.MenuInflater; +import android.view.View; +import android.view.ViewGroup; + +public abstract class AppCompatPreferenceActivity extends PreferenceActivity +{ + + private AppCompatDelegate mDelegate; + + @Override protected void onCreate(Bundle savedInstanceState) + { + getDelegate().installViewFactory(); + getDelegate().onCreate(savedInstanceState); + super.onCreate(savedInstanceState); + } + + @Override protected void onPostCreate(Bundle savedInstanceState) + { + super.onPostCreate(savedInstanceState); + getDelegate().onPostCreate(savedInstanceState); + } + + public ActionBar getSupportActionBar() + { + return getDelegate().getSupportActionBar(); + } + + public void setSupportActionBar(@Nullable Toolbar toolbar) + { + getDelegate().setSupportActionBar(toolbar); + } + + @Override @NonNull public MenuInflater getMenuInflater() + { + return getDelegate().getMenuInflater(); + } + + @Override public void setContentView(@LayoutRes int layoutResID) + { + getDelegate().setContentView(layoutResID); + } + + @Override public void setContentView(View view) + { + getDelegate().setContentView(view); + } + + @Override public void setContentView(View view, ViewGroup.LayoutParams params) + { + getDelegate().setContentView(view, params); + } + + @Override public void addContentView(View view, ViewGroup.LayoutParams params) + { + getDelegate().addContentView(view, params); + } + + @Override protected void onPostResume() + { + super.onPostResume(); + getDelegate().onPostResume(); + } + + @Override protected void onTitleChanged(CharSequence title, int color) + { + super.onTitleChanged(title, color); + getDelegate().setTitle(title); + } + + @Override public void onConfigurationChanged(Configuration newConfig) + { + super.onConfigurationChanged(newConfig); + getDelegate().onConfigurationChanged(newConfig); + } + + @Override protected void onStop() + { + super.onStop(); + getDelegate().onStop(); + } + + @Override protected void onDestroy() + { + super.onDestroy(); + getDelegate().onDestroy(); + } + + public void invalidateOptionsMenu() + { + getDelegate().invalidateOptionsMenu(); + } + + private AppCompatDelegate getDelegate() + { + if (mDelegate == null) + { + mDelegate = AppCompatDelegate.create(this, null); + } + return mDelegate; + } +} \ No newline at end of file diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/BookmarkArrayAdapter.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/BookmarkArrayAdapter.java new file mode 100644 index 0000000..20f2ecf --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/BookmarkArrayAdapter.java @@ -0,0 +1,135 @@ +/* + ArrayAdapter for bookmark lists + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.utils; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import com.freerdp.freerdpcore.R; +import com.freerdp.freerdpcore.domain.BookmarkBase; +import com.freerdp.freerdpcore.domain.ConnectionReference; +import com.freerdp.freerdpcore.domain.ManualBookmark; +import com.freerdp.freerdpcore.domain.PlaceholderBookmark; +import com.freerdp.freerdpcore.presentation.BookmarkActivity; + +import java.util.List; + +public class BookmarkArrayAdapter extends ArrayAdapter +{ + + public BookmarkArrayAdapter(Context context, int textViewResourceId, List objects) + { + super(context, textViewResourceId, objects); + } + + @Override public View getView(int position, View convertView, ViewGroup parent) + { + View curView = convertView; + if (curView == null) + { + LayoutInflater vi = + (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + curView = vi.inflate(R.layout.bookmark_list_item, null); + } + + BookmarkBase bookmark = getItem(position); + TextView label = curView.findViewById(R.id.bookmark_text1); + TextView hostname = curView.findViewById(R.id.bookmark_text2); + ImageView star_icon = curView.findViewById(R.id.bookmark_icon2); + assert label != null; + assert hostname != null; + + label.setText(bookmark.getLabel()); + star_icon.setVisibility(View.VISIBLE); + + String refStr; + if (bookmark.getType() == BookmarkBase.TYPE_MANUAL) + { + hostname.setText(bookmark.get().getHostname()); + refStr = ConnectionReference.getManualBookmarkReference(bookmark.getId()); + star_icon.setImageResource(R.drawable.icon_star_on); + } + else if (bookmark.getType() == BookmarkBase.TYPE_QUICKCONNECT) + { + // just set an empty hostname (with a blank) - the hostname is already displayed in the + // label and in case we just set it to "" the textview will shrunk + hostname.setText(" "); + refStr = ConnectionReference.getHostnameReference(bookmark.getLabel()); + star_icon.setImageResource(R.drawable.icon_star_off); + } + else if (bookmark.getType() == BookmarkBase.TYPE_PLACEHOLDER) + { + hostname.setText(" "); + refStr = ConnectionReference.getPlaceholderReference( + bookmark.get().getName()); + star_icon.setVisibility(View.GONE); + } + else + { + // unknown bookmark type... + refStr = ""; + assert false; + } + + star_icon.setOnClickListener(new OnClickListener() { + @Override public void onClick(View v) + { + // start bookmark editor + Bundle bundle = new Bundle(); + String refStr = v.getTag().toString(); + bundle.putString(BookmarkActivity.PARAM_CONNECTION_REFERENCE, refStr); + + Intent bookmarkIntent = new Intent(getContext(), BookmarkActivity.class); + bookmarkIntent.putExtras(bundle); + getContext().startActivity(bookmarkIntent); + } + }); + + curView.setTag(refStr); + star_icon.setTag(refStr); + + return curView; + } + + public void addItems(List newItems) + { + for (BookmarkBase item : newItems) + add(item); + } + + public void replaceItems(List newItems) + { + clear(); + for (BookmarkBase item : newItems) + add(item); + } + + public void remove(long bookmarkId) + { + for (int i = 0; i < getCount(); i++) + { + BookmarkBase bm = getItem(i); + if (bm.getId() == bookmarkId) + { + remove(bm); + return; + } + } + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/ButtonPreference.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/ButtonPreference.java new file mode 100644 index 0000000..18adaad --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/ButtonPreference.java @@ -0,0 +1,96 @@ +/* + Custom preference item showing a button on the right side + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.utils; + +import android.content.Context; +import android.preference.Preference; +import android.util.AttributeSet; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.LinearLayout; + +import com.freerdp.freerdpcore.R; + +public class ButtonPreference extends Preference +{ + + private OnClickListener buttonOnClickListener; + private String buttonText; + private Button button; + + public ButtonPreference(Context context) + { + super(context); + init(); + } + + public ButtonPreference(Context context, AttributeSet attrs) + { + super(context, attrs); + init(); + } + + public ButtonPreference(Context context, AttributeSet attrs, int defStyle) + { + super(context, attrs, defStyle); + init(); + } + + private void init() + { + setLayoutResource(R.layout.button_preference); + button = null; + buttonText = null; + buttonOnClickListener = null; + } + + @Override public View getView(View convertView, ViewGroup parent) + { + View v = super.getView(convertView, parent); + button = v.findViewById(R.id.preference_button); + if (buttonText != null) + button.setText(buttonText); + if (buttonOnClickListener != null) + button.setOnClickListener(buttonOnClickListener); + + // additional init for ICS - make widget frame visible + // refer to + // http://stackoverflow.com/questions/8762984/custom-preference-broken-in-honeycomb-ics + LinearLayout widgetFrameView = v.findViewById(android.R.id.widget_frame); + widgetFrameView.setVisibility(View.VISIBLE); + + return v; + } + + public void setButtonText(int resId) + { + buttonText = getContext().getResources().getString(resId); + if (button != null) + button.setText(buttonText); + } + + public void setButtonText(String text) + { + buttonText = text; + if (button != null) + button.setText(text); + } + + public void setButtonOnClickListener(OnClickListener listener) + { + if (button != null) + button.setOnClickListener(listener); + else + buttonOnClickListener = listener; + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/ClipboardManagerProxy.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/ClipboardManagerProxy.java new file mode 100644 index 0000000..c5cbead --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/ClipboardManagerProxy.java @@ -0,0 +1,112 @@ +package com.freerdp.freerdpcore.utils; + +import android.annotation.TargetApi; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; + +public abstract class ClipboardManagerProxy +{ + + public static ClipboardManagerProxy getClipboardManager(Context ctx) + { + if (VERSION.SDK_INT < VERSION_CODES.HONEYCOMB) + return new PreHCClipboardManager(ctx); + else + return new HCClipboardManager(ctx); + } + + public abstract void setClipboardData(String data); + + public abstract void addClipboardChangedListener(OnClipboardChangedListener listener); + + public abstract void removeClipboardboardChangedListener(OnClipboardChangedListener listener); + + public abstract void getPrimaryClipManually(); + + public interface OnClipboardChangedListener + { + void onClipboardChanged(String data); + } + + private static class PreHCClipboardManager extends ClipboardManagerProxy + { + + public PreHCClipboardManager(Context ctx) + { + } + + @Override public void setClipboardData(String data) + { + } + + @Override public void addClipboardChangedListener(OnClipboardChangedListener listener) + { + } + + @Override + public void removeClipboardboardChangedListener(OnClipboardChangedListener listener) + { + } + + @Override public void getPrimaryClipManually() + { + } + } + + @TargetApi(11) + private static class HCClipboardManager + extends ClipboardManagerProxy implements ClipboardManager.OnPrimaryClipChangedListener + { + private final ClipboardManager mClipboardManager; + private OnClipboardChangedListener mListener; + + public HCClipboardManager(Context ctx) + { + mClipboardManager = (ClipboardManager)ctx.getSystemService(Context.CLIPBOARD_SERVICE); + } + + @Override public void setClipboardData(String data) + { + mClipboardManager.setPrimaryClip( + ClipData.newPlainText("rdp-clipboard", data == null ? "" : data)); + } + + @Override public void onPrimaryClipChanged() + { + ClipData clip = mClipboardManager.getPrimaryClip(); + String data = null; + + if (clip != null && clip.getItemCount() > 0) + { + CharSequence cs = clip.getItemAt(0).getText(); + if (cs != null) + data = cs.toString(); + } + if (mListener != null) + { + mListener.onClipboardChanged(data); + } + } + + @Override public void addClipboardChangedListener(OnClipboardChangedListener listener) + { + mListener = listener; + mClipboardManager.addPrimaryClipChangedListener(this); + } + + @Override + public void removeClipboardboardChangedListener(OnClipboardChangedListener listener) + { + mListener = null; + mClipboardManager.removePrimaryClipChangedListener(this); + } + + @Override public void getPrimaryClipManually() + { + onPrimaryClipChanged(); + } + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/DoubleGestureDetector.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/DoubleGestureDetector.java new file mode 100644 index 0000000..df3d6e2 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/DoubleGestureDetector.java @@ -0,0 +1,350 @@ +/* + 2 finger gesture detector + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.utils; + +import android.content.Context; +import android.os.Handler; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; + +import com.freerdp.freerdpcore.utils.GestureDetector.OnGestureListener; + +public class DoubleGestureDetector +{ + // timeout during that the second finger has to touch the screen before the double finger + // detection is cancelled + private static final long DOUBLE_TOUCH_TIMEOUT = 100; + // timeout during that an UP event will trigger a single double touch event + private static final long SINGLE_DOUBLE_TOUCH_TIMEOUT = 1000; + // constants for Message.what used by GestureHandler below + private static final int TAP = 1; + // different detection modes + private static final int MODE_UNKNOWN = 0; + private static final int MODE_PINCH_ZOOM = 1; + private static final int MODE_SCROLL = 2; + private static final int SCROLL_SCORE_TO_REACH = 20; + private final OnDoubleGestureListener mListener; + private int mPointerDistanceSquare; + private int mCurrentMode; + private int mScrollDetectionScore; + private ScaleGestureDetector scaleGestureDetector; + private boolean mCancelDetection; + private boolean mDoubleInProgress; + private GestureHandler mHandler; + private MotionEvent mCurrentDownEvent; + private MotionEvent mCurrentDoubleDownEvent; + private MotionEvent mPreviousUpEvent; + private MotionEvent mPreviousPointerUpEvent; + /** + * Creates a GestureDetector with the supplied listener. + * You may only use this constructor from a UI thread (this is the usual situation). + * + * @param context the application's context + * @param listener the listener invoked for all the callbacks, this must + * not be null. + * @throws NullPointerException if {@code listener} is null. + * @see android.os.Handler#Handler() + */ + public DoubleGestureDetector(Context context, Handler handler, OnDoubleGestureListener listener) + { + mListener = listener; + init(context, handler); + } + + private void init(Context context, Handler handler) + { + if (mListener == null) + { + throw new NullPointerException("OnGestureListener must not be null"); + } + + if (handler != null) + mHandler = new GestureHandler(handler); + else + mHandler = new GestureHandler(); + + // we use 1cm distance to decide between scroll and pinch zoom + // - first convert cm to inches + // - then multiply inches by dots per inch + float distInches = 0.5f / 2.54f; + float distPixelsX = distInches * context.getResources().getDisplayMetrics().xdpi; + float distPixelsY = distInches * context.getResources().getDisplayMetrics().ydpi; + + mPointerDistanceSquare = (int)(distPixelsX * distPixelsX + distPixelsY * distPixelsY); + } + + /** + * Set scale gesture detector + * + * @param scaleGestureDetector + */ + public void setScaleGestureDetector(ScaleGestureDetector scaleGestureDetector) + { + this.scaleGestureDetector = scaleGestureDetector; + } + + /** + * Analyzes the given motion event and if applicable triggers the + * appropriate callbacks on the {@link OnGestureListener} supplied. + * + * @param ev The current motion event. + * @return true if the {@link OnGestureListener} consumed the event, + * else false. + */ + public boolean onTouchEvent(MotionEvent ev) + { + boolean handled = false; + final int action = ev.getAction(); + // dumpEvent(ev); + + switch (action & MotionEvent.ACTION_MASK) + { + case MotionEvent.ACTION_DOWN: + if (mCurrentDownEvent != null) + mCurrentDownEvent.recycle(); + + mCurrentMode = MODE_UNKNOWN; + mCurrentDownEvent = MotionEvent.obtain(ev); + mCancelDetection = false; + mDoubleInProgress = false; + mScrollDetectionScore = 0; + handled = true; + break; + + case MotionEvent.ACTION_POINTER_UP: + if (mPreviousPointerUpEvent != null) + mPreviousPointerUpEvent.recycle(); + mPreviousPointerUpEvent = MotionEvent.obtain(ev); + break; + + case MotionEvent.ACTION_POINTER_DOWN: + // more than 2 fingers down? cancel + // 2nd finger touched too late? cancel + if (ev.getPointerCount() > 2 || + (ev.getEventTime() - mCurrentDownEvent.getEventTime()) > DOUBLE_TOUCH_TIMEOUT) + { + cancel(); + break; + } + + // detection cancelled? + if (mCancelDetection) + break; + + // double touch gesture in progress + mDoubleInProgress = true; + if (mCurrentDoubleDownEvent != null) + mCurrentDoubleDownEvent.recycle(); + mCurrentDoubleDownEvent = MotionEvent.obtain(ev); + + // set detection mode to unknown and send a TOUCH timeout event to detect single + // taps + mCurrentMode = MODE_UNKNOWN; + mHandler.sendEmptyMessageDelayed(TAP, SINGLE_DOUBLE_TOUCH_TIMEOUT); + + handled |= mListener.onDoubleTouchDown(ev); + break; + + case MotionEvent.ACTION_MOVE: + + // detection cancelled or not active? + if (mCancelDetection || !mDoubleInProgress || ev.getPointerCount() != 2) + break; + + // determine mode + if (mCurrentMode == MODE_UNKNOWN) + { + // did the pointer distance change? + if (pointerDistanceChanged(mCurrentDoubleDownEvent, ev)) + { + handled |= scaleGestureDetector.onTouchEvent(mCurrentDownEvent); + MotionEvent e = MotionEvent.obtain(ev); + e.setAction(mCurrentDoubleDownEvent.getAction()); + handled |= scaleGestureDetector.onTouchEvent(e); + mCurrentMode = MODE_PINCH_ZOOM; + break; + } + else + { + mScrollDetectionScore++; + if (mScrollDetectionScore >= SCROLL_SCORE_TO_REACH) + mCurrentMode = MODE_SCROLL; + } + } + + switch (mCurrentMode) + { + case MODE_PINCH_ZOOM: + if (scaleGestureDetector != null) + handled |= scaleGestureDetector.onTouchEvent(ev); + break; + + case MODE_SCROLL: + handled = mListener.onDoubleTouchScroll(mCurrentDownEvent, ev); + break; + + default: + handled = true; + break; + } + + break; + + case MotionEvent.ACTION_UP: + // fingers were not removed equally? cancel + if (mPreviousPointerUpEvent != null && + (ev.getEventTime() - mPreviousPointerUpEvent.getEventTime()) > + DOUBLE_TOUCH_TIMEOUT) + { + mPreviousPointerUpEvent.recycle(); + mPreviousPointerUpEvent = null; + cancel(); + break; + } + + // detection cancelled or not active? + if (mCancelDetection || !mDoubleInProgress) + break; + + boolean hasTapEvent = mHandler.hasMessages(TAP); + MotionEvent currentUpEvent = MotionEvent.obtain(ev); + if (mCurrentMode == MODE_UNKNOWN && hasTapEvent) + handled = mListener.onDoubleTouchSingleTap(mCurrentDoubleDownEvent); + else if (mCurrentMode == MODE_PINCH_ZOOM) + handled = scaleGestureDetector.onTouchEvent(ev); + + if (mPreviousUpEvent != null) + mPreviousUpEvent.recycle(); + + // Hold the event we obtained above - listeners may have changed the original. + mPreviousUpEvent = currentUpEvent; + handled |= mListener.onDoubleTouchUp(ev); + break; + + case MotionEvent.ACTION_CANCEL: + cancel(); + break; + } + + if ((action == MotionEvent.ACTION_MOVE) && !handled) + handled = true; + + return handled; + } + + private void cancel() + { + mHandler.removeMessages(TAP); + mCurrentMode = MODE_UNKNOWN; + mCancelDetection = true; + mDoubleInProgress = false; + } + + // returns true of the distance between the two pointers changed + private boolean pointerDistanceChanged(MotionEvent oldEvent, MotionEvent newEvent) + { + int deltaX1 = Math.abs((int)oldEvent.getX(0) - (int)oldEvent.getX(1)); + int deltaX2 = Math.abs((int)newEvent.getX(0) - (int)newEvent.getX(1)); + int distXSquare = (deltaX2 - deltaX1) * (deltaX2 - deltaX1); + + int deltaY1 = Math.abs((int)oldEvent.getY(0) - (int)oldEvent.getY(1)); + int deltaY2 = Math.abs((int)newEvent.getY(0) - (int)newEvent.getY(1)); + int distYSquare = (deltaY2 - deltaY1) * (deltaY2 - deltaY1); + + return (distXSquare + distYSquare) > mPointerDistanceSquare; + } + + /** + * The listener that is used to notify when gestures occur. + * If you want to listen for all the different gestures then implement + * this interface. If you only want to listen for a subset it might + * be easier to extend {@link SimpleOnGestureListener}. + */ + public interface OnDoubleGestureListener { + + /** + * Notified when a multi tap event starts + */ + boolean onDoubleTouchDown(MotionEvent e); + + /** + * Notified when a multi tap event ends + */ + boolean onDoubleTouchUp(MotionEvent e); + + /** + * Notified when a tap occurs with the up {@link MotionEvent} + * that triggered it. + * + * @param e The up motion event that completed the first tap + * @return true if the event is consumed, else false + */ + boolean onDoubleTouchSingleTap(MotionEvent e); + + /** + * Notified when a scroll occurs with the initial on down {@link MotionEvent} and the + * current move {@link MotionEvent}. The distance in x and y is also supplied for + * convenience. + * + * @param e1 The first down motion event that started the scrolling. + * @param e2 The move motion event that triggered the current onScroll. + * @param distanceX The distance along the X axis that has been scrolled since the last + * call to onScroll. This is NOT the distance between {@code e1} + * and {@code e2}. + * @param distanceY The distance along the Y axis that has been scrolled since the last + * call to onScroll. This is NOT the distance between {@code e1} + * and {@code e2}. + * @return true if the event is consumed, else false + */ + boolean onDoubleTouchScroll(MotionEvent e1, MotionEvent e2); + } + + /* + private void dumpEvent(MotionEvent event) { + String names[] = { "DOWN" , "UP" , "MOVE" , "CANCEL" , "OUTSIDE" , + "POINTER_DOWN" , "POINTER_UP" , "7?" , "8?" , "9?" }; + StringBuilder sb = new StringBuilder(); + int action = event.getAction(); + int actionCode = action & MotionEvent.ACTION_MASK; + sb.append("event ACTION_" ).append(names[actionCode]); + if (actionCode == MotionEvent.ACTION_POINTER_DOWN + || actionCode == MotionEvent.ACTION_POINTER_UP) { + sb.append("(pid " ).append( + action >> MotionEvent.ACTION_POINTER_ID_SHIFT); + sb.append(")" ); + } + sb.append("[" ); + for (int i = 0; i < event.getPointerCount(); i++) { + sb.append("#" ).append(i); + sb.append("(pid " ).append(event.getPointerId(i)); + sb.append(")=" ).append((int) event.getX(i)); + sb.append("," ).append((int) event.getY(i)); + if (i + 1 < event.getPointerCount()) + sb.append(";" ); + } + sb.append("]" ); + Log.d("DoubleDetector", sb.toString()); + } + */ + + private class GestureHandler extends Handler + { + GestureHandler() + { + super(); + } + + GestureHandler(Handler handler) + { + super(handler.getLooper()); + } + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/GestureDetector.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/GestureDetector.java new file mode 100644 index 0000000..a110764 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/GestureDetector.java @@ -0,0 +1,620 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + * + * Modified for aFreeRDP by Martin Fleisz (martin.fleisz@thincast.com) + */ + +package com.freerdp.freerdpcore.utils; + +import android.content.Context; +import android.os.Build; +import android.os.Handler; +import android.os.Message; +import android.util.DisplayMetrics; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +public class GestureDetector +{ + + private static final int TAP_TIMEOUT = 100; + private static final int DOUBLE_TAP_TIMEOUT = 200; + // Distance a touch can wander before we think the user is the first touch in a sequence of + // double tap + private static final int LARGE_TOUCH_SLOP = 18; + // Distance between the first touch and second touch to still be considered a double tap + private static final int DOUBLE_TAP_SLOP = 100; + // constants for Message.what used by GestureHandler below + private static final int SHOW_PRESS = 1; + private static final int LONG_PRESS = 2; + private static final int TAP = 3; + private final Handler mHandler; + private final OnGestureListener mListener; + private int mTouchSlopSquare; + private int mLargeTouchSlopSquare; + private int mDoubleTapSlopSquare; + private int mLongpressTimeout = 100; + private OnDoubleTapListener mDoubleTapListener; + private boolean mStillDown; + private boolean mInLongPress; + private boolean mAlwaysInTapRegion; + private boolean mAlwaysInBiggerTapRegion; + private MotionEvent mCurrentDownEvent; + private MotionEvent mPreviousUpEvent; + /** + * True when the user is still touching for the second tap (down, move, and + * up events). Can only be true if there is a double tap listener attached. + */ + private boolean mIsDoubleTapping; + private float mLastMotionY; + private float mLastMotionX; + private boolean mIsLongpressEnabled; + /** + * True if we are at a target API level of >= Froyo or the developer can + * explicitly set it. If true, input events with > 1 pointer will be ignored + * so we can work side by side with multitouch gesture detectors. + */ + private boolean mIgnoreMultitouch; + /** + * Creates a GestureDetector with the supplied listener. + * You may only use this constructor from a UI thread (this is the usual situation). + * + * @param context the application's context + * @param listener the listener invoked for all the callbacks, this must + * not be null. + * @throws NullPointerException if {@code listener} is null. + * @see android.os.Handler#Handler() + */ + public GestureDetector(Context context, OnGestureListener listener) + { + this(context, listener, null); + } + + /** + * Creates a GestureDetector with the supplied listener. + * You may only use this constructor from a UI thread (this is the usual situation). + * + * @param context the application's context + * @param listener the listener invoked for all the callbacks, this must + * not be null. + * @param handler the handler to use + * @throws NullPointerException if {@code listener} is null. + * @see android.os.Handler#Handler() + */ + public GestureDetector(Context context, OnGestureListener listener, Handler handler) + { + this(context, listener, handler, + context != null && + context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.FROYO); + } + + /** + * Creates a GestureDetector with the supplied listener. + * You may only use this constructor from a UI thread (this is the usual situation). + * + * @param context the application's context + * @param listener the listener invoked for all the callbacks, this must + * not be null. + * @param handler the handler to use + * @param ignoreMultitouch whether events involving more than one pointer should + * be ignored. + * @throws NullPointerException if {@code listener} is null. + * @see android.os.Handler#Handler() + */ + public GestureDetector(Context context, OnGestureListener listener, Handler handler, + boolean ignoreMultitouch) + { + if (handler != null) + { + mHandler = new GestureHandler(handler); + } + else + { + mHandler = new GestureHandler(); + } + mListener = listener; + if (listener instanceof OnDoubleTapListener) + { + setOnDoubleTapListener((OnDoubleTapListener)listener); + } + init(context, ignoreMultitouch); + } + + private void init(Context context, boolean ignoreMultitouch) + { + if (mListener == null) + { + throw new NullPointerException("OnGestureListener must not be null"); + } + mIsLongpressEnabled = true; + mIgnoreMultitouch = ignoreMultitouch; + + // Fallback to support pre-donuts releases + int touchSlop, largeTouchSlop, doubleTapSlop; + if (context == null) + { + // noinspection deprecation + touchSlop = ViewConfiguration.getTouchSlop(); + largeTouchSlop = touchSlop + 2; + doubleTapSlop = DOUBLE_TAP_SLOP; + } + else + { + final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + final float density = metrics.density; + final ViewConfiguration configuration = ViewConfiguration.get(context); + touchSlop = configuration.getScaledTouchSlop(); + largeTouchSlop = (int)(density * LARGE_TOUCH_SLOP + 0.5f); + doubleTapSlop = configuration.getScaledDoubleTapSlop(); + } + mTouchSlopSquare = touchSlop * touchSlop; + mLargeTouchSlopSquare = largeTouchSlop * largeTouchSlop; + mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop; + } + + /** + * Sets the listener which will be called for double-tap and related + * gestures. + * + * @param onDoubleTapListener the listener invoked for all the callbacks, or + * null to stop listening for double-tap gestures. + */ + public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) + { + mDoubleTapListener = onDoubleTapListener; + } + + /** + * Set whether longpress is enabled, if this is enabled when a user + * presses and holds down you get a longpress event and nothing further. + * If it's disabled the user can press and hold down and then later + * moved their finger and you will get scroll events. By default + * longpress is enabled. + * + * @param isLongpressEnabled whether longpress should be enabled. + */ + public void setIsLongpressEnabled(boolean isLongpressEnabled) + { + mIsLongpressEnabled = isLongpressEnabled; + } + + /** + * @return true if longpress is enabled, else false. + */ + public boolean isLongpressEnabled() + { + return mIsLongpressEnabled; + } + + public void setLongPressTimeout(int timeout) + { + mLongpressTimeout = timeout; + } + + /** + * Analyzes the given motion event and if applicable triggers the + * appropriate callbacks on the {@link OnGestureListener} supplied. + * + * @param ev The current motion event. + * @return true if the {@link OnGestureListener} consumed the event, + * else false. + */ + public boolean onTouchEvent(MotionEvent ev) + { + final int action = ev.getAction(); + final float y = ev.getY(); + final float x = ev.getX(); + + boolean handled = false; + + switch (action & MotionEvent.ACTION_MASK) + { + case MotionEvent.ACTION_POINTER_DOWN: + if (mIgnoreMultitouch) + { + // Multitouch event - abort. + cancel(); + } + break; + + case MotionEvent.ACTION_POINTER_UP: + // Ending a multitouch gesture and going back to 1 finger + if (mIgnoreMultitouch && ev.getPointerCount() == 2) + { + int index = (((action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> + MotionEvent.ACTION_POINTER_INDEX_SHIFT) == 0) + ? 1 + : 0; + mLastMotionX = ev.getX(index); + mLastMotionY = ev.getY(index); + } + break; + + case MotionEvent.ACTION_DOWN: + if (mDoubleTapListener != null) + { + boolean hadTapMessage = mHandler.hasMessages(TAP); + if (hadTapMessage) + mHandler.removeMessages(TAP); + if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) && + hadTapMessage && + isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) + { + // This is a second tap + mIsDoubleTapping = true; + // Give a callback with the first tap of the double-tap + handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent); + // Give a callback with down event of the double-tap + handled |= mDoubleTapListener.onDoubleTapEvent(ev); + } + else + { + // This is a first tap + mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT); + } + } + + mLastMotionX = x; + mLastMotionY = y; + if (mCurrentDownEvent != null) + { + mCurrentDownEvent.recycle(); + } + mCurrentDownEvent = MotionEvent.obtain(ev); + mAlwaysInTapRegion = true; + mAlwaysInBiggerTapRegion = true; + mStillDown = true; + mInLongPress = false; + + if (mIsLongpressEnabled) + { + mHandler.removeMessages(LONG_PRESS); + mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime() + + TAP_TIMEOUT + + mLongpressTimeout); + } + mHandler.sendEmptyMessageAtTime(SHOW_PRESS, + mCurrentDownEvent.getDownTime() + TAP_TIMEOUT); + handled |= mListener.onDown(ev); + break; + + case MotionEvent.ACTION_MOVE: + if (mIgnoreMultitouch && ev.getPointerCount() > 1) + { + break; + } + final float scrollX = mLastMotionX - x; + final float scrollY = mLastMotionY - y; + if (mIsDoubleTapping) + { + // Give the move events of the double-tap + handled |= mDoubleTapListener.onDoubleTapEvent(ev); + } + else if (mAlwaysInTapRegion) + { + final int deltaX = (int)(x - mCurrentDownEvent.getX()); + final int deltaY = (int)(y - mCurrentDownEvent.getY()); + int distance = (deltaX * deltaX) + (deltaY * deltaY); + if (distance > mTouchSlopSquare) + { + mLastMotionX = x; + mLastMotionY = y; + mAlwaysInTapRegion = false; + mHandler.removeMessages(TAP); + mHandler.removeMessages(SHOW_PRESS); + mHandler.removeMessages(LONG_PRESS); + } + if (distance > mLargeTouchSlopSquare) + { + mAlwaysInBiggerTapRegion = false; + } + handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); + } + else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) + { + handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); + mLastMotionX = x; + mLastMotionY = y; + } + break; + + case MotionEvent.ACTION_UP: + mStillDown = false; + MotionEvent currentUpEvent = MotionEvent.obtain(ev); + if (mIsDoubleTapping) + { + // Finally, give the up event of the double-tap + handled |= mDoubleTapListener.onDoubleTapEvent(ev); + } + else if (mInLongPress) + { + mHandler.removeMessages(TAP); + mListener.onLongPressUp(ev); + mInLongPress = false; + } + else if (mAlwaysInTapRegion) + { + handled = mListener.onSingleTapUp(mCurrentDownEvent); + } + else + { + // A fling must travel the minimum tap distance + } + if (mPreviousUpEvent != null) + { + mPreviousUpEvent.recycle(); + } + // Hold the event we obtained above - listeners may have changed the original. + mPreviousUpEvent = currentUpEvent; + mIsDoubleTapping = false; + mHandler.removeMessages(SHOW_PRESS); + mHandler.removeMessages(LONG_PRESS); + handled |= mListener.onUp(ev); + break; + case MotionEvent.ACTION_CANCEL: + cancel(); + break; + } + return handled; + } + + private void cancel() + { + mHandler.removeMessages(SHOW_PRESS); + mHandler.removeMessages(LONG_PRESS); + mHandler.removeMessages(TAP); + mAlwaysInTapRegion = false; // ensures that we won't receive an OnSingleTap notification + // when a 2-Finger tap is performed + mIsDoubleTapping = false; + mStillDown = false; + if (mInLongPress) + { + mInLongPress = false; + } + } + + private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp, + MotionEvent secondDown) + { + if (!mAlwaysInBiggerTapRegion) + { + return false; + } + + if (secondDown.getEventTime() - firstUp.getEventTime() > DOUBLE_TAP_TIMEOUT) + { + return false; + } + + int deltaX = (int)firstDown.getX() - (int)secondDown.getX(); + int deltaY = (int)firstDown.getY() - (int)secondDown.getY(); + return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare); + } + + private void dispatchLongPress() + { + mHandler.removeMessages(TAP); + mInLongPress = true; + mListener.onLongPress(mCurrentDownEvent); + } + + /** + * The listener that is used to notify when gestures occur. + * If you want to listen for all the different gestures then implement + * this interface. If you only want to listen for a subset it might + * be easier to extend {@link SimpleOnGestureListener}. + */ + public interface OnGestureListener { + + /** + * Notified when a tap occurs with the down {@link MotionEvent} + * that triggered it. This will be triggered immediately for + * every down event. All other events should be preceded by this. + * + * @param e The down motion event. + */ + boolean onDown(MotionEvent e); + + /** + * Notified when a tap finishes with the up {@link MotionEvent} + * that triggered it. This will be triggered immediately for + * every up event. All other events should be preceded by this. + * + * @param e The up motion event. + */ + boolean onUp(MotionEvent e); + + /** + * The user has performed a down {@link MotionEvent} and not performed + * a move or up yet. This event is commonly used to provide visual + * feedback to the user to let them know that their action has been + * recognized i.e. highlight an element. + * + * @param e The down motion event + */ + void onShowPress(MotionEvent e); + + /** + * Notified when a tap occurs with the up {@link MotionEvent} + * that triggered it. + * + * @param e The up motion event that completed the first tap + * @return true if the event is consumed, else false + */ + boolean onSingleTapUp(MotionEvent e); + + /** + * Notified when a scroll occurs with the initial on down {@link MotionEvent} and the + * current move {@link MotionEvent}. The distance in x and y is also supplied for + * convenience. + * + * @param e1 The first down motion event that started the scrolling. + * @param e2 The move motion event that triggered the current onScroll. + * @param distanceX The distance along the X axis that has been scrolled since the last + * call to onScroll. This is NOT the distance between {@code e1} + * and {@code e2}. + * @param distanceY The distance along the Y axis that has been scrolled since the last + * call to onScroll. This is NOT the distance between {@code e1} + * and {@code e2}. + * @return true if the event is consumed, else false + */ + boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY); + + /** + * Notified when a long press occurs with the initial on down {@link MotionEvent} + * that triggered it. + * + * @param e The initial on down motion event that started the longpress. + */ + void onLongPress(MotionEvent e); + + /** + * Notified when a long press ends with the final {@link MotionEvent}. + * + * @param e The up motion event that ended the longpress. + */ + void onLongPressUp(MotionEvent e); + } + + /** + * The listener that is used to notify when a double-tap or a confirmed + * single-tap occur. + */ + public interface OnDoubleTapListener { + /** + * Notified when a single-tap occurs. + *

+ * Unlike {@link OnGestureListener#onSingleTapUp(MotionEvent)}, this + * will only be called after the detector is confident that the user's + * first tap is not followed by a second tap leading to a double-tap + * gesture. + * + * @param e The down motion event of the single-tap. + * @return true if the event is consumed, else false + */ + boolean onSingleTapConfirmed(MotionEvent e); + + /** + * Notified when a double-tap occurs. + * + * @param e The down motion event of the first tap of the double-tap. + * @return true if the event is consumed, else false + */ + boolean onDoubleTap(MotionEvent e); + + /** + * Notified when an event within a double-tap gesture occurs, including + * the down, move, and up events. + * + * @param e The motion event that occurred during the double-tap gesture. + * @return true if the event is consumed, else false + */ + boolean onDoubleTapEvent(MotionEvent e); + } + + /** + * A convenience class to extend when you only want to listen for a subset + * of all the gestures. This implements all methods in the + * {@link OnGestureListener} and {@link OnDoubleTapListener} but does + * nothing and return {@code false} for all applicable methods. + */ + public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener + { + public boolean onSingleTapUp(MotionEvent e) + { + return false; + } + + public void onLongPress(MotionEvent e) + { + } + + public void onLongPressUp(MotionEvent e) + { + } + + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) + { + return false; + } + + public void onShowPress(MotionEvent e) + { + } + + public boolean onDown(MotionEvent e) + { + return false; + } + + public boolean onUp(MotionEvent e) + { + return false; + } + + public boolean onDoubleTap(MotionEvent e) + { + return false; + } + + public boolean onDoubleTapEvent(MotionEvent e) + { + return false; + } + + public boolean onSingleTapConfirmed(MotionEvent e) + { + return false; + } + } + + private class GestureHandler extends Handler + { + GestureHandler() + { + super(); + } + + GestureHandler(Handler handler) + { + super(handler.getLooper()); + } + + @Override public void handleMessage(Message msg) + { + switch (msg.what) + { + case SHOW_PRESS: + mListener.onShowPress(mCurrentDownEvent); + break; + + case LONG_PRESS: + dispatchLongPress(); + break; + + case TAP: + // If the user's finger is still down, do not count it as a tap + if (mDoubleTapListener != null && !mStillDown) + { + mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent); + } + break; + + default: + throw new RuntimeException("Unknown message " + msg); // never + } + } + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/IntEditTextPreference.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/IntEditTextPreference.java new file mode 100644 index 0000000..9457e6f --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/IntEditTextPreference.java @@ -0,0 +1,101 @@ +/* + EditTextPreference to store/load integer values + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.utils; + +import android.content.Context; +import android.content.res.TypedArray; +import android.preference.EditTextPreference; +import android.util.AttributeSet; + +import com.freerdp.freerdpcore.R; + +public class IntEditTextPreference extends EditTextPreference +{ + + private int bounds_min, bounds_max, bounds_default; + + public IntEditTextPreference(Context context) + { + super(context); + init(context, null); + } + + public IntEditTextPreference(Context context, AttributeSet attrs) + { + super(context, attrs); + init(context, attrs); + } + + public IntEditTextPreference(Context context, AttributeSet attrs, int defStyle) + { + super(context, attrs, defStyle); + init(context, attrs); + } + + private void init(Context context, AttributeSet attrs) + { + if (attrs != null) + { + TypedArray array = + context.obtainStyledAttributes(attrs, R.styleable.IntEditTextPreference, 0, 0); + bounds_min = + array.getInt(R.styleable.IntEditTextPreference_bounds_min, Integer.MIN_VALUE); + bounds_max = + array.getInt(R.styleable.IntEditTextPreference_bounds_max, Integer.MAX_VALUE); + bounds_default = array.getInt(R.styleable.IntEditTextPreference_bounds_default, 0); + array.recycle(); + } + else + { + bounds_min = Integer.MIN_VALUE; + bounds_max = Integer.MAX_VALUE; + bounds_default = 0; + } + } + + public void setBounds(int min, int max, int defaultValue) + { + bounds_min = min; + bounds_max = max; + bounds_default = defaultValue; + } + + @Override protected String getPersistedString(String defaultReturnValue) + { + int value = getPersistedInt(-1); + if (value > bounds_max || value < bounds_min) + value = bounds_default; + return String.valueOf(value); + } + + @Override protected boolean persistString(String value) + { + return persistInt(Integer.parseInt(value)); + } + + @Override protected void onDialogClosed(boolean positiveResult) + { + if (positiveResult) + { + // prevent exception when an empty value is persisted + if (getEditText().getText().length() == 0) + getEditText().setText("0"); + + // check bounds + int value = Integer.parseInt(getEditText().getText().toString()); + if (value > bounds_max || value < bounds_min) + value = bounds_default; + getEditText().setText(String.valueOf(value)); + } + + super.onDialogClosed(positiveResult); + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/IntListPreference.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/IntListPreference.java new file mode 100644 index 0000000..1797f39 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/IntListPreference.java @@ -0,0 +1,39 @@ +/* + ListPreference to store/load integer values + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.utils; + +import android.content.Context; +import android.preference.ListPreference; +import android.util.AttributeSet; + +public class IntListPreference extends ListPreference +{ + + public IntListPreference(Context context) + { + super(context); + } + + public IntListPreference(Context context, AttributeSet attrs) + { + super(context, attrs); + } + + @Override protected String getPersistedString(String defaultReturnValue) + { + return String.valueOf(getPersistedInt(-1)); + } + + @Override protected boolean persistString(String value) + { + return persistInt(Integer.parseInt(value)); + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/KeyboardMapper.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/KeyboardMapper.java new file mode 100644 index 0000000..7f6bb5b --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/KeyboardMapper.java @@ -0,0 +1,725 @@ +/* + Android Keyboard Mapping + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.utils; + +import android.content.Context; +import android.view.KeyEvent; + +import com.freerdp.freerdpcore.R; + +public class KeyboardMapper +{ + public static final int KEYBOARD_TYPE_FUNCTIONKEYS = 1; + public static final int KEYBOARD_TYPE_NUMPAD = 2; + public static final int KEYBOARD_TYPE_CURSOR = 3; + + // defines key states for modifier keys - locked means on and no auto-release if an other key is + // pressed + public static final int KEYSTATE_ON = 1; + public static final int KEYSTATE_LOCKED = 2; + public static final int KEYSTATE_OFF = 3; + final static int VK_LBUTTON = 0x01; + final static int VK_RBUTTON = 0x02; + final static int VK_CANCEL = 0x03; + final static int VK_MBUTTON = 0x04; + final static int VK_XBUTTON1 = 0x05; + final static int VK_XBUTTON2 = 0x06; + final static int VK_BACK = 0x08; + final static int VK_TAB = 0x09; + final static int VK_CLEAR = 0x0C; + final static int VK_RETURN = 0x0D; + final static int VK_SHIFT = 0x10; + final static int VK_CONTROL = 0x11; + final static int VK_MENU = 0x12; + final static int VK_PAUSE = 0x13; + final static int VK_CAPITAL = 0x14; + final static int VK_KANA = 0x15; + final static int VK_HANGUEL = 0x15; + final static int VK_HANGUL = 0x15; + final static int VK_JUNJA = 0x17; + final static int VK_FINAL = 0x18; + final static int VK_HANJA = 0x19; + final static int VK_KANJI = 0x19; + final static int VK_ESCAPE = 0x1B; + final static int VK_CONVERT = 0x1C; + final static int VK_NONCONVERT = 0x1D; + final static int VK_ACCEPT = 0x1E; + final static int VK_MODECHANGE = 0x1F; + final static int VK_SPACE = 0x20; + final static int VK_PRIOR = 0x21; + final static int VK_NEXT = 0x22; + final static int VK_END = 0x23; + final static int VK_HOME = 0x24; + final static int VK_LEFT = 0x25; + final static int VK_UP = 0x26; + final static int VK_RIGHT = 0x27; + final static int VK_DOWN = 0x28; + final static int VK_SELECT = 0x29; + final static int VK_PRINT = 0x2A; + final static int VK_EXECUTE = 0x2B; + final static int VK_SNAPSHOT = 0x2C; + final static int VK_INSERT = 0x2D; + final static int VK_DELETE = 0x2E; + final static int VK_HELP = 0x2F; + final static int VK_KEY_0 = 0x30; + final static int VK_KEY_1 = 0x31; + final static int VK_KEY_2 = 0x32; + final static int VK_KEY_3 = 0x33; + final static int VK_KEY_4 = 0x34; + final static int VK_KEY_5 = 0x35; + final static int VK_KEY_6 = 0x36; + final static int VK_KEY_7 = 0x37; + final static int VK_KEY_8 = 0x38; + final static int VK_KEY_9 = 0x39; + final static int VK_KEY_A = 0x41; + final static int VK_KEY_B = 0x42; + final static int VK_KEY_C = 0x43; + final static int VK_KEY_D = 0x44; + final static int VK_KEY_E = 0x45; + final static int VK_KEY_F = 0x46; + final static int VK_KEY_G = 0x47; + final static int VK_KEY_H = 0x48; + final static int VK_KEY_I = 0x49; + final static int VK_KEY_J = 0x4A; + final static int VK_KEY_K = 0x4B; + final static int VK_KEY_L = 0x4C; + final static int VK_KEY_M = 0x4D; + final static int VK_KEY_N = 0x4E; + final static int VK_KEY_O = 0x4F; + final static int VK_KEY_P = 0x50; + final static int VK_KEY_Q = 0x51; + final static int VK_KEY_R = 0x52; + final static int VK_KEY_S = 0x53; + final static int VK_KEY_T = 0x54; + final static int VK_KEY_U = 0x55; + final static int VK_KEY_V = 0x56; + final static int VK_KEY_W = 0x57; + final static int VK_KEY_X = 0x58; + final static int VK_KEY_Y = 0x59; + final static int VK_KEY_Z = 0x5A; + final static int VK_LWIN = 0x5B; + final static int VK_RWIN = 0x5C; + final static int VK_APPS = 0x5D; + final static int VK_SLEEP = 0x5F; + final static int VK_NUMPAD0 = 0x60; + final static int VK_NUMPAD1 = 0x61; + final static int VK_NUMPAD2 = 0x62; + final static int VK_NUMPAD3 = 0x63; + final static int VK_NUMPAD4 = 0x64; + final static int VK_NUMPAD5 = 0x65; + final static int VK_NUMPAD6 = 0x66; + final static int VK_NUMPAD7 = 0x67; + final static int VK_NUMPAD8 = 0x68; + final static int VK_NUMPAD9 = 0x69; + final static int VK_MULTIPLY = 0x6A; + final static int VK_ADD = 0x6B; + final static int VK_SEPARATOR = 0x6C; + final static int VK_SUBTRACT = 0x6D; + final static int VK_DECIMAL = 0x6E; + final static int VK_DIVIDE = 0x6F; + final static int VK_F1 = 0x70; + final static int VK_F2 = 0x71; + final static int VK_F3 = 0x72; + final static int VK_F4 = 0x73; + final static int VK_F5 = 0x74; + final static int VK_F6 = 0x75; + final static int VK_F7 = 0x76; + final static int VK_F8 = 0x77; + final static int VK_F9 = 0x78; + final static int VK_F10 = 0x79; + final static int VK_F11 = 0x7A; + final static int VK_F12 = 0x7B; + final static int VK_F13 = 0x7C; + final static int VK_F14 = 0x7D; + final static int VK_F15 = 0x7E; + final static int VK_F16 = 0x7F; + final static int VK_F17 = 0x80; + final static int VK_F18 = 0x81; + final static int VK_F19 = 0x82; + final static int VK_F20 = 0x83; + final static int VK_F21 = 0x84; + final static int VK_F22 = 0x85; + final static int VK_F23 = 0x86; + final static int VK_F24 = 0x87; + final static int VK_NUMLOCK = 0x90; + final static int VK_SCROLL = 0x91; + final static int VK_LSHIFT = 0xA0; + final static int VK_RSHIFT = 0xA1; + final static int VK_LCONTROL = 0xA2; + final static int VK_RCONTROL = 0xA3; + final static int VK_LMENU = 0xA4; + final static int VK_RMENU = 0xA5; + final static int VK_BROWSER_BACK = 0xA6; + final static int VK_BROWSER_FORWARD = 0xA7; + final static int VK_BROWSER_REFRESH = 0xA8; + final static int VK_BROWSER_STOP = 0xA9; + final static int VK_BROWSER_SEARCH = 0xAA; + final static int VK_BROWSER_FAVORITES = 0xAB; + final static int VK_BROWSER_HOME = 0xAC; + final static int VK_VOLUME_MUTE = 0xAD; + final static int VK_VOLUME_DOWN = 0xAE; + final static int VK_VOLUME_UP = 0xAF; + final static int VK_MEDIA_NEXT_TRACK = 0xB0; + final static int VK_MEDIA_PREV_TRACK = 0xB1; + final static int VK_MEDIA_STOP = 0xB2; + final static int VK_MEDIA_PLAY_PAUSE = 0xB3; + final static int VK_LAUNCH_MAIL = 0xB4; + final static int VK_LAUNCH_MEDIA_SELECT = 0xB5; + final static int VK_LAUNCH_APP1 = 0xB6; + final static int VK_LAUNCH_APP2 = 0xB7; + final static int VK_OEM_1 = 0xBA; + final static int VK_OEM_PLUS = 0xBB; + final static int VK_OEM_COMMA = 0xBC; + final static int VK_OEM_MINUS = 0xBD; + final static int VK_OEM_PERIOD = 0xBE; + final static int VK_OEM_2 = 0xBF; + final static int VK_OEM_3 = 0xC0; + final static int VK_ABNT_C1 = 0xC1; + final static int VK_ABNT_C2 = 0xC2; + final static int VK_OEM_4 = 0xDB; + final static int VK_OEM_5 = 0xDC; + final static int VK_OEM_6 = 0xDD; + final static int VK_OEM_7 = 0xDE; + final static int VK_OEM_8 = 0xDF; + final static int VK_OEM_102 = 0xE2; + final static int VK_PROCESSKEY = 0xE5; + final static int VK_PACKET = 0xE7; + final static int VK_ATTN = 0xF6; + final static int VK_CRSEL = 0xF7; + final static int VK_EXSEL = 0xF8; + final static int VK_EREOF = 0xF9; + final static int VK_PLAY = 0xFA; + final static int VK_ZOOM = 0xFB; + final static int VK_NONAME = 0xFC; + final static int VK_PA1 = 0xFD; + final static int VK_OEM_CLEAR = 0xFE; + final static int VK_UNICODE = 0x80000000; + final static int VK_EXT_KEY = 0x00000100; + // key codes to switch between custom keyboard + private final static int EXTKEY_KBFUNCTIONKEYS = 0x1100; + private final static int EXTKEY_KBNUMPAD = 0x1101; + private final static int EXTKEY_KBCURSOR = 0x1102; + // this flag indicates if we got a VK or a unicode character in our translation map + private static final int KEY_FLAG_UNICODE = 0x80000000; + // this flag indicates if the key is a toggle key (remains down when pressed and goes up if + // pressed again) + private static final int KEY_FLAG_TOGGLE = 0x40000000; + private static int[] keymapAndroid; + private static int[] keymapExt; + private static boolean initialized = false; + private KeyProcessingListener listener = null; + private boolean shiftPressed = false; + private boolean ctrlPressed = false; + private boolean altPressed = false; + private boolean winPressed = false; + private long lastModifierTime; + private int lastModifierKeyCode = -1; + private boolean isShiftLocked = false; + private boolean isCtrlLocked = false; + private boolean isAltLocked = false; + private boolean isWinLocked = false; + + public void init(Context context) + { + if (initialized) + return; + + keymapAndroid = new int[256]; + + keymapAndroid[KeyEvent.KEYCODE_0] = VK_KEY_0; + keymapAndroid[KeyEvent.KEYCODE_1] = VK_KEY_1; + keymapAndroid[KeyEvent.KEYCODE_2] = VK_KEY_2; + keymapAndroid[KeyEvent.KEYCODE_3] = VK_KEY_3; + keymapAndroid[KeyEvent.KEYCODE_4] = VK_KEY_4; + keymapAndroid[KeyEvent.KEYCODE_5] = VK_KEY_5; + keymapAndroid[KeyEvent.KEYCODE_6] = VK_KEY_6; + keymapAndroid[KeyEvent.KEYCODE_7] = VK_KEY_7; + keymapAndroid[KeyEvent.KEYCODE_8] = VK_KEY_8; + keymapAndroid[KeyEvent.KEYCODE_9] = VK_KEY_9; + + keymapAndroid[KeyEvent.KEYCODE_A] = VK_KEY_A; + keymapAndroid[KeyEvent.KEYCODE_B] = VK_KEY_B; + keymapAndroid[KeyEvent.KEYCODE_C] = VK_KEY_C; + keymapAndroid[KeyEvent.KEYCODE_D] = VK_KEY_D; + keymapAndroid[KeyEvent.KEYCODE_E] = VK_KEY_E; + keymapAndroid[KeyEvent.KEYCODE_F] = VK_KEY_F; + keymapAndroid[KeyEvent.KEYCODE_G] = VK_KEY_G; + keymapAndroid[KeyEvent.KEYCODE_H] = VK_KEY_H; + keymapAndroid[KeyEvent.KEYCODE_I] = VK_KEY_I; + keymapAndroid[KeyEvent.KEYCODE_J] = VK_KEY_J; + keymapAndroid[KeyEvent.KEYCODE_K] = VK_KEY_K; + keymapAndroid[KeyEvent.KEYCODE_L] = VK_KEY_L; + keymapAndroid[KeyEvent.KEYCODE_M] = VK_KEY_M; + keymapAndroid[KeyEvent.KEYCODE_N] = VK_KEY_N; + keymapAndroid[KeyEvent.KEYCODE_O] = VK_KEY_O; + keymapAndroid[KeyEvent.KEYCODE_P] = VK_KEY_P; + keymapAndroid[KeyEvent.KEYCODE_Q] = VK_KEY_Q; + keymapAndroid[KeyEvent.KEYCODE_R] = VK_KEY_R; + keymapAndroid[KeyEvent.KEYCODE_S] = VK_KEY_S; + keymapAndroid[KeyEvent.KEYCODE_T] = VK_KEY_T; + keymapAndroid[KeyEvent.KEYCODE_U] = VK_KEY_U; + keymapAndroid[KeyEvent.KEYCODE_V] = VK_KEY_V; + keymapAndroid[KeyEvent.KEYCODE_W] = VK_KEY_W; + keymapAndroid[KeyEvent.KEYCODE_X] = VK_KEY_X; + keymapAndroid[KeyEvent.KEYCODE_Y] = VK_KEY_Y; + keymapAndroid[KeyEvent.KEYCODE_Z] = VK_KEY_Z; + + keymapAndroid[KeyEvent.KEYCODE_DEL] = VK_BACK; + keymapAndroid[KeyEvent.KEYCODE_ENTER] = VK_RETURN; + keymapAndroid[KeyEvent.KEYCODE_SPACE] = VK_SPACE; + keymapAndroid[KeyEvent.KEYCODE_TAB] = VK_TAB; + // keymapAndroid[KeyEvent.KEYCODE_SHIFT_LEFT] = VK_LSHIFT; + // keymapAndroid[KeyEvent.KEYCODE_SHIFT_RIGHT] = VK_RSHIFT; + + // keymapAndroid[KeyEvent.KEYCODE_DPAD_DOWN] = VK_DOWN; + // keymapAndroid[KeyEvent.KEYCODE_DPAD_LEFT] = VK_LEFT; + // keymapAndroid[KeyEvent.KEYCODE_DPAD_RIGHT] = VK_RIGHT; + // keymapAndroid[KeyEvent.KEYCODE_DPAD_UP] = VK_UP; + + // keymapAndroid[KeyEvent.KEYCODE_COMMA] = VK_OEM_COMMA; + // keymapAndroid[KeyEvent.KEYCODE_PERIOD] = VK_OEM_PERIOD; + // keymapAndroid[KeyEvent.KEYCODE_MINUS] = VK_OEM_MINUS; + // keymapAndroid[KeyEvent.KEYCODE_PLUS] = VK_OEM_PLUS; + + // keymapAndroid[KeyEvent.KEYCODE_ALT_LEFT] = VK_LMENU; + // keymapAndroid[KeyEvent.KEYCODE_ALT_RIGHT] = VK_RMENU; + + // keymapAndroid[KeyEvent.KEYCODE_AT] = (KEY_FLAG_UNICODE | 64); + // keymapAndroid[KeyEvent.KEYCODE_APOSTROPHE] = (KEY_FLAG_UNICODE | 39); + // keymapAndroid[KeyEvent.KEYCODE_BACKSLASH] = (KEY_FLAG_UNICODE | 92); + // keymapAndroid[KeyEvent.KEYCODE_COMMA] = (KEY_FLAG_UNICODE | 44); + // keymapAndroid[KeyEvent.KEYCODE_EQUALS] = (KEY_FLAG_UNICODE | 61); + // keymapAndroid[KeyEvent.KEYCODE_GRAVE] = (KEY_FLAG_UNICODE | 96); + // keymapAndroid[KeyEvent.KEYCODE_LEFT_BRACKET] = (KEY_FLAG_UNICODE | 91); + // keymapAndroid[KeyEvent.KEYCODE_RIGHT_BRACKET] = (KEY_FLAG_UNICODE | 93); + // keymapAndroid[KeyEvent.KEYCODE_MINUS] = (KEY_FLAG_UNICODE | 45); + // keymapAndroid[KeyEvent.KEYCODE_PERIOD] = (KEY_FLAG_UNICODE | 46); + // keymapAndroid[KeyEvent.KEYCODE_PLUS] = (KEY_FLAG_UNICODE | 43); + // keymapAndroid[KeyEvent.KEYCODE_POUND] = (KEY_FLAG_UNICODE | 35); + // keymapAndroid[KeyEvent.KEYCODE_SEMICOLON] = (KEY_FLAG_UNICODE | 59); + // keymapAndroid[KeyEvent.KEYCODE_SLASH] = (KEY_FLAG_UNICODE | 47); + // keymapAndroid[KeyEvent.KEYCODE_STAR] = (KEY_FLAG_UNICODE | 42); + + // special keys mapping + keymapExt = new int[256]; + keymapExt[context.getResources().getInteger(R.integer.keycode_F1)] = VK_F1; + keymapExt[context.getResources().getInteger(R.integer.keycode_F2)] = VK_F2; + keymapExt[context.getResources().getInteger(R.integer.keycode_F3)] = VK_F3; + keymapExt[context.getResources().getInteger(R.integer.keycode_F4)] = VK_F4; + keymapExt[context.getResources().getInteger(R.integer.keycode_F5)] = VK_F5; + keymapExt[context.getResources().getInteger(R.integer.keycode_F6)] = VK_F6; + keymapExt[context.getResources().getInteger(R.integer.keycode_F7)] = VK_F7; + keymapExt[context.getResources().getInteger(R.integer.keycode_F8)] = VK_F8; + keymapExt[context.getResources().getInteger(R.integer.keycode_F9)] = VK_F9; + keymapExt[context.getResources().getInteger(R.integer.keycode_F10)] = VK_F10; + keymapExt[context.getResources().getInteger(R.integer.keycode_F11)] = VK_F11; + keymapExt[context.getResources().getInteger(R.integer.keycode_F12)] = VK_F12; + keymapExt[context.getResources().getInteger(R.integer.keycode_tab)] = VK_TAB; + keymapExt[context.getResources().getInteger(R.integer.keycode_print)] = VK_PRINT; + keymapExt[context.getResources().getInteger(R.integer.keycode_insert)] = + VK_INSERT | VK_EXT_KEY; + keymapExt[context.getResources().getInteger(R.integer.keycode_delete)] = + VK_DELETE | VK_EXT_KEY; + keymapExt[context.getResources().getInteger(R.integer.keycode_home)] = VK_HOME | VK_EXT_KEY; + keymapExt[context.getResources().getInteger(R.integer.keycode_end)] = VK_END | VK_EXT_KEY; + keymapExt[context.getResources().getInteger(R.integer.keycode_pgup)] = + VK_PRIOR | VK_EXT_KEY; + keymapExt[context.getResources().getInteger(R.integer.keycode_pgdn)] = VK_NEXT | VK_EXT_KEY; + + // numpad mapping + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_0)] = VK_NUMPAD0; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_1)] = VK_NUMPAD1; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_2)] = VK_NUMPAD2; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_3)] = VK_NUMPAD3; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_4)] = VK_NUMPAD4; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_5)] = VK_NUMPAD5; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_6)] = VK_NUMPAD6; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_7)] = VK_NUMPAD7; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_8)] = VK_NUMPAD8; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_9)] = VK_NUMPAD9; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_numlock)] = VK_NUMLOCK; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_add)] = VK_ADD; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_comma)] = VK_DECIMAL; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_divide)] = + VK_DIVIDE | VK_EXT_KEY; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_enter)] = + VK_RETURN | VK_EXT_KEY; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_multiply)] = + VK_MULTIPLY; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_subtract)] = + VK_SUBTRACT; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_equals)] = + (KEY_FLAG_UNICODE | 61); + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_left_paren)] = + (KEY_FLAG_UNICODE | 40); + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_right_paren)] = + (KEY_FLAG_UNICODE | 41); + + // cursor key codes + keymapExt[context.getResources().getInteger(R.integer.keycode_up)] = VK_UP | VK_EXT_KEY; + keymapExt[context.getResources().getInteger(R.integer.keycode_down)] = VK_DOWN | VK_EXT_KEY; + keymapExt[context.getResources().getInteger(R.integer.keycode_left)] = VK_LEFT | VK_EXT_KEY; + keymapExt[context.getResources().getInteger(R.integer.keycode_right)] = + VK_RIGHT | VK_EXT_KEY; + keymapExt[context.getResources().getInteger(R.integer.keycode_enter)] = + VK_RETURN | VK_EXT_KEY; + keymapExt[context.getResources().getInteger(R.integer.keycode_backspace)] = VK_BACK; + + // shared keys + keymapExt[context.getResources().getInteger(R.integer.keycode_win)] = VK_LWIN | VK_EXT_KEY; + keymapExt[context.getResources().getInteger(R.integer.keycode_menu)] = VK_APPS | VK_EXT_KEY; + keymapExt[context.getResources().getInteger(R.integer.keycode_esc)] = VK_ESCAPE; + + /* keymapExt[context.getResources().getInteger(R.integer.keycode_modifier_ctrl)] = + VK_LCONTROL; keymapExt[context.getResources().getInteger(R.integer.keycode_modifier_alt)] + = VK_LMENU; + keymapExt[context.getResources().getInteger(R.integer.keycode_modifier_shift)] = + VK_LSHIFT; + */ + // get custom keyboard key codes + keymapExt[context.getResources().getInteger(R.integer.keycode_specialkeys_keyboard)] = + EXTKEY_KBFUNCTIONKEYS; + keymapExt[context.getResources().getInteger(R.integer.keycode_numpad_keyboard)] = + EXTKEY_KBNUMPAD; + keymapExt[context.getResources().getInteger(R.integer.keycode_cursor_keyboard)] = + EXTKEY_KBCURSOR; + + keymapExt[context.getResources().getInteger(R.integer.keycode_toggle_shift)] = + (KEY_FLAG_TOGGLE | VK_LSHIFT); + keymapExt[context.getResources().getInteger(R.integer.keycode_toggle_ctrl)] = + (KEY_FLAG_TOGGLE | VK_LCONTROL); + keymapExt[context.getResources().getInteger(R.integer.keycode_toggle_alt)] = + (KEY_FLAG_TOGGLE | VK_LMENU); + keymapExt[context.getResources().getInteger(R.integer.keycode_toggle_win)] = + (KEY_FLAG_TOGGLE | VK_LWIN); + + initialized = true; + } + + public void reset(KeyProcessingListener listener) + { + shiftPressed = false; + ctrlPressed = false; + altPressed = false; + winPressed = false; + setKeyProcessingListener(listener); + } + + public void setKeyProcessingListener(KeyProcessingListener listener) + { + this.listener = listener; + } + + public boolean processAndroidKeyEvent(KeyEvent event) + { + switch (event.getAction()) + { + // we only process down events + case KeyEvent.ACTION_UP: + { + return false; + } + + case KeyEvent.ACTION_DOWN: + { + boolean modifierActive = isModifierPressed(); + // if a modifier is pressed we will send a VK event (if possible) so that key + // combinations will be recognized correctly. Otherwise we will send the unicode + // key. At the end we will reset all modifiers and notify our listener. + int vkcode = getVirtualKeyCode(event.getKeyCode()); + if ((vkcode & KEY_FLAG_UNICODE) != 0) + listener.processUnicodeKey(vkcode & (~KEY_FLAG_UNICODE)); + // if we got a valid vkcode send it - except for letters/numbers if a modifier is + // active + else if (vkcode > 0 && + (event.getMetaState() & (KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON | + KeyEvent.META_SYM_ON)) == 0) + { + listener.processVirtualKey(vkcode, true); + listener.processVirtualKey(vkcode, false); + } + else if (event.isShiftPressed() && vkcode != 0) + { + listener.processVirtualKey(VK_LSHIFT, true); + listener.processVirtualKey(vkcode, true); + listener.processVirtualKey(vkcode, false); + listener.processVirtualKey(VK_LSHIFT, false); + } + else if (event.getUnicodeChar() != 0) + listener.processUnicodeKey(event.getUnicodeChar()); + else + return false; + + // reset any pending toggle states if a modifier was pressed + if (modifierActive) + resetModifierKeysAfterInput(false); + return true; + } + + case KeyEvent.ACTION_MULTIPLE: + { + String str = event.getCharacters(); + for (int i = 0; i < str.length(); i++) + listener.processUnicodeKey(str.charAt(i)); + return true; + } + + default: + break; + } + return false; + } + + public void processCustomKeyEvent(int keycode) + { + int extCode = getExtendedKeyCode(keycode); + if (extCode == 0) + return; + + // toggle button pressed? + if ((extCode & KEY_FLAG_TOGGLE) != 0) + { + processToggleButton(extCode & (~KEY_FLAG_TOGGLE)); + return; + } + + // keyboard switch button pressed? + if (extCode == EXTKEY_KBFUNCTIONKEYS || extCode == EXTKEY_KBNUMPAD || + extCode == EXTKEY_KBCURSOR) + { + switchKeyboard(extCode); + return; + } + + // nope - see if we got a unicode or vk + if ((extCode & KEY_FLAG_UNICODE) != 0) + listener.processUnicodeKey(extCode & (~KEY_FLAG_UNICODE)); + else + { + listener.processVirtualKey(extCode, true); + listener.processVirtualKey(extCode, false); + } + + resetModifierKeysAfterInput(false); + } + + public void sendAltF4() + { + listener.processVirtualKey(VK_LMENU, true); + listener.processVirtualKey(VK_F4, true); + listener.processVirtualKey(VK_F4, false); + listener.processVirtualKey(VK_LMENU, false); + } + + private boolean isModifierPressed() + { + return (shiftPressed || ctrlPressed || altPressed || winPressed); + } + + public int getModifierState(int keycode) + { + int modifierCode = getExtendedKeyCode(keycode); + + // check and get real modifier keycode + if ((modifierCode & KEY_FLAG_TOGGLE) == 0) + return -1; + modifierCode = modifierCode & (~KEY_FLAG_TOGGLE); + + switch (modifierCode) + { + case VK_LSHIFT: + { + return (shiftPressed ? (isShiftLocked ? KEYSTATE_LOCKED : KEYSTATE_ON) + : KEYSTATE_OFF); + } + case VK_LCONTROL: + { + return (ctrlPressed ? (isCtrlLocked ? KEYSTATE_LOCKED : KEYSTATE_ON) + : KEYSTATE_OFF); + } + case VK_LMENU: + { + return (altPressed ? (isAltLocked ? KEYSTATE_LOCKED : KEYSTATE_ON) : KEYSTATE_OFF); + } + case VK_LWIN: + { + return (winPressed ? (isWinLocked ? KEYSTATE_LOCKED : KEYSTATE_ON) : KEYSTATE_OFF); + } + } + + return -1; + } + + private int getVirtualKeyCode(int keycode) + { + if (keycode >= 0 && keycode <= 0xFF) + return keymapAndroid[keycode]; + return 0; + } + + private int getExtendedKeyCode(int keycode) + { + if (keycode >= 0 && keycode <= 0xFF) + return keymapExt[keycode]; + return 0; + } + + private void processToggleButton(int keycode) + { + switch (keycode) + { + case VK_LSHIFT: + { + if (!checkToggleModifierLock(VK_LSHIFT)) + { + isShiftLocked = false; + shiftPressed = !shiftPressed; + listener.processVirtualKey(VK_LSHIFT, shiftPressed); + } + else + isShiftLocked = true; + break; + } + case VK_LCONTROL: + { + if (!checkToggleModifierLock(VK_LCONTROL)) + { + isCtrlLocked = false; + ctrlPressed = !ctrlPressed; + listener.processVirtualKey(VK_LCONTROL, ctrlPressed); + } + else + isCtrlLocked = true; + break; + } + case VK_LMENU: + { + if (!checkToggleModifierLock(VK_LMENU)) + { + isAltLocked = false; + altPressed = !altPressed; + listener.processVirtualKey(VK_LMENU, altPressed); + } + else + isAltLocked = true; + break; + } + case VK_LWIN: + { + if (!checkToggleModifierLock(VK_LWIN)) + { + isWinLocked = false; + winPressed = !winPressed; + listener.processVirtualKey(VK_LWIN | VK_EXT_KEY, winPressed); + } + else + isWinLocked = true; + break; + } + } + listener.modifiersChanged(); + } + + public void clearlAllModifiers() + { + resetModifierKeysAfterInput(true); + } + + private void resetModifierKeysAfterInput(boolean force) + { + if (shiftPressed && (!isShiftLocked || force)) + { + listener.processVirtualKey(VK_LSHIFT, false); + shiftPressed = false; + } + if (ctrlPressed && (!isCtrlLocked || force)) + { + listener.processVirtualKey(VK_LCONTROL, false); + ctrlPressed = false; + } + if (altPressed && (!isAltLocked || force)) + { + listener.processVirtualKey(VK_LMENU, false); + altPressed = false; + } + if (winPressed && (!isWinLocked || force)) + { + listener.processVirtualKey(VK_LWIN | VK_EXT_KEY, false); + winPressed = false; + } + + if (listener != null) + listener.modifiersChanged(); + } + + private void switchKeyboard(int keycode) + { + switch (keycode) + { + case EXTKEY_KBFUNCTIONKEYS: + { + listener.switchKeyboard(KEYBOARD_TYPE_FUNCTIONKEYS); + break; + } + + case EXTKEY_KBNUMPAD: + { + listener.switchKeyboard(KEYBOARD_TYPE_NUMPAD); + break; + } + + case EXTKEY_KBCURSOR: + { + listener.switchKeyboard(KEYBOARD_TYPE_CURSOR); + break; + } + + default: + break; + } + } + + private boolean checkToggleModifierLock(int keycode) + { + long now = System.currentTimeMillis(); + + // was the same modifier hit? + if (lastModifierKeyCode != keycode) + { + lastModifierKeyCode = keycode; + lastModifierTime = now; + return false; + } + + // within a certain time interval? + if (lastModifierTime + 800 > now) + { + lastModifierTime = 0; + return true; + } + else + { + lastModifierTime = now; + return false; + } + } + + // interface that gets called for input handling + public interface KeyProcessingListener { + void processVirtualKey(int virtualKeyCode, boolean down); + + void processUnicodeKey(int unicodeKey); + + void switchKeyboard(int keyboardType); + + void modifiersChanged(); + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/Mouse.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/Mouse.java new file mode 100644 index 0000000..11f1d3e --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/Mouse.java @@ -0,0 +1,64 @@ +/* + Android Mouse Input Mapping + + Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.utils; + +import android.content.Context; + +import com.freerdp.freerdpcore.presentation.ApplicationSettingsActivity; + +public class Mouse +{ + + private final static int PTRFLAGS_LBUTTON = 0x1000; + private final static int PTRFLAGS_RBUTTON = 0x2000; + + private final static int PTRFLAGS_DOWN = 0x8000; + private final static int PTRFLAGS_MOVE = 0x0800; + + private final static int PTRFLAGS_WHEEL = 0x0200; + private final static int PTRFLAGS_WHEEL_NEGATIVE = 0x0100; + + public static int getLeftButtonEvent(Context context, boolean down) + { + if (ApplicationSettingsActivity.getSwapMouseButtons(context)) + return (PTRFLAGS_RBUTTON | (down ? PTRFLAGS_DOWN : 0)); + else + return (PTRFLAGS_LBUTTON | (down ? PTRFLAGS_DOWN : 0)); + } + + public static int getRightButtonEvent(Context context, boolean down) + { + if (ApplicationSettingsActivity.getSwapMouseButtons(context)) + return (PTRFLAGS_LBUTTON | (down ? PTRFLAGS_DOWN : 0)); + else + return (PTRFLAGS_RBUTTON | (down ? PTRFLAGS_DOWN : 0)); + } + + public static int getMoveEvent() + { + return PTRFLAGS_MOVE; + } + + public static int getScrollEvent(Context context, boolean down) + { + int flags = PTRFLAGS_WHEEL; + + // invert scrolling? + if (ApplicationSettingsActivity.getInvertScrolling(context)) + down = !down; + + if (down) + flags |= (PTRFLAGS_WHEEL_NEGATIVE | 0x0088); + else + flags |= 0x0078; + return flags; + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/RDPFileParser.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/RDPFileParser.java new file mode 100644 index 0000000..f124dbf --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/RDPFileParser.java @@ -0,0 +1,111 @@ +/* + Simple .RDP file parser + + Copyright 2013 Blaz Bacnik + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.utils; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.HashMap; +import java.util.Locale; + +public class RDPFileParser +{ + + private static final int MAX_ERRORS = 20; + private static final int MAX_LINES = 500; + + private HashMap options; + + public RDPFileParser() + { + init(); + } + + public RDPFileParser(String filename) throws IOException + { + init(); + parse(filename); + } + + private void init() + { + options = new HashMap<>(); + } + + public void parse(String filename) throws IOException + { + BufferedReader br = new BufferedReader(new FileReader(filename)); + String line = null; + + int errors = 0; + int lines = 0; + boolean ok; + + while ((line = br.readLine()) != null) + { + lines++; + ok = false; + + if (errors > MAX_ERRORS || lines > MAX_LINES) + { + br.close(); + throw new IOException("Parsing limits exceeded"); + } + + String[] fields = line.split(":", 3); + + if (fields.length == 3) + { + if (fields[1].equals("s")) + { + options.put(fields[0].toLowerCase(Locale.ENGLISH), fields[2]); + ok = true; + } + else if (fields[1].equals("i")) + { + try + { + Integer i = Integer.parseInt(fields[2]); + options.put(fields[0].toLowerCase(Locale.ENGLISH), i); + ok = true; + } + catch (NumberFormatException e) + { + } + } + else if (fields[1].equals("b")) + { + ok = true; + } + } + + if (!ok) + errors++; + } + br.close(); + } + + public String getString(String optionName) + { + if (options.get(optionName) instanceof String) + return (String)options.get(optionName); + else + return null; + } + + public Integer getInteger(String optionName) + { + if (options.get(optionName) instanceof Integer) + return (Integer)options.get(optionName); + else + return null; + } +} diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/SeparatedListAdapter.java b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/SeparatedListAdapter.java new file mode 100644 index 0000000..cf1475c --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/utils/SeparatedListAdapter.java @@ -0,0 +1,208 @@ +/* + Separated List Adapter + Taken from http://jsharkey.org/blog/2008/08/18/separating-lists-with-headers-in-android-09/ + + Copyright Jeff Sharkey + + This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + If a copy of the MPL was not distributed with this file, You can obtain one at + http://mozilla.org/MPL/2.0/. +*/ + +package com.freerdp.freerdpcore.utils; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Adapter; +import android.widget.ArrayAdapter; +import android.widget.BaseAdapter; + +import com.freerdp.freerdpcore.R; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class SeparatedListAdapter extends BaseAdapter +{ + + public final static int TYPE_SECTION_HEADER = 0; + public final Map sections = new LinkedHashMap<>(); + public final ArrayAdapter headers; + + public SeparatedListAdapter(Context context) + { + headers = new ArrayAdapter<>(context, R.layout.list_header); + } + + public void addSection(String section, Adapter adapter) + { + this.headers.add(section); + this.sections.put(section, adapter); + } + + public void setSectionTitle(int section, String title) + { + String oldTitle = this.headers.getItem(section); + + // remove/add to headers array + this.headers.remove(oldTitle); + this.headers.insert(title, section); + + // remove/add to section map + Adapter adapter = this.sections.get(oldTitle); + this.sections.remove(oldTitle); + this.sections.put(title, adapter); + } + + public Object getItem(int position) + { + for (int i = 0; i < headers.getCount(); i++) + { + String section = headers.getItem(i); + Adapter adapter = sections.get(section); + + // ignore empty sections + if (adapter.getCount() > 0) + { + int size = adapter.getCount() + 1; + + // check if position inside this section + if (position == 0) + return section; + if (position < size) + return adapter.getItem(position - 1); + + // otherwise jump into next section + position -= size; + } + } + return null; + } + + public int getCount() + { + // total together all sections, plus one for each section header (except if the section is + // empty) + int total = 0; + for (Adapter adapter : this.sections.values()) + total += ((adapter.getCount() > 0) ? adapter.getCount() + 1 : 0); + return total; + } + + public int getViewTypeCount() + { + // assume that headers count as one, then total all sections + int total = 1; + for (Adapter adapter : this.sections.values()) + total += adapter.getViewTypeCount(); + return total; + } + + public int getItemViewType(int position) + { + int type = 1; + for (int i = 0; i < headers.getCount(); i++) + { + String section = headers.getItem(i); + Adapter adapter = sections.get(section); + + // skip empty sections + if (adapter.getCount() > 0) + { + int size = adapter.getCount() + 1; + + // check if position inside this section + if (position == 0) + return TYPE_SECTION_HEADER; + if (position < size) + return type + adapter.getItemViewType(position - 1); + + // otherwise jump into next section + position -= size; + type += adapter.getViewTypeCount(); + } + } + return -1; + } + + public boolean areAllItemsSelectable() + { + return false; + } + + public boolean isEnabled(int position) + { + return (getItemViewType(position) != TYPE_SECTION_HEADER); + } + + @Override public View getView(int position, View convertView, ViewGroup parent) + { + int sectionnum = 0; + for (int i = 0; i < headers.getCount(); i++) + { + String section = headers.getItem(i); + Adapter adapter = sections.get(section); + + // skip empty sections + if (adapter.getCount() > 0) + { + int size = adapter.getCount() + 1; + + // check if position inside this section + if (position == 0) + return headers.getView(sectionnum, convertView, parent); + if (position < size) + return adapter.getView(position - 1, null, parent); + + // otherwise jump into next section + position -= size; + } + sectionnum++; + } + return null; + } + + @Override public long getItemId(int position) + { + for (int i = 0; i < headers.getCount(); i++) + { + String section = headers.getItem(i); + Adapter adapter = sections.get(section); + if (adapter.getCount() > 0) + { + int size = adapter.getCount() + 1; + + // check if position inside this section + if (position < size) + return adapter.getItemId(position - 1); + + // otherwise jump into next section + position -= size; + } + } + return -1; + } + + public String getSectionForPosition(int position) + { + int curPos = 0; + for (int i = 0; i < headers.getCount(); i++) + { + String section = headers.getItem(i); + Adapter adapter = sections.get(section); + if (adapter.getCount() > 0) + { + int size = adapter.getCount() + 1; + + // check if position inside this section + if (position >= curPos && position < (curPos + size)) + return section; + + // otherwise jump into next section + curPos += size; + } + } + return null; + } +} \ No newline at end of file diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_button_add.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_button_add.png new file mode 100644 index 0000000..19104c8 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_button_add.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_edittext_clear.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_edittext_clear.png new file mode 100644 index 0000000..ae18557 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_edittext_clear.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_edittext_search.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_edittext_search.png new file mode 100644 index 0000000..2283a91 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_edittext_search.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_launcher_freerdp.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_launcher_freerdp.png new file mode 100644 index 0000000..ff31f25 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_launcher_freerdp.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_about.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_about.png new file mode 100644 index 0000000..43cf97c Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_about.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_add.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_add.png new file mode 100644 index 0000000..46b50d6 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_add.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_close.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_close.png new file mode 100644 index 0000000..82d4170 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_close.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_disconnect.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_disconnect.png new file mode 100644 index 0000000..192c57b Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_disconnect.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_ext_keyboard.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_ext_keyboard.png new file mode 100644 index 0000000..2ed0352 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_ext_keyboard.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_help.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_help.png new file mode 100644 index 0000000..8ba2751 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_help.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_preferences.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_preferences.png new file mode 100644 index 0000000..d6ea16c Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_preferences.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_settings.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_settings.png new file mode 100644 index 0000000..8dcef3e Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_settings.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_sys_keyboard.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_sys_keyboard.png new file mode 100644 index 0000000..3d824d3 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_sys_keyboard.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_touch_pointer.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_touch_pointer.png new file mode 100644 index 0000000..121b545 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_menu_touch_pointer.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_star_off.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_star_off.png new file mode 100644 index 0000000..eeebc09 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_star_off.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_star_on.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_star_on.png new file mode 100644 index 0000000..e6fef76 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/icon_star_on.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/search_plate.9.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/search_plate.9.png new file mode 100644 index 0000000..8ab6ad7 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/search_plate.9.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/sym_keyboard_delete.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/sym_keyboard_delete.png new file mode 100644 index 0000000..104c1b9 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/sym_keyboard_delete.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/sym_keyboard_feedback_delete.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/sym_keyboard_feedback_delete.png new file mode 100644 index 0000000..a6d8733 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/sym_keyboard_feedback_delete.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/sym_keyboard_feedback_return.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/sym_keyboard_feedback_return.png new file mode 100644 index 0000000..19b33da Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/sym_keyboard_feedback_return.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/sym_keyboard_return.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/sym_keyboard_return.png new file mode 100644 index 0000000..cf2b963 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-hdpi/sym_keyboard_return.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_button_add.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_button_add.png new file mode 100644 index 0000000..312f5f9 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_button_add.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_edittext_search.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_edittext_search.png new file mode 100644 index 0000000..421c3e8 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_edittext_search.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_launcher_freerdp.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_launcher_freerdp.png new file mode 100644 index 0000000..49726f4 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_launcher_freerdp.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_about.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_about.png new file mode 100644 index 0000000..d0c26e2 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_about.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_add.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_add.png new file mode 100644 index 0000000..2eaa369 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_add.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_disconnect.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_disconnect.png new file mode 100644 index 0000000..99c1ad4 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_disconnect.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_exit.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_exit.png new file mode 100644 index 0000000..90a813e Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_exit.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_ext_keyboard.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_ext_keyboard.png new file mode 100644 index 0000000..02aeed3 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_ext_keyboard.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_help.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_help.png new file mode 100644 index 0000000..5e4e17a Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_help.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_preferences.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_preferences.png new file mode 100644 index 0000000..34beda8 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_preferences.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_settings.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_settings.png new file mode 100644 index 0000000..61a2fdd Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_settings.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_sys_keyboard.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_sys_keyboard.png new file mode 100644 index 0000000..2fb6887 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_sys_keyboard.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_touch_pointer.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_touch_pointer.png new file mode 100644 index 0000000..066ce3f Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_menu_touch_pointer.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_star_off.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_star_off.png new file mode 100644 index 0000000..b0dce0e Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_star_off.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_star_on.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_star_on.png new file mode 100644 index 0000000..b09dec7 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/icon_star_on.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/search_plate.9.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/search_plate.9.png new file mode 100644 index 0000000..8347635 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/search_plate.9.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/sym_keyboard_delete.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/sym_keyboard_delete.png new file mode 100644 index 0000000..07f95d5 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/sym_keyboard_delete.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/sym_keyboard_feedback_delete.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/sym_keyboard_feedback_delete.png new file mode 100644 index 0000000..064a2c2 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/sym_keyboard_feedback_delete.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/sym_keyboard_feedback_return.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/sym_keyboard_feedback_return.png new file mode 100644 index 0000000..7e7b3de Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/sym_keyboard_feedback_return.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/sym_keyboard_return.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/sym_keyboard_return.png new file mode 100644 index 0000000..1d8a4ea Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-ldpi/sym_keyboard_return.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_button_add.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_button_add.png new file mode 100644 index 0000000..33c42e0 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_button_add.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_edittext_clear.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_edittext_clear.png new file mode 100644 index 0000000..ae18557 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_edittext_clear.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_edittext_search.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_edittext_search.png new file mode 100644 index 0000000..60112c1 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_edittext_search.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_launcher_freerdp.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_launcher_freerdp.png new file mode 100644 index 0000000..6b18c0a Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_launcher_freerdp.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_about.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_about.png new file mode 100644 index 0000000..04442c8 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_about.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_add.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_add.png new file mode 100644 index 0000000..014ad59 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_add.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_disconnect.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_disconnect.png new file mode 100644 index 0000000..9a78d64 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_disconnect.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_exit.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_exit.png new file mode 100644 index 0000000..7213223 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_exit.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_ext_keyboard.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_ext_keyboard.png new file mode 100644 index 0000000..f8a055b Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_ext_keyboard.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_help.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_help.png new file mode 100644 index 0000000..36041a5 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_help.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_preferences.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_preferences.png new file mode 100644 index 0000000..3a297aa Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_preferences.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_settings.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_settings.png new file mode 100644 index 0000000..f78c41f Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_settings.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_sys_keyboard.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_sys_keyboard.png new file mode 100644 index 0000000..a7d5585 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_sys_keyboard.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_touch_pointer.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_touch_pointer.png new file mode 100644 index 0000000..b76585a Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_menu_touch_pointer.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_star_off.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_star_off.png new file mode 100644 index 0000000..f2cafb5 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_star_off.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_star_on.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_star_on.png new file mode 100644 index 0000000..f203ce2 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/icon_star_on.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/search_plate.9.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/search_plate.9.png new file mode 100644 index 0000000..144481c Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/search_plate.9.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/sym_keyboard_delete.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/sym_keyboard_delete.png new file mode 100644 index 0000000..98a2bc2 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/sym_keyboard_delete.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/sym_keyboard_feedback_delete.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/sym_keyboard_feedback_delete.png new file mode 100644 index 0000000..617d4e9 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/sym_keyboard_feedback_delete.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/sym_keyboard_feedback_return.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/sym_keyboard_feedback_return.png new file mode 100644 index 0000000..af0fea3 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/sym_keyboard_feedback_return.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/sym_keyboard_return.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/sym_keyboard_return.png new file mode 100644 index 0000000..27e26fc Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable-mdpi/sym_keyboard_return.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/button_background.xml b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/button_background.xml new file mode 100644 index 0000000..a6aeb24 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/button_background.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/icon_button_cancel.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/icon_button_cancel.png new file mode 100644 index 0000000..99c1ad4 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/icon_button_cancel.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/icon_launcher_freerdp.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/icon_launcher_freerdp.png new file mode 100644 index 0000000..53c5b36 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/icon_launcher_freerdp.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/separator_background.xml b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/separator_background.xml new file mode 100644 index 0000000..4cd72ac --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/separator_background.xml @@ -0,0 +1,19 @@ + + + + + + + + + \ No newline at end of file diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_arrows.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_arrows.png new file mode 100644 index 0000000..ec6959c Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_arrows.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_arrows_black.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_arrows_black.png new file mode 100644 index 0000000..53dc892 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_arrows_black.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_down_arrow.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_down_arrow.png new file mode 100644 index 0000000..331fea5 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_down_arrow.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_down_arrow_black.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_down_arrow_black.png new file mode 100644 index 0000000..a17bcd7 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_down_arrow_black.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_left_arrow.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_left_arrow.png new file mode 100644 index 0000000..0e67f89 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_left_arrow.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_left_arrow_black.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_left_arrow_black.png new file mode 100644 index 0000000..afc2b5c Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_left_arrow_black.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_menu.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_menu.png new file mode 100644 index 0000000..b296f33 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_menu.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_menu_black.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_menu_black.png new file mode 100644 index 0000000..fe5c072 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_menu_black.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_right_arrow.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_right_arrow.png new file mode 100644 index 0000000..d44ae11 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_right_arrow.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_right_arrow_black.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_right_arrow_black.png new file mode 100644 index 0000000..25c011b Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_right_arrow_black.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_up_arrow.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_up_arrow.png new file mode 100644 index 0000000..ca62134 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_up_arrow.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_up_arrow_black.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_up_arrow_black.png new file mode 100644 index 0000000..96577f4 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_up_arrow_black.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_winkey.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_winkey.png new file mode 100644 index 0000000..8abbd6c Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_winkey.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_winkey_black.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_winkey_black.png new file mode 100644 index 0000000..778ad4f Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/sym_keyboard_winkey_black.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_active.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_active.png new file mode 100644 index 0000000..ac498e4 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_active.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_default.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_default.png new file mode 100644 index 0000000..98f9f5a Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_default.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_extkeyboard.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_extkeyboard.png new file mode 100644 index 0000000..f1fff6d Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_extkeyboard.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_keyboard.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_keyboard.png new file mode 100644 index 0000000..08880bb Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_keyboard.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_lclick.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_lclick.png new file mode 100644 index 0000000..90f2b1b Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_lclick.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_rclick.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_rclick.png new file mode 100644 index 0000000..dcb6904 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_rclick.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_reset.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_reset.png new file mode 100644 index 0000000..b34b02e Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_reset.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_scroll.png b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_scroll.png new file mode 100644 index 0000000..c5275a4 Binary files /dev/null and b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/drawable/touch_pointer_scroll.png differ diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/layout/activity_about.xml b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/layout/activity_about.xml new file mode 100644 index 0000000..1e22849 --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/layout/activity_about.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/layout/bookmark_list_item.xml b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/layout/bookmark_list_item.xml new file mode 100644 index 0000000..913b3ef --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/layout/bookmark_list_item.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + diff --git a/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/layout/button_preference.xml b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/layout/button_preference.xml new file mode 100644 index 0000000..407f3ee --- /dev/null +++ b/third_party/FreeRDP/client/Android/Studio/freeRDPCore/src/main/res/layout/button_preference.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/third_party/FreeRDP/client/Mac/Clipboard.h b/third_party/FreeRDP/client/Mac/Clipboard.h new file mode 100644 index 0000000..5883d3b --- /dev/null +++ b/third_party/FreeRDP/client/Mac/Clipboard.h @@ -0,0 +1,31 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * 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. + */ + +#import "mfreerdp.h" +#import "mf_client.h" + +#import "freerdp/freerdp.h" +#import "freerdp/channels/channels.h" +#import "freerdp/client/cliprdr.h" + +int mac_cliprdr_send_client_format_list(CliprdrClientContext* cliprdr); + +void mac_cliprdr_init(mfContext* mfc, CliprdrClientContext* cliprdr); +void mac_cliprdr_uninit(mfContext* mfc, CliprdrClientContext* cliprdr); diff --git a/third_party/FreeRDP/client/Mac/Clipboard.m b/third_party/FreeRDP/client/Mac/Clipboard.m new file mode 100644 index 0000000..1562f32 --- /dev/null +++ b/third_party/FreeRDP/client/Mac/Clipboard.m @@ -0,0 +1,433 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * 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. + */ + +#import "Clipboard.h" +#import "MRDPView.h" + +int mac_cliprdr_send_client_format_list(CliprdrClientContext *cliprdr) +{ + UINT32 formatId; + UINT32 numFormats; + UINT32 *pFormatIds; + const char *formatName; + CLIPRDR_FORMAT *formats; + CLIPRDR_FORMAT_LIST formatList = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(cliprdr); + mfContext *mfc = (mfContext *)cliprdr->custom; + WINPR_ASSERT(mfc); + + pFormatIds = nullptr; + numFormats = ClipboardGetFormatIds(mfc->clipboard, &pFormatIds); + + formats = (CLIPRDR_FORMAT *)calloc(numFormats, sizeof(CLIPRDR_FORMAT)); + + if (!formats) + return -1; + + for (UINT32 index = 0; index < numFormats; index++) + { + formatId = pFormatIds[index]; + formatName = ClipboardGetFormatName(mfc->clipboard, formatId); + + formats[index].formatId = formatId; + formats[index].formatName = nullptr; + + if ((formatId > CF_MAX) && formatName) + formats[index].formatName = _strdup(formatName); + } + + formatList.common.msgFlags = 0; + formatList.numFormats = numFormats; + formatList.formats = formats; + formatList.common.msgType = CB_FORMAT_LIST; + + mfc->cliprdr->ClientFormatList(mfc->cliprdr, &formatList); + + for (UINT32 index = 0; index < numFormats; index++) + { + free(formats[index].formatName); + } + + free(pFormatIds); + free(formats); + + return 1; +} + +static int mac_cliprdr_send_client_format_list_response(CliprdrClientContext *cliprdr, BOOL status) +{ + CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse; + + formatListResponse.common.msgType = CB_FORMAT_LIST_RESPONSE; + formatListResponse.common.msgFlags = status ? CB_RESPONSE_OK : CB_RESPONSE_FAIL; + formatListResponse.common.dataLen = 0; + + cliprdr->ClientFormatListResponse(cliprdr, &formatListResponse); + + return 1; +} + +static UINT mac_cliprdr_send_client_format_data_request(CliprdrClientContext *cliprdr, + UINT32 formatId) +{ + CLIPRDR_FORMAT_DATA_REQUEST formatDataRequest = WINPR_C_ARRAY_INIT; + WINPR_ASSERT(cliprdr); + + if (formatId == 0) + return CHANNEL_RC_OK; + + mfContext *mfc = (mfContext *)cliprdr->custom; + WINPR_ASSERT(mfc); + + formatDataRequest.common.msgType = CB_FORMAT_DATA_REQUEST; + formatDataRequest.common.msgFlags = 0; + + formatDataRequest.requestedFormatId = formatId; + mfc->requestedFormatId = formatId; + (void)ResetEvent(mfc->clipboardRequestEvent); + + return cliprdr->ClientFormatDataRequest(cliprdr, &formatDataRequest); +} + +static int mac_cliprdr_send_client_capabilities(CliprdrClientContext *cliprdr) +{ + CLIPRDR_CAPABILITIES capabilities; + CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet; + + capabilities.cCapabilitiesSets = 1; + capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET *)&(generalCapabilitySet); + + generalCapabilitySet.capabilitySetType = CB_CAPSTYPE_GENERAL; + generalCapabilitySet.capabilitySetLength = 12; + + generalCapabilitySet.version = CB_CAPS_VERSION_2; + generalCapabilitySet.generalFlags = CB_USE_LONG_FORMAT_NAMES; + + cliprdr->ClientCapabilities(cliprdr, &capabilities); + + return 1; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT mac_cliprdr_monitor_ready(CliprdrClientContext *cliprdr, + const CLIPRDR_MONITOR_READY *monitorReady) +{ + mfContext *mfc = (mfContext *)cliprdr->custom; + + mfc->clipboardSync = TRUE; + mac_cliprdr_send_client_capabilities(cliprdr); + mac_cliprdr_send_client_format_list(cliprdr); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT mac_cliprdr_server_capabilities(CliprdrClientContext *cliprdr, + const CLIPRDR_CAPABILITIES *capabilities) +{ + CLIPRDR_CAPABILITY_SET *capabilitySet; + mfContext *mfc = (mfContext *)cliprdr->custom; + + for (UINT32 index = 0; index < capabilities->cCapabilitiesSets; index++) + { + capabilitySet = &(capabilities->capabilitySets[index]); + + if ((capabilitySet->capabilitySetType == CB_CAPSTYPE_GENERAL) && + (capabilitySet->capabilitySetLength >= CB_CAPSTYPE_GENERAL_LEN)) + { + CLIPRDR_GENERAL_CAPABILITY_SET *generalCapabilitySet = + (CLIPRDR_GENERAL_CAPABILITY_SET *)capabilitySet; + + mfc->clipboardCapabilities = generalCapabilitySet->generalFlags; + break; + } + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT mac_cliprdr_server_format_list(CliprdrClientContext *cliprdr, + const CLIPRDR_FORMAT_LIST *formatList) +{ + WINPR_ASSERT(cliprdr); + + mfContext *mfc = (mfContext *)cliprdr->custom; + WINPR_ASSERT(mfc); + + if (mfc->serverFormats) + { + for (UINT32 index = 0; index < mfc->numServerFormats; index++) + { + free(mfc->serverFormats[index].formatName); + } + + free(mfc->serverFormats); + mfc->serverFormats = nullptr; + mfc->numServerFormats = 0; + } + + if (formatList->numFormats < 1) + return CHANNEL_RC_OK; + + mfc->numServerFormats = formatList->numFormats; + mfc->serverFormats = (CLIPRDR_FORMAT *)calloc(mfc->numServerFormats, sizeof(CLIPRDR_FORMAT)); + + if (!mfc->serverFormats) + return CHANNEL_RC_NO_MEMORY; + + for (UINT32 index = 0; index < mfc->numServerFormats; index++) + { + mfc->serverFormats[index].formatId = formatList->formats[index].formatId; + mfc->serverFormats[index].formatName = nullptr; + + if (formatList->formats[index].formatName) + mfc->serverFormats[index].formatName = _strdup(formatList->formats[index].formatName); + } + + mac_cliprdr_send_client_format_list_response(cliprdr, TRUE); + + uint32_t formatId = 0; + for (UINT32 index = 0; index < mfc->numServerFormats; index++) + { + const CLIPRDR_FORMAT *format = &(mfc->serverFormats[index]); + + if (format->formatId == CF_UNICODETEXT) + formatId = format->formatId; + else if (format->formatId == CF_OEMTEXT) + { + if (formatId == 0) + formatId == CF_OEMTEXT; + } + else if (format->formatId == CF_TEXT) + { + if (formatId == 0) + formatId == CF_TEXT; + } + } + + return mac_cliprdr_send_client_format_data_request(cliprdr, formatId); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +mac_cliprdr_server_format_list_response(CliprdrClientContext *cliprdr, + const CLIPRDR_FORMAT_LIST_RESPONSE *formatListResponse) +{ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +mac_cliprdr_server_lock_clipboard_data(CliprdrClientContext *cliprdr, + const CLIPRDR_LOCK_CLIPBOARD_DATA *lockClipboardData) +{ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +mac_cliprdr_server_unlock_clipboard_data(CliprdrClientContext *cliprdr, + const CLIPRDR_UNLOCK_CLIPBOARD_DATA *unlockClipboardData) +{ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +mac_cliprdr_server_format_data_request(CliprdrClientContext *cliprdr, + const CLIPRDR_FORMAT_DATA_REQUEST *formatDataRequest) +{ + BYTE *data; + UINT32 size; + UINT32 formatId; + CLIPRDR_FORMAT_DATA_RESPONSE response = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(cliprdr); + + mfContext *mfc = (mfContext *)cliprdr->custom; + WINPR_ASSERT(mfc); + + formatId = formatDataRequest->requestedFormatId; + data = (BYTE *)ClipboardGetData(mfc->clipboard, formatId, &size); + + response.common.msgFlags = CB_RESPONSE_OK; + response.common.dataLen = size; + response.requestedFormatData = data; + + if (!data) + { + response.common.msgFlags = CB_RESPONSE_FAIL; + response.common.dataLen = 0; + response.requestedFormatData = nullptr; + } + + cliprdr->ClientFormatDataResponse(cliprdr, &response); + + free(data); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +mac_cliprdr_server_format_data_response(CliprdrClientContext *cliprdr, + const CLIPRDR_FORMAT_DATA_RESPONSE *formatDataResponse) +{ + UINT32 formatId; + CLIPRDR_FORMAT *format = nullptr; + mfContext *mfc = (mfContext *)cliprdr->custom; + MRDPView *view = (MRDPView *)mfc->view; + + if (formatDataResponse->common.msgFlags & CB_RESPONSE_FAIL) + { + (void)SetEvent(mfc->clipboardRequestEvent); + return ERROR_INTERNAL_ERROR; + } + + for (UINT32 index = 0; index < mfc->numServerFormats; index++) + { + if (mfc->requestedFormatId == mfc->serverFormats[index].formatId) + format = &(mfc->serverFormats[index]); + } + + if (!format) + { + (void)SetEvent(mfc->clipboardRequestEvent); + return ERROR_INTERNAL_ERROR; + } + + if (format->formatName) + formatId = ClipboardRegisterFormat(mfc->clipboard, format->formatName); + else + formatId = format->formatId; + + const size_t size = formatDataResponse->common.dataLen; + + ClipboardSetData(mfc->clipboard, formatId, formatDataResponse->requestedFormatData, size); + + (void)SetEvent(mfc->clipboardRequestEvent); + + if ((formatId == CF_TEXT) || (formatId == CF_OEMTEXT) || (formatId == CF_UNICODETEXT)) + { + formatId = ClipboardRegisterFormat(mfc->clipboard, "text/plain"); + + UINT32 dstSize = 0; + char *data = ClipboardGetData(mfc->clipboard, formatId, &dstSize); + + dstSize = strnlen(data, dstSize); /* we need the size without the null terminator */ + + NSString *str = [[NSString alloc] initWithBytes:(void *)data + length:dstSize + encoding:NSUTF8StringEncoding]; + free(data); + + NSArray *types = [[NSArray alloc] initWithObjects:NSPasteboardTypeString, nil]; + [view->pasteboard_wr declareTypes:types owner:view]; + [view->pasteboard_wr setString:str forType:NSPasteboardTypeString]; + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +mac_cliprdr_server_file_contents_request(CliprdrClientContext *cliprdr, + const CLIPRDR_FILE_CONTENTS_REQUEST *fileContentsRequest) +{ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT mac_cliprdr_server_file_contents_response( + CliprdrClientContext *cliprdr, const CLIPRDR_FILE_CONTENTS_RESPONSE *fileContentsResponse) +{ + return CHANNEL_RC_OK; +} + +void mac_cliprdr_init(mfContext *mfc, CliprdrClientContext *cliprdr) +{ + cliprdr->custom = (void *)mfc; + mfc->cliprdr = cliprdr; + + mfc->clipboard = ClipboardCreate(); + mfc->clipboardRequestEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + + cliprdr->MonitorReady = mac_cliprdr_monitor_ready; + cliprdr->ServerCapabilities = mac_cliprdr_server_capabilities; + cliprdr->ServerFormatList = mac_cliprdr_server_format_list; + cliprdr->ServerFormatListResponse = mac_cliprdr_server_format_list_response; + cliprdr->ServerLockClipboardData = mac_cliprdr_server_lock_clipboard_data; + cliprdr->ServerUnlockClipboardData = mac_cliprdr_server_unlock_clipboard_data; + cliprdr->ServerFormatDataRequest = mac_cliprdr_server_format_data_request; + cliprdr->ServerFormatDataResponse = mac_cliprdr_server_format_data_response; + cliprdr->ServerFileContentsRequest = mac_cliprdr_server_file_contents_request; + cliprdr->ServerFileContentsResponse = mac_cliprdr_server_file_contents_response; +} + +void mac_cliprdr_uninit(mfContext *mfc, CliprdrClientContext *cliprdr) +{ + cliprdr->custom = nullptr; + mfc->cliprdr = nullptr; + + ClipboardDestroy(mfc->clipboard); + (void)CloseHandle(mfc->clipboardRequestEvent); +} diff --git a/third_party/FreeRDP/client/Mac/Credits.rtf b/third_party/FreeRDP/client/Mac/Credits.rtf new file mode 100644 index 0000000..41b9f40 --- /dev/null +++ b/third_party/FreeRDP/client/Mac/Credits.rtf @@ -0,0 +1,21 @@ +{\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf320 +{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\vieww9600\viewh8400\viewkind0 +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720 + +\f0\b\fs24 \cf0 Engineering: +\b0 \ + Jay sorg\ + Marc-Andre Moreau\ + Vic Lee\ + Otvaio Salvador \ + Laxmikant Rashinkar\ + and others\ +\ + +\b Human Interface Design: +\b0 \ + Laxmikant Rashinkar\ + Jay Sorg\ +} \ No newline at end of file diff --git a/third_party/FreeRDP/client/Mac/Info.plist b/third_party/FreeRDP/client/Mac/Info.plist new file mode 100644 index 0000000..fd111db --- /dev/null +++ b/third_party/FreeRDP/client/Mac/Info.plist @@ -0,0 +1,30 @@ + + + + + NSCameraUsageDescription + This application requires camera access to redirect it to the remote host + NSMicrophoneUsageDescription + This application requires microphone access to redirect it to the remote host + CFBundleDevelopmentRegion + English + CFBundleIconFile + + CFBundleIdentifier + FreeRDP.Mac + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + NSPrincipalClass + + + diff --git a/third_party/FreeRDP/client/Mac/Keyboard.h b/third_party/FreeRDP/client/Mac/Keyboard.h new file mode 100644 index 0000000..27efcdf --- /dev/null +++ b/third_party/FreeRDP/client/Mac/Keyboard.h @@ -0,0 +1,27 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * MacFreeRDP + * + * Copyright 2014 Marc-Andre Moreau + * + * 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. + */ + +enum APPLE_KEYBOARD_TYPE +{ + APPLE_KEYBOARD_TYPE_ANSI, + APPLE_KEYBOARD_TYPE_ISO, + APPLE_KEYBOARD_TYPE_JIS +}; + +enum APPLE_KEYBOARD_TYPE mac_detect_keyboard_type(void); diff --git a/third_party/FreeRDP/client/Mac/Keyboard.m b/third_party/FreeRDP/client/Mac/Keyboard.m new file mode 100644 index 0000000..31682d1 --- /dev/null +++ b/third_party/FreeRDP/client/Mac/Keyboard.m @@ -0,0 +1,240 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * MacFreeRDP + * + * Copyright 2014 Marc-Andre Moreau + * + * 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. + */ + +#import "Keyboard.h" + +#include + +#include +#include + +typedef struct +{ + uint32_t ProductId; + enum APPLE_KEYBOARD_TYPE Type; +} APPLE_KEYBOARD_DESC; + +/* VendorID: 0x05AC (Apple, Inc.) */ + +static const APPLE_KEYBOARD_DESC APPLE_KEYBOARDS[] = { + { 0x200, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x201, APPLE_KEYBOARD_TYPE_ANSI }, /* USB Keyboard [Alps or Logitech, M2452] */ + { 0x202, APPLE_KEYBOARD_TYPE_ANSI }, /* Keyboard [ALPS] */ + { 0x203, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x204, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x205, APPLE_KEYBOARD_TYPE_ANSI }, /* Extended Keyboard [Mitsumi] */ + { 0x206, APPLE_KEYBOARD_TYPE_ANSI }, /* Extended Keyboard [Mitsumi] */ + { 0x207, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x208, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x209, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x20A, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x20B, APPLE_KEYBOARD_TYPE_ANSI }, /* Pro Keyboard [Mitsumi, A1048/US layout] */ + { 0x20C, APPLE_KEYBOARD_TYPE_ANSI }, /* Extended Keyboard [Mitsumi] */ + { 0x20D, APPLE_KEYBOARD_TYPE_ANSI }, /* Pro Keyboard [Mitsumi, A1048/JIS layout] */ + { 0x20E, APPLE_KEYBOARD_TYPE_ANSI }, /* Internal Keyboard/Trackpad (ANSI) */ + { 0x20F, APPLE_KEYBOARD_TYPE_ISO }, /* Internal Keyboard/Trackpad (ISO) */ + { 0x210, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x211, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x212, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x213, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x214, APPLE_KEYBOARD_TYPE_ANSI }, /* Internal Keyboard/Trackpad (ANSI) */ + { 0x215, APPLE_KEYBOARD_TYPE_ISO }, /* Internal Keyboard/Trackpad (ISO) */ + { 0x216, APPLE_KEYBOARD_TYPE_JIS }, /* Internal Keyboard/Trackpad (JIS) */ + { 0x217, APPLE_KEYBOARD_TYPE_ANSI }, /* Internal Keyboard/Trackpad (ANSI) */ + { 0x218, APPLE_KEYBOARD_TYPE_ISO }, /* Internal Keyboard/Trackpad (ISO) */ + { 0x219, APPLE_KEYBOARD_TYPE_JIS }, /* Internal Keyboard/Trackpad (JIS) */ + { 0x21A, APPLE_KEYBOARD_TYPE_ANSI }, /* Internal Keyboard/Trackpad (ANSI) */ + { 0x21B, APPLE_KEYBOARD_TYPE_ISO }, /* Internal Keyboard/Trackpad (ISO) */ + { 0x21C, APPLE_KEYBOARD_TYPE_JIS }, /* Internal Keyboard/Trackpad (JIS) */ + { 0x21D, APPLE_KEYBOARD_TYPE_ANSI }, /* Aluminum Mini Keyboard (ANSI) */ + { 0x21E, APPLE_KEYBOARD_TYPE_ISO }, /* Aluminum Mini Keyboard (ISO) */ + { 0x21F, APPLE_KEYBOARD_TYPE_JIS }, /* Aluminum Mini Keyboard (JIS) */ + { 0x220, APPLE_KEYBOARD_TYPE_ANSI }, /* Aluminum Keyboard (ANSI) */ + { 0x221, APPLE_KEYBOARD_TYPE_JIS }, /* Aluminum Keyboard (JIS) */ + { 0x222, APPLE_KEYBOARD_TYPE_JIS }, /* Aluminum Keyboard (JIS) */ + { 0x223, APPLE_KEYBOARD_TYPE_ANSI }, /* Internal Keyboard/Trackpad (ANSI) */ + { 0x224, APPLE_KEYBOARD_TYPE_ISO }, /* Internal Keyboard/Trackpad (ISO) */ + { 0x225, APPLE_KEYBOARD_TYPE_JIS }, /* Internal Keyboard/Trackpad (JIS) */ + { 0x226, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x227, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x228, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x229, APPLE_KEYBOARD_TYPE_ANSI }, /* Internal Keyboard/Trackpad (MacBook Pro) (ANSI) */ + { 0x22A, APPLE_KEYBOARD_TYPE_ISO }, /* Internal Keyboard/Trackpad (MacBook Pro) (ISO) */ + { 0x22B, APPLE_KEYBOARD_TYPE_JIS }, /* Internal Keyboard/Trackpad (MacBook Pro) (JIS) */ + { 0x22C, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x22D, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x22E, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x22F, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x230, APPLE_KEYBOARD_TYPE_ANSI }, /* Internal Keyboard/Trackpad (MacBook Pro 4,1) (ANSI) */ + { 0x231, APPLE_KEYBOARD_TYPE_ISO }, /* Internal Keyboard/Trackpad (MacBook Pro 4,1) (ISO) */ + { 0x232, APPLE_KEYBOARD_TYPE_JIS }, /* Internal Keyboard/Trackpad (MacBook Pro 4,1) (JIS) */ + { 0x233, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x234, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x235, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x236, APPLE_KEYBOARD_TYPE_ANSI }, /* Internal Keyboard/Trackpad (ANSI) */ + { 0x237, APPLE_KEYBOARD_TYPE_ISO }, /* Internal Keyboard/Trackpad (ISO) */ + { 0x238, APPLE_KEYBOARD_TYPE_JIS }, /* Internal Keyboard/Trackpad (JIS) */ + { 0x239, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x23A, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x23B, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x23C, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x23D, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x23E, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x23F, APPLE_KEYBOARD_TYPE_ANSI }, /* Internal Keyboard/Trackpad (ANSI) */ + { 0x240, APPLE_KEYBOARD_TYPE_ISO }, /* Internal Keyboard/Trackpad (ISO) */ + { 0x241, APPLE_KEYBOARD_TYPE_JIS }, /* Internal Keyboard/Trackpad (JIS) */ + { 0x242, APPLE_KEYBOARD_TYPE_ANSI }, /* Internal Keyboard/Trackpad (ANSI) */ + { 0x243, APPLE_KEYBOARD_TYPE_ISO }, /* Internal Keyboard/Trackpad (ISO) */ + { 0x244, APPLE_KEYBOARD_TYPE_JIS }, /* Internal Keyboard/Trackpad (JIS) */ + { 0x245, APPLE_KEYBOARD_TYPE_ANSI }, /* Internal Keyboard/Trackpad (ANSI) */ + { 0x246, APPLE_KEYBOARD_TYPE_ISO }, /* Internal Keyboard/Trackpad (ISO) */ + { 0x247, APPLE_KEYBOARD_TYPE_JIS }, /* Internal Keyboard/Trackpad (JIS) */ + { 0x248, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x249, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x24A, APPLE_KEYBOARD_TYPE_ISO }, /* Internal Keyboard/Trackpad (MacBook Air) (ISO) */ + { 0x24B, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x24C, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x24D, APPLE_KEYBOARD_TYPE_ISO }, /* Internal Keyboard/Trackpad (MacBook Air) (ISO) */ + { 0x24E, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x24F, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x250, APPLE_KEYBOARD_TYPE_ISO }, /* Aluminium Keyboard (ISO) */ + { 0x251, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x252, APPLE_KEYBOARD_TYPE_ANSI }, /* Internal Keyboard/Trackpad (ANSI) */ + { 0x253, APPLE_KEYBOARD_TYPE_ISO }, /* Internal Keyboard/Trackpad (ISO) */ + { 0x254, APPLE_KEYBOARD_TYPE_JIS }, /* Internal Keyboard/Trackpad (JIS) */ + { 0x255, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x256, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x257, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x258, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x259, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x25A, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x25B, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x25C, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x25D, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x25E, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x25F, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x260, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x261, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x262, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x263, APPLE_KEYBOARD_TYPE_ANSI }, /* Apple Internal Keyboard / Trackpad (MacBook Retina) */ + { 0x264, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x265, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x266, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x267, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x268, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x269, APPLE_KEYBOARD_TYPE_ANSI }, + { 0x26A, APPLE_KEYBOARD_TYPE_ANSI } +}; + +static enum APPLE_KEYBOARD_TYPE mac_identify_keyboard_type(uint32_t vendorID, uint32_t productID) +{ + enum APPLE_KEYBOARD_TYPE type = APPLE_KEYBOARD_TYPE_ANSI; + + if (vendorID != 0x05AC) /* Apple, Inc. */ + return type; + + if ((productID < 0x200) || (productID > 0x26A)) + return type; + + type = APPLE_KEYBOARDS[productID - 0x200].Type; + return type; +} + +enum APPLE_KEYBOARD_TYPE mac_detect_keyboard_type(void) +{ + CFSetRef deviceCFSetRef = nullptr; + IOHIDDeviceRef inIOHIDDeviceRef = nullptr; + IOHIDManagerRef tIOHIDManagerRef = nullptr; + IOHIDDeviceRef *tIOHIDDeviceRefs = nil; + enum APPLE_KEYBOARD_TYPE type = APPLE_KEYBOARD_TYPE_ANSI; + tIOHIDManagerRef = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + + if (!tIOHIDManagerRef) + return type; + + IOHIDManagerSetDeviceMatching(tIOHIDManagerRef, nullptr); + IOReturn tIOReturn = IOHIDManagerOpen(tIOHIDManagerRef, kIOHIDOptionsTypeNone); + + if (noErr != tIOReturn) + return type; + + deviceCFSetRef = IOHIDManagerCopyDevices(tIOHIDManagerRef); + + if (!deviceCFSetRef) + return type; + + CFIndex deviceIndex, deviceCount = CFSetGetCount(deviceCFSetRef); + tIOHIDDeviceRefs = malloc(sizeof(IOHIDDeviceRef) * deviceCount); + + if (!tIOHIDDeviceRefs) + return type; + + CFSetGetValues(deviceCFSetRef, (const void **)tIOHIDDeviceRefs); + CFRelease(deviceCFSetRef); + deviceCFSetRef = nullptr; + + for (deviceIndex = 0; deviceIndex < deviceCount; deviceIndex++) + { + CFTypeRef tCFTypeRef; + uint32_t vendorID = 0; + uint32_t productID = 0; + uint32_t countryCode = 0; + enum APPLE_KEYBOARD_TYPE ltype; + + if (!tIOHIDDeviceRefs[deviceIndex]) + continue; + + inIOHIDDeviceRef = tIOHIDDeviceRefs[deviceIndex]; + tCFTypeRef = IOHIDDeviceGetProperty(inIOHIDDeviceRef, CFSTR(kIOHIDVendorIDKey)); + + if (tCFTypeRef) + CFNumberGetValue((CFNumberRef)tCFTypeRef, kCFNumberSInt32Type, &vendorID); + + tCFTypeRef = IOHIDDeviceGetProperty(inIOHIDDeviceRef, CFSTR(kIOHIDProductIDKey)); + + if (tCFTypeRef) + CFNumberGetValue((CFNumberRef)tCFTypeRef, kCFNumberSInt32Type, &productID); + + tCFTypeRef = IOHIDDeviceGetProperty(inIOHIDDeviceRef, CFSTR(kIOHIDCountryCodeKey)); + + if (tCFTypeRef) + CFNumberGetValue((CFNumberRef)tCFTypeRef, kCFNumberSInt32Type, &countryCode); + + ltype = mac_identify_keyboard_type(vendorID, productID); + + if (ltype != APPLE_KEYBOARD_TYPE_ANSI) + { + type = ltype; + break; + } + } + + free(tIOHIDDeviceRefs); + + if (deviceCFSetRef) + { + CFRelease(deviceCFSetRef); + deviceCFSetRef = nullptr; + } + + if (tIOHIDManagerRef) + CFRelease(tIOHIDManagerRef); + + return type; +} diff --git a/third_party/FreeRDP/client/Mac/MRDPCursor.h b/third_party/FreeRDP/client/Mac/MRDPCursor.h new file mode 100644 index 0000000..6b16d79 --- /dev/null +++ b/third_party/FreeRDP/client/Mac/MRDPCursor.h @@ -0,0 +1,34 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * MacFreeRDP + * + * Copyright 2012 Thomas Goddard + * + * 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. + */ + +#import + +#include "freerdp/graphics.h" + +@interface MRDPCursor : NSObject +{ + @public + rdpPointer *pointer; + BYTE *cursor_data; + NSBitmapImageRep *bmiRep; + NSCursor *nsCursor; + NSImage *nsImage; +} + +@end diff --git a/third_party/FreeRDP/client/Mac/MRDPCursor.m b/third_party/FreeRDP/client/Mac/MRDPCursor.m new file mode 100644 index 0000000..6df6267 --- /dev/null +++ b/third_party/FreeRDP/client/Mac/MRDPCursor.m @@ -0,0 +1,24 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * MacFreeRDP + * + * Copyright 2012 Thomas Goddard + * + * 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. + */ + +#import "MRDPCursor.h" + +@implementation MRDPCursor + +@end diff --git a/third_party/FreeRDP/client/Mac/MRDPView.h b/third_party/FreeRDP/client/Mac/MRDPView.h new file mode 100644 index 0000000..d7d978f --- /dev/null +++ b/third_party/FreeRDP/client/Mac/MRDPView.h @@ -0,0 +1,91 @@ +#ifndef FREERDP_CLIENT_MAC_MRDPVIEW_H +#define FREERDP_CLIENT_MAC_MRDPVIEW_H + +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * MacFreeRDP + * + * Copyright 2012 Thomas Goddard + * + * 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. + */ + +#import + +#import "mfreerdp.h" +#import "mf_client.h" +#import "Keyboard.h" + +#import + +@interface MRDPView : NSView +{ + mfContext *mfc; + NSBitmapImageRep *bmiRep; + NSMutableArray *cursors; + NSMutableArray *windows; + NSTimer *pasteboard_timer; + NSCursor *currentCursor; + NSRect prevWinPosition; + freerdp *instance; + rdpContext *context; + CGContextRef bitmap_context; + char *pixel_data; + int argc; + char **argv; + DWORD kbdModFlags; + BOOL initialized; + NSPoint savedDragLocation; + BOOL firstCreateWindow; + BOOL isMoveSizeInProgress; + BOOL skipResizeOnce; + BOOL saveInitialDragLoc; + BOOL skipMoveWindowOnce; + @public + NSPasteboard *pasteboard_rd; + NSPasteboard *pasteboard_wr; + int pasteboard_changecount; + int pasteboard_format; + int is_connected; +} + +- (int)rdpStart:(rdpContext *)rdp_context; +- (void)setCursor:(NSCursor *)cursor; +- (void)setScrollOffset:(int)xOffset y:(int)yOffset w:(int)width h:(int)height; + +- (void)onPasteboardTimerFired:(NSTimer *)timer; +- (void)pause; +- (void)resume; +- (void)releaseResources; + +@property(assign) int is_connected; + +@end + +BOOL mac_pre_connect(freerdp *instance); +BOOL mac_post_connect(freerdp *instance); +void mac_post_disconnect(freerdp *instance); +BOOL mac_authenticate_ex(freerdp *instance, char **username, char **password, char **domain, + rdp_auth_reason reason); + +DWORD mac_verify_certificate_ex(freerdp *instance, const char *host, UINT16 port, + const char *common_name, const char *subject, const char *issuer, + const char *fingerprint, DWORD flags); +DWORD mac_verify_changed_certificate_ex(freerdp *instance, const char *host, UINT16 port, + const char *common_name, const char *subject, + const char *issuer, const char *fingerprint, + const char *old_subject, const char *old_issuer, + const char *old_fingerprint, DWORD flags); + +int mac_logon_error_info(freerdp *instance, UINT32 data, UINT32 type); +#endif /* FREERDP_CLIENT_MAC_MRDPVIEW_H */ diff --git a/third_party/FreeRDP/client/Mac/MRDPView.m b/third_party/FreeRDP/client/Mac/MRDPView.m new file mode 100644 index 0000000..2d9a20d --- /dev/null +++ b/third_party/FreeRDP/client/Mac/MRDPView.m @@ -0,0 +1,1542 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * MacFreeRDP + * + * Copyright 2012 Thomas Goddard + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "mf_client.h" +#import "mfreerdp.h" +#import "MRDPView.h" +#import "MRDPCursor.h" +#import "Clipboard.h" +#import "PasswordDialog.h" +#import "CertificateDialog.h" + +#include +#include +#include +#include +#include + +#include + +#import "freerdp/freerdp.h" +#import "freerdp/types.h" +#import "freerdp/config.h" +#import "freerdp/channels/channels.h" +#import "freerdp/gdi/gdi.h" +#import "freerdp/gdi/dc.h" +#import "freerdp/gdi/region.h" +#import "freerdp/graphics.h" +#import "freerdp/client/file.h" +#import "freerdp/client/cmdline.h" +#import "freerdp/log.h" + +#import + +#define TAG CLIENT_TAG("mac") + +static BOOL mf_Pointer_New(rdpContext *context, rdpPointer *pointer); +static void mf_Pointer_Free(rdpContext *context, rdpPointer *pointer); +static BOOL mf_Pointer_Set(rdpContext *context, rdpPointer *pointer); +static BOOL mf_Pointer_SetNull(rdpContext *context); +static BOOL mf_Pointer_SetDefault(rdpContext *context); +static BOOL mf_Pointer_SetPosition(rdpContext *context, UINT32 x, UINT32 y); + +static BOOL mac_begin_paint(rdpContext *context); +static BOOL mac_end_paint(rdpContext *context); +static BOOL mac_desktop_resize(rdpContext *context); + +static void input_activity_cb(freerdp *instance); + +static DWORD WINAPI mac_client_thread(void *param); +static void windows_to_apple_cords(MRDPView *view, NSRect *r); +static CGContextRef mac_create_bitmap_context(rdpContext *context); + +@implementation MRDPView + +@synthesize is_connected; + +- (int)rdpStart:(rdpContext *)rdp_context +{ + rdpSettings *settings; + EmbedWindowEventArgs e; + [self initializeView]; + + WINPR_ASSERT(rdp_context); + context = rdp_context; + mfc = (mfContext *)rdp_context; + + instance = context->instance; + WINPR_ASSERT(instance); + + settings = context->settings; + WINPR_ASSERT(settings); + + EventArgsInit(&e, "mfreerdp"); + e.embed = TRUE; + e.handle = (void *)self; + PubSub_OnEmbedWindow(context->pubSub, context, &e); + NSScreen *screen = [[NSScreen screens] objectAtIndex:0]; + NSRect screenFrame = [screen frame]; + + if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)) + { + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, screenFrame.size.width)) + return -1; + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, screenFrame.size.height)) + return -1; + [self enterFullScreenMode:[NSScreen mainScreen] withOptions:nil]; + } + else + { + [self exitFullScreenModeWithOptions:nil]; + } + + mfc->client_height = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + mfc->client_width = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + + if (!(mfc->common.thread = + CreateThread(nullptr, 0, mac_client_thread, (void *)context, 0, &mfc->mainThreadId))) + { + WLog_ERR(TAG, "failed to create client thread"); + return -1; + } + + return 0; +} + +DWORD WINAPI mac_client_thread(void *param) +{ + @autoreleasepool + { + int status; + DWORD rc; + HANDLE events[16] = WINPR_C_ARRAY_INIT; + HANDLE inputEvent; + DWORD nCount; + DWORD nCountTmp; + DWORD nCountBase; + rdpContext *context = (rdpContext *)param; + mfContext *mfc = (mfContext *)context; + freerdp *instance = context->instance; + MRDPView *view = mfc->view; + rdpSettings *settings = context->settings; + status = freerdp_connect(context->instance); + + if (!status) + { + [view setIs_connected:0]; + return 0; + } + + [view setIs_connected:1]; + nCount = 0; + events[nCount++] = mfc->stopEvent; + + if (!(inputEvent = + freerdp_get_message_queue_event_handle(instance, FREERDP_INPUT_MESSAGE_QUEUE))) + { + WLog_ERR(TAG, "failed to get input event handle"); + goto disconnect; + } + + events[nCount++] = inputEvent; + + nCountBase = nCount; + + while (!freerdp_shall_disconnect_context(instance->context)) + { + nCount = nCountBase; + { + if (!(nCountTmp = freerdp_get_event_handles(context, &events[nCount], 16 - nCount))) + { + WLog_ERR(TAG, "freerdp_get_event_handles failed"); + break; + } + + nCount += nCountTmp; + } + rc = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + + if (rc >= (WAIT_OBJECT_0 + nCount)) + { + WLog_ERR(TAG, "WaitForMultipleObjects failed (0x%08X)", rc); + break; + } + + if (rc == WAIT_OBJECT_0) + { + /* stop event triggered */ + break; + } + + if (WaitForSingleObject(inputEvent, 0) == WAIT_OBJECT_0) + { + input_activity_cb(instance); + } + + { + if (!freerdp_check_event_handles(context)) + { + WLog_ERR(TAG, "freerdp_check_event_handles failed"); + break; + } + } + } + + disconnect: + [view setIs_connected:0]; + freerdp_disconnect(instance); + + ExitThread(0); + return 0; + } +} + +- (id)initWithFrame:(NSRect)frame +{ + self = [super initWithFrame:frame]; + + if (self) + { + // Initialization code here. + } + + return self; +} + +- (void)viewDidLoad +{ + [self initializeView]; +} + +- (void)initializeView +{ + if (!initialized) + { + cursors = [[NSMutableArray alloc] initWithCapacity:10]; + // setup a mouse tracking area + NSTrackingArea *trackingArea = [[NSTrackingArea alloc] + initWithRect:[self visibleRect] + options:NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | + NSTrackingCursorUpdate | NSTrackingEnabledDuringMouseDrag | + NSTrackingActiveWhenFirstResponder + owner:self + userInfo:nil]; + [self addTrackingArea:trackingArea]; + // Set the default cursor + currentCursor = [NSCursor arrowCursor]; + initialized = YES; + } +} + +- (void)setCursor:(NSCursor *)cursor +{ + self->currentCursor = cursor; + dispatch_async(dispatch_get_main_queue(), ^{ + [[self window] invalidateCursorRectsForView:self]; + }); +} + +- (void)resetCursorRects +{ + [self addCursorRect:[self visibleRect] cursor:currentCursor]; +} + +- (BOOL)acceptsFirstResponder +{ + return YES; +} + +- (void)mouseMoved:(NSEvent *)event +{ + [super mouseMoved:event]; + + if (!self.is_connected) + return; + + NSPoint loc = [event locationInWindow]; + int x = (int)loc.x; + int y = (int)loc.y; + mf_scale_mouse_event(context, PTR_FLAGS_MOVE, x, y); +} + +- (void)mouseDown:(NSEvent *)event +{ + [super mouseDown:event]; + + if (!self.is_connected) + return; + + NSPoint loc = [event locationInWindow]; + int x = (int)loc.x; + int y = (int)loc.y; + mf_press_mouse_button(context, 0, x, y, TRUE); +} + +- (void)mouseUp:(NSEvent *)event +{ + [super mouseUp:event]; + + if (!self.is_connected) + return; + + NSPoint loc = [event locationInWindow]; + int x = (int)loc.x; + int y = (int)loc.y; + mf_press_mouse_button(context, 0, x, y, FALSE); +} + +- (void)rightMouseDown:(NSEvent *)event +{ + [super rightMouseDown:event]; + + if (!self.is_connected) + return; + + NSPoint loc = [event locationInWindow]; + int x = (int)loc.x; + int y = (int)loc.y; + mf_press_mouse_button(context, 1, x, y, TRUE); +} + +- (void)rightMouseUp:(NSEvent *)event +{ + [super rightMouseUp:event]; + + if (!self.is_connected) + return; + + NSPoint loc = [event locationInWindow]; + int x = (int)loc.x; + int y = (int)loc.y; + mf_press_mouse_button(context, 1, x, y, FALSE); +} + +- (void)otherMouseDown:(NSEvent *)event +{ + [super otherMouseDown:event]; + + if (!self.is_connected) + return; + + NSPoint loc = [event locationInWindow]; + int x = (int)loc.x; + int y = (int)loc.y; + int pressed = [event buttonNumber]; + mf_press_mouse_button(context, pressed, x, y, TRUE); +} + +- (void)otherMouseUp:(NSEvent *)event +{ + [super otherMouseUp:event]; + + if (!self.is_connected) + return; + + NSPoint loc = [event locationInWindow]; + int x = (int)loc.x; + int y = (int)loc.y; + int pressed = [event buttonNumber]; + mf_press_mouse_button(context, pressed, x, y, FALSE); +} + +- (void)scrollWheel:(NSEvent *)event +{ + UINT16 flags; + [super scrollWheel:event]; + + if (!self.is_connected) + return; + + float dx = [event deltaX]; + float dy = [event deltaY]; + /* 1 event = 120 units */ + UINT16 units = 0; + + if (fabsf(dy) > FLT_EPSILON) + { + flags = PTR_FLAGS_WHEEL; + units = fabsf(dy) * 120; + + if (dy < 0) + flags |= PTR_FLAGS_WHEEL_NEGATIVE; + } + else if (fabsf(dx) > FLT_EPSILON) + { + flags = PTR_FLAGS_HWHEEL; + units = fabsf(dx) * 120; + + if (dx > 0) + flags |= PTR_FLAGS_WHEEL_NEGATIVE; + } + else + return; + + /* Wheel rotation steps: + * + * positive: 0 ... 0xFF -> slow ... fast + * negative: 0 ... 0xFF -> fast ... slow + */ + UINT16 step = units; + if (step > 0xFF) + step = 0xFF; + + /* Negative rotation, so count down steps from top + * 9bit twos complement */ + if (flags & PTR_FLAGS_WHEEL_NEGATIVE) + step = 0x100 - step; + + mf_scale_mouse_event(context, flags | step, 0, 0); +} + +- (void)mouseDragged:(NSEvent *)event +{ + [super mouseDragged:event]; + + if (!self.is_connected) + return; + + NSPoint loc = [event locationInWindow]; + int x = (int)loc.x; + int y = (int)loc.y; + // send mouse motion event to RDP server + mf_scale_mouse_event(context, PTR_FLAGS_MOVE, x, y); +} + +static DWORD fixKeyCode(DWORD keyCode, unichar keyChar, enum APPLE_KEYBOARD_TYPE type) +{ + /** + * In 99% of cases, the given key code is truly keyboard independent. + * This function handles the remaining 1% of edge cases. + * + * Hungarian Keyboard: This is 'QWERTZ' and not 'QWERTY'. + * The '0' key is on the left of the '1' key, where '~' is on a US keyboard. + * A special 'i' letter key with acute is found on the right of the left shift key. + * On the hungarian keyboard, the 'i' key is at the left of the 'Y' key + * Some international keyboards have a corresponding key which would be at + * the left of the 'Z' key when using a QWERTY layout. + * + * The Apple Hungarian keyboard sends inverted key codes for the '0' and 'i' keys. + * When using the US keyboard layout, key codes are left as-is (inverted). + * When using the Hungarian keyboard layout, key codes are swapped (non-inverted). + * This means that when using the Hungarian keyboard layout with a US keyboard, + * the keys corresponding to '0' and 'i' will effectively be inverted. + * + * To fix the '0' and 'i' key inversion, we use the corresponding output character + * provided by OS X and check for a character to key code mismatch: for instance, + * when the output character is '0' for the key code corresponding to the 'i' key. + */ +#if 0 + switch (keyChar) + { + case '0': + case 0x00A7: /* section sign */ + if (keyCode == APPLE_VK_ISO_Section) + keyCode = APPLE_VK_ANSI_Grave; + + break; + + case 0x00ED: /* latin small letter i with acute */ + case 0x00CD: /* latin capital letter i with acute */ + if (keyCode == APPLE_VK_ANSI_Grave) + keyCode = APPLE_VK_ISO_Section; + + break; + } + +#endif + + /* Perform keycode correction for all ISO keyboards */ + + if (type == APPLE_KEYBOARD_TYPE_ISO) + { + if (keyCode == APPLE_VK_ANSI_Grave) + keyCode = APPLE_VK_ISO_Section; + else if (keyCode == APPLE_VK_ISO_Section) + keyCode = APPLE_VK_ANSI_Grave; + } + + return keyCode; +} + +- (void)flagsChanged:(NSEvent *)event +{ + if (!is_connected) + return; + + DWORD modFlags = [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask; + + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->context); + + rdpInput *input = instance->context->input; + +#if defined(WITH_DEBUG_KBD) + WLog_DBG(TAG, "flagsChanged: modFlags: 0x%04X kbdModFlags: 0x%04X", modFlags, kbdModFlags); +#endif + + updateFlagStates(input, modFlags, kbdModFlags); + kbdModFlags = modFlags; +} + +- (void)keyDown:(NSEvent *)event +{ + DWORD keyCode; + DWORD keyFlags; + DWORD vkcode; + DWORD scancode; + unichar keyChar; + NSString *characters; + + if (!is_connected) + return; + + [self flagsChanged:event]; + + keyFlags = KBD_FLAGS_DOWN; + keyCode = [event keyCode]; + characters = [event charactersIgnoringModifiers]; + + if ([characters length] > 0) + { + keyChar = [characters characterAtIndex:0]; + keyCode = fixKeyCode(keyCode, keyChar, mfc->appleKeyboardType); + } + + vkcode = GetVirtualKeyCodeFromKeycode(keyCode, WINPR_KEYCODE_TYPE_APPLE); + scancode = GetVirtualScanCodeFromVirtualKeyCode(vkcode, 4); + keyFlags |= (scancode & KBDEXT) ? KBDEXT : 0; + scancode &= 0xFF; + vkcode &= 0xFF; + +#if defined(WITH_DEBUG_KBD) + WLog_DBG(TAG, "keyDown: keyCode: 0x%04X scancode: 0x%04X vkcode: 0x%04X keyFlags: %d name: %s", + keyCode, scancode, vkcode, keyFlags, GetVirtualKeyName(vkcode)); +#endif + + WINPR_ASSERT(instance->context); + freerdp_input_send_keyboard_event(instance->context->input, keyFlags, scancode); +} + +- (void)keyUp:(NSEvent *)event +{ + DWORD keyCode; + DWORD keyFlags; + DWORD vkcode; + DWORD scancode; + unichar keyChar; + NSString *characters; + + if (!is_connected) + return; + + [self flagsChanged:event]; + + keyFlags = KBD_FLAGS_RELEASE; + keyCode = [event keyCode]; + characters = [event charactersIgnoringModifiers]; + + if ([characters length] > 0) + { + keyChar = [characters characterAtIndex:0]; + keyCode = fixKeyCode(keyCode, keyChar, mfc->appleKeyboardType); + } + + vkcode = GetVirtualKeyCodeFromKeycode(keyCode, WINPR_KEYCODE_TYPE_APPLE); + scancode = GetVirtualScanCodeFromVirtualKeyCode(vkcode, 4); + keyFlags |= (scancode & KBDEXT) ? KBDEXT : 0; + scancode &= 0xFF; + vkcode &= 0xFF; +#if defined(WITH_DEBUG_KBD) + WLog_DBG(TAG, "keyUp: key: 0x%04X scancode: 0x%04X vkcode: 0x%04X keyFlags: %d name: %s", + keyCode, scancode, vkcode, keyFlags, GetVirtualKeyName(vkcode)); +#endif + WINPR_ASSERT(instance->context); + freerdp_input_send_keyboard_event(instance->context->input, keyFlags, scancode); +} + +static BOOL updateFlagState(rdpInput *input, DWORD modFlags, DWORD aKbdModFlags, DWORD flag) +{ + BOOL press = ((modFlags & flag) != 0) && ((aKbdModFlags & flag) == 0); + BOOL release = ((modFlags & flag) == 0) && ((aKbdModFlags & flag) != 0); + DWORD keyFlags = 0; + const char *name = nullptr; + DWORD scancode = 0; + + if ((modFlags & flag) == (aKbdModFlags & flag)) + return TRUE; + + switch (flag) + { + case NSEventModifierFlagCapsLock: + name = "NSEventModifierFlagCapsLock"; + scancode = RDP_SCANCODE_CAPSLOCK; + release = press = TRUE; + break; + case NSEventModifierFlagShift: + name = "NSEventModifierFlagShift"; + scancode = RDP_SCANCODE_LSHIFT; + break; + + case NSEventModifierFlagControl: + name = "NSEventModifierFlagControl"; + scancode = RDP_SCANCODE_LCONTROL; + break; + + case NSEventModifierFlagOption: + name = "NSEventModifierFlagOption"; + scancode = RDP_SCANCODE_LMENU; + break; + + case NSEventModifierFlagCommand: + name = "NSEventModifierFlagCommand"; + scancode = RDP_SCANCODE_LWIN; + break; + + case NSEventModifierFlagNumericPad: + name = "NSEventModifierFlagNumericPad"; + scancode = RDP_SCANCODE_NUMLOCK; + release = press = TRUE; + break; + + case NSEventModifierFlagHelp: + name = "NSEventModifierFlagHelp"; + scancode = RDP_SCANCODE_HELP; + break; + + case NSEventModifierFlagFunction: + name = "NSEventModifierFlagFunction"; + scancode = RDP_SCANCODE_HELP; + break; + + default: + WLog_ERR(TAG, "Invalid flag: 0x%08" PRIx32 ", not supported", flag); + return FALSE; + } + + keyFlags = (scancode & KBDEXT); + scancode &= 0xFF; + +#if defined(WITH_DEBUG_KBD) + if (press || release) + WLog_DBG(TAG, "changing flag %s[0x%08" PRIx32 "] to %s", name, flag, + press ? "DOWN" : "RELEASE"); +#endif + + if (press) + { + if (!freerdp_input_send_keyboard_event(input, keyFlags | KBD_FLAGS_DOWN, scancode)) + return FALSE; + } + + if (release) + { + if (!freerdp_input_send_keyboard_event(input, keyFlags | KBD_FLAGS_RELEASE, scancode)) + return FALSE; + } + + return TRUE; +} + +static BOOL updateFlagStates(rdpInput *input, UINT32 modFlags, UINT32 aKbdModFlags) +{ + updateFlagState(input, modFlags, aKbdModFlags, NSEventModifierFlagCapsLock); + updateFlagState(input, modFlags, aKbdModFlags, NSEventModifierFlagShift); + updateFlagState(input, modFlags, aKbdModFlags, NSEventModifierFlagControl); + updateFlagState(input, modFlags, aKbdModFlags, NSEventModifierFlagOption); + updateFlagState(input, modFlags, aKbdModFlags, NSEventModifierFlagCommand); + updateFlagState(input, modFlags, aKbdModFlags, NSEventModifierFlagNumericPad); + return TRUE; +} + +static BOOL releaseFlagStates(rdpInput *input, UINT32 aKbdModFlags) +{ + return updateFlagStates(input, 0, aKbdModFlags); +} + +- (void)releaseResources +{ + for (int i = 0; i < argc; i++) + free(argv[i]); + + if (!is_connected) + return; + + free(pixel_data); +} + +- (void)drawRect:(NSRect)rect +{ + if (!context) + return; + + if (self->bitmap_context) + { + CGContextRef cgContext = [[NSGraphicsContext currentContext] CGContext]; + CGImageRef cgImage = CGBitmapContextCreateImage(self->bitmap_context); + CGContextSaveGState(cgContext); + CGContextClipToRect( + cgContext, CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height)); + CGContextDrawImage(cgContext, + CGRectMake(0, 0, [self bounds].size.width, [self bounds].size.height), + cgImage); + CGContextRestoreGState(cgContext); + CGImageRelease(cgImage); + } + else + { + /* Fill the screen with black */ + [[NSColor blackColor] set]; + NSRectFill([self bounds]); + } +} + +- (void)onPasteboardTimerFired:(NSTimer *)timer +{ + UINT32 formatId; + BOOL formatMatch; + int changeCount; + NSData *formatData; + NSString *formatString; + const char *formatType; + NSPasteboardItem *item; + changeCount = (int)[pasteboard_rd changeCount]; + + if (changeCount == pasteboard_changecount) + return; + + pasteboard_changecount = changeCount; + NSArray *items = [pasteboard_rd pasteboardItems]; + + if ([items count] < 1) + return; + + item = [items objectAtIndex:0]; + /** + * System-Declared Uniform Type Identifiers: + * https://developer.apple.com/library/ios/documentation/Miscellaneous/Reference/UTIRef/Articles/System-DeclaredUniformTypeIdentifiers.html + */ + formatMatch = FALSE; + + for (NSString *type in [item types]) + { + formatType = [type UTF8String]; + + if (strcmp(formatType, "public.utf8-plain-text") == 0) + { + formatData = [item dataForType:type]; + + if (formatData == nil) + { + break; + } + + formatString = [[NSString alloc] initWithData:formatData encoding:NSUTF8StringEncoding]; + + const char *data = [formatString cStringUsingEncoding:NSUTF8StringEncoding]; + const size_t dataLen = [formatString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + formatId = ClipboardRegisterFormat(mfc->clipboard, "text/plain"); + ClipboardSetData(mfc->clipboard, formatId, data, dataLen + 1); + [formatString release]; + + formatMatch = TRUE; + + break; + } + } + + if (!formatMatch) + ClipboardEmpty(mfc->clipboard); + + if (mfc->clipboardSync) + mac_cliprdr_send_client_format_list(mfc->cliprdr); +} + +- (void)pause +{ + dispatch_async(dispatch_get_main_queue(), ^{ + [self->pasteboard_timer invalidate]; + }); + NSArray *trackingAreas = self.trackingAreas; + + for (NSTrackingArea *ta in trackingAreas) + { + [self removeTrackingArea:ta]; + } + releaseFlagStates(instance->context->input, kbdModFlags); + kbdModFlags = 0; +} + +- (void)resume +{ + if (!self.is_connected) + return; + + releaseFlagStates(instance->context->input, kbdModFlags); + kbdModFlags = 0; + freerdp_input_send_focus_in_event(instance->context->input, 0); + + dispatch_async(dispatch_get_main_queue(), ^{ + self->pasteboard_timer = + [NSTimer scheduledTimerWithTimeInterval:0.5 + target:self + selector:@selector(onPasteboardTimerFired:) + userInfo:nil + repeats:YES]; + + NSTrackingArea *trackingArea = [[NSTrackingArea alloc] + initWithRect:[self visibleRect] + options:NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | + NSTrackingCursorUpdate | NSTrackingEnabledDuringMouseDrag | + NSTrackingActiveWhenFirstResponder + owner:self + userInfo:nil]; + [self addTrackingArea:trackingArea]; + [trackingArea release]; + }); +} + +- (void)setScrollOffset:(int)xOffset y:(int)yOffset w:(int)width h:(int)height +{ + WINPR_ASSERT(mfc); + + mfc->yCurrentScroll = yOffset; + mfc->xCurrentScroll = xOffset; + mfc->client_height = height; + mfc->client_width = width; +} + +static void mac_OnChannelConnectedEventHandler(void *context, const ChannelConnectedEventArgs *e) +{ + rdpSettings *settings; + mfContext *mfc = (mfContext *)context; + + WINPR_ASSERT(mfc); + WINPR_ASSERT(e); + + settings = mfc->common.context.settings; + WINPR_ASSERT(settings); + + if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) + { + mac_cliprdr_init(mfc, (CliprdrClientContext *)e->pInterface); + } + else if (strcmp(e->name, ENCOMSP_SVC_CHANNEL_NAME) == 0) + { + } + else + freerdp_client_OnChannelConnectedEventHandler(context, e); +} + +static void mac_OnChannelDisconnectedEventHandler(void *context, + const ChannelDisconnectedEventArgs *e) +{ + rdpSettings *settings; + mfContext *mfc = (mfContext *)context; + + WINPR_ASSERT(mfc); + WINPR_ASSERT(e); + + settings = mfc->common.context.settings; + WINPR_ASSERT(settings); + + if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) + { + mac_cliprdr_uninit(mfc, (CliprdrClientContext *)e->pInterface); + } + else if (strcmp(e->name, ENCOMSP_SVC_CHANNEL_NAME) == 0) + { + } + else + freerdp_client_OnChannelDisconnectedEventHandler(context, e); +} + +BOOL mac_pre_connect(freerdp *instance) +{ + rdpSettings *settings; + rdpUpdate *update; + + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->context); + + update = instance->context->update; + WINPR_ASSERT(update); + + update->BeginPaint = mac_begin_paint; + update->EndPaint = mac_end_paint; + update->DesktopResize = mac_desktop_resize; + + settings = instance->context->settings; + WINPR_ASSERT(settings); + + if (!freerdp_settings_get_string(settings, FreeRDP_ServerHostname)) + { + WLog_ERR(TAG, "error: server hostname was not specified with /v:[:port]"); + return FALSE; + } + + if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMajorType, OSMAJORTYPE_MACINTOSH)) + return FALSE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMinorType, OSMINORTYPE_MACINTOSH)) + return FALSE; + PubSub_SubscribeChannelConnected(instance->context->pubSub, mac_OnChannelConnectedEventHandler); + PubSub_SubscribeChannelDisconnected(instance->context->pubSub, + mac_OnChannelDisconnectedEventHandler); + + return TRUE; +} + +BOOL mac_post_connect(freerdp *instance) +{ + rdpGdi *gdi; + rdpPointer rdp_pointer = WINPR_C_ARRAY_INIT; + mfContext *mfc; + MRDPView *view; + + WINPR_ASSERT(instance); + + mfc = (mfContext *)instance->context; + WINPR_ASSERT(mfc); + + view = (MRDPView *)mfc->view; + WINPR_ASSERT(view); + + rdp_pointer.size = sizeof(rdpPointer); + rdp_pointer.New = mf_Pointer_New; + rdp_pointer.Free = mf_Pointer_Free; + rdp_pointer.Set = mf_Pointer_Set; + rdp_pointer.SetNull = mf_Pointer_SetNull; + rdp_pointer.SetDefault = mf_Pointer_SetDefault; + rdp_pointer.SetPosition = mf_Pointer_SetPosition; + + if (!gdi_init(instance, PIXEL_FORMAT_BGRX32)) + return FALSE; + + gdi = instance->context->gdi; + view->bitmap_context = mac_create_bitmap_context(instance->context); + graphics_register_pointer(instance->context->graphics, &rdp_pointer); + /* setup pasteboard (aka clipboard) for copy operations (write only) */ + view->pasteboard_wr = [NSPasteboard generalPasteboard]; + /* setup pasteboard for read operations */ + dispatch_async(dispatch_get_main_queue(), ^{ + view->pasteboard_rd = [NSPasteboard generalPasteboard]; + view->pasteboard_changecount = -1; + }); + [view resume]; + mfc->appleKeyboardType = mac_detect_keyboard_type(); + return TRUE; +} + +void mac_post_disconnect(freerdp *instance) +{ + mfContext *mfc; + MRDPView *view; + if (!instance || !instance->context) + return; + + mfc = (mfContext *)instance->context; + view = (MRDPView *)mfc->view; + + [view pause]; + + PubSub_UnsubscribeChannelConnected(instance->context->pubSub, + mac_OnChannelConnectedEventHandler); + PubSub_UnsubscribeChannelDisconnected(instance->context->pubSub, + mac_OnChannelDisconnectedEventHandler); + gdi_free(instance); +} + +static BOOL mac_show_auth_dialog(MRDPView *view, NSString *title, char **username, char **password, + char **domain) +{ + WINPR_ASSERT(view); + WINPR_ASSERT(title); + WINPR_ASSERT(username); + WINPR_ASSERT(password); + WINPR_ASSERT(domain); + + PasswordDialog *dialog = [PasswordDialog new]; + + dialog.serverHostname = title; + + if (*username) + dialog.username = [NSString stringWithCString:*username encoding:NSUTF8StringEncoding]; + + if (*password) + dialog.password = [NSString stringWithCString:*password encoding:NSUTF8StringEncoding]; + + if (*domain) + dialog.domain = [NSString stringWithCString:*domain encoding:NSUTF8StringEncoding]; + + free(*username); + free(*password); + free(*domain); + *username = nullptr; + *password = nullptr; + *domain = nullptr; + + dispatch_sync(dispatch_get_main_queue(), ^{ + [dialog performSelectorOnMainThread:@selector(runModal:) + withObject:[view window] + waitUntilDone:TRUE]; + }); + BOOL ok = dialog.modalCode; + + if (ok) + { + const char *submittedUsername = [dialog.username cStringUsingEncoding:NSUTF8StringEncoding]; + const size_t submittedUsernameLen = + [dialog.username lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + if (submittedUsername && (submittedUsernameLen > 0)) + *username = strndup(submittedUsername, submittedUsernameLen); + + if (!(*username)) + return FALSE; + + const char *submittedPassword = [dialog.password cStringUsingEncoding:NSUTF8StringEncoding]; + const size_t submittedPasswordLen = + [dialog.password lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + if (submittedPassword && (submittedPasswordLen > 0)) + *password = strndup(submittedPassword, submittedPasswordLen); + + if (!(*password)) + return FALSE; + + const char *submittedDomain = [dialog.domain cStringUsingEncoding:NSUTF8StringEncoding]; + const size_t submittedDomainLen = + [dialog.domain lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + if (submittedDomain && (submittedDomainLen > 0)) + { + *domain = strndup(submittedDomain, submittedDomainLen); + if (!(*domain)) + return FALSE; + } + } + + return ok; +} + +static BOOL mac_authenticate_raw(freerdp *instance, char **username, char **password, char **domain, + rdp_auth_reason reason) +{ + BOOL pinOnly = FALSE; + + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->context); + WINPR_ASSERT(instance->context->settings); + + const rdpSettings *settings = instance->context->settings; + mfContext *mfc = (mfContext *)instance->context; + MRDPView *view = (MRDPView *)mfc->view; + NSString *title = nullptr; + + switch (reason) + { + case AUTH_SMARTCARD_PIN: + pinOnly = TRUE; + title = [NSString + stringWithFormat:@"%@:%u", + [NSString stringWithCString:freerdp_settings_get_string( + settings, FreeRDP_ServerHostname) + encoding:NSUTF8StringEncoding], + freerdp_settings_get_uint32(settings, FreeRDP_ServerPort)]; + break; + case AUTH_TLS: + case AUTH_RDP: + case AUTH_NLA: + title = [NSString + stringWithFormat:@"%@:%u", + [NSString stringWithCString:freerdp_settings_get_string( + settings, FreeRDP_ServerHostname) + encoding:NSUTF8StringEncoding], + freerdp_settings_get_uint32(settings, FreeRDP_ServerPort)]; + break; + case GW_AUTH_HTTP: + case GW_AUTH_RDG: + case GW_AUTH_RPC: + title = [NSString + stringWithFormat:@"%@:%u", + [NSString stringWithCString:freerdp_settings_get_string( + settings, FreeRDP_GatewayHostname) + encoding:NSUTF8StringEncoding], + freerdp_settings_get_uint32(settings, FreeRDP_GatewayPort)]; + break; + default: + return FALSE; + } + + if (!username || !password || !domain) + return FALSE; + + if (!*username && !pinOnly) + { + if (!mac_show_auth_dialog(view, title, username, password, domain)) + goto fail; + } + else if (!*domain && !pinOnly) + { + if (!mac_show_auth_dialog(view, title, username, password, domain)) + goto fail; + } + else if (!*password) + { + if (!mac_show_auth_dialog(view, title, username, password, domain)) + goto fail; + } + + return TRUE; +fail: + free(*username); + free(*domain); + free(*password); + *username = nullptr; + *domain = nullptr; + *password = nullptr; + return FALSE; +} + +BOOL mac_authenticate_ex(freerdp *instance, char **username, char **password, char **domain, + rdp_auth_reason reason) +{ + WINPR_ASSERT(instance); + WINPR_ASSERT(username); + WINPR_ASSERT(password); + WINPR_ASSERT(domain); + + NSString *title; + switch (reason) + { + case AUTH_NLA: + break; + + case AUTH_TLS: + case AUTH_RDP: + case AUTH_SMARTCARD_PIN: /* in this case password is pin code */ + if ((*username) && (*password)) + return TRUE; + break; + case GW_AUTH_HTTP: + case GW_AUTH_RDG: + case GW_AUTH_RPC: + break; + default: + return FALSE; + } + + return mac_authenticate_raw(instance, username, password, domain, reason); +} + +DWORD mac_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) +{ + mfContext *mfc = (mfContext *)instance->context; + MRDPView *view = (MRDPView *)mfc->view; + CertificateDialog *dialog = [CertificateDialog new]; + const char *type = "RDP-Server"; + char hostname[8192] = WINPR_C_ARRAY_INIT; + + if (flags & VERIFY_CERT_FLAG_GATEWAY) + type = "RDP-Gateway"; + + if (flags & VERIFY_CERT_FLAG_REDIRECT) + type = "RDP-Redirect"; + + sprintf_s(hostname, sizeof(hostname), "%s %s:%" PRIu16, type, host, port); + dialog.serverHostname = [NSString stringWithCString:hostname encoding:NSUTF8StringEncoding]; + dialog.commonName = [NSString stringWithCString:common_name encoding:NSUTF8StringEncoding]; + dialog.subject = [NSString stringWithCString:subject encoding:NSUTF8StringEncoding]; + dialog.issuer = [NSString stringWithCString:issuer encoding:NSUTF8StringEncoding]; + dialog.fingerprint = [NSString stringWithCString:fingerprint encoding:NSUTF8StringEncoding]; + + if (flags & VERIFY_CERT_FLAG_MISMATCH) + dialog.hostMismatch = TRUE; + + if (flags & VERIFY_CERT_FLAG_CHANGED) + dialog.changed = TRUE; + + [dialog performSelectorOnMainThread:@selector(runModal:) + withObject:[view window] + waitUntilDone:TRUE]; + return dialog.result; +} + +DWORD mac_verify_changed_certificate_ex(freerdp *instance, const char *host, UINT16 port, + const char *common_name, const char *subject, + const char *issuer, const char *fingerprint, + const char *old_subject, const char *old_issuer, + const char *old_fingerprint, DWORD flags) +{ + mfContext *mfc = (mfContext *)instance->context; + MRDPView *view = (MRDPView *)mfc->view; + CertificateDialog *dialog = [CertificateDialog new]; + const char *type = "RDP-Server"; + char hostname[8192]; + + if (flags & VERIFY_CERT_FLAG_GATEWAY) + type = "RDP-Gateway"; + + if (flags & VERIFY_CERT_FLAG_REDIRECT) + type = "RDP-Redirect"; + + sprintf_s(hostname, sizeof(hostname), "%s %s:%" PRIu16, type, host, port); + dialog.serverHostname = [NSString stringWithCString:hostname encoding:NSUTF8StringEncoding]; + dialog.commonName = [NSString stringWithCString:common_name encoding:NSUTF8StringEncoding]; + dialog.subject = [NSString stringWithCString:subject encoding:NSUTF8StringEncoding]; + dialog.issuer = [NSString stringWithCString:issuer encoding:NSUTF8StringEncoding]; + dialog.fingerprint = [NSString stringWithCString:fingerprint encoding:NSUTF8StringEncoding]; + + if (flags & VERIFY_CERT_FLAG_MISMATCH) + dialog.hostMismatch = TRUE; + + if (flags & VERIFY_CERT_FLAG_CHANGED) + dialog.changed = TRUE; + + [dialog performSelectorOnMainThread:@selector(runModal:) + withObject:[view window] + waitUntilDone:TRUE]; + return dialog.result; +} + +int mac_logon_error_info(freerdp *instance, UINT32 data, UINT32 type) +{ + const char *str_data = freerdp_get_logon_error_info_data(data); + const char *str_type = freerdp_get_logon_error_info_type(type); + // TODO: Error message dialog + WLog_INFO(TAG, "Logon Error Info %s [%s]", str_data, str_type); + return 1; +} + +BOOL mf_Pointer_New(rdpContext *context, rdpPointer *pointer) +{ + rdpGdi *gdi; + NSRect rect; + NSImage *image; + NSPoint hotSpot; + NSCursor *cursor; + BYTE *cursor_data; + NSMutableArray *ma; + NSBitmapImageRep *bmiRep; + MRDPCursor *mrdpCursor = [[MRDPCursor alloc] init]; + mfContext *mfc = (mfContext *)context; + MRDPView *view; + UINT32 format; + + if (!mfc || !context || !pointer) + return FALSE; + + view = (MRDPView *)mfc->view; + gdi = context->gdi; + + if (!gdi || !view) + return FALSE; + + rect.size.width = pointer->width; + rect.size.height = pointer->height; + rect.origin.x = pointer->xPos; + rect.origin.y = pointer->yPos; + cursor_data = (BYTE *)malloc(rect.size.width * rect.size.height * 4); + + if (!cursor_data) + return FALSE; + + mrdpCursor->cursor_data = cursor_data; + format = PIXEL_FORMAT_RGBA32; + + if (!freerdp_image_copy_from_pointer_data(cursor_data, format, 0, 0, 0, pointer->width, + pointer->height, pointer->xorMaskData, + pointer->lengthXorMask, pointer->andMaskData, + pointer->lengthAndMask, pointer->xorBpp, nullptr)) + { + free(cursor_data); + mrdpCursor->cursor_data = nullptr; + return FALSE; + } + + /* store cursor bitmap image in representation - required by NSImage */ + bmiRep = [[NSBitmapImageRep alloc] + initWithBitmapDataPlanes:(unsigned char **)&cursor_data + pixelsWide:rect.size.width + pixelsHigh:rect.size.height + bitsPerSample:8 + samplesPerPixel:4 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSDeviceRGBColorSpace + bitmapFormat:0 + bytesPerRow:rect.size.width * FreeRDPGetBytesPerPixel(format) + bitsPerPixel:0]; + mrdpCursor->bmiRep = bmiRep; + /* create an image using above representation */ + image = [[NSImage alloc] initWithSize:[bmiRep size]]; + [image addRepresentation:bmiRep]; + mrdpCursor->nsImage = image; + /* need hotspot to create cursor */ + hotSpot.x = pointer->xPos; + hotSpot.y = pointer->yPos; + cursor = [[NSCursor alloc] initWithImage:image hotSpot:hotSpot]; + mrdpCursor->nsCursor = cursor; + mrdpCursor->pointer = pointer; + /* save cursor for later use in mf_Pointer_Set() */ + ma = view->cursors; + [ma addObject:mrdpCursor]; + return TRUE; +} + +void mf_Pointer_Free(rdpContext *context, rdpPointer *pointer) +{ + mfContext *mfc = (mfContext *)context; + MRDPView *view = (MRDPView *)mfc->view; + NSMutableArray *ma = view->cursors; + + for (MRDPCursor *cursor in ma) + { + if (cursor->pointer == pointer) + { + cursor->nsImage = nil; + cursor->nsCursor = nil; + cursor->bmiRep = nil; + free(cursor->cursor_data); + [ma removeObject:cursor]; + return; + } + } +} + +BOOL mf_Pointer_Set(rdpContext *context, rdpPointer *pointer) +{ + mfContext *mfc = (mfContext *)context; + MRDPView *view = (MRDPView *)mfc->view; + NSMutableArray *ma = view->cursors; + + for (MRDPCursor *cursor in ma) + { + if (cursor->pointer == pointer) + { + [view setCursor:cursor->nsCursor]; + return TRUE; + } + } + + NSLog(@"Cursor not found"); + return TRUE; +} + +BOOL mf_Pointer_SetNull(rdpContext *context) +{ + return TRUE; +} + +BOOL mf_Pointer_SetDefault(rdpContext *context) +{ + mfContext *mfc = (mfContext *)context; + MRDPView *view = (MRDPView *)mfc->view; + [view setCursor:[NSCursor arrowCursor]]; + return TRUE; +} + +static BOOL mf_Pointer_SetPosition(rdpContext *context, UINT32 x, UINT32 y) +{ + mfContext *mfc = (mfContext *)context; + + if (!mfc) + return FALSE; + + /* TODO: Set pointer position */ + return TRUE; +} + +CGContextRef mac_create_bitmap_context(rdpContext *context) +{ + CGContextRef bitmap_context; + rdpGdi *gdi = context->gdi; + UINT32 bpp = FreeRDPGetBytesPerPixel(gdi->dstFormat); + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + + if (bpp == 2) + { + bitmap_context = CGBitmapContextCreate( + gdi->primary_buffer, gdi->width, gdi->height, 5, gdi->stride, colorSpace, + kCGBitmapByteOrder16Little | kCGImageAlphaNoneSkipFirst); + } + else + { + bitmap_context = CGBitmapContextCreate( + gdi->primary_buffer, gdi->width, gdi->height, 8, gdi->stride, colorSpace, + kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst); + } + + CGColorSpaceRelease(colorSpace); + return bitmap_context; +} + +BOOL mac_begin_paint(rdpContext *context) +{ + rdpGdi *gdi = context->gdi; + + if (!gdi) + return FALSE; + + gdi->primary->hdc->hwnd->invalid->null = TRUE; + return TRUE; +} + +BOOL mac_end_paint(rdpContext *context) +{ + NSRect newDrawRect; + + if ((!context) || (!context->gdi)) + return FALSE; + + mfContext *mfc = (mfContext *)context; + MRDPView *view = (MRDPView *)mfc->view; + WINPR_ASSERT(view); + + rdpGdi *gdi = context->gdi; + + if (!gdi) + return FALSE; + + const int ww = mfc->client_width; + const int wh = mfc->client_height; + const int dw = freerdp_settings_get_uint32(mfc->common.context.settings, FreeRDP_DesktopWidth); + const int dh = freerdp_settings_get_uint32(mfc->common.context.settings, FreeRDP_DesktopHeight); + + HGDI_DC hdc = gdi->primary->hdc; + WINPR_ASSERT(hdc); + if (!hdc->hwnd) + return TRUE; + + HGDI_WND hwnd = hdc->hwnd; + WINPR_ASSERT(hwnd->invalid || (hwnd->ninvalid == 0)); + + if (hwnd->invalid->null) + return TRUE; + + HGDI_RGN invalid = gdi->primary->hdc->hwnd->invalid; + newDrawRect.origin.x = invalid->x; + newDrawRect.origin.y = invalid->y; + newDrawRect.size.width = invalid->w; + newDrawRect.size.height = invalid->h; + + if (freerdp_settings_get_bool(mfc->common.context.settings, FreeRDP_SmartSizing) && + (ww != dw || wh != dh)) + { + newDrawRect.origin.y = newDrawRect.origin.y * wh / dh - 1; + newDrawRect.size.height = newDrawRect.size.height * wh / dh + 1; + newDrawRect.origin.x = newDrawRect.origin.x * ww / dw - 1; + newDrawRect.size.width = newDrawRect.size.width * ww / dw + 1; + } + else + { + newDrawRect.origin.y = newDrawRect.origin.y - 1; + newDrawRect.size.height = newDrawRect.size.height + 1; + newDrawRect.origin.x = newDrawRect.origin.x - 1; + newDrawRect.size.width = newDrawRect.size.width + 1; + } + + windows_to_apple_cords(mfc->view, &newDrawRect); + dispatch_sync(dispatch_get_main_queue(), ^{ + [view setNeedsDisplayInRect:newDrawRect]; + }); + gdi->primary->hdc->hwnd->ninvalid = 0; + return TRUE; +} + +BOOL mac_desktop_resize(rdpContext *context) +{ + ResizeWindowEventArgs e; + mfContext *mfc = (mfContext *)context; + MRDPView *view = (MRDPView *)mfc->view; + rdpSettings *settings = context->settings; + + if (!context->gdi) + return TRUE; + + /** + * TODO: Fix resizing race condition. We should probably implement a message to be + * put on the update message queue to be able to properly flush pending updates, + * resize, and then continue with post-resizing graphical updates. + */ + CGContextRef old_context = view->bitmap_context; + view->bitmap_context = nullptr; + CGContextRelease(old_context); + mfc->width = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + mfc->height = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + + if (!gdi_resize(context->gdi, mfc->width, mfc->height)) + return FALSE; + + view->bitmap_context = mac_create_bitmap_context(context); + + if (!view->bitmap_context) + return FALSE; + + mfc->client_width = mfc->width; + mfc->client_height = mfc->height; + [view setFrameSize:NSMakeSize(mfc->width, mfc->height)]; + EventArgsInit(&e, "mfreerdp"); + e.width = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + e.height = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + PubSub_OnResizeWindow(context->pubSub, context, &e); + return TRUE; +} + +void input_activity_cb(freerdp *instance) +{ + int status; + wMessage message; + wMessageQueue *queue; + status = 1; + queue = freerdp_get_message_queue(instance, FREERDP_INPUT_MESSAGE_QUEUE); + + if (queue) + { + while (MessageQueue_Peek(queue, &message, TRUE)) + { + status = freerdp_message_queue_process_message(instance, FREERDP_INPUT_MESSAGE_QUEUE, + &message); + + if (!status) + break; + } + } + else + { + WLog_ERR(TAG, "input_activity_cb: No queue!"); + } +} + +/** + * given a rect with 0,0 at the top left (windows cords) + * convert it to a rect with 0,0 at the bottom left (apple cords) + * + * Note: the formula works for conversions in both directions. + * + */ + +void windows_to_apple_cords(MRDPView *view, NSRect *r) +{ + dispatch_sync(dispatch_get_main_queue(), ^{ + r->origin.y = [view frame].size.height - (r->origin.y + r->size.height); + }); +} + +@end diff --git a/third_party/FreeRDP/client/Mac/ModuleOptions.cmake b/third_party/FreeRDP/client/Mac/ModuleOptions.cmake new file mode 100644 index 0000000..10ab91c --- /dev/null +++ b/third_party/FreeRDP/client/Mac/ModuleOptions.cmake @@ -0,0 +1,3 @@ +set(FREERDP_CLIENT_NAME "mfreerdp") +set(FREERDP_CLIENT_PLATFORM "MacOSX") +set(FREERDP_CLIENT_VENDOR "FreeRDP") diff --git a/third_party/FreeRDP/client/Mac/PasswordDialog.h b/third_party/FreeRDP/client/Mac/PasswordDialog.h new file mode 100644 index 0000000..eb24c5c --- /dev/null +++ b/third_party/FreeRDP/client/Mac/PasswordDialog.h @@ -0,0 +1,49 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * MacFreeRDP + * + * Copyright 2013 Christian Hofstaedtler + * + * 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. + */ + +#import + +@interface PasswordDialog : NSWindowController +{ + @public + NSTextField *usernameText; + NSTextField *passwordText; + NSTextField *messageLabel; + NSString *serverHostname; + NSString *username; + NSString *password; + NSString *domain; + BOOL modalCode; +} +@property(retain) IBOutlet NSTextField *usernameText; +@property(retain) IBOutlet NSTextField *passwordText; +@property(retain) IBOutlet NSTextField *messageLabel; + +- (IBAction)onOK:(NSObject *)sender; +- (IBAction)onCancel:(NSObject *)sender; + +@property(retain) NSString *serverHostname; +@property(retain) NSString *username; +@property(retain) NSString *password; +@property(retain) NSString *domain; +@property(readonly) BOOL modalCode; + +- (BOOL)runModal:(NSWindow *)mainWindow; + +@end diff --git a/third_party/FreeRDP/client/Mac/PasswordDialog.m b/third_party/FreeRDP/client/Mac/PasswordDialog.m new file mode 100644 index 0000000..7217794 --- /dev/null +++ b/third_party/FreeRDP/client/Mac/PasswordDialog.m @@ -0,0 +1,140 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * MacFreeRDP + * + * Copyright 2013 Christian Hofstaedtler + * + * 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. + */ + +#import "PasswordDialog.h" +#import + +#import + +@interface PasswordDialog () + +@property BOOL modalCode; + +@end + +@implementation PasswordDialog + +@synthesize usernameText; +@synthesize passwordText; +@synthesize messageLabel; +@synthesize serverHostname; +@synthesize username; +@synthesize password; +@synthesize domain; +@synthesize modalCode; + +- (id)init +{ + return [self initWithWindowNibName:@"PasswordDialog"]; +} + +- (void)windowDidLoad +{ + [super windowDidLoad]; + // Implement this method to handle any initialization after your window controller's window has + // been loaded from its nib file. + [self.window setTitle:self.serverHostname]; + [self.messageLabel + setStringValue:[NSString stringWithFormat:@"Authenticate to %@", self.serverHostname]]; + NSMutableString *domainUser = [[NSMutableString alloc] initWithString:@""]; + + if (self.domain != nil && + [[self.domain stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] + length] > 0) + { + [domainUser appendFormat:@"%@\\", self.domain]; + } + + if (self.username != nil) + { + [domainUser appendString:self.username]; + [self.window makeFirstResponder:self.passwordText]; + } + + [self.usernameText setStringValue:domainUser]; +} + +- (IBAction)onOK:(NSObject *)sender +{ + char *submittedUser = nullptr; + char *submittedDomain = nullptr; + + if (freerdp_parse_username( + [self.usernameText.stringValue cStringUsingEncoding:NSUTF8StringEncoding], + &submittedUser, &submittedDomain)) + { + if (submittedUser) + self.username = [NSString stringWithCString:submittedUser + encoding:NSUTF8StringEncoding]; + if (submittedDomain) + self.domain = [NSString stringWithCString:submittedDomain + encoding:NSUTF8StringEncoding]; + } + else + { + self.username = self.usernameText.stringValue; + } + + self.password = self.passwordText.stringValue; + free(submittedUser); + free(submittedDomain); + [NSApp stopModalWithCode:TRUE]; +} + +- (IBAction)onCancel:(NSObject *)sender +{ + [NSApp stopModalWithCode:FALSE]; +} + +- (BOOL)runModal:(NSWindow *)mainWindow +{ + if ([mainWindow respondsToSelector:@selector(beginSheet:completionHandler:)]) + { + [mainWindow beginSheet:self.window completionHandler:nil]; + self.modalCode = [NSApp runModalForWindow:self.window]; + [mainWindow endSheet:self.window]; + } + else + { + [NSApp beginSheet:self.window + modalForWindow:mainWindow + modalDelegate:nil + didEndSelector:nil + contextInfo:nil]; + self.modalCode = [NSApp runModalForWindow:self.window]; + [NSApp endSheet:self.window]; + } + + [self.window orderOut:nil]; + return self.modalCode; +} + +- (void)dealloc +{ + [usernameText release]; + [passwordText release]; + [messageLabel release]; + [serverHostname release]; + [username release]; + [password release]; + [domain release]; + [super dealloc]; +} + +@end diff --git a/third_party/FreeRDP/client/Mac/PasswordDialog.xib b/third_party/FreeRDP/client/Mac/PasswordDialog.xib new file mode 100644 index 0000000..3911c14 --- /dev/null +++ b/third_party/FreeRDP/client/Mac/PasswordDialog.xib @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NSAllRomanInputSourcesLocaleIdentifier + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/third_party/FreeRDP/client/Mac/cli/AppDelegate.h b/third_party/FreeRDP/client/Mac/cli/AppDelegate.h new file mode 100644 index 0000000..e860039 --- /dev/null +++ b/third_party/FreeRDP/client/Mac/cli/AppDelegate.h @@ -0,0 +1,26 @@ +// +// AppDelegate.h +// MacClient2 +// +// Created by Benoît et Kathy on 2013-05-08. +// +// + +#import +#import +#import + +@interface AppDelegate : NSObject +{ + @public + NSWindow *window; + rdpContext *context; + MRDPView *mrdpView; +} + +- (void)rdpConnectError:(NSString *)customMessage; + +@property(assign) IBOutlet NSWindow *window; +@property(assign) rdpContext *context; + +@end diff --git a/third_party/FreeRDP/client/Mac/cli/AppDelegate.m b/third_party/FreeRDP/client/Mac/cli/AppDelegate.m new file mode 100644 index 0000000..59b84eb --- /dev/null +++ b/third_party/FreeRDP/client/Mac/cli/AppDelegate.m @@ -0,0 +1,325 @@ +// +// AppDelegate.m +// MacClient2 +// +// Created by Benoît et Kathy on 2013-05-08. +// +// + +#import "AppDelegate.h" +#import +#import +#import + +#import +#import + +static AppDelegate *_singleDelegate = nil; +void AppDelegate_ConnectionResultEventHandler(void *context, const ConnectionResultEventArgs *e); +void AppDelegate_ErrorInfoEventHandler(void *ctx, const ErrorInfoEventArgs *e); +void AppDelegate_EmbedWindowEventHandler(void *context, const EmbedWindowEventArgs *e); +void AppDelegate_ResizeWindowEventHandler(void *context, const ResizeWindowEventArgs *e); +void mac_set_view_size(rdpContext *context, MRDPView *view); + +@implementation AppDelegate + +- (void)dealloc +{ + [super dealloc]; +} + +@synthesize window = window; + +@synthesize context = context; + +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification +{ + int status; + mfContext *mfc; + _singleDelegate = self; + [self CreateContext]; + status = [self ParseCommandLineArguments]; + mfc = (mfContext *)context; + WINPR_ASSERT(mfc); + + mfc->view = (void *)mrdpView; + + if (status == 0) + { + NSScreen *screen = [[NSScreen screens] objectAtIndex:0]; + NSRect screenFrame = [screen frame]; + rdpSettings *settings = context->settings; + + WINPR_ASSERT(settings); + + if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)) + { + (void)freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, + screenFrame.size.width); + (void)freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, + screenFrame.size.height); + } + + PubSub_SubscribeConnectionResult(context->pubSub, AppDelegate_ConnectionResultEventHandler); + PubSub_SubscribeErrorInfo(context->pubSub, AppDelegate_ErrorInfoEventHandler); + PubSub_SubscribeEmbedWindow(context->pubSub, AppDelegate_EmbedWindowEventHandler); + PubSub_SubscribeResizeWindow(context->pubSub, AppDelegate_ResizeWindowEventHandler); + freerdp_client_start(context); + NSString *winTitle; + const char *WindowTitle = freerdp_settings_get_string(settings, FreeRDP_WindowTitle); + + if (WindowTitle && WindowTitle[0]) + { + winTitle = [[NSString alloc] + initWithFormat:@"%@", [NSString stringWithCString:WindowTitle + encoding:NSUTF8StringEncoding]]; + } + else + { + const char *name = freerdp_settings_get_string(settings, FreeRDP_ServerHostname); + const UINT32 port = freerdp_settings_get_uint32(settings, FreeRDP_ServerPort); + winTitle = [[NSString alloc] + initWithFormat:@"%@:%u", + [NSString stringWithCString:name encoding:NSUTF8StringEncoding], + port]; + } + + [window setTitle:winTitle]; + } + else + { + [NSApp terminate:self]; + } +} + +- (void)applicationWillBecomeActive:(NSNotification *)notification +{ + [mrdpView resume]; +} + +- (void)applicationWillResignActive:(NSNotification *)notification +{ + [mrdpView pause]; +} + +- (void)applicationWillTerminate:(NSNotification *)notification +{ + NSLog(@"Stopping...\n"); + freerdp_client_stop(context); + [mrdpView releaseResources]; + _singleDelegate = nil; + NSLog(@"Stopped.\n"); + [NSApp terminate:self]; +} + +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender +{ + return YES; +} + +- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app +{ + return YES; +} + +- (int)ParseCommandLineArguments +{ + int i; + int length; + int status; + char *cptr; + NSArray *args = [[NSProcessInfo processInfo] arguments]; + context->argc = (int)[args count]; + context->argv = malloc(sizeof(char *) * context->argc); + i = 0; + + for (NSString *str in args) + { + /* filter out some arguments added by XCode */ + if ([str isEqualToString:@"YES"]) + continue; + + if ([str isEqualToString:@"-NSDocumentRevisionsDebugMode"]) + continue; + + length = (int)([str lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1); + cptr = (char *)malloc(length); + sprintf_s(cptr, length, "%s", [str UTF8String]); + context->argv[i++] = cptr; + } + + context->argc = i; + status = freerdp_client_settings_parse_command_line(context->settings, context->argc, + context->argv, FALSE); + freerdp_client_settings_command_line_status_print(context->settings, status, context->argc, + context->argv); + return status; +} + +- (void)CreateContext +{ + RDP_CLIENT_ENTRY_POINTS clientEntryPoints = WINPR_C_ARRAY_INIT; + + clientEntryPoints.Size = sizeof(RDP_CLIENT_ENTRY_POINTS); + clientEntryPoints.Version = RDP_CLIENT_INTERFACE_VERSION; + RdpClientEntry(&clientEntryPoints); + context = freerdp_client_context_new(&clientEntryPoints); +} + +- (void)ReleaseContext +{ + mfContext *mfc; + MRDPView *view; + mfc = (mfContext *)context; + view = (MRDPView *)mfc->view; + [view exitFullScreenModeWithOptions:nil]; + [view releaseResources]; + [view release]; + mfc->view = nil; + freerdp_client_context_free(context); + context = nil; +} + +/** ********************************************************************* + * called when we fail to connect to a RDP server - Make sure this is called from the main thread. + ***********************************************************************/ + +- (void)rdpConnectError:(NSString *)withMessage +{ + mfContext *mfc; + MRDPView *view; + mfc = (mfContext *)context; + view = (MRDPView *)mfc->view; + [view exitFullScreenModeWithOptions:nil]; + NSString *message = withMessage ? withMessage : @"Error connecting to server"; + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:message]; + [alert beginSheetModalForWindow:[self window] + modalDelegate:self + didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) + contextInfo:nil]; +} + +/** ********************************************************************* + * just a terminate selector for above call + ***********************************************************************/ + +- (void)alertDidEnd:(NSAlert *)a returnCode:(NSInteger)rc contextInfo:(void *)ci +{ + [NSApp terminate:nil]; +} + +@end + +/** ********************************************************************* + * On connection error, display message and quit application + ***********************************************************************/ + +void AppDelegate_ConnectionResultEventHandler(void *ctx, const ConnectionResultEventArgs *e) +{ + rdpContext *context = (rdpContext *)ctx; + NSLog(@"ConnectionResult event result:%d\n", e->result); + + if (_singleDelegate) + { + if (e->result != 0) + { + NSString *message = nil; + DWORD code = freerdp_get_last_error(context); + switch (code) + { + case FREERDP_ERROR_AUTHENTICATION_FAILED: + message = [NSString + stringWithFormat:@"%@", @"Authentication failure, check credentials."]; + break; + default: + break; + } + + // Making sure this should be invoked on the main UI thread. + [_singleDelegate performSelectorOnMainThread:@selector(rdpConnectError:) + withObject:message + waitUntilDone:FALSE]; + } + } +} + +void AppDelegate_ErrorInfoEventHandler(void *ctx, const ErrorInfoEventArgs *e) +{ + NSLog(@"ErrorInfo event code:%d\n", e->code); + + if (_singleDelegate) + { + // Retrieve error message associated with error code + NSString *message = nil; + + if (e->code != ERRINFO_NONE) + { + const char *errorMessage = freerdp_get_error_info_string(e->code); + message = [[NSString alloc] initWithUTF8String:errorMessage]; + } + + // Making sure this should be invoked on the main UI thread. + [_singleDelegate performSelectorOnMainThread:@selector(rdpConnectError:) + withObject:message + waitUntilDone:TRUE]; + [message release]; + } +} + +void AppDelegate_EmbedWindowEventHandler(void *ctx, const EmbedWindowEventArgs *e) +{ + rdpContext *context = (rdpContext *)ctx; + + if (_singleDelegate) + { + mfContext *mfc = (mfContext *)context; + _singleDelegate->mrdpView = mfc->view; + + if (_singleDelegate->window) + { + [[_singleDelegate->window contentView] addSubview:mfc->view]; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + mac_set_view_size(context, mfc->view); + }); + } +} + +void AppDelegate_ResizeWindowEventHandler(void *ctx, const ResizeWindowEventArgs *e) +{ + rdpContext *context = (rdpContext *)ctx; + (void)fprintf(stderr, "ResizeWindowEventHandler: %d %d\n", e->width, e->height); + + if (_singleDelegate) + { + mfContext *mfc = (mfContext *)context; + dispatch_async(dispatch_get_main_queue(), ^{ + mac_set_view_size(context, mfc->view); + }); + } +} + +void mac_set_view_size(rdpContext *context, MRDPView *view) +{ + // set client area to specified dimensions + NSRect innerRect; + innerRect.origin.x = 0; + innerRect.origin.y = 0; + innerRect.size.width = freerdp_settings_get_uint32(context->settings, FreeRDP_DesktopWidth); + innerRect.size.height = freerdp_settings_get_uint32(context->settings, FreeRDP_DesktopHeight); + [view setFrame:innerRect]; + // calculate window of same size, but keep position + NSRect outerRect = [[view window] frame]; + outerRect.size = [[view window] frameRectForContentRect:innerRect].size; + // we are not in RemoteApp mode, disable larger than resolution + [[view window] setContentMaxSize:innerRect.size]; + // set window to given area + [[view window] setFrame:outerRect display:YES]; + // set window to front + [NSApp activateIgnoringOtherApps:YES]; + + if (freerdp_settings_get_bool(context->settings, FreeRDP_Fullscreen)) + [[view window] toggleFullScreen:nil]; +} diff --git a/third_party/FreeRDP/client/Mac/cli/CMakeLists.txt b/third_party/FreeRDP/client/Mac/cli/CMakeLists.txt new file mode 100644 index 0000000..6b1faf5 --- /dev/null +++ b/third_party/FreeRDP/client/Mac/cli/CMakeLists.txt @@ -0,0 +1,88 @@ +cmake_minimum_required(VERSION 3.13) + +if(NOT FREERDP_DEFAULT_PROJECT_VERSION) + set(FREERDP_DEFAULT_PROJECT_VERSION "1.0.0.0") +endif() + +project(MacFreeRDP VERSION ${FREERDP_DEFAULT_PROJECT_VERSION}) + +message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}") + +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../cmake/) +include(CommonConfigOptions) + +# Import libraries +find_library(FOUNDATION_LIBRARY Foundation REQUIRED) +find_library(COCOA_LIBRARY Cocoa REQUIRED) +find_library(APPKIT_LIBRARY AppKit REQUIRED) + +string(TIMESTAMP VERSION_YEAR "%Y") +set(MACOSX_BUNDLE_INFO_STRING "MacFreeRDP") +set(MACOSX_BUNDLE_ICON_FILE "FreeRDP.icns") +set(MACOSX_BUNDLE_GUI_IDENTIFIER "com.freerdp.mac") +set(MACOSX_BUNDLE_BUNDLE_IDENTIFIER "FreeRDP-client.Mac") +set(MACOSX_BUNDLE_LONG_VERSION_STRING "MacFreeRDP Client Version ${FREERDP_VERSION}") +set(MACOSX_BUNDLE_BUNDLE_NAME "MacFreeRDP") +set(MACOSX_BUNDLE_SHORT_VERSION_STRING ${FREERDP_VERSION}) +set(MACOSX_BUNDLE_BUNDLE_VERSION ${FREERDP_VERSION}) +set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2013-${VERSION_YEAR}. All Rights Reserved.") + +set(MACOSX_BUNDLE_NSMAIN_NIB_FILE "MainMenu") +set(MACOSX_BUNDLE_NSPRINCIPAL_CLASS "NSApplication") + +set(XIBS MainMenu.xib) + +set(SOURCES "") + +set(OBJECTIVE_SOURCES main.m AppDelegate.m) + +list(APPEND SOURCES ${OBJECTIVE_SOURCES}) + +set(HEADERS AppDelegate.h) + +set(RESOURCES "en.lproj/InfoPlist.strings" ${MACOSX_BUNDLE_ICON_FILE}) +# Include XIB file in Xcode resources. +if("${CMAKE_GENERATOR}" MATCHES "Xcode") + message(STATUS "Adding Xcode XIB resources for ${MODULE_NAME}") + list(APPEND RESOURCES ${XIBS}) + set(IS_XCODE ON) +endif() + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in ${CMAKE_CURRENT_BINARY_DIR}/Info.plist @ONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/PkgInfo.in ${CMAKE_CURRENT_BINARY_DIR}/PkgInfo @ONLY) + +add_executable(${PROJECT_NAME} ${HEADERS} ${SOURCES} ${RESOURCES}) + +if(WITH_BINARY_VERSIONING) + set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "${PROJECT_NAME}${PROJECT_VERSION_MAJOR}") +endif() +set_target_properties(${PROJECT_NAME} PROPERTIES RESOURCE "${RESOURCES}") +set_target_properties(${PROJECT_NAME} PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_BINARY_DIR}/Info.plist) + +target_link_libraries( + ${PROJECT_NAME} PRIVATE ${COCOA_LIBRARY} ${FOUNDATION_LIBRARY} ${APPKIT_LIBRARY} MacFreeRDP-library +) + +if(NOT IS_XCODE) + find_program(IBTOOL ibtool REQUIRED HINTS "/usr/bin" "${OSX_DEVELOPER_ROOT}/usr/bin") + + # Compile the .xib files using the 'ibtool' program with the destination being the app package + + foreach(xib ${XIBS}) + get_filename_component(XIB_WE ${xib} NAME_WE) + set(NIB ${CMAKE_CURRENT_BINARY_DIR}/${XIB_WE}.nib) + list(APPEND NIBS ${NIB}) + + add_custom_command( + TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${IBTOOL} --errors --warnings --notices --output-format + human-readable-text --compile ${NIB} ${CMAKE_CURRENT_SOURCE_DIR}/${xib} + COMMENT "Compiling ${xib}" + ) + endforeach() + + install(FILES ${NIBS} DESTINATION ${CMAKE_INSTALL_DATADIR}) +endif() + +install(TARGETS ${PROJECT_NAME} COMPONENT client RESOURCE DESTINATION ${CMAKE_INSTALL_DATADIR}) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/Info.plist DESTINATION ${CMAKE_INSTALL_PREFIX}) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/PkgInfo DESTINATION ${CMAKE_INSTALL_PREFIX}) diff --git a/third_party/FreeRDP/client/Mac/cli/FreeRDP.icns b/third_party/FreeRDP/client/Mac/cli/FreeRDP.icns new file mode 100644 index 0000000..88bd44c Binary files /dev/null and b/third_party/FreeRDP/client/Mac/cli/FreeRDP.icns differ diff --git a/third_party/FreeRDP/client/Mac/cli/Info.plist.in b/third_party/FreeRDP/client/Mac/cli/Info.plist.in new file mode 100644 index 0000000..198a144 --- /dev/null +++ b/third_party/FreeRDP/client/Mac/cli/Info.plist.in @@ -0,0 +1,38 @@ + + + + + NSCameraUsageDescription + This application requires camera access to redirect it to the remote host + NSMicrophoneUsageDescription + This application requires microphone access to redirect it to the remote host + CFBundleDevelopmentRegion + en + CFBundleExecutable + + CFBundleIconFile + FreeRDP + CFBundleIdentifier + FreeRDP.Mac + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSMinimumSystemVersion + + NSHumanReadableCopyright + Copyright © 2012 __MyCompanyName__. All rights reserved. + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/third_party/FreeRDP/client/Mac/cli/MacClient2-Info.plist b/third_party/FreeRDP/client/Mac/cli/MacClient2-Info.plist new file mode 100644 index 0000000..6efd7bd --- /dev/null +++ b/third_party/FreeRDP/client/Mac/cli/MacClient2-Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + + CFBundleIdentifier + awakecoding.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSMinimumSystemVersion + ${MACOSX_DEPLOYMENT_TARGET} + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/third_party/FreeRDP/client/Mac/cli/MacClient2-Prefix.pch b/third_party/FreeRDP/client/Mac/cli/MacClient2-Prefix.pch new file mode 100644 index 0000000..f81d505 --- /dev/null +++ b/third_party/FreeRDP/client/Mac/cli/MacClient2-Prefix.pch @@ -0,0 +1,7 @@ +// +// Prefix header for all source files of the 'MacClient2' target in the 'MacClient2' project +// + +#ifdef __OBJC__ + #import +#endif diff --git a/third_party/FreeRDP/client/Mac/cli/MainMenu.xib b/third_party/FreeRDP/client/Mac/cli/MainMenu.xib new file mode 100644 index 0000000..f647699 --- /dev/null +++ b/third_party/FreeRDP/client/Mac/cli/MainMenu.xib @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/third_party/FreeRDP/client/Mac/cli/PkgInfo.in b/third_party/FreeRDP/client/Mac/cli/PkgInfo.in new file mode 100644 index 0000000..bd04210 --- /dev/null +++ b/third_party/FreeRDP/client/Mac/cli/PkgInfo.in @@ -0,0 +1 @@ +APPL???? \ No newline at end of file diff --git a/third_party/FreeRDP/client/Mac/cli/en.lproj/Credits.rtf b/third_party/FreeRDP/client/Mac/cli/en.lproj/Credits.rtf new file mode 100644 index 0000000..46576ef --- /dev/null +++ b/third_party/FreeRDP/client/Mac/cli/en.lproj/Credits.rtf @@ -0,0 +1,29 @@ +{\rtf0\ansi{\fonttbl\f0\fswiss Helvetica;} +{\colortbl;\red255\green255\blue255;} +\paperw9840\paperh8400 +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural + +\f0\b\fs24 \cf0 Engineering: +\b0 \ + Some people\ +\ + +\b Human Interface Design: +\b0 \ + Some other people\ +\ + +\b Testing: +\b0 \ + Hopefully not nobody\ +\ + +\b Documentation: +\b0 \ + Whoever\ +\ + +\b With special thanks to: +\b0 \ + Mom\ +} diff --git a/third_party/FreeRDP/client/Mac/cli/en.lproj/InfoPlist.strings b/third_party/FreeRDP/client/Mac/cli/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..477b28f --- /dev/null +++ b/third_party/FreeRDP/client/Mac/cli/en.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git a/third_party/FreeRDP/client/Mac/cli/en.lproj/MainMenu.xib b/third_party/FreeRDP/client/Mac/cli/en.lproj/MainMenu.xib new file mode 100644 index 0000000..dd4e190 --- /dev/null +++ b/third_party/FreeRDP/client/Mac/cli/en.lproj/MainMenu.xib @@ -0,0 +1,3299 @@ + + + + 1080 + 12D78 + 3084 + 1187.37 + 626.00 + + com.apple.InterfaceBuilder.CocoaPlugin + 3084 + + + IBNSLayoutConstraint + NSCustomObject + NSCustomView + NSMenu + NSMenuItem + NSView + NSWindowTemplate + + + com.apple.InterfaceBuilder.CocoaPlugin + + + PluginDependencyRecalculationVersion + + + + + NSApplication + + + FirstResponder + + + NSApplication + + + AMainMenu + + + + MacClient2 + + 1048576 + 2147483647 + + NSImage + NSMenuCheckmark + + + NSImage + NSMenuMixedState + + submenuAction: + + MacClient2 + + + + About MacClient2 + + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Preferences… + , + 1048576 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Services + + 1048576 + 2147483647 + + + submenuAction: + + Services + + _NSServicesMenu + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Hide MacClient2 + h + 1048576 + 2147483647 + + + + + + Hide Others + h + 1572864 + 2147483647 + + + + + + Show All + + 1048576 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Quit MacClient2 + q + 1048576 + 2147483647 + + + + + _NSAppleMenu + + + + + File + + 1048576 + 2147483647 + + + submenuAction: + + File + + + + New + n + 1048576 + 2147483647 + + + + + + Open… + o + 1048576 + 2147483647 + + + + + + Open Recent + + 1048576 + 2147483647 + + + submenuAction: + + Open Recent + + + + Clear Menu + + 1048576 + 2147483647 + + + + + _NSRecentDocumentsMenu + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Close + w + 1048576 + 2147483647 + + + + + + Save… + s + 1048576 + 2147483647 + + + + + + Revert to Saved + + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Page Setup... + P + 1179648 + 2147483647 + + + + + + + Print… + p + 1048576 + 2147483647 + + + + + + + + + Edit + + 1048576 + 2147483647 + + + submenuAction: + + Edit + + + + Undo + z + 1048576 + 2147483647 + + + + + + Redo + Z + 1179648 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Cut + x + 1048576 + 2147483647 + + + + + + Copy + c + 1048576 + 2147483647 + + + + + + Paste + v + 1048576 + 2147483647 + + + + + + Paste and Match Style + V + 1572864 + 2147483647 + + + + + + Delete + + 1048576 + 2147483647 + + + + + + Select All + a + 1048576 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Find + + 1048576 + 2147483647 + + + submenuAction: + + Find + + + + Find… + f + 1048576 + 2147483647 + + + 1 + + + + Find and Replace… + f + 1572864 + 2147483647 + + + 12 + + + + Find Next + g + 1048576 + 2147483647 + + + 2 + + + + Find Previous + G + 1179648 + 2147483647 + + + 3 + + + + Use Selection for Find + e + 1048576 + 2147483647 + + + 7 + + + + Jump to Selection + j + 1048576 + 2147483647 + + + + + + + + + Spelling and Grammar + + 1048576 + 2147483647 + + + submenuAction: + + Spelling and Grammar + + + + Show Spelling and Grammar + : + 1048576 + 2147483647 + + + + + + Check Document Now + ; + 1048576 + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Check Spelling While Typing + + 1048576 + 2147483647 + + + + + + Check Grammar With Spelling + + 1048576 + 2147483647 + + + + + + Correct Spelling Automatically + + 2147483647 + + + + + + + + + Substitutions + + 1048576 + 2147483647 + + + submenuAction: + + Substitutions + + + + Show Substitutions + + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Smart Copy/Paste + f + 1048576 + 2147483647 + + + 1 + + + + Smart Quotes + g + 1048576 + 2147483647 + + + 2 + + + + Smart Dashes + + 2147483647 + + + + + + Smart Links + G + 1179648 + 2147483647 + + + 3 + + + + Text Replacement + + 2147483647 + + + + + + + + + Transformations + + 2147483647 + + + submenuAction: + + Transformations + + + + Make Upper Case + + 2147483647 + + + + + + Make Lower Case + + 2147483647 + + + + + + Capitalize + + 2147483647 + + + + + + + + + Speech + + 1048576 + 2147483647 + + + submenuAction: + + Speech + + + + Start Speaking + + 1048576 + 2147483647 + + + + + + Stop Speaking + + 1048576 + 2147483647 + + + + + + + + + + + + Format + + 2147483647 + + + submenuAction: + + Format + + + + Font + + 2147483647 + + + submenuAction: + + Font + + + + Show Fonts + t + 1048576 + 2147483647 + + + + + + Bold + b + 1048576 + 2147483647 + + + 2 + + + + Italic + i + 1048576 + 2147483647 + + + 1 + + + + Underline + u + 1048576 + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Bigger + + + 1048576 + 2147483647 + + + 3 + + + + Smaller + - + 1048576 + 2147483647 + + + 4 + + + + YES + YES + + + 2147483647 + + + + + + Kern + + 2147483647 + + + submenuAction: + + Kern + + + + Use Default + + 2147483647 + + + + + + Use None + + 2147483647 + + + + + + Tighten + + 2147483647 + + + + + + Loosen + + 2147483647 + + + + + + + + + Ligatures + + 2147483647 + + + submenuAction: + + Ligatures + + + + Use Default + + 2147483647 + + + + + + Use None + + 2147483647 + + + + + + Use All + + 2147483647 + + + + + + + + + Baseline + + 2147483647 + + + submenuAction: + + Baseline + + + + Use Default + + 2147483647 + + + + + + Superscript + + 2147483647 + + + + + + Subscript + + 2147483647 + + + + + + Raise + + 2147483647 + + + + + + Lower + + 2147483647 + + + + + + + + + YES + YES + + + 2147483647 + + + + + + Show Colors + C + 1048576 + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Copy Style + c + 1572864 + 2147483647 + + + + + + Paste Style + v + 1572864 + 2147483647 + + + + + _NSFontMenu + + + + + Text + + 2147483647 + + + submenuAction: + + Text + + + + Align Left + { + 1048576 + 2147483647 + + + + + + Center + | + 1048576 + 2147483647 + + + + + + Justify + + 2147483647 + + + + + + Align Right + } + 1048576 + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Writing Direction + + 2147483647 + + + submenuAction: + + Writing Direction + + + + YES + Paragraph + + 2147483647 + + + + + + CURlZmF1bHQ + + 2147483647 + + + + + + CUxlZnQgdG8gUmlnaHQ + + 2147483647 + + + + + + CVJpZ2h0IHRvIExlZnQ + + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + YES + Selection + + 2147483647 + + + + + + CURlZmF1bHQ + + 2147483647 + + + + + + CUxlZnQgdG8gUmlnaHQ + + 2147483647 + + + + + + CVJpZ2h0IHRvIExlZnQ + + 2147483647 + + + + + + + + + YES + YES + + + 2147483647 + + + + + + Show Ruler + + 2147483647 + + + + + + Copy Ruler + c + 1310720 + 2147483647 + + + + + + Paste Ruler + v + 1310720 + 2147483647 + + + + + + + + + + + + View + + 1048576 + 2147483647 + + + submenuAction: + + View + + + + Show Toolbar + t + 1572864 + 2147483647 + + + + + + Customize Toolbar… + + 1048576 + 2147483647 + + + + + + + + + Window + + 1048576 + 2147483647 + + + submenuAction: + + Window + + + + Minimize + m + 1048576 + 2147483647 + + + + + + Zoom + + 1048576 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Bring All to Front + + 1048576 + 2147483647 + + + + + _NSWindowsMenu + + + + + Help + + 2147483647 + + + submenuAction: + + Help + + + + MacClient2 Help + ? + 1048576 + 2147483647 + + + + + _NSHelpMenu + + + + _NSMainMenu + + + 15 + 2 + {{335, 390}, {480, 360}} + 1954021376 + MacClient2 + NSWindow + + + + + 256 + + + + 268 + {480, 360} + + _NS:9 + MRDPView + + + {480, 360} + + + + {{0, 0}, {1440, 878}} + {10000000000000, 10000000000000} + YES + + + AppDelegate + + + NSFontManager + + + + + + + terminate: + + + + 449 + + + + orderFrontStandardAboutPanel: + + + + 142 + + + + delegate + + + + 495 + + + + performMiniaturize: + + + + 37 + + + + arrangeInFront: + + + + 39 + + + + print: + + + + 86 + + + + runPageLayout: + + + + 87 + + + + clearRecentDocuments: + + + + 127 + + + + performClose: + + + + 193 + + + + toggleContinuousSpellChecking: + + + + 222 + + + + undo: + + + + 223 + + + + copy: + + + + 224 + + + + checkSpelling: + + + + 225 + + + + paste: + + + + 226 + + + + stopSpeaking: + + + + 227 + + + + cut: + + + + 228 + + + + showGuessPanel: + + + + 230 + + + + redo: + + + + 231 + + + + selectAll: + + + + 232 + + + + startSpeaking: + + + + 233 + + + + delete: + + + + 235 + + + + performZoom: + + + + 240 + + + + performFindPanelAction: + + + + 241 + + + + centerSelectionInVisibleArea: + + + + 245 + + + + toggleGrammarChecking: + + + + 347 + + + + toggleSmartInsertDelete: + + + + 355 + + + + toggleAutomaticQuoteSubstitution: + + + + 356 + + + + toggleAutomaticLinkDetection: + + + + 357 + + + + saveDocument: + + + + 362 + + + + revertDocumentToSaved: + + + + 364 + + + + runToolbarCustomizationPalette: + + + + 365 + + + + toggleToolbarShown: + + + + 366 + + + + hide: + + + + 367 + + + + hideOtherApplications: + + + + 368 + + + + unhideAllApplications: + + + + 370 + + + + newDocument: + + + + 373 + + + + openDocument: + + + + 374 + + + + raiseBaseline: + + + + 426 + + + + lowerBaseline: + + + + 427 + + + + copyFont: + + + + 428 + + + + subscript: + + + + 429 + + + + superscript: + + + + 430 + + + + tightenKerning: + + + + 431 + + + + underline: + + + + 432 + + + + orderFrontColorPanel: + + + + 433 + + + + useAllLigatures: + + + + 434 + + + + loosenKerning: + + + + 435 + + + + pasteFont: + + + + 436 + + + + unscript: + + + + 437 + + + + useStandardKerning: + + + + 438 + + + + useStandardLigatures: + + + + 439 + + + + turnOffLigatures: + + + + 440 + + + + turnOffKerning: + + + + 441 + + + + toggleAutomaticSpellingCorrection: + + + + 456 + + + + orderFrontSubstitutionsPanel: + + + + 458 + + + + toggleAutomaticDashSubstitution: + + + + 461 + + + + toggleAutomaticTextReplacement: + + + + 463 + + + + uppercaseWord: + + + + 464 + + + + capitalizeWord: + + + + 467 + + + + lowercaseWord: + + + + 468 + + + + pasteAsPlainText: + + + + 486 + + + + performFindPanelAction: + + + + 487 + + + + performFindPanelAction: + + + + 488 + + + + performFindPanelAction: + + + + 489 + + + + showHelp: + + + + 493 + + + + alignCenter: + + + + 518 + + + + pasteRuler: + + + + 519 + + + + toggleRuler: + + + + 520 + + + + alignRight: + + + + 521 + + + + copyRuler: + + + + 522 + + + + alignJustified: + + + + 523 + + + + alignLeft: + + + + 524 + + + + makeBaseWritingDirectionNatural: + + + + 525 + + + + makeBaseWritingDirectionLeftToRight: + + + + 526 + + + + makeBaseWritingDirectionRightToLeft: + + + + 527 + + + + makeTextWritingDirectionNatural: + + + + 528 + + + + makeTextWritingDirectionLeftToRight: + + + + 529 + + + + makeTextWritingDirectionRightToLeft: + + + + 530 + + + + performFindPanelAction: + + + + 535 + + + + addFontTrait: + + + + 421 + + + + addFontTrait: + + + + 422 + + + + modifyFont: + + + + 423 + + + + orderFrontFontPanel: + + + + 424 + + + + modifyFont: + + + + 425 + + + + mrdpView + + + + 549 + + + + window + + + + 550 + + + + + + 0 + + + + + + -2 + + + File's Owner + + + -1 + + + First Responder + + + -3 + + + Application + + + 29 + + + + + + + + + + + + + + 19 + + + + + + + + 56 + + + + + + + + 217 + + + + + + + + 83 + + + + + + + + 81 + + + + + + + + + + + + + + + + + 75 + + + + + 78 + + + + + 72 + + + + + 82 + + + + + 124 + + + + + + + + 77 + + + + + 73 + + + + + 79 + + + + + 112 + + + + + 74 + + + + + 125 + + + + + + + + 126 + + + + + 205 + + + + + + + + + + + + + + + + + + + + + + 202 + + + + + 198 + + + + + 207 + + + + + 214 + + + + + 199 + + + + + 203 + + + + + 197 + + + + + 206 + + + + + 215 + + + + + 218 + + + + + + + + 216 + + + + + + + + 200 + + + + + + + + + + + + + 219 + + + + + 201 + + + + + 204 + + + + + 220 + + + + + + + + + + + + + 213 + + + + + 210 + + + + + 221 + + + + + 208 + + + + + 209 + + + + + 57 + + + + + + + + + + + + + + + + + + 58 + + + + + 134 + + + + + 150 + + + + + 136 + + + + + 144 + + + + + 129 + + + + + 143 + + + + + 236 + + + + + 131 + + + + + + + + 149 + + + + + 145 + + + + + 130 + + + + + 24 + + + + + + + + + + + 92 + + + + + 5 + + + + + 239 + + + + + 23 + + + + + 295 + + + + + + + + 296 + + + + + + + + + 297 + + + + + 298 + + + + + 211 + + + + + + + + 212 + + + + + + + + + 195 + + + + + 196 + + + + + 346 + + + + + 348 + + + + + + + + 349 + + + + + + + + + + + + + + 350 + + + + + 351 + + + + + 354 + + + + + 371 + + + + + + + + 372 + + + + + 6 + 0 + + 6 + 1 + + 0.0 + + 1000 + + 8 + 29 + 3 + + + + 4 + 0 + + 4 + 1 + + 0.0 + + 1000 + + 8 + 29 + 3 + + + + 5 + 0 + + 5 + 1 + + 0.0 + + 1000 + + 8 + 29 + 3 + + + + 3 + 0 + + 3 + 1 + + 0.0 + + 1000 + + 8 + 29 + 3 + + + + + + + 375 + + + + + + + + 376 + + + + + + + + + 377 + + + + + + + + 388 + + + + + + + + + + + + + + + + + + + + + + + 389 + + + + + 390 + + + + + 391 + + + + + 392 + + + + + 393 + + + + + 394 + + + + + 395 + + + + + 396 + + + + + 397 + + + + + + + + 398 + + + + + + + + 399 + + + + + + + + 400 + + + + + 401 + + + + + 402 + + + + + 403 + + + + + 404 + + + + + 405 + + + + + + + + + + + + 406 + + + + + 407 + + + + + 408 + + + + + 409 + + + + + 410 + + + + + 411 + + + + + + + + + + 412 + + + + + 413 + + + + + 414 + + + + + 415 + + + + + + + + + + + 416 + + + + + 417 + + + + + 418 + + + + + 419 + + + + + 420 + + + + + 450 + + + + + + + + 451 + + + + + + + + + + 452 + + + + + 453 + + + + + 454 + + + + + 457 + + + + + 459 + + + + + 460 + + + + + 462 + + + + + 465 + + + + + 466 + + + + + 485 + + + + + 490 + + + + + + + + 491 + + + + + + + + 492 + + + + + 494 + + + + + 496 + + + + + + + + 497 + + + + + + + + + + + + + + + + + 498 + + + + + 499 + + + + + 500 + + + + + 501 + + + + + 502 + + + + + 503 + + + + + + + + 504 + + + + + 505 + + + + + 506 + + + + + 507 + + + + + 508 + + + + + + + + + + + + + + + + 509 + + + + + 510 + + + + + 511 + + + + + 512 + + + + + 513 + + + + + 514 + + + + + 515 + + + + + 516 + + + + + 517 + + + + + 534 + + + + + 536 + + + + + 542 + + + + + 544 + + + + + 545 + + + + + 546 + + + + + + + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + {{380, 496}, {480, 360}} + + + + + + + + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + + + + + 550 + + + 0 + IBCocoaFramework + YES + 3 + + {11, 11} + {10, 3} + + YES + + diff --git a/third_party/FreeRDP/client/Mac/cli/main.m b/third_party/FreeRDP/client/Mac/cli/main.m new file mode 100644 index 0000000..ea53615 --- /dev/null +++ b/third_party/FreeRDP/client/Mac/cli/main.m @@ -0,0 +1,26 @@ +// +// main.m +// MacClient2 +// +// Created by Benoît et Kathy on 2013-05-08. +// +// + +#import +#include + +int main(int argc, char *argv[]) +{ + freerdp_client_warn_deprecated(argc, argv); + for (int i = 0; i < argc; i++) + { + char *ctemp = argv[i]; + if (memcmp(ctemp, "/p:", 3) == 0 || memcmp(ctemp, "-p:", 3) == 0) + { + memset(ctemp + 3, '*', strlen(ctemp) - 3); + } + } + + const char **cargv = (const char **)argv; + return NSApplicationMain(argc, cargv); +} diff --git a/third_party/FreeRDP/client/Mac/en.lproj/InfoPlist.strings b/third_party/FreeRDP/client/Mac/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..477b28f --- /dev/null +++ b/third_party/FreeRDP/client/Mac/en.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git a/third_party/FreeRDP/client/Mac/main.m b/third_party/FreeRDP/client/Mac/main.m new file mode 100644 index 0000000..b4663f1 --- /dev/null +++ b/third_party/FreeRDP/client/Mac/main.m @@ -0,0 +1,25 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * MacFreeRDP + * + * Copyright 2012 Thomas Goddard + * + * 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. + */ + +#import + +int main(int argc, char *argv[]) +{ + return NSApplicationMain(argc, (const char *const *)argv); +} diff --git a/third_party/FreeRDP/client/Mac/mf_client.h b/third_party/FreeRDP/client/Mac/mf_client.h new file mode 100644 index 0000000..5f21c6c --- /dev/null +++ b/third_party/FreeRDP/client/Mac/mf_client.h @@ -0,0 +1,46 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Windows Client + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_MAC_CLIENT_H +#define FREERDP_CLIENT_MAC_CLIENT_H + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + + FREERDP_API void mf_press_mouse_button(void* context, int button, int x, int y, BOOL down); + FREERDP_API void mf_scale_mouse_event(void* context, UINT16 flags, UINT16 x, UINT16 y); + FREERDP_API void mf_scale_mouse_event_ex(void* context, UINT16 flags, UINT16 x, UINT16 y); + + /** + * Client Interface + */ + + FREERDP_API int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_CLIENT_MAC_CLIENT_H */ diff --git a/third_party/FreeRDP/client/Mac/mf_client.m b/third_party/FreeRDP/client/Mac/mf_client.m new file mode 100644 index 0000000..f88209e --- /dev/null +++ b/third_party/FreeRDP/client/Mac/mf_client.m @@ -0,0 +1,220 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Client Interface + * + * Copyright 2013 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "mfreerdp.h" + +#include + +#include +#include +#include + +#include "MRDPView.h" + +/** + * Client Interface + */ + +static BOOL mfreerdp_client_global_init(void) +{ + freerdp_handle_signals(); + return TRUE; +} + +static void mfreerdp_client_global_uninit(void) +{ +} + +static int mfreerdp_client_start(rdpContext *context) +{ + MRDPView *view; + mfContext *mfc = (mfContext *)context; + + if (mfc->view == nullptr) + { + // view not specified beforehand. Create view dynamically + mfc->view = [[MRDPView alloc] + initWithFrame:NSMakeRect( + 0, 0, + freerdp_settings_get_uint32(context->settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(context->settings, + FreeRDP_DesktopHeight))]; + mfc->view_ownership = TRUE; + } + + view = (MRDPView *)mfc->view; + return [view rdpStart:context]; +} + +static int mfreerdp_client_stop(rdpContext *context) +{ + mfContext *mfc = (mfContext *)context; + + freerdp_client_common_stop(context); + + if (mfc->view_ownership) + { + MRDPView *view = (MRDPView *)mfc->view; + [view releaseResources]; + [view release]; + mfc->view = nil; + } + + return 0; +} + +static BOOL mfreerdp_client_new(freerdp *instance, rdpContext *context) +{ + mfContext *mfc; + + WINPR_ASSERT(instance); + + mfc = (mfContext *)instance->context; + WINPR_ASSERT(mfc); + + mfc->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + if (!mfc->stopEvent) + return FALSE; + context->instance->PreConnect = mac_pre_connect; + context->instance->PostConnect = mac_post_connect; + context->instance->PostDisconnect = mac_post_disconnect; + context->instance->AuthenticateEx = mac_authenticate_ex; + context->instance->VerifyCertificateEx = mac_verify_certificate_ex; + context->instance->VerifyChangedCertificateEx = mac_verify_changed_certificate_ex; + context->instance->LogonErrorInfo = mac_logon_error_info; + return TRUE; +} + +static void mfreerdp_client_free(freerdp *instance, rdpContext *context) +{ + mfContext *mfc; + + if (!instance || !context) + return; + + mfc = (mfContext *)instance->context; + (void)CloseHandle(mfc->stopEvent); +} + +static void mf_scale_mouse_coordinates(mfContext *mfc, UINT16 *px, UINT16 *py) +{ + UINT16 x = *px; + UINT16 y = *py; + UINT32 ww = mfc->client_width; + UINT32 wh = mfc->client_height; + UINT32 dw = freerdp_settings_get_uint32(mfc->common.context.settings, FreeRDP_DesktopWidth); + UINT32 dh = freerdp_settings_get_uint32(mfc->common.context.settings, FreeRDP_DesktopHeight); + + if (!freerdp_settings_get_bool(mfc->common.context.settings, FreeRDP_SmartSizing) || + ((ww == dw) && (wh == dh))) + { + y = y + mfc->yCurrentScroll; + x = x + mfc->xCurrentScroll; + + y -= (dh - wh); + x -= (dw - ww); + } + else + { + y = y * dh / wh + mfc->yCurrentScroll; + x = x * dw / ww + mfc->xCurrentScroll; + } + + *px = x; + *py = y; +} + +void mf_scale_mouse_event(void *context, UINT16 flags, UINT16 x, UINT16 y) +{ + mfContext *mfc = (mfContext *)context; + MRDPView *view = (MRDPView *)mfc->view; + // Convert to windows coordinates + y = [view frame].size.height - y; + + if ((flags & (PTR_FLAGS_WHEEL | PTR_FLAGS_HWHEEL)) == 0) + mf_scale_mouse_coordinates(mfc, &x, &y); + freerdp_client_send_button_event(&mfc->common, FALSE, flags, x, y); +} + +void mf_scale_mouse_event_ex(void *context, UINT16 flags, UINT16 x, UINT16 y) +{ + mfContext *mfc = (mfContext *)context; + MRDPView *view = (MRDPView *)mfc->view; + // Convert to windows coordinates + y = [view frame].size.height - y; + + mf_scale_mouse_coordinates(mfc, &x, &y); + freerdp_client_send_extended_button_event(&mfc->common, FALSE, flags, x, y); +} + +void mf_press_mouse_button(void *context, int button, int x, int y, BOOL down) +{ + UINT16 flags = 0; + UINT16 xflags = 0; + + if (down) + { + flags |= PTR_FLAGS_DOWN; + xflags |= PTR_XFLAGS_DOWN; + } + + switch (button) + { + case 0: + mf_scale_mouse_event(context, flags | PTR_FLAGS_BUTTON1, x, y); + break; + + case 1: + mf_scale_mouse_event(context, flags | PTR_FLAGS_BUTTON2, x, y); + break; + + case 2: + mf_scale_mouse_event(context, flags | PTR_FLAGS_BUTTON3, x, y); + break; + + case 3: + mf_scale_mouse_event_ex(context, xflags | PTR_XFLAGS_BUTTON1, x, y); + break; + + case 4: + mf_scale_mouse_event_ex(context, xflags | PTR_XFLAGS_BUTTON2, x, y); + break; + + default: + break; + } +} + +int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS *pEntryPoints) +{ + WINPR_ASSERT(pEntryPoints); + + pEntryPoints->Version = 1; + pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1); + pEntryPoints->GlobalInit = mfreerdp_client_global_init; + pEntryPoints->GlobalUninit = mfreerdp_client_global_uninit; + pEntryPoints->ContextSize = sizeof(mfContext); + pEntryPoints->ClientNew = mfreerdp_client_new; + pEntryPoints->ClientFree = mfreerdp_client_free; + pEntryPoints->ClientStart = mfreerdp_client_start; + pEntryPoints->ClientStop = mfreerdp_client_stop; + return 0; +} diff --git a/third_party/FreeRDP/client/Mac/mfreerdp.h b/third_party/FreeRDP/client/Mac/mfreerdp.h new file mode 100644 index 0000000..bc93150 --- /dev/null +++ b/third_party/FreeRDP/client/Mac/mfreerdp.h @@ -0,0 +1,86 @@ +#ifndef FREERDP_CLIENT_MAC_FREERDP_H +#define FREERDP_CLIENT_MAC_FREERDP_H + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "Keyboard.h" +#include + +typedef struct +{ + rdpClientContext common; + + void* view; + BOOL view_ownership; + + int width; + int height; + int offset_x; + int offset_y; + int fs_toggle; + int fullscreen; + int percentscreen; + char window_title[64]; + int client_x; + int client_y; + int client_width; + int client_height; + + HANDLE stopEvent; + HANDLE keyboardThread; + enum APPLE_KEYBOARD_TYPE appleKeyboardType; + + DWORD mainThreadId; + DWORD keyboardThreadId; + + BOOL clipboardSync; + wClipboard* clipboard; + UINT32 numServerFormats; + UINT32 requestedFormatId; + HANDLE clipboardRequestEvent; + CLIPRDR_FORMAT* serverFormats; + CliprdrClientContext* cliprdr; + UINT32 clipboardCapabilities; + + rdpFile* connectionRdpFile; + + // Keep track of window size and position, disable when in fullscreen mode. + BOOL disablewindowtracking; + + // These variables are required for horizontal scrolling. + BOOL updating_scrollbars; + BOOL xScrollVisible; + int xMinScroll; // minimum horizontal scroll value + int xCurrentScroll; // current horizontal scroll value + int xMaxScroll; // maximum horizontal scroll value + + // These variables are required for vertical scrolling. + BOOL yScrollVisible; + int yMinScroll; // minimum vertical scroll value + int yCurrentScroll; // current vertical scroll value + int yMaxScroll; // maximum vertical scroll value + + CGEventFlags kbdFlags; +} mfContext; + +#endif /* FREERDP_CLIENT_MAC_FREERDP_H */ diff --git a/third_party/FreeRDP/client/SDL/CMakeLists.txt b/third_party/FreeRDP/client/SDL/CMakeLists.txt new file mode 100644 index 0000000..9c8bce9 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/CMakeLists.txt @@ -0,0 +1,104 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP SDL Client +# +# Copyright 2024 Armin Novak +# Copyright 2024 Thincast Technologies GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +cmake_minimum_required(VERSION 3.13) + +if(POLICY CMP0091) + cmake_policy(SET CMP0091 NEW) +endif() +if(NOT FREERDP_DEFAULT_PROJECT_VERSION) + set(FREERDP_DEFAULT_PROJECT_VERSION "1.0.0.0") +endif() + +project(sdl-freerdp LANGUAGES CXX VERSION ${FREERDP_DEFAULT_PROJECT_VERSION}) + +message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}") + +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/) +include(ProjectCXXStandard) +include(CommonConfigOptions) + +include(ConfigureFreeRDP) +include(CXXCompilerFlags) + +option(WITH_DEBUG_SDL_EVENTS "[dangerous, not for release builds!] Debug SDL events" ${DEFAULT_DEBUG_OPTION}) +option(WITH_DEBUG_SDL_KBD_EVENTS "[dangerous, not for release builds!] Debug SDL keyboard events" + ${DEFAULT_DEBUG_OPTION} +) +option(WITH_WIN_CONSOLE "Build ${PROJECT_NAME} with console support" ON) +option(WITH_SDL_LINK_SHARED "link SDL dynamic or static" ON) + +if(WITH_WIN_CONSOLE) + set(WIN32_GUI_FLAG "TRUE") +else() + set(WIN32_GUI_FLAG "WIN32") +endif() + +if(WITH_DEBUG_SDL_EVENTS) + add_compile_definitions(WITH_DEBUG_SDL_EVENTS) +endif() +if(WITH_DEBUG_SDL_KBD_EVENTS) + add_compile_definitions(WITH_DEBUG_SDL_KBD_EVENTS) +endif() + +include(CMakeDependentOption) + +find_package(SDL3) + +cmake_dependent_option(WITH_CLIENT_SDL_VERSIONED "append sdl version to client binaries" OFF WITH_CLIENT_SDL OFF) +if(NOT WITHOUT_FREERDP_3x_DEPRECATED) + # Require 2.0.20 for ubuntu 22.04. + # older versions do not have the SDL2::SDL2 et al targets + find_package(SDL2 2.0.20) + cmake_dependent_option( + WITH_CLIENT_SDL2 "[deprecated,experimental] build deprecated,experimental SDL2 client" ${SDL2_FOUND} + WITH_CLIENT_SDL OFF + ) +endif() +cmake_dependent_option(WITH_CLIENT_SDL3 "build SDL3 client" ${SDL3_FOUND} WITH_CLIENT_SDL OFF) + +if(WITH_CLIENT_SDL2 AND WITH_CLIENT_SDL3) + message("Building both, SDL2 and SDL3 clients, forcing WITH_CLIENT_SDL_VERSIONED=ON") + set(WITH_CLIENT_SDL_VERSIONED ON) +endif() + +if(NOT SDL2_FOUND AND NOT SDL3_FOUND) + message(WARNING "No SDL library detected, giving up. Install SDL2 or SDL3 development package to fix") +endif() + +if((WITH_CLIENT_SDL2 AND SDL2_FOUND) OR (WITH_CLIENT_SDL3 AND SDL3_FOUND)) + add_subdirectory(common) + include_directories(common) +endif() + +if(NOT WITHOUT_FREERDP_3x_DEPRECATED) + if(WITH_CLIENT_SDL2) + if(SDL2_FOUND) + add_subdirectory(SDL2) + else() + message(WARNING "SDL2 requested but not found, continuing build without SDL2 client") + endif() + endif() +endif() + +if(WITH_CLIENT_SDL3) + if(SDL3_FOUND) + add_subdirectory(SDL3) + else() + message(WARNING "SDL3 requested but not found, continuing build without SDL3 client") + endif() +endif() diff --git a/third_party/FreeRDP/client/SDL/SDL2/CMakeLists.txt b/third_party/FreeRDP/client/SDL/SDL2/CMakeLists.txt new file mode 100644 index 0000000..e29f738 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/CMakeLists.txt @@ -0,0 +1,88 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP SDL Client +# +# Copyright 2022 Armin Novak +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +set(MODULE_NAME "sdl2-freerdp") + +find_package(SDL2 REQUIRED) + +find_package(Threads REQUIRED) + +add_subdirectory(dialogs) +set(SRCS + sdl_types.hpp + sdl_utils.cpp + sdl_utils.hpp + sdl_kbd.cpp + sdl_kbd.hpp + sdl_touch.cpp + sdl_touch.hpp + sdl_pointer.cpp + sdl_pointer.hpp + sdl_disp.cpp + sdl_disp.hpp + sdl_monitor.cpp + sdl_monitor.hpp + sdl_freerdp.hpp + sdl_freerdp.cpp + sdl_channels.hpp + sdl_channels.cpp + sdl_window.hpp + sdl_window.cpp +) + +list( + APPEND + LIBS + winpr + freerdp + freerdp-client + Threads::Threads + sdl2_client_res + sdl2-dialogs + sdl-common-aad-view + sdl-common-prefs +) + +if(NOT WITH_SDL_LINK_SHARED) + list(APPEND LIBS SDL2::SDL2-static) + set_target_properties(SDL2::SDL2-static PROPERTIES SYSTEM TRUE) +else() + list(APPEND LIBS SDL2::SDL2) + set_target_properties(SDL2::SDL2 PROPERTIES SYSTEM TRUE) +endif() + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +addtargetwithresourcefile(${MODULE_NAME} "${WIN32_GUI_FLAG}" "${PROJECT_VERSION}" SRCS) + +target_link_libraries(${MODULE_NAME} PRIVATE ${LIBS}) + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Client/SDL") + +get_target_property(SDL_CLIENT_BINARY_NAME ${MODULE_NAME} OUTPUT_NAME) +if(NOT WITH_CLIENT_SDL_VERSIONED) + string(REPLACE "${MODULE_NAME}" "${PROJECT_NAME}" SDL_CLIENT_BINARY_NAME "${SDL_CLIENT_BINARY_NAME}") + set_target_properties(${MODULE_NAME} PROPERTIES OUTPUT_NAME ${SDL_CLIENT_BINARY_NAME}) +endif() + +string(TIMESTAMP SDL_CLIENT_YEAR "%Y") +set(SDL_CLIENT_UUID "com.freerdp.client.sdl2") +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/sdl_config.hpp.in ${CMAKE_CURRENT_BINARY_DIR}/sdl_config.hpp @ONLY) + +install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT client) +install_freerdp_desktop("${MODULE_NAME}" "${SDL_CLIENT_UUID}") + +add_subdirectory(man) diff --git a/third_party/FreeRDP/client/SDL/SDL2/dialogs/CMakeLists.txt b/third_party/FreeRDP/client/SDL/SDL2/dialogs/CMakeLists.txt new file mode 100644 index 0000000..2f3c438 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/dialogs/CMakeLists.txt @@ -0,0 +1,72 @@ +set(SRCS + sdl_button.hpp + sdl_button.cpp + sdl_buttons.hpp + sdl_buttons.cpp + sdl_dialogs.cpp + sdl_dialogs.hpp + sdl_widget.hpp + sdl_widget.cpp + sdl_input.hpp + sdl_input.cpp + sdl_input_widgets.cpp + sdl_input_widgets.hpp + sdl_select.hpp + sdl_select.cpp + sdl_selectlist.hpp + sdl_selectlist.cpp + sdl_connection_dialog.cpp + sdl_connection_dialog.hpp +) + +list(APPEND LIBS sdl2_client_res winpr) + +if(NOT WITH_SDL_LINK_SHARED) + list(APPEND LIBS SDL2::SDL2-static) +else() + list(APPEND LIBS SDL2::SDL2) +endif() + +macro(find_sdl_component name) + find_package(${name}) + if(NOT ${name}_FOUND) + find_package(PkgConfig REQUIRED) + pkg_check_modules(${name} REQUIRED ${name}) + + if(BUILD_SHARED_LIBS) + list(APPEND LIBS ${${name}_LIBRARIES}) + link_directories(${${name}_LIBRARY_DIRS}) + include_directories(SYSTEM ${${name}_INCLUDE_DIRS}) + else() + list(APPEND LIBS ${${name}_STATIC_LIBRARIES}) + link_directories(${${name}_STATIC_LIBRARY_DIRS}) + include_directories(SYSTEM ${${name}_STATIC_INCLUDE_DIRS}) + endif() + else() + if(WITH_SDL_LINK_SHARED) + list(APPEND LIBS ${name}::${name}) + set_target_properties(${name}::${name} PROPERTIES SYSTEM TRUE) + else() + list(APPEND LIBS ${name}::${name}-static) + set_target_properties(${name}::${name}-static PROPERTIES SYSTEM TRUE) + endif() + endif() +endmacro() + +find_sdl_component(SDL2_ttf) + +option(WITH_SDL_IMAGE_DIALOGS "Build with SDL_image support (recommended)" OFF) +if(WITH_SDL_IMAGE_DIALOGS) + find_sdl_component(SDL2_image) + add_compile_definitions(WITH_SDL_IMAGE_DIALOGS) +endif() + +add_subdirectory(res) + +add_library(sdl2-dialogs STATIC ${SRCS}) + +target_link_libraries(sdl2-dialogs PRIVATE ${LIBS}) + +if(BUILD_TESTING_INTERNAL OR BUILD_TESTING) + # add_subdirectory(test) +endif() diff --git a/third_party/FreeRDP/client/SDL/SDL2/dialogs/res/CMakeLists.txt b/third_party/FreeRDP/client/SDL/SDL2/dialogs/res/CMakeLists.txt new file mode 100644 index 0000000..54bed3e --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/dialogs/res/CMakeLists.txt @@ -0,0 +1,30 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP SDL Client +# +# Copyright 2024 Armin Novak +# Copyright 2024 Thincast Technologies GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(SRCS sdl2_resource_manager.cpp sdl2_resource_manager.hpp) + +add_library(sdl2_client_res STATIC ${SRCS}) + +if(NOT WITH_SDL_LINK_SHARED) + target_link_libraries(sdl2_client_res ${SDL2_STATIC_LIBRARIES}) +else() + target_link_libraries(sdl2_client_res ${SDL2_LIBRARIES}) +endif() + +set_target_properties(sdl2_client_res PROPERTIES POSITION_INDEPENDENT_CODE ON INTERPROCEDURAL_OPTIMIZATION OFF) +target_link_libraries(sdl2_client_res sdl-common-client-res) diff --git a/third_party/FreeRDP/client/SDL/SDL2/dialogs/res/sdl2_resource_manager.cpp b/third_party/FreeRDP/client/SDL/SDL2/dialogs/res/sdl2_resource_manager.cpp new file mode 100644 index 0000000..ae7c075 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/dialogs/res/sdl2_resource_manager.cpp @@ -0,0 +1,37 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "sdl2_resource_manager.hpp" +#include + +SDL_RWops* SDL2ResourceManager::get(const std::string& type, const std::string& id) +{ + if (useCompiledResources()) + { + auto d = data(type, id); + if (!d) + return nullptr; + + auto s = d->size(); + if (s > INT32_MAX) + return nullptr; + return SDL_RWFromConstMem(d->data(), static_cast(s)); + } + + auto name = filename(type, id); + return SDL_RWFromFile(name.c_str(), "rb"); +} diff --git a/third_party/FreeRDP/client/SDL/SDL2/dialogs/res/sdl2_resource_manager.hpp b/third_party/FreeRDP/client/SDL/SDL2/dialogs/res/sdl2_resource_manager.hpp new file mode 100644 index 0000000..de1fcea --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/dialogs/res/sdl2_resource_manager.hpp @@ -0,0 +1,38 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include +#include + +#include + +class SDL2ResourceManager : public SDLResourceManager +{ + public: + SDL2ResourceManager() = delete; + SDL2ResourceManager(const SDL2ResourceManager& other) = delete; + SDL2ResourceManager(const SDL2ResourceManager&& other) = delete; + ~SDL2ResourceManager() = delete; + SDL2ResourceManager& operator=(const SDL2ResourceManager& other) = delete; + SDL2ResourceManager& operator=(SDL2ResourceManager&& other) = delete; + + static SDL_RWops* get(const std::string& type, const std::string& id); +}; diff --git a/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_button.cpp b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_button.cpp new file mode 100644 index 0000000..e591890 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_button.cpp @@ -0,0 +1,71 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client Channels + * + * Copyright 2023 Armin Novak + * 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 +#include + +#include "sdl_button.hpp" + +static const SDL_Color buttonbackgroundcolor = { 0x69, 0x66, 0x63, 0xff }; +static const SDL_Color buttonhighlightcolor = { 0xcd, 0xca, 0x35, 0x60 }; +static const SDL_Color buttonmouseovercolor = { 0x66, 0xff, 0x66, 0x60 }; +static const SDL_Color buttonfontcolor = { 0xd1, 0xcf, 0xcd, 0xff }; + +SdlButton::SdlButton(SDL_Renderer* renderer, std::string label, int id, SDL_Rect rect) + : SdlWidget(renderer, rect, false), _name(std::move(label)), _id(id) +{ + assert(renderer); + + update_text(renderer, _name, buttonfontcolor, buttonbackgroundcolor); +} + +SdlButton::SdlButton(SdlButton&& other) noexcept = default; + +SdlButton::~SdlButton() = default; + +bool SdlButton::highlight(SDL_Renderer* renderer) +{ + assert(renderer); + + std::vector colors = { buttonbackgroundcolor, buttonhighlightcolor }; + if (!fill(renderer, colors)) + return false; + return update_text(renderer, _name, buttonfontcolor); +} + +bool SdlButton::mouseover(SDL_Renderer* renderer) +{ + std::vector colors = { buttonbackgroundcolor, buttonmouseovercolor }; + if (!fill(renderer, colors)) + return false; + return update_text(renderer, _name, buttonfontcolor); +} + +bool SdlButton::update(SDL_Renderer* renderer) +{ + assert(renderer); + + return update_text(renderer, _name, buttonfontcolor, buttonbackgroundcolor); +} + +int SdlButton::id() const +{ + return _id; +} diff --git a/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_button.hpp b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_button.hpp new file mode 100644 index 0000000..9fcb350 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_button.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include "sdl_widget.hpp" + +class SdlButton : public SdlWidget +{ + public: + SdlButton(SDL_Renderer* renderer, std::string label, int id, SDL_Rect rect); + SdlButton(const SdlButton& other) = delete; + SdlButton(SdlButton&& other) noexcept; + ~SdlButton() override; + + SdlButton& operator=(const SdlButton& other) = delete; + SdlButton& operator=(SdlButton&& other) = delete; + + bool highlight(SDL_Renderer* renderer); + bool mouseover(SDL_Renderer* renderer); + bool update(SDL_Renderer* renderer); + + [[nodiscard]] int id() const; + + private: + std::string _name; + int _id; +}; diff --git a/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_buttons.cpp b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_buttons.cpp new file mode 100644 index 0000000..e5b0f2a --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_buttons.cpp @@ -0,0 +1,112 @@ +#include +#include + +#include + +#include "sdl_buttons.hpp" + +static const Uint32 hpadding = 10; + +SdlButtonList::~SdlButtonList() = default; + +bool SdlButtonList::populate(SDL_Renderer* renderer, const std::vector& labels, + const std::vector& ids, Sint32 total_width, Sint32 offsetY, + Sint32 width, Sint32 height) +{ + assert(renderer); + assert(width >= 0); + assert(height >= 0); + assert(labels.size() == ids.size()); + + _list.clear(); + size_t button_width = + ids.size() * (WINPR_ASSERTING_INT_CAST(uint32_t, width) + hpadding) + hpadding; + size_t offsetX = + WINPR_ASSERTING_INT_CAST(uint32_t, total_width) - + std::min(WINPR_ASSERTING_INT_CAST(uint32_t, total_width), button_width); + for (size_t x = 0; x < ids.size(); x++) + { + const size_t curOffsetX = offsetX + x * (static_cast(width) + hpadding); + const SDL_Rect rect = { static_cast(curOffsetX), offsetY, width, height }; + _list.emplace_back(renderer, labels.at(x), ids.at(x), rect); + } + return true; +} + +SdlButton* SdlButtonList::get_selected(const SDL_MouseButtonEvent& button) +{ + const Sint32 x = button.x; + const Sint32 y = button.y; + + return get_selected(x, y); +} + +SdlButton* SdlButtonList::get_selected(Sint32 x, Sint32 y) +{ + for (auto& btn : _list) + { + auto r = btn.rect(); + if ((x >= r.x) && (x <= r.x + r.w) && (y >= r.y) && (y <= r.y + r.h)) + return &btn; + } + return nullptr; +} + +bool SdlButtonList::set_highlight_next(bool reset) +{ + if (reset) + _highlighted = nullptr; + else + { + auto next = _highlight_index++; + _highlight_index %= _list.size(); + auto& element = _list.at(next); + _highlighted = &element; + } + return true; +} + +bool SdlButtonList::set_highlight(size_t index) +{ + if (index >= _list.size()) + { + _highlighted = nullptr; + return false; + } + auto& element = _list.at(index); + _highlighted = &element; + _highlight_index = ++index % _list.size(); + return true; +} + +bool SdlButtonList::set_mouseover(Sint32 x, Sint32 y) +{ + _mouseover = get_selected(x, y); + return _mouseover != nullptr; +} + +void SdlButtonList::clear() +{ + _list.clear(); + _mouseover = nullptr; + _highlighted = nullptr; + _highlight_index = 0; +} + +bool SdlButtonList::update(SDL_Renderer* renderer) +{ + assert(renderer); + + for (auto& btn : _list) + { + if (!btn.update(renderer)) + return false; + } + + if (_highlighted) + _highlighted->highlight(renderer); + + if (_mouseover) + _mouseover->mouseover(renderer); + return true; +} diff --git a/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_buttons.hpp b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_buttons.hpp new file mode 100644 index 0000000..215f1d2 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_buttons.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +#include "sdl_button.hpp" + +class SdlButtonList +{ + public: + SdlButtonList() = default; + virtual ~SdlButtonList(); + + bool populate(SDL_Renderer* renderer, const std::vector& labels, + const std::vector& ids, Sint32 total_width, Sint32 offsetY, Sint32 width, + Sint32 height); + + bool update(SDL_Renderer* renderer); + SdlButton* get_selected(const SDL_MouseButtonEvent& button); + SdlButton* get_selected(Sint32 x, Sint32 y); + + bool set_highlight_next(bool reset = false); + bool set_highlight(size_t index); + bool set_mouseover(Sint32 x, Sint32 y); + + void clear(); + + SdlButtonList(const SdlButtonList& other) = delete; + SdlButtonList(SdlButtonList&& other) = delete; + SdlButtonList& operator=(const SdlButtonList& other) = delete; + SdlButtonList& operator=(SdlButtonList&& other) = delete; + + private: + std::vector _list; + SdlButton* _highlighted = nullptr; + size_t _highlight_index = 0; + SdlButton* _mouseover = nullptr; +}; diff --git a/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_connection_dialog.cpp b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_connection_dialog.cpp new file mode 100644 index 0000000..5552890 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_connection_dialog.cpp @@ -0,0 +1,539 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include + +#include "sdl_connection_dialog.hpp" +#include "../sdl_utils.hpp" +#include "../sdl_freerdp.hpp" +#include "res/sdl2_resource_manager.hpp" + +static const SDL_Color backgroundcolor = { 0x38, 0x36, 0x35, 0xff }; +static const SDL_Color textcolor = { 0xd1, 0xcf, 0xcd, 0xff }; +static const SDL_Color infocolor = { 0x43, 0xe0, 0x0f, 0x60 }; +static const SDL_Color warncolor = { 0xcd, 0xca, 0x35, 0x60 }; +static const SDL_Color errorcolor = { 0xf7, 0x22, 0x30, 0x60 }; + +static const Uint32 vpadding = 5; +static const Uint32 hpadding = 5; + +SDLConnectionDialog::SDLConnectionDialog(rdpContext* context) : _context(context) +{ + SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO); + hide(); +} + +SDLConnectionDialog::~SDLConnectionDialog() +{ + resetTimer(); + destroyWindow(); + SDL_Quit(); +} + +bool SDLConnectionDialog::visible() const +{ + if (!_window || !_renderer) + return false; + + auto flags = SDL_GetWindowFlags(_window); + return (flags & (SDL_WINDOW_HIDDEN | SDL_WINDOW_MINIMIZED)) == 0; +} + +bool SDLConnectionDialog::setTitle(const char* fmt, ...) +{ + std::scoped_lock lock(_mux); + va_list ap = {}; + va_start(ap, fmt); + _title = print(fmt, ap); + va_end(ap); + + return show(MSG_NONE); +} + +bool SDLConnectionDialog::showInfo(const char* fmt, ...) +{ + va_list ap = {}; + va_start(ap, fmt); + auto rc = show(MSG_INFO, fmt, ap); + va_end(ap); + return rc; +} + +bool SDLConnectionDialog::showWarn(const char* fmt, ...) +{ + va_list ap = {}; + va_start(ap, fmt); + auto rc = show(MSG_WARN, fmt, ap); + va_end(ap); + return rc; +} + +bool SDLConnectionDialog::showError(const char* fmt, ...) +{ + va_list ap = {}; + va_start(ap, fmt); + auto rc = show(MSG_ERROR, fmt, ap); + va_end(ap); + if (!rc) + return rc; + return setTimer(); +} + +bool SDLConnectionDialog::show() +{ + std::scoped_lock lock(_mux); + return show(_type_active); +} + +bool SDLConnectionDialog::hide() +{ + std::scoped_lock lock(_mux); + return show(MSG_DISCARD); +} + +bool SDLConnectionDialog::running() const +{ + std::scoped_lock lock(_mux); + return _running; +} + +bool SDLConnectionDialog::update() +{ + std::scoped_lock lock(_mux); + switch (_type) + { + case MSG_INFO: + case MSG_WARN: + case MSG_ERROR: + _type_active = _type; + createWindow(); + break; + case MSG_DISCARD: + resetTimer(); + destroyWindow(); + break; + default: + if (_window) + { + SDL_SetWindowTitle(_window, _title.c_str()); + } + break; + } + _type = MSG_NONE; + return true; +} + +bool SDLConnectionDialog::setModal() +{ + if (_window) + { + auto sdl = get_context(_context); + if (sdl->windows.empty()) + return true; + + auto parent = sdl->windows.begin()->second.window(); + SDL_SetWindowModalFor(_window, parent); + SDL_RaiseWindow(_window); + } + return true; +} + +bool SDLConnectionDialog::clearWindow(SDL_Renderer* renderer) +{ + assert(renderer); + + const int drc = SDL_SetRenderDrawColor(renderer, backgroundcolor.r, backgroundcolor.g, + backgroundcolor.b, backgroundcolor.a); + if (widget_log_error(drc, "SDL_SetRenderDrawColor")) + return false; + + const int rcls = SDL_RenderClear(renderer); + return !widget_log_error(rcls, "SDL_RenderClear"); +} + +bool SDLConnectionDialog::update(SDL_Renderer* renderer) +{ + std::scoped_lock lock(_mux); + if (!renderer) + return false; + + if (!clearWindow(renderer)) + return false; + + for (auto& btn : _list) + { + if (!btn.widget.update_text(renderer, _msg, btn.fgcolor, btn.bgcolor)) + return false; + } + + if (!_buttons.update(renderer)) + return false; + + SDL_RenderPresent(renderer); + return true; +} + +bool SDLConnectionDialog::wait(bool ignoreRdpContext) +{ + while (running()) + { + if (!ignoreRdpContext) + { + if (freerdp_shall_disconnect_context(_context)) + return false; + } + std::this_thread::yield(); + } + return true; +} + +bool SDLConnectionDialog::handle(const SDL_Event& event) +{ + Uint32 windowID = 0; + if (_window) + { + windowID = SDL_GetWindowID(_window); + } + + switch (event.type) + { + case SDL_USEREVENT_RETRY_DIALOG: + return update(); + case SDL_QUIT: + resetTimer(); + destroyWindow(); + return false; + case SDL_KEYDOWN: + case SDL_KEYUP: + if (visible()) + { + auto& ev = reinterpret_cast(event); + update(_renderer); + switch (event.key.keysym.sym) + { + case SDLK_RETURN: + case SDLK_RETURN2: + case SDLK_ESCAPE: + case SDLK_KP_ENTER: + if (event.type == SDL_KEYUP) + { + freerdp_abort_connect_context(_context); + sdl_push_quit(); + } + break; + case SDLK_TAB: + _buttons.set_highlight_next(); + break; + default: + break; + } + + return windowID == ev.windowID; + } + return false; + case SDL_MOUSEMOTION: + if (visible()) + { + auto& ev = reinterpret_cast(event); + + _buttons.set_mouseover(event.button.x, event.button.y); + update(_renderer); + return windowID == ev.windowID; + } + return false; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + if (visible()) + { + auto& ev = reinterpret_cast(event); + update(_renderer); + + auto button = _buttons.get_selected(event.button); + if (button) + { + if (event.type == SDL_MOUSEBUTTONUP) + { + freerdp_abort_connect_context(_context); + sdl_push_quit(); + } + } + + return windowID == ev.windowID; + } + return false; + case SDL_MOUSEWHEEL: + if (visible()) + { + auto& ev = reinterpret_cast(event); + update(_renderer); + return windowID == ev.windowID; + } + return false; + case SDL_FINGERUP: + case SDL_FINGERDOWN: + if (visible()) + { + auto& ev = reinterpret_cast(event); + update(_renderer); +#if SDL_VERSION_ATLEAST(2, 0, 18) + return windowID == ev.windowID; +#else + return false; +#endif + } + return false; + case SDL_WINDOWEVENT: + { + auto& ev = reinterpret_cast(event); + switch (ev.event) + { + case SDL_WINDOWEVENT_CLOSE: + if (windowID == ev.windowID) + { + freerdp_abort_connect_context(_context); + sdl_push_quit(); + } + break; + default: + update(_renderer); + setModal(); + break; + } + + return windowID == ev.windowID; + } + default: + return false; + } +} + +bool SDLConnectionDialog::createWindow() +{ + destroyWindow(); + + const int widget_height = 50; + const int widget_width = 600; + const int total_height = 300; + + auto flags = WINPR_ASSERTING_INT_CAST( + uint32_t, SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_MOUSE_FOCUS | SDL_WINDOW_INPUT_FOCUS); + auto rc = SDL_CreateWindowAndRenderer(widget_width, total_height, flags, &_window, &_renderer); + if (rc != 0) + { + widget_log_error(rc, "SDL_CreateWindowAndRenderer"); + return false; + } + SDL_SetWindowTitle(_window, _title.c_str()); + setModal(); + + SDL_Color res_bgcolor; + switch (_type_active) + { + case MSG_INFO: + res_bgcolor = infocolor; + break; + case MSG_WARN: + res_bgcolor = warncolor; + break; + case MSG_ERROR: + res_bgcolor = errorcolor; + break; + case MSG_DISCARD: + default: + res_bgcolor = backgroundcolor; + break; + } + +#if defined(WITH_SDL_IMAGE_DIALOGS) + std::string res_name; + switch (_type_active) + { + case MSG_INFO: + res_name = "icon_info.svg"; + break; + case MSG_WARN: + res_name = "icon_warning.svg"; + break; + case MSG_ERROR: + res_name = "icon_error.svg"; + break; + case MSG_DISCARD: + default: + res_name = ""; + break; + } + + int height = (total_height - 3ul * vpadding) / 2ul; + SDL_Rect iconRect{ hpadding, vpadding, widget_width / 4ul - 2ul * hpadding, height }; + widget_cfg_t icon{ textcolor, + res_bgcolor, + { _renderer, iconRect, + SDL2ResourceManager::get(SDLResourceManager::typeImages(), res_name) } }; + _list.emplace_back(std::move(icon)); + + iconRect.y += height; + + widget_cfg_t logo{ textcolor, + backgroundcolor, + { _renderer, iconRect, + SDL2ResourceManager::get(SDLResourceManager::typeImages(), + "FreeRDP_Icon.svg") } }; + _list.emplace_back(std::move(logo)); + + SDL_Rect rect = { widget_width / 4ul, vpadding, widget_width * 3ul / 4ul, + total_height - 3ul * vpadding - widget_height }; +#else + SDL_Rect rect = { hpadding, vpadding, widget_width - 2ul * hpadding, + total_height - 2ul * vpadding }; +#endif + + widget_cfg_t w{ textcolor, backgroundcolor, { _renderer, rect, false } }; + w.widget.set_wrap(true, widget_width); + _list.emplace_back(std::move(w)); + rect.y += widget_height + vpadding; + + const std::vector buttonids = { 1 }; + const std::vector buttonlabels = { "cancel" }; + _buttons.populate(_renderer, buttonlabels, buttonids, widget_width, + total_height - widget_height - vpadding, + static_cast(widget_width / 2), static_cast(widget_height)); + _buttons.set_highlight(0); + + SDL_ShowWindow(_window); + SDL_RaiseWindow(_window); + + return true; +} + +void SDLConnectionDialog::destroyWindow() +{ + _buttons.clear(); + _list.clear(); + SDL_DestroyRenderer(_renderer); + SDL_DestroyWindow(_window); + _renderer = nullptr; + _window = nullptr; +} + +bool SDLConnectionDialog::show(MsgType type, const char* fmt, va_list ap) +{ + std::scoped_lock lock(_mux); + _msg = print(fmt, ap); + return show(type); +} + +bool SDLConnectionDialog::show(MsgType type) +{ + _type = type; + return sdl_push_user_event(SDL_USEREVENT_RETRY_DIALOG); +} + +std::string SDLConnectionDialog::print(const char* fmt, va_list ap) +{ + int size = -1; + std::string res; + + do + { + res.resize(128); + if (size > 0) + res.resize(WINPR_ASSERTING_INT_CAST(size_t, size)); + + va_list copy = {}; + va_copy(copy, ap); + WINPR_PRAGMA_DIAG_PUSH + WINPR_PRAGMA_DIAG_IGNORED_FORMAT_NONLITERAL + size = vsnprintf(res.data(), res.size(), fmt, copy); + WINPR_PRAGMA_DIAG_POP + va_end(copy); + + } while ((size > 0) && (static_cast(size) > res.size())); + + return res; +} + +bool SDLConnectionDialog::setTimer(Uint32 timeoutMS) +{ + std::scoped_lock lock(_mux); + resetTimer(); + + _timer = SDL_AddTimer(timeoutMS, &SDLConnectionDialog::timeout, this); + _running = true; + return true; +} + +void SDLConnectionDialog::resetTimer() +{ + if (_running) + SDL_RemoveTimer(_timer); + _running = false; +} + +Uint32 SDLConnectionDialog::timeout([[maybe_unused]] Uint32 intervalMS, void* pvthis) +{ + auto self = static_cast(pvthis); + self->hide(); + self->_running = false; + return 0; +} + +SDLConnectionDialogHider::SDLConnectionDialogHider(freerdp* instance) + : SDLConnectionDialogHider(get(instance)) +{ +} + +SDLConnectionDialogHider::SDLConnectionDialogHider(rdpContext* context) + : SDLConnectionDialogHider(get(context)) +{ +} + +SDLConnectionDialogHider::SDLConnectionDialogHider(SDLConnectionDialog* dialog) : _dialog(dialog) +{ + if (_dialog) + { + _visible = _dialog->visible(); + if (_visible) + { + _dialog->hide(); + } + } +} + +SDLConnectionDialogHider::~SDLConnectionDialogHider() +{ + if (_dialog && _visible) + { + _dialog->show(); + } +} + +SDLConnectionDialog* SDLConnectionDialogHider::get(freerdp* instance) +{ + if (!instance) + return nullptr; + return get(instance->context); +} + +SDLConnectionDialog* SDLConnectionDialogHider::get(rdpContext* context) +{ + auto sdl = get_context(context); + if (!sdl) + return nullptr; + return sdl->connection_dialog.get(); +} diff --git a/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_connection_dialog.hpp b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_connection_dialog.hpp new file mode 100644 index 0000000..bca4daf --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_connection_dialog.hpp @@ -0,0 +1,131 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +#include + +#include + +#include "sdl_widget.hpp" +#include "sdl_buttons.hpp" + +class SDLConnectionDialog +{ + public: + explicit SDLConnectionDialog(rdpContext* context); + SDLConnectionDialog(const SDLConnectionDialog& other) = delete; + SDLConnectionDialog(const SDLConnectionDialog&& other) = delete; + virtual ~SDLConnectionDialog(); + + SDLConnectionDialog& operator=(const SDLConnectionDialog& other) = delete; + SDLConnectionDialog& operator=(SDLConnectionDialog&& other) = delete; + + bool visible() const; + + bool setTitle(const char* fmt, ...); + bool showInfo(const char* fmt, ...); + bool showWarn(const char* fmt, ...); + bool showError(const char* fmt, ...); + + bool show(); + bool hide(); + + bool running() const; + bool wait(bool ignoreRdpContextQuit = false); + + bool handle(const SDL_Event& event); + + private: + enum MsgType + { + MSG_NONE, + MSG_INFO, + MSG_WARN, + MSG_ERROR, + MSG_DISCARD + }; + + bool createWindow(); + void destroyWindow(); + + bool update(); + + bool setModal(); + + static bool clearWindow(SDL_Renderer* renderer); + + bool update(SDL_Renderer* renderer); + + bool show(MsgType type, const char* fmt, va_list ap); + bool show(MsgType type); + + static std::string print(const char* fmt, va_list ap); + bool setTimer(Uint32 timeoutMS = 15000); + void resetTimer(); + + static Uint32 timeout(Uint32 intervalMS, void* pvthis); + + struct widget_cfg_t + { + SDL_Color fgcolor = {}; + SDL_Color bgcolor = {}; + SdlWidget widget; + }; + + rdpContext* _context = nullptr; + SDL_Window* _window = nullptr; + SDL_Renderer* _renderer = nullptr; + mutable std::mutex _mux; + std::string _title; + std::string _msg; + MsgType _type = MSG_NONE; + MsgType _type_active = MSG_NONE; + SDL_TimerID _timer = -1; + bool _running = false; + std::vector _list; + SdlButtonList _buttons; +}; + +class SDLConnectionDialogHider +{ + public: + explicit SDLConnectionDialogHider(freerdp* instance); + explicit SDLConnectionDialogHider(rdpContext* context); + + explicit SDLConnectionDialogHider(SDLConnectionDialog* dialog); + SDLConnectionDialogHider(const SDLConnectionDialogHider& other) = delete; + SDLConnectionDialogHider(SDLConnectionDialogHider&& other) = delete; + SDLConnectionDialogHider& operator=(const SDLConnectionDialogHider& other) = delete; + SDLConnectionDialogHider& operator=(SDLConnectionDialogHider&& other) = delete; + + ~SDLConnectionDialogHider(); + + private: + SDLConnectionDialog* get(freerdp* instance); + static SDLConnectionDialog* get(rdpContext* context); + + SDLConnectionDialog* _dialog = nullptr; + bool _visible = false; +}; diff --git a/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_dialogs.cpp b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_dialogs.cpp new file mode 100644 index 0000000..3f3a240 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_dialogs.cpp @@ -0,0 +1,626 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include +#include + +#include + +#include "../sdl_freerdp.hpp" +#include "sdl_dialogs.hpp" +#include "sdl_input.hpp" +#include "sdl_input_widgets.hpp" +#include "sdl_select.hpp" +#include "sdl_selectlist.hpp" + +enum +{ + SHOW_DIALOG_ACCEPT_REJECT = 1, + SHOW_DIALOG_TIMED_ACCEPT = 2 +}; + +static const char* type_str_for_flags(UINT32 flags) +{ + const char* type = "RDP-Server"; + + if (flags & VERIFY_CERT_FLAG_GATEWAY) + type = "RDP-Gateway"; + + if (flags & VERIFY_CERT_FLAG_REDIRECT) + type = "RDP-Redirect"; + return type; +} + +static BOOL sdl_wait_for_result(rdpContext* context, Uint32 type, SDL_Event* result) +{ + const SDL_Event empty = {}; + + WINPR_ASSERT(context); + WINPR_ASSERT(result); + + while (!freerdp_shall_disconnect_context(context)) + { + *result = empty; + const int rc = SDL_PeepEvents(result, 1, SDL_GETEVENT, type, type); + if (rc > 0) + return TRUE; + Sleep(1); + } + return FALSE; +} + +static int sdl_show_dialog(rdpContext* context, const char* title, const char* message, + Sint32 flags) +{ + SDL_Event event = {}; + + if (!sdl_push_user_event(SDL_USEREVENT_SHOW_DIALOG, title, message, flags)) + return 0; + + if (!sdl_wait_for_result(context, SDL_USEREVENT_SHOW_RESULT, &event)) + return 0; + + return event.user.code; +} + +BOOL sdl_authenticate_ex(freerdp* instance, char** username, char** password, char** domain, + rdp_auth_reason reason) +{ + SDL_Event event = {}; + BOOL res = FALSE; + + SDLConnectionDialogHider hider(instance); + + const char* target = freerdp_settings_get_server_name(instance->context->settings); + switch (reason) + { + case AUTH_NLA: + break; + + case AUTH_TLS: + case AUTH_RDP: + case AUTH_SMARTCARD_PIN: /* in this case password is pin code */ + if ((*username) && (*password)) + return TRUE; + break; + case GW_AUTH_HTTP: + case GW_AUTH_RDG: + case GW_AUTH_RPC: + target = + freerdp_settings_get_string(instance->context->settings, FreeRDP_GatewayHostname); + break; + default: + break; + } + + char* title = nullptr; + size_t titlesize = 0; + winpr_asprintf(&title, &titlesize, "Credentials required for %s", target); + + CStringPtr scope(title, free); + char* u = nullptr; + char* d = nullptr; + char* p = nullptr; + + assert(username); + assert(domain); + assert(password); + + u = *username; + d = *domain; + p = *password; + + if (!sdl_push_user_event(SDL_USEREVENT_AUTH_DIALOG, title, u, d, p, reason)) + return res; + + if (!sdl_wait_for_result(instance->context, SDL_USEREVENT_AUTH_RESULT, &event)) + return res; + + auto arg = reinterpret_cast(event.padding); + + res = arg->result > 0; + + free(*username); + free(*domain); + free(*password); + *username = arg->user; + *domain = arg->domain; + *password = arg->password; + + return res; +} + +BOOL sdl_choose_smartcard(freerdp* instance, SmartcardCertInfo** cert_list, DWORD count, + DWORD* choice, BOOL gateway) +{ + BOOL res = FALSE; + + WINPR_ASSERT(instance); + WINPR_ASSERT(cert_list); + WINPR_ASSERT(choice); + + SDLConnectionDialogHider hider(instance); + std::vector strlist; + std::vector list; + for (DWORD i = 0; i < count; i++) + { + const SmartcardCertInfo* cert = cert_list[i]; + char* reader = ConvertWCharToUtf8Alloc(cert->reader, nullptr); + char* container_name = ConvertWCharToUtf8Alloc(cert->containerName, nullptr); + + char* msg = nullptr; + size_t len = 0; + + winpr_asprintf(&msg, &len, + "%s\n\tReader: %s\n\tUser: %s@%s\n\tSubject: %s\n\tIssuer: %s\n\tUPN: %s", + container_name, reader, cert->userHint, cert->domainHint, cert->subject, + cert->issuer, cert->upn); + + strlist.emplace_back(msg); + free(msg); + free(reader); + free(container_name); + + auto& m = strlist.back(); + list.push_back(m.c_str()); + } + + SDL_Event event = {}; + const char* title = "Select a logon smartcard certificate"; + if (gateway) + title = "Select a gateway logon smartcard certificate"; + if (!sdl_push_user_event(SDL_USEREVENT_SCARD_DIALOG, title, list.data(), count)) + return res; + + if (!sdl_wait_for_result(instance->context, SDL_USEREVENT_SCARD_RESULT, &event)) + return res; + + res = (event.user.code >= 0); + *choice = static_cast(event.user.code); + + return res; +} + +SSIZE_T sdl_retry_dialog(freerdp* instance, const char* what, size_t current, + [[maybe_unused]] void* userarg) +{ + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->context); + WINPR_ASSERT(what); + + auto sdl = get_context(instance->context); + auto settings = instance->context->settings; + const size_t delay = freerdp_settings_get_uint32(settings, FreeRDP_TcpConnectTimeout); + std::scoped_lock lock(sdl->critical); + if (!sdl->connection_dialog) + return WINPR_ASSERTING_INT_CAST(SSIZE_T, delay); + + sdl->connection_dialog->setTitle("Retry connection to %s", + freerdp_settings_get_server_name(instance->context->settings)); + + if ((strcmp(what, "arm-transport") != 0) && (strcmp(what, "connection") != 0)) + { + sdl->connection_dialog->showError("Unknown module %s, aborting", what); + return -1; + } + + if (current == 0) + { + if (strcmp(what, "arm-transport") == 0) + sdl->connection_dialog->showWarn("[%s] Starting your VM. It may take up to 5 minutes", + what); + } + + const BOOL enabled = freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled); + + if (!enabled) + { + sdl->connection_dialog->showError( + "Automatic reconnection disabled, terminating. Try to connect again later"); + return -1; + } + + const size_t max = freerdp_settings_get_uint32(settings, FreeRDP_AutoReconnectMaxRetries); + + if (current >= max) + { + sdl->connection_dialog->showError( + "[%s] retries exceeded. Your VM failed to start. Try again later or contact your " + "tech support for help if this keeps happening.", + what); + return -1; + } + + sdl->connection_dialog->showInfo("[%s] retry %" PRIuz "/%" PRIuz ", delaying %" PRIuz + "ms before next attempt", + what, current + 1, max, delay); + return WINPR_ASSERTING_INT_CAST(SSIZE_T, delay); +} + +BOOL sdl_present_gateway_message(freerdp* instance, [[maybe_unused]] UINT32 type, + BOOL isDisplayMandatory, BOOL isConsentMandatory, size_t length, + const WCHAR* wmessage) +{ + if (!isDisplayMandatory) + return TRUE; + + char* title = nullptr; + size_t len = 0; + winpr_asprintf(&title, &len, "[gateway]"); + + Sint32 flags = 0; + if (isConsentMandatory) + flags = SHOW_DIALOG_ACCEPT_REJECT; + else if (isDisplayMandatory) + flags = SHOW_DIALOG_TIMED_ACCEPT; + char* message = ConvertWCharNToUtf8Alloc(wmessage, length, nullptr); + + SDLConnectionDialogHider hider(instance); + const int rc = sdl_show_dialog(instance->context, title, message, flags); + free(title); + free(message); + return rc > 0; +} + +int sdl_logon_error_info(freerdp* instance, UINT32 data, UINT32 type) +{ + int rc = -1; + const char* str_data = freerdp_get_logon_error_info_data(data); + const char* str_type = freerdp_get_logon_error_info_type(type); + + if (!instance || !instance->context) + return -1; + + /* ignore LOGON_MSG_SESSION_CONTINUE message */ + if (type == LOGON_MSG_SESSION_CONTINUE) + return 0; + + SDLConnectionDialogHider hider(instance); + + char* title = nullptr; + size_t tlen = 0; + winpr_asprintf(&title, &tlen, "[%s] info", + freerdp_settings_get_server_name(instance->context->settings)); + + char* message = nullptr; + size_t mlen = 0; + winpr_asprintf(&message, &mlen, "Logon Error Info %s [%s]", str_data, str_type); + + rc = sdl_show_dialog(instance->context, title, message, SHOW_DIALOG_ACCEPT_REJECT); + free(title); + free(message); + return rc; +} + +static DWORD sdl_show_ceritifcate_dialog(rdpContext* context, const char* title, + const char* message) +{ + SDLConnectionDialogHider hider(context); + if (!sdl_push_user_event(SDL_USEREVENT_CERT_DIALOG, title, message)) + return 0; + + SDL_Event event = {}; + if (!sdl_wait_for_result(context, SDL_USEREVENT_CERT_RESULT, &event)) + return 0; + return static_cast(event.user.code); +} + +static char* sdl_pem_cert(const char* pem) +{ + rdpCertificate* cert = freerdp_certificate_new_from_pem(pem); + if (!cert) + return nullptr; + + char* fp = freerdp_certificate_get_fingerprint(cert); + char* start = freerdp_certificate_get_validity(cert, TRUE); + char* end = freerdp_certificate_get_validity(cert, FALSE); + freerdp_certificate_free(cert); + + char* str = nullptr; + size_t slen = 0; + winpr_asprintf(&str, &slen, + "Valid from: %s\n" + "Valid to: %s\n" + "Thumbprint: %s\n", + start, end, fp); + free(fp); + free(start); + free(end); + return str; +} + +DWORD sdl_verify_changed_certificate_ex(freerdp* instance, const char* host, UINT16 port, + const char* common_name, const char* subject, + const char* issuer, const char* new_fingerprint, + const char* old_subject, const char* old_issuer, + const char* old_fingerprint, DWORD flags) +{ + const char* type = type_str_for_flags(flags); + + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->context); + WINPR_ASSERT(instance->context->settings); + + SDLConnectionDialogHider hider(instance); + /* Newer versions of FreeRDP allow exposing the whole PEM by setting + * FreeRDP_CertificateCallbackPreferPEM to TRUE + */ + char* new_fp_str = nullptr; + size_t len = 0; + if (flags & VERIFY_CERT_FLAG_FP_IS_PEM) + new_fp_str = sdl_pem_cert(new_fingerprint); + else + winpr_asprintf(&new_fp_str, &len, "Thumbprint: %s\n", new_fingerprint); + + /* Newer versions of FreeRDP allow exposing the whole PEM by setting + * FreeRDP_CertificateCallbackPreferPEM to TRUE + */ + char* old_fp_str = nullptr; + size_t olen = 0; + if (flags & VERIFY_CERT_FLAG_FP_IS_PEM) + old_fp_str = sdl_pem_cert(old_fingerprint); + else + winpr_asprintf(&old_fp_str, &olen, "Thumbprint: %s\n", old_fingerprint); + + const char* collission_str = ""; + if (flags & VERIFY_CERT_FLAG_MATCH_LEGACY_SHA1) + { + collission_str = + "A matching entry with legacy SHA1 was found in local known_hosts2 store.\n" + "If you just upgraded from a FreeRDP version before 2.0 this is expected.\n" + "The hashing algorithm has been upgraded from SHA1 to SHA256.\n" + "All manually accepted certificates must be reconfirmed!\n" + "\n"; + } + + char* title = nullptr; + size_t tlen = 0; + winpr_asprintf(&title, &tlen, "Certificate for %s:%" PRIu16 " (%s) has changed", host, port, + type); + + char* message = nullptr; + size_t mlen = 0; + winpr_asprintf(&message, &mlen, + "New Certificate details:\n" + "Common Name: %s\n" + "Subject: %s\n" + "Issuer: %s\n" + "%s\n" + "Old Certificate details:\n" + "Subject: %s\n" + "Issuer: %s\n" + "%s\n" + "%s\n" + "The above X.509 certificate does not match the certificate used for previous " + "connections.\n" + "This may indicate that the certificate has been tampered with.\n" + "Please contact the administrator of the RDP server and clarify.\n", + common_name, subject, issuer, new_fp_str, old_subject, old_issuer, old_fp_str, + collission_str); + + const DWORD rc = sdl_show_ceritifcate_dialog(instance->context, title, message); + free(title); + free(message); + free(new_fp_str); + free(old_fp_str); + + return rc; +} + +DWORD sdl_verify_certificate_ex(freerdp* instance, const char* host, UINT16 port, + const char* common_name, const char* subject, const char* issuer, + const char* fingerprint, DWORD flags) +{ + const char* type = type_str_for_flags(flags); + + /* Newer versions of FreeRDP allow exposing the whole PEM by setting + * FreeRDP_CertificateCallbackPreferPEM to TRUE + */ + char* fp_str = nullptr; + size_t len = 0; + if (flags & VERIFY_CERT_FLAG_FP_IS_PEM) + fp_str = sdl_pem_cert(fingerprint); + else + winpr_asprintf(&fp_str, &len, "Thumbprint: %s\n", fingerprint); + + char* title = nullptr; + size_t tlen = 0; + winpr_asprintf(&title, &tlen, "New certificate for %s:%" PRIu16 " (%s)", host, port, type); + + char* message = nullptr; + size_t mlen = 0; + winpr_asprintf( + &message, &mlen, + "Common Name: %s\n" + "Subject: %s\n" + "Issuer: %s\n" + "%s\n" + "The above X.509 certificate could not be verified, possibly because you do not have\n" + "the CA certificate in your certificate store, or the certificate has expired.\n" + "Please look at the OpenSSL documentation on how to add a private CA to the store.\n", + common_name, subject, issuer, fp_str); + + SDLConnectionDialogHider hider(instance); + const DWORD rc = sdl_show_ceritifcate_dialog(instance->context, title, message); + free(fp_str); + free(title); + free(message); + return rc; +} + +BOOL sdl_cert_dialog_show(const char* title, const char* message) +{ + int buttonid = -1; + enum + { + BUTTONID_CERT_ACCEPT_PERMANENT = 23, + BUTTONID_CERT_ACCEPT_TEMPORARY = 24, + BUTTONID_CERT_DENY = 25 + }; + const SDL_MessageBoxButtonData buttons[] = { + { 0, BUTTONID_CERT_ACCEPT_PERMANENT, "permanent" }, + { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, BUTTONID_CERT_ACCEPT_TEMPORARY, "temporary" }, + { SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, BUTTONID_CERT_DENY, "cancel" } + }; + + const SDL_MessageBoxData data = { SDL_MESSAGEBOX_WARNING, nullptr, title, message, + ARRAYSIZE(buttons), buttons, nullptr }; + const int rc = SDL_ShowMessageBox(&data, &buttonid); + + Sint32 value = -1; + if (rc < 0) + value = 0; + else + { + switch (buttonid) + { + case BUTTONID_CERT_ACCEPT_PERMANENT: + value = 1; + break; + case BUTTONID_CERT_ACCEPT_TEMPORARY: + value = 2; + break; + default: + value = 0; + break; + } + } + + return sdl_push_user_event(SDL_USEREVENT_CERT_RESULT, value); +} + +BOOL sdl_message_dialog_show(const char* title, const char* message, Sint32 flags) +{ + int buttonid = -1; + enum + { + BUTTONID_SHOW_ACCEPT = 24, + BUTTONID_SHOW_DENY = 25 + }; + const SDL_MessageBoxButtonData buttons[] = { + { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, BUTTONID_SHOW_ACCEPT, "accept" }, + { SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, BUTTONID_SHOW_DENY, "cancel" } + }; + + const int button_cnt = (flags & SHOW_DIALOG_ACCEPT_REJECT) ? 2 : 1; + const SDL_MessageBoxData data = { + SDL_MESSAGEBOX_WARNING, nullptr, title, message, button_cnt, buttons, nullptr + }; + const int rc = SDL_ShowMessageBox(&data, &buttonid); + + Sint32 value = -1; + if (rc < 0) + value = 0; + else + { + switch (buttonid) + { + case BUTTONID_SHOW_ACCEPT: + value = 1; + break; + default: + value = 0; + break; + } + } + + return sdl_push_user_event(SDL_USEREVENT_SHOW_RESULT, value); +} + +BOOL sdl_auth_dialog_show(const SDL_UserAuthArg* args) +{ + const std::vector auth = { "Username: ", "Domain: ", + "Password: " }; + const std::vector authPin = { "Device: ", "PIN: " }; + const std::vector gw = { "GatewayUsername: ", "GatewayDomain: ", + "GatewayPassword: " }; + std::vector prompt; + Sint32 rc = -1; + + switch (args->result) + { + case AUTH_SMARTCARD_PIN: + prompt = authPin; + break; + case AUTH_TLS: + case AUTH_RDP: + case AUTH_NLA: + prompt = auth; + break; + case GW_AUTH_HTTP: + case GW_AUTH_RDG: + case GW_AUTH_RPC: + prompt = gw; + break; + default: + break; + } + + std::vector result; + + if (!prompt.empty()) + { + std::vector initial{ args->user ? args->user : "Smartcard", "" }; + std::vector flags = { SdlInputWidget::SDL_INPUT_READONLY, + SdlInputWidget::SDL_INPUT_MASK }; + if (args->result != AUTH_SMARTCARD_PIN) + { + initial = { args->user ? args->user : "", args->domain ? args->domain : "", + args->password ? args->password : "" }; + flags = { 0, 0, SdlInputWidget::SDL_INPUT_MASK }; + } + SdlInputWidgetList ilist(args->title, prompt, initial, flags); + rc = ilist.run(result); + } + + if ((result.size() < prompt.size())) + rc = -1; + + char* user = nullptr; + char* domain = nullptr; + char* pwd = nullptr; + if (rc > 0) + { + user = _strdup(result.at(0).c_str()); + if (args->result == AUTH_SMARTCARD_PIN) + pwd = _strdup(result.at(1).c_str()); + else + { + domain = _strdup(result.at(1).c_str()); + pwd = _strdup(result.at(2).c_str()); + } + } + return sdl_push_user_event(SDL_USEREVENT_AUTH_RESULT, user, domain, pwd, rc); +} + +BOOL sdl_scard_dialog_show(const char* title, Sint32 count, const char** list) +{ + const auto scount = WINPR_ASSERTING_INT_CAST(size_t, count); + std::vector vlist; + vlist.reserve(scount); + for (size_t x = 0; x < scount; x++) + vlist.emplace_back(list[x]); + SdlSelectList slist(title, vlist); + Sint32 value = slist.run(); + return sdl_push_user_event(SDL_USEREVENT_SCARD_RESULT, value); +} diff --git a/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_dialogs.hpp b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_dialogs.hpp new file mode 100644 index 0000000..ae9bbe6 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_dialogs.hpp @@ -0,0 +1,53 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include "../sdl_types.hpp" +#include "../sdl_utils.hpp" + +BOOL sdl_authenticate_ex(freerdp* instance, char** username, char** password, char** domain, + rdp_auth_reason reason); +BOOL sdl_choose_smartcard(freerdp* instance, SmartcardCertInfo** cert_list, DWORD count, + DWORD* choice, BOOL gateway); + +SSIZE_T sdl_retry_dialog(freerdp* instance, const char* what, size_t current, void* userarg); + +DWORD sdl_verify_certificate_ex(freerdp* instance, const char* host, UINT16 port, + const char* common_name, const char* subject, const char* issuer, + const char* fingerprint, DWORD flags); + +DWORD sdl_verify_changed_certificate_ex(freerdp* instance, const char* host, UINT16 port, + const char* common_name, const char* subject, + const char* issuer, const char* new_fingerprint, + const char* old_subject, const char* old_issuer, + const char* old_fingerprint, DWORD flags); + +int sdl_logon_error_info(freerdp* instance, UINT32 data, UINT32 type); + +BOOL sdl_present_gateway_message(freerdp* instance, UINT32 type, BOOL isDisplayMandatory, + BOOL isConsentMandatory, size_t length, const WCHAR* message); + +BOOL sdl_message_dialog_show(const char* title, const char* message, Sint32 flags); +BOOL sdl_cert_dialog_show(const char* title, const char* message); +BOOL sdl_scard_dialog_show(const char* title, Sint32 count, const char** list); +BOOL sdl_auth_dialog_show(const SDL_UserAuthArg* args); diff --git a/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_input.cpp b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_input.cpp new file mode 100644 index 0000000..9019307 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_input.cpp @@ -0,0 +1,177 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "sdl_input.hpp" + +#include + +#include +#include + +#include +#include + +#include "sdl_widget.hpp" +#include "sdl_button.hpp" +#include "sdl_buttons.hpp" + +static const SDL_Color inputbackgroundcolor = { 0x56, 0x56, 0x56, 0xff }; +static const SDL_Color inputhighlightcolor = { 0x80, 0, 0, 0x60 }; +static const SDL_Color inputmouseovercolor = { 0, 0x80, 0, 0x60 }; +static const SDL_Color inputfontcolor = { 0xd1, 0xcf, 0xcd, 0xff }; +static const SDL_Color labelbackgroundcolor = { 0x56, 0x56, 0x56, 0xff }; +static const SDL_Color labelfontcolor = { 0xd1, 0xcf, 0xcd, 0xff }; +static const Uint32 vpadding = 5; +static const Uint32 hpadding = 10; + +SdlInputWidget::SdlInputWidget(SDL_Renderer* renderer, std::string label, std::string initial, + Uint32 flags, size_t offset, size_t width, size_t height) + : _flags(flags), _text(std::move(initial)), _text_label(std::move(label)), + _label(renderer, + { 0, static_cast(offset * (height + vpadding)), static_cast(width), + static_cast(height) }, + false), + _input(renderer, + { static_cast(width + hpadding), static_cast(offset * (height + vpadding)), + static_cast(width), static_cast(height) }, + true), + _highlight(false), _mouseover(false) +{ +} + +SdlInputWidget::SdlInputWidget(SdlInputWidget&& other) noexcept + : _flags(other._flags), _text(std::move(other._text)), + _text_label(std::move(other._text_label)), _label(std::move(other._label)), + _input(std::move(other._input)), _highlight(other._highlight), _mouseover(other._mouseover) +{ +} + +bool SdlInputWidget::fill_label(SDL_Renderer* renderer, SDL_Color color) +{ + if (!_label.fill(renderer, color)) + return false; + return _label.update_text(renderer, _text_label, labelfontcolor); +} + +bool SdlInputWidget::update_label(SDL_Renderer* renderer) +{ + return _label.update_text(renderer, _text_label, labelfontcolor, labelbackgroundcolor); +} + +bool SdlInputWidget::set_mouseover(SDL_Renderer* renderer, bool mouseOver) +{ + if (readonly()) + return true; + _mouseover = mouseOver; + return update_input(renderer); +} + +bool SdlInputWidget::set_highlight(SDL_Renderer* renderer, bool highlight) +{ + if (readonly()) + return true; + _highlight = highlight; + return update_input(renderer); +} + +bool SdlInputWidget::update_input(SDL_Renderer* renderer) +{ + std::vector colors = { inputbackgroundcolor }; + if (_highlight) + colors.push_back(inputhighlightcolor); + if (_mouseover) + colors.push_back(inputmouseovercolor); + + if (!_input.fill(renderer, colors)) + return false; + return update_input(renderer, inputfontcolor); +} + +bool SdlInputWidget::resize_input(size_t size) +{ + _text.resize(size); + + return true; +} + +bool SdlInputWidget::set_str(SDL_Renderer* renderer, const std::string& text) +{ + if (readonly()) + return true; + _text = text; + if (!resize_input(_text.size())) + return false; + return update_input(renderer); +} + +bool SdlInputWidget::remove_str(SDL_Renderer* renderer, size_t count) +{ + if (readonly()) + return true; + + assert(renderer); + if (_text.empty()) + return true; + + if (!resize_input(_text.size() - count)) + return false; + return update_input(renderer); +} + +bool SdlInputWidget::append_str(SDL_Renderer* renderer, const std::string& text) +{ + assert(renderer); + if (readonly()) + return true; + + _text.append(text); + if (!resize_input(_text.size())) + return false; + return update_input(renderer); +} + +const SDL_Rect& SdlInputWidget::input_rect() const +{ + return _input.rect(); +} + +std::string SdlInputWidget::value() const +{ + return _text; +} + +bool SdlInputWidget::readonly() const +{ + return (_flags & SDL_INPUT_READONLY) != 0; +} + +bool SdlInputWidget::update_input(SDL_Renderer* renderer, SDL_Color fgcolor) +{ + std::string text = _text; + if (!text.empty()) + { + if (_flags & SDL_INPUT_MASK) + { + for (char& x : text) + x = '*'; + } + } + + return _input.update_text(renderer, text, fgcolor); +} diff --git a/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_input.hpp b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_input.hpp new file mode 100644 index 0000000..10a8574 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_input.hpp @@ -0,0 +1,74 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include +#include "sdl_widget.hpp" + +class SdlInputWidget +{ + public: + enum + { + SDL_INPUT_MASK = 1, + SDL_INPUT_READONLY = 2 + }; + + SdlInputWidget(SDL_Renderer* renderer, std::string label, std::string initial, Uint32 flags, + size_t offset, size_t width, size_t height); + SdlInputWidget(SdlInputWidget&& other) noexcept; + SdlInputWidget(const SdlInputWidget& other) = delete; + ~SdlInputWidget() = default; + + SdlInputWidget& operator=(const SdlInputWidget& other) = delete; + SdlInputWidget& operator=(SdlInputWidget&& other) = delete; + + bool fill_label(SDL_Renderer* renderer, SDL_Color color); + bool update_label(SDL_Renderer* renderer); + + bool set_mouseover(SDL_Renderer* renderer, bool mouseOver); + bool set_highlight(SDL_Renderer* renderer, bool highlight); + bool update_input(SDL_Renderer* renderer); + bool resize_input(size_t size); + + bool set_str(SDL_Renderer* renderer, const std::string& text); + bool remove_str(SDL_Renderer* renderer, size_t count); + bool append_str(SDL_Renderer* renderer, const std::string& text); + + [[nodiscard]] const SDL_Rect& input_rect() const; + [[nodiscard]] std::string value() const; + + [[nodiscard]] bool readonly() const; + + protected: + bool update_input(SDL_Renderer* renderer, SDL_Color fgcolor); + + private: + Uint32 _flags; + std::string _text; + std::string _text_label; + SdlWidget _label; + SdlWidget _input; + bool _highlight; + bool _mouseover; +}; diff --git a/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_input_widgets.cpp b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_input_widgets.cpp new file mode 100644 index 0000000..7cedd6b --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_input_widgets.cpp @@ -0,0 +1,299 @@ +#include +#include +#include +#include + +#include "sdl_input_widgets.hpp" + +static const Uint32 vpadding = 5; + +SdlInputWidgetList::SdlInputWidgetList(const std::string& title, + const std::vector& labels, + const std::vector& initial, + const std::vector& flags) + : _window(nullptr), _renderer(nullptr) +{ + assert(labels.size() == initial.size()); + assert(labels.size() == flags.size()); + const std::vector buttonids = { INPUT_BUTTON_ACCEPT, INPUT_BUTTON_CANCEL }; + const std::vector buttonlabels = { "accept", "cancel" }; + + const size_t widget_width = 300; + const size_t widget_heigth = 50; + + const size_t total_width = widget_width + widget_width; + const size_t input_height = labels.size() * (widget_heigth + vpadding) + vpadding; + const size_t total_height = input_height + widget_heigth; + + assert(total_width <= INT32_MAX); + assert(total_height <= INT32_MAX); + Uint32 wflags = SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_MOUSE_FOCUS | SDL_WINDOW_INPUT_FOCUS; + auto rc = + SDL_CreateWindowAndRenderer(static_cast(total_width), static_cast(total_height), + wflags, &_window, &_renderer); + if (rc != 0) + widget_log_error(rc, "SDL_CreateWindowAndRenderer"); + else + { + SDL_SetWindowTitle(_window, title.c_str()); + for (size_t x = 0; x < labels.size(); x++) + _list.emplace_back(_renderer, labels.at(x), initial.at(x), flags.at(x), x, widget_width, + widget_heigth); + + _buttons.populate(_renderer, buttonlabels, buttonids, total_width, + static_cast(input_height), static_cast(widget_width), + static_cast(widget_heigth)); + _buttons.set_highlight(0); + } +} + +ssize_t SdlInputWidgetList::next(ssize_t current) +{ + size_t iteration = 0; + auto val = static_cast(current); + + do + { + if (iteration >= _list.size()) + return -1; + + if (iteration == 0) + { + if (current < 0) + val = 0; + else + val++; + } + else + val++; + iteration++; + val %= _list.size(); + } while (!valid(static_cast(val))); + return static_cast(val); +} + +bool SdlInputWidgetList::valid(ssize_t current) const +{ + if (current < 0) + return false; + auto s = static_cast(current); + if (s >= _list.size()) + return false; + return !_list.at(s).readonly(); +} + +SdlInputWidget* SdlInputWidgetList::get(ssize_t index) +{ + if (index < 0) + return nullptr; + auto s = static_cast(index); + if (s >= _list.size()) + return nullptr; + return &_list.at(s); +} + +SdlInputWidgetList::~SdlInputWidgetList() +{ + _list.clear(); + _buttons.clear(); + SDL_DestroyRenderer(_renderer); + SDL_DestroyWindow(_window); +} + +bool SdlInputWidgetList::update(SDL_Renderer* renderer) +{ + for (auto& btn : _list) + { + if (!btn.update_label(renderer)) + return false; + if (!btn.update_input(renderer)) + return false; + } + + return _buttons.update(renderer); +} + +ssize_t SdlInputWidgetList::get_index(const SDL_MouseButtonEvent& button) +{ + const Sint32 x = button.x; + const Sint32 y = button.y; + + assert(_list.size() <= std::numeric_limits::max()); + for (size_t i = 0; i < _list.size(); i++) + { + auto& cur = _list.at(i); + auto r = cur.input_rect(); + + if ((x >= r.x) && (x <= r.x + r.w) && (y >= r.y) && (y <= r.y + r.h)) + return static_cast(i); + } + return -1; +} + +int SdlInputWidgetList::run(std::vector& result) +{ + int res = -1; + ssize_t LastActiveTextInput = -1; + ssize_t CurrentActiveTextInput = next(-1); + + if (!_window || !_renderer) + return -2; + + try + { + bool running = true; + std::vector pressed; + while (running) + { + if (!clear_window(_renderer)) + throw; + + if (!update(_renderer)) + throw; + + if (!_buttons.update(_renderer)) + throw; + + SDL_Event event = {}; + SDL_WaitEvent(&event); + switch (event.type) + { + case SDL_KEYUP: + { + auto it = std::remove(pressed.begin(), pressed.end(), event.key.keysym.sym); + pressed.erase(it, pressed.end()); + + switch (event.key.keysym.sym) + { + case SDLK_BACKSPACE: + { + auto cur = get(CurrentActiveTextInput); + if (cur) + { + if (!cur->remove_str(_renderer, 1)) + throw; + } + } + break; + case SDLK_TAB: + CurrentActiveTextInput = next(CurrentActiveTextInput); + break; + case SDLK_RETURN: + case SDLK_RETURN2: + case SDLK_KP_ENTER: + running = false; + res = INPUT_BUTTON_ACCEPT; + break; + case SDLK_ESCAPE: + running = false; + res = INPUT_BUTTON_CANCEL; + break; + case SDLK_v: + if (pressed.size() == 2) + { + if ((pressed.at(0) == SDLK_LCTRL) || (pressed.at(0) == SDLK_RCTRL)) + { + auto cur = get(CurrentActiveTextInput); + if (cur) + { + auto text = SDL_GetClipboardText(); + cur->set_str(_renderer, text); + } + } + } + break; + default: + break; + } + } + break; + case SDL_KEYDOWN: + pressed.push_back(event.key.keysym.sym); + break; + case SDL_TEXTINPUT: + { + auto cur = get(CurrentActiveTextInput); + if (cur) + { + if (!cur->append_str(_renderer, event.text.text)) + throw; + } + } + break; + case SDL_MOUSEMOTION: + { + auto TextInputIndex = get_index(event.button); + for (auto& cur : _list) + { + if (!cur.set_mouseover(_renderer, false)) + throw; + } + if (TextInputIndex >= 0) + { + auto& cur = _list.at(static_cast(TextInputIndex)); + if (!cur.set_mouseover(_renderer, true)) + throw; + } + + _buttons.set_mouseover(event.button.x, event.button.y); + } + break; + case SDL_MOUSEBUTTONDOWN: + { + auto val = get_index(event.button); + if (valid(val)) + CurrentActiveTextInput = val; + + auto button = _buttons.get_selected(event.button); + if (button) + { + running = false; + if (button->id() == INPUT_BUTTON_CANCEL) + res = INPUT_BUTTON_CANCEL; + else + res = INPUT_BUTTON_ACCEPT; + } + } + break; + case SDL_QUIT: + res = INPUT_BUTTON_CANCEL; + running = false; + break; + default: + break; + } + + if (LastActiveTextInput != CurrentActiveTextInput) + { + if (CurrentActiveTextInput < 0) + SDL_StopTextInput(); + else + SDL_StartTextInput(); + LastActiveTextInput = CurrentActiveTextInput; + } + + for (auto& cur : _list) + { + if (!cur.set_highlight(_renderer, false)) + throw; + } + auto cur = get(CurrentActiveTextInput); + if (cur) + { + if (!cur->set_highlight(_renderer, true)) + throw; + } + + SDL_RenderPresent(_renderer); + } + + for (auto& cur : _list) + result.push_back(cur.value()); + } + catch (...) + { + res = -2; + } + + return res; +} diff --git a/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_input_widgets.hpp b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_input_widgets.hpp new file mode 100644 index 0000000..7f4de64 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_input_widgets.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include + +#include "sdl_input.hpp" +#include "sdl_buttons.hpp" + +class SdlInputWidgetList +{ + public: + SdlInputWidgetList(const std::string& title, const std::vector& labels, + const std::vector& initial, const std::vector& flags); + SdlInputWidgetList(const SdlInputWidgetList& other) = delete; + SdlInputWidgetList(SdlInputWidgetList&& other) = delete; + + SdlInputWidgetList& operator=(const SdlInputWidgetList& other) = delete; + SdlInputWidgetList& operator=(SdlInputWidgetList&& other) = delete; + + virtual ~SdlInputWidgetList(); + + int run(std::vector& result); + + protected: + bool update(SDL_Renderer* renderer); + ssize_t get_index(const SDL_MouseButtonEvent& button); + + private: + enum + { + INPUT_BUTTON_ACCEPT = 1, + INPUT_BUTTON_CANCEL = -2 + }; + + ssize_t next(ssize_t current); + [[nodiscard]] bool valid(ssize_t current) const; + SdlInputWidget* get(ssize_t index); + + SDL_Window* _window; + SDL_Renderer* _renderer; + std::vector _list; + SdlButtonList _buttons; +}; diff --git a/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_select.cpp b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_select.cpp new file mode 100644 index 0000000..d80fa28 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_select.cpp @@ -0,0 +1,72 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +#include +#include + +#include "sdl_select.hpp" +#include "sdl_widget.hpp" +#include "sdl_button.hpp" +#include "sdl_buttons.hpp" +#include "sdl_input_widgets.hpp" + +static const SDL_Color labelmouseovercolor = { 0, 0x80, 0, 0x60 }; +static const SDL_Color labelbackgroundcolor = { 0x69, 0x66, 0x63, 0xff }; +static const SDL_Color labelhighlightcolor = { 0xcd, 0xca, 0x35, 0x60 }; +static const SDL_Color labelfontcolor = { 0xd1, 0xcf, 0xcd, 0xff }; + +SdlSelectWidget::SdlSelectWidget(SDL_Renderer* renderer, std::string label, SDL_Rect rect) + : SdlWidget(renderer, rect, true), _text(std::move(label)), _mouseover(false), _highlight(false) +{ + update_text(renderer); +} + +SdlSelectWidget::SdlSelectWidget(SdlSelectWidget&& other) noexcept = default; + +SdlSelectWidget::~SdlSelectWidget() = default; + +bool SdlSelectWidget::set_mouseover(SDL_Renderer* renderer, bool mouseOver) +{ + _mouseover = mouseOver; + return update_text(renderer); +} + +bool SdlSelectWidget::set_highlight(SDL_Renderer* renderer, bool highlight) +{ + _highlight = highlight; + return update_text(renderer); +} + +bool SdlSelectWidget::update_text(SDL_Renderer* renderer) +{ + assert(renderer); + std::vector colors = { labelbackgroundcolor }; + if (_highlight) + colors.push_back(labelhighlightcolor); + if (_mouseover) + colors.push_back(labelmouseovercolor); + if (!fill(renderer, colors)) + return false; + return SdlWidget::update_text(renderer, _text, labelfontcolor); +} diff --git a/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_select.hpp b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_select.hpp new file mode 100644 index 0000000..446d029 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_select.hpp @@ -0,0 +1,47 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include +#include "sdl_widget.hpp" + +class SdlSelectWidget : public SdlWidget +{ + public: + SdlSelectWidget(SDL_Renderer* renderer, std::string label, SDL_Rect rect); + SdlSelectWidget(SdlSelectWidget&& other) noexcept; + ~SdlSelectWidget() override; + + bool set_mouseover(SDL_Renderer* renderer, bool mouseOver); + bool set_highlight(SDL_Renderer* renderer, bool highlight); + bool update_text(SDL_Renderer* renderer); + + SdlSelectWidget(const SdlSelectWidget& other) = delete; + SdlSelectWidget& operator=(const SdlSelectWidget& other) = delete; + SdlSelectWidget& operator=(SdlSelectWidget&& other) = delete; + + private: + std::string _text; + bool _mouseover; + bool _highlight; +}; diff --git a/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_selectlist.cpp b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_selectlist.cpp new file mode 100644 index 0000000..4ae7f02 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_selectlist.cpp @@ -0,0 +1,217 @@ +#include + +#include + +#include "sdl_selectlist.hpp" + +static const Uint32 vpadding = 5; + +SdlSelectList::SdlSelectList(const std::string& title, const std::vector& labels) + : _window(nullptr), _renderer(nullptr) +{ + const size_t widget_height = 50; + const size_t widget_width = 600; + + const size_t total_height = labels.size() * (widget_height + vpadding) + vpadding; + const size_t height = total_height + widget_height; + assert(widget_width <= INT32_MAX); + assert(height <= INT32_MAX); + + auto flags = WINPR_ASSERTING_INT_CAST( + uint32_t, SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_MOUSE_FOCUS | SDL_WINDOW_INPUT_FOCUS); + auto rc = SDL_CreateWindowAndRenderer(static_cast(widget_width), static_cast(height), + flags, &_window, &_renderer); + if (rc != 0) + widget_log_error(rc, "SDL_CreateWindowAndRenderer"); + else + { + SDL_SetWindowTitle(_window, title.c_str()); + + SDL_Rect rect = { 0, 0, widget_width, widget_height }; + for (auto& label : labels) + { + _list.emplace_back(_renderer, label, rect); + rect.y += widget_height + vpadding; + } + + const std::vector buttonids = { INPUT_BUTTON_ACCEPT, INPUT_BUTTON_CANCEL }; + const std::vector buttonlabels = { "accept", "cancel" }; + _buttons.populate(_renderer, buttonlabels, buttonids, widget_width, + static_cast(total_height), static_cast(widget_width / 2), + static_cast(widget_height)); + _buttons.set_highlight(0); + } +} + +SdlSelectList::~SdlSelectList() +{ + _list.clear(); + _buttons.clear(); + SDL_DestroyRenderer(_renderer); + SDL_DestroyWindow(_window); +} + +int SdlSelectList::run() +{ + int res = -2; + ssize_t CurrentActiveTextInput = 0; + bool running = true; + + if (!_window || !_renderer) + return -2; + try + { + while (running) + { + if (!clear_window(_renderer)) + throw; + + if (!update_text()) + throw; + + if (!_buttons.update(_renderer)) + throw; + + SDL_Event event = {}; + SDL_WaitEvent(&event); + switch (event.type) + { + case SDL_KEYDOWN: + switch (event.key.keysym.sym) + { + case SDLK_UP: + case SDLK_BACKSPACE: + if (CurrentActiveTextInput > 0) + CurrentActiveTextInput--; + else + CurrentActiveTextInput = + WINPR_ASSERTING_INT_CAST(ssize_t, _list.size() - 1); + break; + case SDLK_DOWN: + case SDLK_TAB: + { + if (CurrentActiveTextInput < 0) + CurrentActiveTextInput = 0; + else + CurrentActiveTextInput++; + + const auto s = _list.size(); + if (s <= 0) + CurrentActiveTextInput = 0; + else + CurrentActiveTextInput = + CurrentActiveTextInput % WINPR_ASSERTING_INT_CAST(ssize_t, s); + } + break; + case SDLK_RETURN: + case SDLK_RETURN2: + case SDLK_KP_ENTER: + running = false; + res = WINPR_ASSERTING_INT_CAST(int, CurrentActiveTextInput); + break; + case SDLK_ESCAPE: + running = false; + res = INPUT_BUTTON_CANCEL; + break; + default: + break; + } + break; + case SDL_MOUSEMOTION: + { + ssize_t TextInputIndex = get_index(event.button); + reset_mouseover(); + if (TextInputIndex >= 0) + { + auto& cur = _list.at(WINPR_ASSERTING_INT_CAST(size_t, TextInputIndex)); + if (!cur.set_mouseover(_renderer, true)) + throw; + } + + _buttons.set_mouseover(event.button.x, event.button.y); + } + break; + case SDL_MOUSEBUTTONDOWN: + { + auto button = _buttons.get_selected(event.button); + if (button) + { + running = false; + if (button->id() == INPUT_BUTTON_CANCEL) + res = INPUT_BUTTON_CANCEL; + else + res = static_cast(CurrentActiveTextInput); + } + else + { + CurrentActiveTextInput = get_index(event.button); + } + } + break; + case SDL_QUIT: + res = INPUT_BUTTON_CANCEL; + running = false; + break; + default: + break; + } + + reset_highlight(); + if (CurrentActiveTextInput >= 0) + { + auto& cur = _list.at(WINPR_ASSERTING_INT_CAST(size_t, CurrentActiveTextInput)); + if (!cur.set_highlight(_renderer, true)) + throw; + } + + SDL_RenderPresent(_renderer); + } + } + catch (...) + { + return -1; + } + return res; +} + +ssize_t SdlSelectList::get_index(const SDL_MouseButtonEvent& button) +{ + const Sint32 x = button.x; + const Sint32 y = button.y; + for (size_t i = 0; i < _list.size(); i++) + { + auto& cur = _list.at(i); + auto r = cur.rect(); + + if ((x >= r.x) && (x <= r.x + r.w) && (y >= r.y) && (y <= r.y + r.h)) + return WINPR_ASSERTING_INT_CAST(ssize_t, i); + } + return -1; +} + +bool SdlSelectList::update_text() +{ + for (auto& cur : _list) + { + if (!cur.update_text(_renderer)) + return false; + } + + return true; +} + +void SdlSelectList::reset_mouseover() +{ + for (auto& cur : _list) + { + cur.set_mouseover(_renderer, false); + } +} + +void SdlSelectList::reset_highlight() +{ + for (auto& cur : _list) + { + cur.set_highlight(_renderer, false); + } +} diff --git a/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_selectlist.hpp b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_selectlist.hpp new file mode 100644 index 0000000..5e09db6 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_selectlist.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include +#include + +#include + +#include "sdl_select.hpp" +#include "sdl_button.hpp" +#include "sdl_buttons.hpp" + +class SdlSelectList +{ + public: + SdlSelectList(const std::string& title, const std::vector& labels); + virtual ~SdlSelectList(); + + int run(); + + SdlSelectList(const SdlSelectList& other) = delete; + SdlSelectList(SdlSelectList&& other) = delete; + SdlSelectList& operator=(const SdlSelectList& other) = delete; + SdlSelectList& operator=(SdlSelectList&& other) = delete; + + private: + enum + { + INPUT_BUTTON_ACCEPT = 0, + INPUT_BUTTON_CANCEL = -2 + }; + + ssize_t get_index(const SDL_MouseButtonEvent& button); + bool update_text(); + void reset_mouseover(); + void reset_highlight(); + + SDL_Window* _window; + SDL_Renderer* _renderer; + std::vector _list; + SdlButtonList _buttons; +}; diff --git a/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_widget.cpp b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_widget.cpp new file mode 100644 index 0000000..bb1ba71 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_widget.cpp @@ -0,0 +1,291 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include +#include + +#include "sdl_widget.hpp" +#include "../sdl_utils.hpp" + +#include "res/sdl2_resource_manager.hpp" + +#include + +#if defined(WITH_SDL_IMAGE_DIALOGS) +#include +#endif + +#define TAG CLIENT_TAG("SDL.widget") + +static const SDL_Color backgroundcolor = { 0x38, 0x36, 0x35, 0xff }; + +static const Uint32 hpadding = 10; + +SdlWidget::SdlWidget([[maybe_unused]] SDL_Renderer* renderer, SDL_Rect rect, bool input) + : _rect(rect), _input(input) +{ + assert(renderer); + + auto ops = SDL2ResourceManager::get(SDLResourceManager::typeFonts(), + "OpenSans-VariableFont_wdth,wght.ttf"); + if (!ops) + widget_log_error(-1, "SDLResourceManager::get"); + else + { + _font = TTF_OpenFontRW(ops, 1, 64); + if (!_font) + widget_log_error(-1, "TTF_OpenFontRW"); + } +} + +#if defined(WITH_SDL_IMAGE_DIALOGS) +SdlWidget::SdlWidget(SDL_Renderer* renderer, SDL_Rect rect, SDL_RWops* ops) : _rect(rect) +{ + if (ops) + { + _image = IMG_LoadTexture_RW(renderer, ops, 1); + if (!_image) + widget_log_error(-1, "IMG_LoadTextureTyped_RW"); + } +} +#endif + +SdlWidget::SdlWidget(SdlWidget&& other) noexcept + : _font(other._font), _image(other._image), _rect(other._rect), _input(other._input), + _wrap(other._wrap), _text_width(other._text_width) +{ + other._font = nullptr; + other._image = nullptr; +} + +SDL_Texture* SdlWidget::render_text(SDL_Renderer* renderer, const std::string& text, + SDL_Color fgcolor, SDL_Rect& src, SDL_Rect& dst) +{ + auto surface = TTF_RenderUTF8_Blended(_font, text.c_str(), fgcolor); + if (!surface) + { + widget_log_error(-1, "TTF_RenderText_Blended"); + return nullptr; + } + + auto texture = SDL_CreateTextureFromSurface(renderer, surface); + SDL_FreeSurface(surface); + if (!texture) + { + widget_log_error(-1, "SDL_CreateTextureFromSurface"); + return nullptr; + } + + TTF_SizeUTF8(_font, text.c_str(), &src.w, &src.h); + + /* Do some magic: + * - Add padding before and after text + * - if text is too long only show the last elements + * - if text is too short only update used space + */ + dst = _rect; + dst.x += hpadding; + dst.w -= 2 * hpadding; + const auto scale = static_cast(dst.h) / static_cast(src.h); + const auto sws = static_cast(src.w) * scale; + const auto dws = static_cast(dst.w) / scale; + if (static_cast(dst.w) > sws) + dst.w = static_cast(sws); + if (static_cast(src.w) > dws) + { + src.x = src.w - static_cast(dws); + src.w = static_cast(dws); + } + return texture; +} + +static int scale(int w, int h) +{ + const auto dw = static_cast(w); + const auto dh = static_cast(h); + const auto scale = dh / dw; + const auto dr = dh * scale; + return static_cast(dr); +} + +SDL_Texture* SdlWidget::render_text_wrapped(SDL_Renderer* renderer, const std::string& text, + SDL_Color fgcolor, SDL_Rect& src, SDL_Rect& dst) +{ + Sint32 w = 0; + Sint32 h = 0; + TTF_SizeUTF8(_font, " ", &w, &h); + + assert(_text_width <= UINT32_MAX); + auto surface = TTF_RenderUTF8_Blended_Wrapped(_font, text.c_str(), fgcolor, + static_cast(_text_width)); + if (!surface) + { + widget_log_error(-1, "TTF_RenderText_Blended"); + return nullptr; + } + + src.w = surface->w; + src.h = surface->h; + + auto texture = SDL_CreateTextureFromSurface(renderer, surface); + SDL_FreeSurface(surface); + if (!texture) + { + widget_log_error(-1, "SDL_CreateTextureFromSurface"); + return nullptr; + } + + /* Do some magic: + * - Add padding before and after text + * - if text is too long only show the last elements + * - if text is too short only update used space + */ + dst = _rect; + dst.x += hpadding; + dst.w -= 2 * hpadding; + auto dh = scale(src.w, src.h); + dst.h = std::min(dh, dst.h); + + return texture; +} + +SdlWidget::~SdlWidget() +{ + TTF_CloseFont(_font); + if (_image) + SDL_DestroyTexture(_image); +} + +bool SdlWidget::error_ex(Sint32 res, const char* what, const char* file, size_t line, + const char* fkt) +{ + static wLog* log = nullptr; + if (!log) + log = WLog_Get(TAG); + return sdl_log_error_ex(res, log, what, file, line, fkt); +} + +static bool draw_rect(SDL_Renderer* renderer, const SDL_Rect* rect, SDL_Color color) +{ + const int drc = SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a); + if (widget_log_error(drc, "SDL_SetRenderDrawColor")) + return false; + + const int rc = SDL_RenderFillRect(renderer, rect); + return !widget_log_error(rc, "SDL_RenderFillRect"); +} + +bool SdlWidget::fill(SDL_Renderer* renderer, SDL_Color color) +{ + std::vector colors = { color }; + return fill(renderer, colors); +} + +bool SdlWidget::fill(SDL_Renderer* renderer, const std::vector& colors) +{ + assert(renderer); + SDL_BlendMode mode = SDL_BLENDMODE_INVALID; + SDL_GetRenderDrawBlendMode(renderer, &mode); + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE); + for (auto color : colors) + { + draw_rect(renderer, &_rect, color); + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_ADD); + } + SDL_SetRenderDrawBlendMode(renderer, mode); + return true; +} + +bool SdlWidget::update_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor, + SDL_Color bgcolor) +{ + assert(renderer); + + if (!fill(renderer, bgcolor)) + return false; + return update_text(renderer, text, fgcolor); +} + +bool SdlWidget::wrap() const +{ + return _wrap; +} + +bool SdlWidget::set_wrap(bool wrap, size_t width) +{ + _wrap = wrap; + _text_width = width; + return _wrap; +} + +const SDL_Rect& SdlWidget::rect() const +{ + return _rect; +} + +bool SdlWidget::update_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor) +{ + + if (text.empty()) + return true; + + SDL_Rect src{}; + SDL_Rect dst{}; + + SDL_Texture* texture = nullptr; + if (_image) + { + texture = _image; + dst = _rect; + auto rc = SDL_QueryTexture(_image, nullptr, nullptr, &src.w, &src.h); + if (rc < 0) + widget_log_error(rc, "SDL_QueryTexture"); + } + else if (_wrap) + texture = render_text_wrapped(renderer, text, fgcolor, src, dst); + else + texture = render_text(renderer, text, fgcolor, src, dst); + if (!texture) + return false; + + const int rc = SDL_RenderCopy(renderer, texture, &src, &dst); + if (!_image) + SDL_DestroyTexture(texture); + if (rc < 0) + return !widget_log_error(rc, "SDL_RenderCopy"); + return true; +} + +bool clear_window(SDL_Renderer* renderer) +{ + assert(renderer); + + const int drc = SDL_SetRenderDrawColor(renderer, backgroundcolor.r, backgroundcolor.g, + backgroundcolor.b, backgroundcolor.a); + if (widget_log_error(drc, "SDL_SetRenderDrawColor")) + return false; + + const int rcls = SDL_RenderClear(renderer); + return !widget_log_error(rcls, "SDL_RenderClear"); +} diff --git a/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_widget.hpp b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_widget.hpp new file mode 100644 index 0000000..4d49a91 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/dialogs/sdl_widget.hpp @@ -0,0 +1,90 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include +#include + +#if defined(_MSC_VER) +#include +typedef SSIZE_T ssize_t; +#endif + +#if !defined(HAS_NOEXCEPT) +#if defined(__clang__) +#if __has_feature(cxx_noexcept) +#define HAS_NOEXCEPT +#endif +#elif defined(__GXX_EXPERIMENTAL_CXX0X__) && __GNUC__ * 10 + __GNUC_MINOR__ >= 46 || \ + defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 190023026 +#define HAS_NOEXCEPT +#endif +#endif + +#ifndef HAS_NOEXCEPT +#define noexcept +#endif + +class SdlWidget +{ + public: + SdlWidget(SDL_Renderer* renderer, SDL_Rect rect, bool input); +#if defined(WITH_SDL_IMAGE_DIALOGS) + SdlWidget(SDL_Renderer* renderer, SDL_Rect rect, SDL_RWops* ops); +#endif + SdlWidget(SdlWidget&& other) noexcept; + virtual ~SdlWidget(); + + bool fill(SDL_Renderer* renderer, SDL_Color color); + bool fill(SDL_Renderer* renderer, const std::vector& colors); + bool update_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor); + bool update_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor, + SDL_Color bgcolor); + + [[nodiscard]] bool wrap() const; + bool set_wrap(bool wrap = true, size_t width = 0); + [[nodiscard]] const SDL_Rect& rect() const; + +#define widget_log_error(res, what) SdlWidget::error_ex(res, what, __FILE__, __LINE__, __func__) + static bool error_ex(Sint32 res, const char* what, const char* file, size_t line, + const char* fkt); + + SdlWidget(const SdlWidget& other) = delete; + SdlWidget& operator=(const SdlWidget& other) = delete; + SdlWidget& operator=(SdlWidget&& other) = delete; + + private: + SDL_Texture* render_text(SDL_Renderer* renderer, const std::string& text, SDL_Color fgcolor, + SDL_Rect& src, SDL_Rect& dst); + SDL_Texture* render_text_wrapped(SDL_Renderer* renderer, const std::string& text, + SDL_Color fgcolor, SDL_Rect& src, SDL_Rect& dst); + + TTF_Font* _font = nullptr; + SDL_Texture* _image = nullptr; + SDL_Rect _rect = {}; + bool _input = false; + bool _wrap = false; + size_t _text_width = 0; +}; + +bool clear_window(SDL_Renderer* renderer); diff --git a/third_party/FreeRDP/client/SDL/SDL2/dialogs/test/CMakeLists.txt b/third_party/FreeRDP/client/SDL/SDL2/dialogs/test/CMakeLists.txt new file mode 100644 index 0000000..a6a9464 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/dialogs/test/CMakeLists.txt @@ -0,0 +1,25 @@ +set(MODULE_NAME "TestSDL") +set(MODULE_PREFIX "TEST_SDL") + +set(DRIVER ${MODULE_NAME}.cpp) + +disable_warnings_for_directory(${CMAKE_CURRENT_BINARY_DIR}) + +set(TEST_SRCS TestSDLDialogs.cpp) + +create_test_sourcelist(SRCS ${DRIVER} ${TEST_SRCS}) + +add_library(${MODULE_NAME} ${SRCS}) + +list(APPEND LIBS dialogs) + +target_link_libraries(${MODULE_NAME} ${LIBS}) + +set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}") + +foreach(test ${TESTS}) + get_filename_component(TestName ${test} NAME_WE) + add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName}) +endforeach() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "FreeRDP/Client/Test") diff --git a/third_party/FreeRDP/client/SDL/SDL2/dialogs/test/TestSDLDialogs.cpp b/third_party/FreeRDP/client/SDL/SDL2/dialogs/test/TestSDLDialogs.cpp new file mode 100644 index 0000000..558fb4c --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/dialogs/test/TestSDLDialogs.cpp @@ -0,0 +1,99 @@ +#include "../sdl_selectlist.hpp" +#include "../sdl_input_widgets.hpp" + +#include +#include + +BOOL sdl_log_error_ex(Uint32 res, wLog* log, const char* what, const char* file, size_t line, + const char* fkt) +{ + return FALSE; +} + +static bool test_input_dialog() +{ + const auto title = "sometitle"; + std::vector labels; + std::vector initial; + std::vector flags; + for (size_t x = 0; x < 12; x++) + { + labels.push_back("label" + std::to_string(x)); + initial.push_back(std::to_string(x)); + + Uint32 flag = 0; + if ((x % 2) != 0) + flag |= SdlInputWidget::SDL_INPUT_MASK; + if ((x % 3) == 0) + flag |= SdlInputWidget::SDL_INPUT_READONLY; + + flags.push_back(flag); + } + SdlInputWidgetList list{ title, labels, initial, flags }; + std::vector result; + auto rc = list.run(result); + if (rc < 0) + { + return false; + } + if (result.size() != labels.size()) + { + return false; + } + return true; +} + +static bool test_select_dialog() +{ + const auto title = "sometitle"; + std::vector labels; + for (size_t x = 0; x < 12; x++) + { + labels.push_back("label" + std::to_string(x)); + } + SdlSelectList list{ title, labels }; + auto rc = list.run(); + if (rc < 0) + { + return false; + } + if (static_cast(rc) >= labels.size()) + return false; + + return true; +} + +extern "C" +{ + FREERDP_API int TestSDLDialogs(int argc, char* argv[]); +} + +int TestSDLDialogs(int argc, char* argv[]) +{ + int rc = 0; + + (void)argc; + (void)argv; + +#if 0 + SDL_Init(SDL_INIT_VIDEO); + try + { +#if 1 + if (!test_input_dialog()) + throw -1; +#endif +#if 1 + if (!test_select_dialog()) + throw -2; +#endif + } + catch (int e) + { + rc = e; + } + SDL_Quit(); + +#endif + return rc; +} diff --git a/third_party/FreeRDP/client/SDL/SDL2/man/CMakeLists.txt b/third_party/FreeRDP/client/SDL/SDL2/man/CMakeLists.txt new file mode 100644 index 0000000..9483e2b --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/man/CMakeLists.txt @@ -0,0 +1,15 @@ +set(DEPS + ../../../common/man/freerdp-global-options.1 + ../../../common/man/freerdp-global-envvar.1 + ../../common/man/sdl-freerdp-config.1 + ../../../common/man/freerdp-global-config.1 + ../../common/man/sdl-global-config.1 + ../../common/man/sdl-freerdp-examples.1 + ../../../common/man/freerdp-global-links.1 +) + +include(GetSysconfDir) +get_sysconf_dir("" SYSCONF_DIR) +set(SDL_WIKI_BASE_URL "https://wiki.libsdl.org/SDL2") +set(VAR_NAMES "VENDOR" "PRODUCT" "VENDOR_PRODUCT" "SYSCONF_DIR" "SDL_WIKI_BASE_URL") +generate_and_install_freerdp_man_from_xml(${MODULE_NAME} "1" "${DEPS}" "${VAR_NAMES}") diff --git a/third_party/FreeRDP/client/SDL/SDL2/man/sdl2-freerdp.1.in b/third_party/FreeRDP/client/SDL/SDL2/man/sdl2-freerdp.1.in new file mode 100644 index 0000000..6fb12b2 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/man/sdl2-freerdp.1.in @@ -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)\&. diff --git a/third_party/FreeRDP/client/SDL/SDL2/sdl_channels.cpp b/third_party/FreeRDP/client/SDL/SDL2/sdl_channels.cpp new file mode 100644 index 0000000..dccbb79 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/sdl_channels.cpp @@ -0,0 +1,83 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client Channels + * + * Copyright 2022 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include +#include +#include + +#include "sdl_channels.hpp" +#include "sdl_freerdp.hpp" +#include "sdl_disp.hpp" + +void sdl_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e) +{ + auto sdl = get_context(context); + + WINPR_ASSERT(sdl); + WINPR_ASSERT(e); + + if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0) + { + } + else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) + { + auto clip = reinterpret_cast(e->pInterface); + WINPR_ASSERT(clip); + clip->custom = context; + } + else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0) + { + auto disp = reinterpret_cast(e->pInterface); + WINPR_ASSERT(disp); + sdl->disp.init(disp); + } + else + freerdp_client_OnChannelConnectedEventHandler(context, e); +} + +void sdl_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e) +{ + auto sdl = get_context(context); + + WINPR_ASSERT(sdl); + WINPR_ASSERT(e); + + // TODO(nin): Set resizeable depending on disp channel and /dynamic-resolution + if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0) + { + } + else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) + { + auto clip = reinterpret_cast(e->pInterface); + WINPR_ASSERT(clip); + clip->custom = nullptr; + } + else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0) + { + auto disp = reinterpret_cast(e->pInterface); + WINPR_ASSERT(disp); + sdl->disp.uninit(disp); + } + else + freerdp_client_OnChannelDisconnectedEventHandler(context, e); +} diff --git a/third_party/FreeRDP/client/SDL/SDL2/sdl_channels.hpp b/third_party/FreeRDP/client/SDL/SDL2/sdl_channels.hpp new file mode 100644 index 0000000..a5c9f7d --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/sdl_channels.hpp @@ -0,0 +1,29 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client Channels + * + * Copyright 2022 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +int sdl_on_channel_connected(freerdp* instance, const char* name, void* pInterface); +int sdl_on_channel_disconnected(freerdp* instance, const char* name, void* pInterface); + +void sdl_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e); +void sdl_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e); diff --git a/third_party/FreeRDP/client/SDL/SDL2/sdl_config.hpp.in b/third_party/FreeRDP/client/SDL/SDL2/sdl_config.hpp.in new file mode 100644 index 0000000..97ee81a --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/sdl_config.hpp.in @@ -0,0 +1,30 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL config template + * + * Copyright 2025 Armin Novak + * 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"; diff --git a/third_party/FreeRDP/client/SDL/SDL2/sdl_disp.cpp b/third_party/FreeRDP/client/SDL/SDL2/sdl_disp.cpp new file mode 100644 index 0000000..5f9b833 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/sdl_disp.cpp @@ -0,0 +1,481 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Display Control Channel + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include + +#include + +#include "sdl_disp.hpp" +#include "sdl_kbd.hpp" +#include "sdl_utils.hpp" +#include "sdl_freerdp.hpp" + +#include +#define TAG CLIENT_TAG("sdl.disp") + +static constexpr UINT64 RESIZE_MIN_DELAY = 200; /* minimum delay in ms between two resizes */ +static constexpr unsigned MAX_RETRIES = 5; + +BOOL sdlDispContext::settings_changed() +{ + auto settings = _sdl->context()->settings; + WINPR_ASSERT(settings); + + if (_lastSentWidth != _targetWidth) + return TRUE; + + if (_lastSentHeight != _targetHeight) + return TRUE; + + if (_lastSentDesktopOrientation != + freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation)) + return TRUE; + + if (_lastSentDesktopScaleFactor != + freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor)) + return TRUE; + + if (_lastSentDeviceScaleFactor != + freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor)) + return TRUE; + /* TODO + if (_fullscreen != _sdl->fullscreen) + return TRUE; + */ + return FALSE; +} + +BOOL sdlDispContext::update_last_sent() +{ + WINPR_ASSERT(_sdl); + + auto settings = _sdl->context()->settings; + WINPR_ASSERT(settings); + + _lastSentWidth = _targetWidth; + _lastSentHeight = _targetHeight; + _lastSentDesktopOrientation = freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation); + _lastSentDesktopScaleFactor = freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor); + _lastSentDeviceScaleFactor = freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor); + // TODO _fullscreen = _sdl->fullscreen; + return TRUE; +} + +BOOL sdlDispContext::sendResize() +{ + DISPLAY_CONTROL_MONITOR_LAYOUT layout = {}; + auto settings = _sdl->context()->settings; + + if (!settings) + return FALSE; + + if (!_activated || !_disp) + return TRUE; + + if (GetTickCount64() - _lastSentDate < RESIZE_MIN_DELAY) + return TRUE; + + _lastSentDate = GetTickCount64(); + + if (!settings_changed()) + return TRUE; + + const UINT32 mcount = freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount); + if (_sdl->fullscreen && (mcount > 0)) + { + auto monitors = static_cast( + freerdp_settings_get_pointer(settings, FreeRDP_MonitorDefArray)); + if (sendLayout(monitors, mcount) != CHANNEL_RC_OK) + return FALSE; + } + else + { + _waitingResize = TRUE; + layout.Flags = DISPLAY_CONTROL_MONITOR_PRIMARY; + layout.Top = layout.Left = 0; + layout.Width = WINPR_ASSERTING_INT_CAST(uint32_t, _targetWidth); + layout.Height = WINPR_ASSERTING_INT_CAST(uint32_t, _targetHeight); + layout.Orientation = freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation); + layout.DesktopScaleFactor = + freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor); + layout.DeviceScaleFactor = freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor); + layout.PhysicalWidth = WINPR_ASSERTING_INT_CAST(uint32_t, _targetWidth); + layout.PhysicalHeight = WINPR_ASSERTING_INT_CAST(uint32_t, _targetHeight); + + if (IFCALLRESULT(CHANNEL_RC_OK, _disp->SendMonitorLayout, _disp, 1, &layout) != + CHANNEL_RC_OK) + return FALSE; + } + return update_last_sent(); +} + +BOOL sdlDispContext::set_window_resizable() +{ + _sdl->update_resizeable(TRUE); + return TRUE; +} + +static BOOL sdl_disp_check_context(void* context, SdlContext** ppsdl, sdlDispContext** ppsdlDisp, + rdpSettings** ppSettings) +{ + if (!context) + return FALSE; + + auto sdl = get_context(context); + if (!sdl) + return FALSE; + + if (!sdl->context()->settings) + return FALSE; + + *ppsdl = sdl; + *ppsdlDisp = &sdl->disp; + *ppSettings = sdl->context()->settings; + return TRUE; +} + +void sdlDispContext::OnActivated(void* context, const ActivatedEventArgs* e) +{ + SdlContext* sdl = nullptr; + sdlDispContext* sdlDisp = nullptr; + rdpSettings* settings = nullptr; + + if (!sdl_disp_check_context(context, &sdl, &sdlDisp, &settings)) + return; + + sdlDisp->_waitingResize = FALSE; + + if (sdlDisp->_activated && !freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)) + { + sdlDisp->set_window_resizable(); + + if (e->firstActivation) + return; + + sdlDisp->addTimer(); + } +} + +void sdlDispContext::OnGraphicsReset(void* context, const GraphicsResetEventArgs* e) +{ + SdlContext* sdl = nullptr; + sdlDispContext* sdlDisp = nullptr; + rdpSettings* settings = nullptr; + + WINPR_UNUSED(e); + if (!sdl_disp_check_context(context, &sdl, &sdlDisp, &settings)) + return; + + sdlDisp->_waitingResize = FALSE; + + if (sdlDisp->_activated && !freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)) + { + sdlDisp->set_window_resizable(); + sdlDisp->addTimer(); + } +} + +Uint32 sdlDispContext::OnTimer(Uint32 interval, void* param) +{ + auto ctx = static_cast(param); + if (!ctx) + return 0; + + SdlContext* sdl = ctx->_sdl; + if (!sdl) + return 0; + + sdlDispContext* sdlDisp = nullptr; + rdpSettings* settings = nullptr; + + if (!sdl_disp_check_context(sdl->context(), &sdl, &sdlDisp, &settings)) + return 0; + + WLog_Print(sdl->log, WLOG_TRACE, "checking for display changes..."); + if (!sdlDisp->_activated || freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)) + return 0; + else + { + auto rc = sdlDisp->sendResize(); + if (!rc) + WLog_Print(sdl->log, WLOG_TRACE, "sent new display layout, result %d", rc); + } + if (sdlDisp->_timer_retries++ >= MAX_RETRIES) + { + WLog_Print(sdl->log, WLOG_TRACE, "deactivate timer, retries exceeded"); + return 0; + } + + WLog_Print(sdl->log, WLOG_TRACE, "fire timer one more time"); + return interval; +} + +UINT sdlDispContext::sendLayout(const rdpMonitor* monitors, size_t nmonitors) +{ + UINT ret = CHANNEL_RC_OK; + + WINPR_ASSERT(monitors); + WINPR_ASSERT(nmonitors > 0); + + auto settings = _sdl->context()->settings; + WINPR_ASSERT(settings); + + std::vector layouts; + layouts.resize(nmonitors); + + for (size_t i = 0; i < nmonitors; i++) + { + auto monitor = &monitors[i]; + auto& layout = layouts.at(i); + + layout.Flags = (monitor->is_primary ? DISPLAY_CONTROL_MONITOR_PRIMARY : 0); + layout.Left = monitor->x; + layout.Top = monitor->y; + layout.Width = WINPR_ASSERTING_INT_CAST(uint32_t, monitor->width); + layout.Height = WINPR_ASSERTING_INT_CAST(uint32_t, monitor->height); + layout.Orientation = ORIENTATION_LANDSCAPE; + layout.PhysicalWidth = monitor->attributes.physicalWidth; + layout.PhysicalHeight = monitor->attributes.physicalHeight; + + switch (monitor->attributes.orientation) + { + case 90: + layout.Orientation = ORIENTATION_PORTRAIT; + break; + + case 180: + layout.Orientation = ORIENTATION_LANDSCAPE_FLIPPED; + break; + + case 270: + layout.Orientation = ORIENTATION_PORTRAIT_FLIPPED; + break; + + case 0: + default: + /* MS-RDPEDISP - 2.2.2.2.1: + * Orientation (4 bytes): A 32-bit unsigned integer that specifies the + * orientation of the monitor in degrees. Valid values are 0, 90, 180 + * or 270 + * + * So we default to ORIENTATION_LANDSCAPE + */ + layout.Orientation = ORIENTATION_LANDSCAPE; + break; + } + + layout.DesktopScaleFactor = + freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor); + layout.DeviceScaleFactor = freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor); + } + + WINPR_ASSERT(_disp); + const size_t len = layouts.size(); + WINPR_ASSERT(len <= UINT32_MAX); + ret = IFCALLRESULT(CHANNEL_RC_OK, _disp->SendMonitorLayout, _disp, static_cast(len), + layouts.data()); + return ret; +} + +BOOL sdlDispContext::addTimer() +{ + if (SDL_WasInit(SDL_INIT_TIMER) == 0) + return FALSE; + + SDL_RemoveTimer(_timer); + WLog_Print(_sdl->log, WLOG_TRACE, "adding new display check timer"); + + _timer_retries = 0; + sendResize(); + _timer = SDL_AddTimer(1000, sdlDispContext::OnTimer, this); + return TRUE; +} + +#if SDL_VERSION_ATLEAST(2, 0, 10) +BOOL sdlDispContext::handle_display_event(const SDL_DisplayEvent* ev) +{ + WINPR_ASSERT(ev); + + switch (ev->event) + { +#if SDL_VERSION_ATLEAST(2, 0, 14) + case SDL_DISPLAYEVENT_CONNECTED: + SDL_Log("A new display with id %u was connected", ev->display); + return TRUE; + case SDL_DISPLAYEVENT_DISCONNECTED: + SDL_Log("The display with id %u was disconnected", ev->display); + return TRUE; +#endif + case SDL_DISPLAYEVENT_ORIENTATION: + SDL_Log("The orientation of display with id %u was changed", ev->display); + return TRUE; + default: + return TRUE; + } +} +#endif + +BOOL sdlDispContext::handle_window_event(const SDL_WindowEvent* ev) +{ + WINPR_ASSERT(ev); +#if defined(WITH_DEBUG_SDL_EVENTS) + SDL_Log("got windowEvent %s [0x%08" PRIx32 "]", sdl_window_event_str(ev->event).c_str(), + ev->event); +#endif + auto bordered = freerdp_settings_get_bool(_sdl->context()->settings, FreeRDP_Decorations) + ? SDL_TRUE + : SDL_FALSE; + + auto it = _sdl->windows.find(ev->windowID); + if (it != _sdl->windows.end()) + it->second.setBordered(bordered); + + switch (ev->event) + { + case SDL_WINDOWEVENT_HIDDEN: + case SDL_WINDOWEVENT_MINIMIZED: + return gdi_send_suppress_output(_sdl->context()->gdi, TRUE); + + case SDL_WINDOWEVENT_EXPOSED: + case SDL_WINDOWEVENT_SHOWN: + case SDL_WINDOWEVENT_MAXIMIZED: + case SDL_WINDOWEVENT_RESTORED: + return gdi_send_suppress_output(_sdl->context()->gdi, FALSE); + + case SDL_WINDOWEVENT_RESIZED: + case SDL_WINDOWEVENT_SIZE_CHANGED: + _targetWidth = ev->data1; + _targetHeight = ev->data2; + return addTimer(); + + case SDL_WINDOWEVENT_LEAVE: + WINPR_ASSERT(_sdl); + _sdl->input.keyboard_grab(ev->windowID, false); + return TRUE; + case SDL_WINDOWEVENT_ENTER: + WINPR_ASSERT(_sdl); + _sdl->input.keyboard_grab(ev->windowID, true); + return _sdl->input.keyboard_focus_in(); + case SDL_WINDOWEVENT_FOCUS_GAINED: + case SDL_WINDOWEVENT_TAKE_FOCUS: + return _sdl->input.keyboard_focus_in(); + + default: + return TRUE; + } +} + +UINT sdlDispContext::DisplayControlCaps(DispClientContext* disp, UINT32 maxNumMonitors, + UINT32 maxMonitorAreaFactorA, UINT32 maxMonitorAreaFactorB) +{ + /* we're called only if dynamic resolution update is activated */ + WINPR_ASSERT(disp); + + auto sdlDisp = reinterpret_cast(disp->custom); + return sdlDisp->DisplayControlCaps(maxNumMonitors, maxMonitorAreaFactorA, + maxMonitorAreaFactorB); +} + +UINT sdlDispContext::DisplayControlCaps(UINT32 maxNumMonitors, UINT32 maxMonitorAreaFactorA, + UINT32 maxMonitorAreaFactorB) +{ + auto settings = _sdl->context()->settings; + WINPR_ASSERT(settings); + + WLog_DBG(TAG, + "DisplayControlCapsPdu: MaxNumMonitors: %" PRIu32 " MaxMonitorAreaFactorA: %" PRIu32 + " MaxMonitorAreaFactorB: %" PRIu32 "", + maxNumMonitors, maxMonitorAreaFactorA, maxMonitorAreaFactorB); + _activated = TRUE; + + if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)) + return CHANNEL_RC_OK; + + WLog_DBG(TAG, "DisplayControlCapsPdu: setting the window as resizable"); + return set_window_resizable() ? CHANNEL_RC_OK : CHANNEL_RC_NO_MEMORY; +} + +BOOL sdlDispContext::init(DispClientContext* disp) +{ + if (!disp) + return FALSE; + + auto settings = _sdl->context()->settings; + + if (!settings) + return FALSE; + + _disp = disp; + disp->custom = this; + + if (freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate)) + { + disp->DisplayControlCaps = sdlDispContext::DisplayControlCaps; + } + + _sdl->update_resizeable(TRUE); + return TRUE; +} + +BOOL sdlDispContext::uninit(DispClientContext* disp) +{ + if (!disp) + return FALSE; + + _disp = nullptr; + _sdl->update_resizeable(FALSE); + return TRUE; +} + +sdlDispContext::sdlDispContext(SdlContext* sdl) : _sdl(sdl) +{ + SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO); + + WINPR_ASSERT(_sdl); + WINPR_ASSERT(_sdl->context()->settings); + WINPR_ASSERT(_sdl->context()->pubSub); + + auto settings = _sdl->context()->settings; + auto pubSub = _sdl->context()->pubSub; + + _lastSentWidth = _targetWidth = + WINPR_ASSERTING_INT_CAST(int, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth)); + _lastSentHeight = _targetHeight = + WINPR_ASSERTING_INT_CAST(int, freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)); + if (PubSub_SubscribeActivated(pubSub, sdlDispContext::OnActivated) < 0) + throw std::exception(); + if (PubSub_SubscribeGraphicsReset(pubSub, sdlDispContext::OnGraphicsReset) < 0) + throw std::exception(); + addTimer(); +} + +sdlDispContext::~sdlDispContext() +{ + wPubSub* pubSub = _sdl->context()->pubSub; + WINPR_ASSERT(pubSub); + + PubSub_UnsubscribeActivated(pubSub, sdlDispContext::OnActivated); + PubSub_UnsubscribeGraphicsReset(pubSub, sdlDispContext::OnGraphicsReset); + SDL_RemoveTimer(_timer); + SDL_Quit(); +} diff --git a/third_party/FreeRDP/client/SDL/SDL2/sdl_disp.hpp b/third_party/FreeRDP/client/SDL/SDL2/sdl_disp.hpp new file mode 100644 index 0000000..e8ed19e --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/sdl_disp.hpp @@ -0,0 +1,82 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Display Control Channel + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include + +#include "sdl_types.hpp" + +#include + +class sdlDispContext +{ + + public: + explicit sdlDispContext(SdlContext* sdl); + sdlDispContext(const sdlDispContext& other) = delete; + sdlDispContext(sdlDispContext&& other) = delete; + ~sdlDispContext(); + + sdlDispContext& operator=(const sdlDispContext& other) = delete; + sdlDispContext& operator=(sdlDispContext&& other) = delete; + + BOOL init(DispClientContext* disp); + BOOL uninit(DispClientContext* disp); + +#if SDL_VERSION_ATLEAST(2, 0, 10) + BOOL handle_display_event(const SDL_DisplayEvent* ev); +#endif + + BOOL handle_window_event(const SDL_WindowEvent* ev); + + private: + UINT DisplayControlCaps(UINT32 maxNumMonitors, UINT32 maxMonitorAreaFactorA, + UINT32 maxMonitorAreaFactorB); + BOOL set_window_resizable(); + + BOOL sendResize(); + BOOL settings_changed(); + BOOL update_last_sent(); + UINT sendLayout(const rdpMonitor* monitors, size_t nmonitors); + + BOOL addTimer(); + + static UINT DisplayControlCaps(DispClientContext* disp, UINT32 maxNumMonitors, + UINT32 maxMonitorAreaFactorA, UINT32 maxMonitorAreaFactorB); + static void OnActivated(void* context, const ActivatedEventArgs* e); + static void OnGraphicsReset(void* context, const GraphicsResetEventArgs* e); + static Uint32 SDLCALL OnTimer(Uint32 interval, void* param); + + SdlContext* _sdl = nullptr; + DispClientContext* _disp = nullptr; + int _lastSentWidth = -1; + int _lastSentHeight = -1; + UINT64 _lastSentDate = 0; + int _targetWidth = -1; + int _targetHeight = -1; + BOOL _activated = FALSE; + BOOL _waitingResize = FALSE; + UINT16 _lastSentDesktopOrientation = 0; + UINT32 _lastSentDesktopScaleFactor = 0; + UINT32 _lastSentDeviceScaleFactor = 0; + SDL_TimerID _timer = 0; + unsigned _timer_retries = 0; +}; diff --git a/third_party/FreeRDP/client/SDL/SDL2/sdl_freerdp.cpp b/third_party/FreeRDP/client/SDL/SDL2/sdl_freerdp.cpp new file mode 100644 index 0000000..3f9898f --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/sdl_freerdp.cpp @@ -0,0 +1,1782 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP SDL UI + * + * Copyright 2022 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "sdl_channels.hpp" +#include "sdl_freerdp.hpp" +#include "sdl_utils.hpp" +#include "sdl_disp.hpp" +#include "sdl_monitor.hpp" +#include "sdl_kbd.hpp" +#include "sdl_touch.hpp" +#include "sdl_pointer.hpp" +#include "sdl_prefs.hpp" +#include "dialogs/sdl_dialogs.hpp" +#include "scoped_guard.hpp" + +#include + +#if defined(WITH_WEBVIEW) +#include +#endif + +#define SDL_TAG CLIENT_TAG("SDL") + +enum SDL_EXIT_CODE +{ + /* section 0-15: protocol-independent codes */ + SDL_EXIT_SUCCESS = 0, + SDL_EXIT_DISCONNECT = 1, + SDL_EXIT_LOGOFF = 2, + SDL_EXIT_IDLE_TIMEOUT = 3, + SDL_EXIT_LOGON_TIMEOUT = 4, + SDL_EXIT_CONN_REPLACED = 5, + SDL_EXIT_OUT_OF_MEMORY = 6, + SDL_EXIT_CONN_DENIED = 7, + SDL_EXIT_CONN_DENIED_FIPS = 8, + SDL_EXIT_USER_PRIVILEGES = 9, + SDL_EXIT_FRESH_CREDENTIALS_REQUIRED = 10, + SDL_EXIT_DISCONNECT_BY_USER = 11, + + /* section 16-31: license error set */ + SDL_EXIT_LICENSE_INTERNAL = 16, + SDL_EXIT_LICENSE_NO_LICENSE_SERVER = 17, + SDL_EXIT_LICENSE_NO_LICENSE = 18, + SDL_EXIT_LICENSE_BAD_CLIENT_MSG = 19, + SDL_EXIT_LICENSE_HWID_DOESNT_MATCH = 20, + SDL_EXIT_LICENSE_BAD_CLIENT = 21, + SDL_EXIT_LICENSE_CANT_FINISH_PROTOCOL = 22, + SDL_EXIT_LICENSE_CLIENT_ENDED_PROTOCOL = 23, + SDL_EXIT_LICENSE_BAD_CLIENT_ENCRYPTION = 24, + SDL_EXIT_LICENSE_CANT_UPGRADE = 25, + SDL_EXIT_LICENSE_NO_REMOTE_CONNECTIONS = 26, + + /* section 32-127: RDP protocol error set */ + SDL_EXIT_RDP = 32, + + /* section 128-254: xfreerdp specific exit codes */ + SDL_EXIT_PARSE_ARGUMENTS = 128, + SDL_EXIT_MEMORY = 129, + SDL_EXIT_PROTOCOL = 130, + SDL_EXIT_CONN_FAILED = 131, + SDL_EXIT_AUTH_FAILURE = 132, + SDL_EXIT_NEGO_FAILURE = 133, + SDL_EXIT_LOGON_FAILURE = 134, + SDL_EXIT_ACCOUNT_LOCKED_OUT = 135, + SDL_EXIT_PRE_CONNECT_FAILED = 136, + SDL_EXIT_CONNECT_UNDEFINED = 137, + SDL_EXIT_POST_CONNECT_FAILED = 138, + SDL_EXIT_DNS_ERROR = 139, + SDL_EXIT_DNS_NAME_NOT_FOUND = 140, + SDL_EXIT_CONNECT_FAILED = 141, + SDL_EXIT_MCS_CONNECT_INITIAL_ERROR = 142, + SDL_EXIT_TLS_CONNECT_FAILED = 143, + SDL_EXIT_INSUFFICIENT_PRIVILEGES = 144, + SDL_EXIT_CONNECT_CANCELLED = 145, + + SDL_EXIT_CONNECT_TRANSPORT_FAILED = 147, + SDL_EXIT_CONNECT_PASSWORD_EXPIRED = 148, + SDL_EXIT_CONNECT_PASSWORD_MUST_CHANGE = 149, + SDL_EXIT_CONNECT_KDC_UNREACHABLE = 150, + SDL_EXIT_CONNECT_ACCOUNT_DISABLED = 151, + SDL_EXIT_CONNECT_PASSWORD_CERTAINLY_EXPIRED = 152, + SDL_EXIT_CONNECT_CLIENT_REVOKED = 153, + SDL_EXIT_CONNECT_WRONG_PASSWORD = 154, + SDL_EXIT_CONNECT_ACCESS_DENIED = 155, + SDL_EXIT_CONNECT_ACCOUNT_RESTRICTION = 156, + SDL_EXIT_CONNECT_ACCOUNT_EXPIRED = 157, + SDL_EXIT_CONNECT_LOGON_TYPE_NOT_GRANTED = 158, + SDL_EXIT_CONNECT_NO_OR_MISSING_CREDENTIALS = 159, + + SDL_EXIT_UNKNOWN = 255, +}; + +struct sdl_exit_code_map_t +{ + UINT32 error; + int code; + const char* code_tag; +}; + +#define ENTRY(x, y) { x, y, #y } +static const struct sdl_exit_code_map_t sdl_exit_code_map[] = { + ENTRY(FREERDP_ERROR_SUCCESS, SDL_EXIT_SUCCESS), ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_DISCONNECT), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LOGOFF), ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_IDLE_TIMEOUT), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LOGON_TIMEOUT), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_CONN_REPLACED), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_OUT_OF_MEMORY), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_CONN_DENIED), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_CONN_DENIED_FIPS), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_USER_PRIVILEGES), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_FRESH_CREDENTIALS_REQUIRED), + ENTRY(ERRINFO_LOGOFF_BY_USER, SDL_EXIT_DISCONNECT_BY_USER), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_UNKNOWN), + + /* section 16-31: license error set */ + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_INTERNAL), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_NO_LICENSE_SERVER), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_NO_LICENSE), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_BAD_CLIENT_MSG), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_HWID_DOESNT_MATCH), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_BAD_CLIENT), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_CANT_FINISH_PROTOCOL), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_CLIENT_ENDED_PROTOCOL), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_BAD_CLIENT_ENCRYPTION), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_CANT_UPGRADE), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_NO_REMOTE_CONNECTIONS), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_LICENSE_CANT_UPGRADE), + + /* section 32-127: RDP protocol error set */ + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_RDP), + + /* section 128-254: xfreerdp specific exit codes */ + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_PARSE_ARGUMENTS), ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_MEMORY), + ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_PROTOCOL), ENTRY(FREERDP_ERROR_NONE, SDL_EXIT_CONN_FAILED), + + ENTRY(FREERDP_ERROR_AUTHENTICATION_FAILED, SDL_EXIT_AUTH_FAILURE), + ENTRY(FREERDP_ERROR_SECURITY_NEGO_CONNECT_FAILED, SDL_EXIT_NEGO_FAILURE), + ENTRY(FREERDP_ERROR_CONNECT_LOGON_FAILURE, SDL_EXIT_LOGON_FAILURE), + ENTRY(FREERDP_ERROR_CONNECT_ACCOUNT_LOCKED_OUT, SDL_EXIT_ACCOUNT_LOCKED_OUT), + ENTRY(FREERDP_ERROR_PRE_CONNECT_FAILED, SDL_EXIT_PRE_CONNECT_FAILED), + ENTRY(FREERDP_ERROR_CONNECT_UNDEFINED, SDL_EXIT_CONNECT_UNDEFINED), + ENTRY(FREERDP_ERROR_POST_CONNECT_FAILED, SDL_EXIT_POST_CONNECT_FAILED), + ENTRY(FREERDP_ERROR_DNS_ERROR, SDL_EXIT_DNS_ERROR), + ENTRY(FREERDP_ERROR_DNS_NAME_NOT_FOUND, SDL_EXIT_DNS_NAME_NOT_FOUND), + ENTRY(FREERDP_ERROR_CONNECT_FAILED, SDL_EXIT_CONNECT_FAILED), + ENTRY(FREERDP_ERROR_MCS_CONNECT_INITIAL_ERROR, SDL_EXIT_MCS_CONNECT_INITIAL_ERROR), + ENTRY(FREERDP_ERROR_TLS_CONNECT_FAILED, SDL_EXIT_TLS_CONNECT_FAILED), + ENTRY(FREERDP_ERROR_INSUFFICIENT_PRIVILEGES, SDL_EXIT_INSUFFICIENT_PRIVILEGES), + ENTRY(FREERDP_ERROR_CONNECT_CANCELLED, SDL_EXIT_CONNECT_CANCELLED), + ENTRY(FREERDP_ERROR_CONNECT_TRANSPORT_FAILED, SDL_EXIT_CONNECT_TRANSPORT_FAILED), + ENTRY(FREERDP_ERROR_CONNECT_PASSWORD_EXPIRED, SDL_EXIT_CONNECT_PASSWORD_EXPIRED), + ENTRY(FREERDP_ERROR_CONNECT_PASSWORD_MUST_CHANGE, SDL_EXIT_CONNECT_PASSWORD_MUST_CHANGE), + ENTRY(FREERDP_ERROR_CONNECT_KDC_UNREACHABLE, SDL_EXIT_CONNECT_KDC_UNREACHABLE), + ENTRY(FREERDP_ERROR_CONNECT_ACCOUNT_DISABLED, SDL_EXIT_CONNECT_ACCOUNT_DISABLED), + ENTRY(FREERDP_ERROR_CONNECT_PASSWORD_CERTAINLY_EXPIRED, + SDL_EXIT_CONNECT_PASSWORD_CERTAINLY_EXPIRED), + ENTRY(FREERDP_ERROR_CONNECT_CLIENT_REVOKED, SDL_EXIT_CONNECT_CLIENT_REVOKED), + ENTRY(FREERDP_ERROR_CONNECT_WRONG_PASSWORD, SDL_EXIT_CONNECT_WRONG_PASSWORD), + ENTRY(FREERDP_ERROR_CONNECT_ACCESS_DENIED, SDL_EXIT_CONNECT_ACCESS_DENIED), + ENTRY(FREERDP_ERROR_CONNECT_ACCOUNT_RESTRICTION, SDL_EXIT_CONNECT_ACCOUNT_RESTRICTION), + ENTRY(FREERDP_ERROR_CONNECT_ACCOUNT_EXPIRED, SDL_EXIT_CONNECT_ACCOUNT_EXPIRED), + ENTRY(FREERDP_ERROR_CONNECT_LOGON_TYPE_NOT_GRANTED, SDL_EXIT_CONNECT_LOGON_TYPE_NOT_GRANTED), + ENTRY(FREERDP_ERROR_CONNECT_NO_OR_MISSING_CREDENTIALS, + SDL_EXIT_CONNECT_NO_OR_MISSING_CREDENTIALS) +}; + +static const struct sdl_exit_code_map_t* sdl_map_entry_by_code(int exit_code) +{ + for (const auto& x : sdl_exit_code_map) + { + const struct sdl_exit_code_map_t* cur = &x; + if (cur->code == exit_code) + return cur; + } + return nullptr; +} + +static void sdl_hide_connection_dialog(SdlContext* sdl) +{ + WINPR_ASSERT(sdl); + std::scoped_lock lock(sdl->critical); + if (sdl->connection_dialog) + sdl->connection_dialog->hide(); +} + +static const struct sdl_exit_code_map_t* sdl_map_entry_by_error(UINT32 error) +{ + for (const auto& x : sdl_exit_code_map) + { + const struct sdl_exit_code_map_t* cur = &x; + if (cur->error == static_cast(error)) + return cur; + } + return nullptr; +} + +static int sdl_map_error_to_exit_code(UINT32 error) +{ + const struct sdl_exit_code_map_t* entry = sdl_map_entry_by_error(error); + if (entry) + return entry->code; + + return SDL_EXIT_CONN_FAILED; +} + +static const char* sdl_map_error_to_code_tag(UINT32 error) +{ + const struct sdl_exit_code_map_t* entry = sdl_map_entry_by_error(error); + if (entry) + return entry->code_tag; + return nullptr; +} + +static const char* sdl_map_to_code_tag(int code) +{ + const struct sdl_exit_code_map_t* entry = sdl_map_entry_by_code(code); + if (entry) + return entry->code_tag; + return nullptr; +} + +static int error_info_to_error(freerdp* instance, DWORD* pcode, char** msg, size_t* len) +{ + const DWORD code = freerdp_error_info(instance); + const char* name = freerdp_get_error_info_name(code); + const char* str = freerdp_get_error_info_string(code); + const int exit_code = sdl_map_error_to_exit_code(code); + + winpr_asprintf(msg, len, "Terminate with %s due to ERROR_INFO %s [0x%08" PRIx32 "]: %s", + sdl_map_error_to_code_tag(code), name, code, str); + WLog_DBG(SDL_TAG, "%s", *msg); + if (pcode) + *pcode = code; + return exit_code; +} + +/* This function is called whenever a new frame starts. + * It can be used to reset invalidated areas. */ +static BOOL sdl_begin_paint(rdpContext* context) +{ + auto sdl = get_context(context); + + WINPR_ASSERT(sdl); + + HANDLE handles[] = { sdl->update_complete.handle(), freerdp_abort_event(context) }; + const DWORD status = WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, INFINITE); + switch (status) + { + case WAIT_OBJECT_0: + break; + default: + return FALSE; + } + sdl->update_complete.clear(); + + auto gdi = context->gdi; + WINPR_ASSERT(gdi); + WINPR_ASSERT(gdi->primary); + + HGDI_DC hdc = gdi->primary->hdc; + WINPR_ASSERT(hdc); + if (!hdc->hwnd) + return TRUE; + + HGDI_WND hwnd = hdc->hwnd; + WINPR_ASSERT(hwnd->invalid); + hwnd->invalid->null = TRUE; + hwnd->ninvalid = 0; + + return TRUE; +} + +static BOOL sdl_redraw(SdlContext* sdl) +{ + WINPR_ASSERT(sdl); + + auto gdi = sdl->context()->gdi; + return gdi_send_suppress_output(gdi, FALSE); +} + +class SdlEventUpdateTriggerGuard +{ + private: + SdlContext* _sdl; + + public: + explicit SdlEventUpdateTriggerGuard(SdlContext* sdl) : _sdl(sdl) + { + } + ~SdlEventUpdateTriggerGuard() + { + _sdl->update_complete.set(); + } + SdlEventUpdateTriggerGuard(const SdlEventUpdateTriggerGuard&) = delete; + SdlEventUpdateTriggerGuard(SdlEventUpdateTriggerGuard&&) = delete; + SdlEventUpdateTriggerGuard& operator=(const SdlEventUpdateTriggerGuard&) = delete; + SdlEventUpdateTriggerGuard& operator=(SdlEventUpdateTriggerGuard&&) = delete; +}; + +static bool sdl_draw_to_window_rect([[maybe_unused]] SdlContext* sdl, SdlWindow& window, + SDL_Surface* surface, SDL_Point offset, SDL_Rect srcRect) +{ + SDL_Rect dstRect = { offset.x + srcRect.x, offset.y + srcRect.y, srcRect.w, srcRect.h }; + return window.blit(surface, srcRect, dstRect); +} + +static bool sdl_draw_to_window_rect(SdlContext* sdl, SdlWindow& window, SDL_Surface* surface, + SDL_Point offset, const std::vector& rects = {}) +{ + if (rects.empty()) + { + return sdl_draw_to_window_rect(sdl, window, surface, offset, + { 0, 0, surface->w, surface->h }); + } + for (auto& srcRect : rects) + { + if (!sdl_draw_to_window_rect(sdl, window, surface, offset, srcRect)) + return false; + } + return true; +} + +static bool sdl_draw_to_window_scaled_rect(SdlContext* sdl, SdlWindow& window, SDL_Surface* surface, + SDL_Rect srcRect) +{ + SDL_Rect dstRect = srcRect; + sdl_scale_coordinates(sdl, window.id(), &dstRect.x, &dstRect.y, FALSE, TRUE); + sdl_scale_coordinates(sdl, window.id(), &dstRect.w, &dstRect.h, FALSE, TRUE); + return window.blit(surface, srcRect, dstRect); +} + +static BOOL sdl_draw_to_window_scaled_rect(SdlContext* sdl, SdlWindow& window, SDL_Surface* surface, + const std::vector& rects = {}) +{ + if (rects.empty()) + { + return sdl_draw_to_window_scaled_rect(sdl, window, surface, + { 0, 0, surface->w, surface->h }); + } + for (const auto& srcRect : rects) + { + if (!sdl_draw_to_window_scaled_rect(sdl, window, surface, srcRect)) + return FALSE; + } + return TRUE; +} + +static BOOL sdl_draw_to_window(SdlContext* sdl, SdlWindow& window, + const std::vector& rects = {}) +{ + WINPR_ASSERT(sdl); + + auto context = sdl->context(); + auto gdi = context->gdi; + + auto size = window.rect(); + + if (!freerdp_settings_get_bool(context->settings, FreeRDP_SmartSizing)) + { + if (gdi->width < size.w) + { + window.setOffsetX((size.w - gdi->width) / 2); + } + if (gdi->height < size.h) + { + window.setOffsetY((size.h - gdi->height) / 2); + } + + auto surface = sdl->primary.get(); + if (!sdl_draw_to_window_rect(sdl, window, surface, { window.offsetX(), window.offsetY() }, + rects)) + return FALSE; + } + else + { + if (!sdl_draw_to_window_scaled_rect(sdl, window, sdl->primary.get(), rects)) + return FALSE; + } + window.updateSurface(); + return TRUE; +} + +static BOOL sdl_draw_to_window(SdlContext* sdl, std::map& windows, + const std::vector& rects = {}) +{ + for (auto& window : windows) + { + if (!sdl_draw_to_window(sdl, window.second, rects)) + return FALSE; + } + + return TRUE; +} + +static BOOL sdl_end_paint_process(rdpContext* context) +{ + auto sdl = get_context(context); + + WINPR_ASSERT(context); + + SdlEventUpdateTriggerGuard guard(sdl); + + auto gdi = context->gdi; + WINPR_ASSERT(gdi); + WINPR_ASSERT(gdi->primary); + + HGDI_DC hdc = gdi->primary->hdc; + WINPR_ASSERT(hdc); + if (!hdc->hwnd) + return TRUE; + + HGDI_WND hwnd = hdc->hwnd; + WINPR_ASSERT(hwnd->invalid || (hwnd->ninvalid == 0)); + + if (hwnd->invalid->null) + return TRUE; + + WINPR_ASSERT(hwnd->invalid); + if (gdi->suppressOutput || hwnd->invalid->null) + return TRUE; + + const INT32 ninvalid = hwnd->ninvalid; + const GDI_RGN* cinvalid = hwnd->cinvalid; + + if (ninvalid < 1) + return TRUE; + + std::vector rects; + for (INT32 x = 0; x < ninvalid; x++) + { + auto& rgn = cinvalid[x]; + rects.push_back({ rgn.x, rgn.y, rgn.w, rgn.h }); + } + + return sdl_draw_to_window(sdl, sdl->windows, rects); +} + +/* This function is called when the library completed composing a new + * frame. Read out the changed areas and blit them to your output device. + * The image buffer will have the format specified by gdi_init + */ +static BOOL sdl_end_paint(rdpContext* context) +{ + auto sdl = get_context(context); + WINPR_ASSERT(sdl); + + std::scoped_lock lock(sdl->critical); + const BOOL rc = sdl_push_user_event(SDL_USEREVENT_UPDATE, context); + + return rc; +} + +static void sdl_destroy_primary(SdlContext* sdl) +{ + if (!sdl) + return; + sdl->primary.reset(); + sdl->primary_format.reset(); +} + +/* Create a SDL surface from the GDI buffer */ +static BOOL sdl_create_primary(SdlContext* sdl) +{ + rdpGdi* gdi = nullptr; + + WINPR_ASSERT(sdl); + + gdi = sdl->context()->gdi; + WINPR_ASSERT(gdi); + + sdl_destroy_primary(sdl); + sdl->primary = SDLSurfacePtr( + SDL_CreateRGBSurfaceWithFormatFrom(gdi->primary_buffer, static_cast(gdi->width), + static_cast(gdi->height), + static_cast(FreeRDPGetBitsPerPixel(gdi->dstFormat)), + static_cast(gdi->stride), sdl->sdl_pixel_format), + SDL_FreeSurface); + sdl->primary_format = SDLPixelFormatPtr(SDL_AllocFormat(sdl->sdl_pixel_format), SDL_FreeFormat); + + if (!sdl->primary || !sdl->primary_format) + return FALSE; + + SDL_SetSurfaceBlendMode(sdl->primary.get(), SDL_BLENDMODE_NONE); + SDL_FillRect(sdl->primary.get(), nullptr, + SDL_MapRGBA(sdl->primary_format.get(), 0, 0, 0, 0xff)); + + return TRUE; +} + +static BOOL sdl_desktop_resize(rdpContext* context) +{ + rdpGdi* gdi = nullptr; + rdpSettings* settings = nullptr; + auto sdl = get_context(context); + + WINPR_ASSERT(sdl); + WINPR_ASSERT(context); + + settings = context->settings; + WINPR_ASSERT(settings); + + std::scoped_lock lock(sdl->critical); + gdi = context->gdi; + if (!gdi_resize(gdi, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight))) + return FALSE; + return sdl_create_primary(sdl); +} + +/* This function is called to output a System BEEP */ +static BOOL sdl_play_sound(rdpContext* context, const PLAY_SOUND_UPDATE* play_sound) +{ + /* TODO: Implement */ + WINPR_UNUSED(context); + WINPR_UNUSED(play_sound); + return TRUE; +} + +static BOOL sdl_wait_for_init(SdlContext* sdl) +{ + WINPR_ASSERT(sdl); + sdl->initialize.set(); + + HANDLE handles[] = { sdl->initialized.handle(), freerdp_abort_event(sdl->context()) }; + + const DWORD rc = WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, INFINITE); + switch (rc) + { + case WAIT_OBJECT_0: + return TRUE; + default: + return FALSE; + } +} + +/* Called before a connection is established. + * Set all configuration options to support and load channels here. */ +static BOOL sdl_pre_connect(freerdp* instance) +{ + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->context); + + auto sdl = get_context(instance->context); + + auto settings = instance->context->settings; + WINPR_ASSERT(settings); + + if (!freerdp_settings_set_bool(settings, FreeRDP_CertificateCallbackPreferPEM, TRUE)) + return FALSE; + + /* Optional OS identifier sent to server */ + if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMajorType, OSMAJORTYPE_UNIX)) + return FALSE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMinorType, OSMINORTYPE_NATIVE_SDL)) + return FALSE; + /* OrderSupport is initialized at this point. + * Only override it if you plan to implement custom order + * callbacks or deactivate certain features. */ + /* Register the channel listeners. + * They are required to set up / tear down channels if they are loaded. */ + if (PubSub_SubscribeChannelConnected(instance->context->pubSub, + sdl_OnChannelConnectedEventHandler) < 0) + return FALSE; + if (PubSub_SubscribeChannelDisconnected(instance->context->pubSub, + sdl_OnChannelDisconnectedEventHandler) < 0) + return FALSE; + + if (!freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly)) + { + UINT32 maxWidth = 0; + UINT32 maxHeight = 0; + + if (!sdl_wait_for_init(sdl)) + return FALSE; + + std::scoped_lock lock(sdl->critical); + if (!freerdp_settings_get_bool(settings, FreeRDP_UseCommonStdioCallbacks)) + sdl->connection_dialog = std::make_unique(instance->context); + if (sdl->connection_dialog) + { + sdl->connection_dialog->setTitle("Connecting to '%s'", + freerdp_settings_get_server_name(settings)); + sdl->connection_dialog->showInfo( + "The connection is being established\n\nPlease wait..."); + } + if (!sdl_detect_monitors(sdl, &maxWidth, &maxHeight)) + return FALSE; + + if ((maxWidth != 0) && (maxHeight != 0) && + !freerdp_settings_get_bool(settings, FreeRDP_SmartSizing)) + { + WLog_Print(sdl->log, WLOG_INFO, "Update size to %ux%u", maxWidth, maxHeight); + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, maxWidth)) + return FALSE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, maxHeight)) + return FALSE; + } + } + else + { + /* Check +auth-only has a username and password. */ + if (!freerdp_settings_get_string(settings, FreeRDP_Password)) + { + WLog_Print(sdl->log, WLOG_INFO, "auth-only, but no password set. Please provide one."); + return FALSE; + } + + if (!freerdp_settings_set_bool(settings, FreeRDP_DeactivateClientDecoding, TRUE)) + return FALSE; + + WLog_Print(sdl->log, WLOG_INFO, "Authentication only. Don't connect SDL."); + } + + /* TODO: Any code your client requires */ + return TRUE; +} + +static const char* sdl_window_get_title(rdpSettings* settings) +{ + const char* windowTitle = nullptr; + UINT32 port = 0; + BOOL addPort = 0; + const char* name = nullptr; + const char* prefix = "FreeRDP:"; + + if (!settings) + return nullptr; + + windowTitle = freerdp_settings_get_string(settings, FreeRDP_WindowTitle); + if (windowTitle) + return windowTitle; + + name = freerdp_settings_get_server_name(settings); + port = freerdp_settings_get_uint32(settings, FreeRDP_ServerPort); + + addPort = (port != 3389); + + char buffer[MAX_PATH + 64] = {}; + + if (!addPort) + (void)sprintf_s(buffer, sizeof(buffer), "%s %s", prefix, name); + else + (void)sprintf_s(buffer, sizeof(buffer), "%s %s:%" PRIu32, prefix, name, port); + + if (!freerdp_settings_set_string(settings, FreeRDP_WindowTitle, buffer)) + return nullptr; + return freerdp_settings_get_string(settings, FreeRDP_WindowTitle); +} + +static void sdl_term_handler([[maybe_unused]] int signum, [[maybe_unused]] const char* signame, + [[maybe_unused]] void* context) +{ + sdl_push_quit(); +} + +static void sdl_cleanup_sdl(SdlContext* sdl) +{ + if (!sdl) + return; + + std::scoped_lock lock(sdl->critical); + sdl->windows.clear(); + sdl->connection_dialog.reset(); + + sdl_destroy_primary(sdl); + + freerdp_del_signal_cleanup_handler(sdl->context(), sdl_term_handler); + TTF_Quit(); + SDL_Quit(); +} + +static BOOL sdl_create_windows(SdlContext* sdl) +{ + WINPR_ASSERT(sdl); + + auto settings = sdl->context()->settings; + auto title = sdl_window_get_title(settings); + + ScopeGuard guard([&]() { sdl->windows_created.set(); }); + + UINT32 windowCount = freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount); + + for (UINT32 x = 0; x < windowCount; x++) + { + auto id = sdl_monitor_id_for_index(sdl, x); + if (id < 0) + return FALSE; + auto monitor = static_cast( + freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorDefArray, x)); + + Uint32 w = WINPR_ASSERTING_INT_CAST(uint32_t, monitor->width); + Uint32 h = WINPR_ASSERTING_INT_CAST(uint32_t, monitor->height); + if (!(freerdp_settings_get_bool(settings, FreeRDP_UseMultimon) || + freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))) + { + w = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + h = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + } + + Uint32 flags = SDL_WINDOW_SHOWN; + auto startupX = SDL_WINDOWPOS_CENTERED_DISPLAY(id); + auto startupY = SDL_WINDOWPOS_CENTERED_DISPLAY(id); + + if (monitor->attributes.desktopScaleFactor > 100) + { +#if SDL_VERSION_ATLEAST(2, 0, 1) + flags |= SDL_WINDOW_ALLOW_HIGHDPI; +#endif + } + + if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen) && + !freerdp_settings_get_bool(settings, FreeRDP_UseMultimon)) + { + flags |= SDL_WINDOW_FULLSCREEN; + } + + if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon)) + { + flags |= SDL_WINDOW_BORDERLESS; + } + + if (!freerdp_settings_get_bool(settings, FreeRDP_Decorations)) + flags |= SDL_WINDOW_BORDERLESS; + + SdlWindow window{ title, + static_cast(startupX), + static_cast(startupY), + static_cast(w), + static_cast(h), + flags }; + + if (!window.window()) + return FALSE; + + if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon)) + { + auto r = window.rect(); + window.setOffsetX(0 - r.x); + window.setOffsetY(0 - r.y); + } + + sdl->windows.insert({ window.id(), std::move(window) }); + } + + return TRUE; +} + +static BOOL sdl_wait_create_windows(SdlContext* sdl) +{ + std::scoped_lock lock(sdl->critical); + sdl->windows_created.clear(); + if (!sdl_push_user_event(SDL_USEREVENT_CREATE_WINDOWS, sdl)) + return FALSE; + + HANDLE handles[] = { sdl->initialized.handle(), freerdp_abort_event(sdl->context()) }; + + const DWORD rc = WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, INFINITE); + switch (rc) + { + case WAIT_OBJECT_0: + return TRUE; + default: + return FALSE; + } +} + +static bool shall_abort(SdlContext* sdl) +{ + std::scoped_lock lock(sdl->critical); + if (freerdp_shall_disconnect_context(sdl->context())) + { + if (sdl->rdp_thread_running) + return false; + if (!sdl->connection_dialog) + return true; + return !sdl->connection_dialog->running(); + } + return false; +} + +static int sdl_run(SdlContext* sdl) +{ + int rc = -1; + WINPR_ASSERT(sdl); + + HANDLE handles[] = { sdl->initialize.handle(), freerdp_abort_event(sdl->context()) }; + const DWORD status = WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, INFINITE); + switch (status) + { + case WAIT_OBJECT_0: + break; + default: + return 0; + } + + SDL_Init(SDL_INIT_VIDEO); + TTF_Init(); +#if SDL_VERSION_ATLEAST(2, 0, 16) + SDL_SetHint(SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED, "0"); +#endif +#if SDL_VERSION_ATLEAST(2, 0, 8) + SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0"); +#endif + + if (!freerdp_add_signal_cleanup_handler(sdl->context(), sdl_term_handler)) + return -1; + + sdl->initialized.set(); + + while (!shall_abort(sdl)) + { + SDL_Event windowEvent = {}; + while (!shall_abort(sdl) && SDL_WaitEventTimeout(nullptr, 1000)) + { + /* Only poll standard SDL events and SDL_USEREVENTS meant to create dialogs. + * do not process the dialog return value events here. + */ + const int prc = SDL_PeepEvents(&windowEvent, 1, SDL_GETEVENT, SDL_FIRSTEVENT, + SDL_USEREVENT_RETRY_DIALOG); + if (prc < 0) + { + if (sdl_log_error(prc, sdl->log, "SDL_PeepEvents")) + continue; + } + +#if defined(WITH_DEBUG_SDL_EVENTS) + SDL_Log("got event %s [0x%08" PRIx32 "]", sdl_event_type_str(windowEvent.type), + windowEvent.type); +#endif + std::scoped_lock lock(sdl->critical); + /* The session might have been disconnected while we were waiting for a new SDL event. + * In that case ignore the SDL event and terminate. */ + if (freerdp_shall_disconnect_context(sdl->context())) + continue; + + if (sdl->connection_dialog) + { + if (sdl->connection_dialog->handle(windowEvent)) + { + continue; + } + } + + switch (windowEvent.type) + { + case SDL_QUIT: + freerdp_abort_connect_context(sdl->context()); + break; + case SDL_KEYDOWN: + case SDL_KEYUP: + { + const SDL_KeyboardEvent* ev = &windowEvent.key; + sdl->input.keyboard_handle_event(ev); + } + break; + case SDL_KEYMAPCHANGED: + { + } + break; // TODO: Switch keyboard layout + case SDL_MOUSEMOTION: + { + const SDL_MouseMotionEvent* ev = &windowEvent.motion; + sdl_handle_mouse_motion(sdl, ev); + } + break; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + { + const SDL_MouseButtonEvent* ev = &windowEvent.button; + sdl_handle_mouse_button(sdl, ev); + } + break; + case SDL_MOUSEWHEEL: + { + const SDL_MouseWheelEvent* ev = &windowEvent.wheel; + sdl_handle_mouse_wheel(sdl, ev); + } + break; + case SDL_FINGERDOWN: + { + const SDL_TouchFingerEvent* ev = &windowEvent.tfinger; + sdl_handle_touch_down(sdl, ev); + } + break; + case SDL_FINGERUP: + { + const SDL_TouchFingerEvent* ev = &windowEvent.tfinger; + sdl_handle_touch_up(sdl, ev); + } + break; + case SDL_FINGERMOTION: + { + const SDL_TouchFingerEvent* ev = &windowEvent.tfinger; + sdl_handle_touch_motion(sdl, ev); + } + break; +#if SDL_VERSION_ATLEAST(2, 0, 10) + case SDL_DISPLAYEVENT: + { + const SDL_DisplayEvent* ev = &windowEvent.display; + sdl->disp.handle_display_event(ev); + } + break; +#endif + case SDL_WINDOWEVENT: + { + const SDL_WindowEvent* ev = &windowEvent.window; + auto window = sdl->windows.find(ev->windowID); + if (window != sdl->windows.end()) + sdl->disp.handle_window_event(ev); + switch (ev->event) + { + case SDL_WINDOWEVENT_RESIZED: + case SDL_WINDOWEVENT_SIZE_CHANGED: + { + if (window != sdl->windows.end()) + { + window->second.fill(); + window->second.updateSurface(); + } + } + break; + case SDL_WINDOWEVENT_MOVED: + { + if (window != sdl->windows.end()) + { + auto r = window->second.rect(); + auto id = window->second.id(); + WLog_DBG(SDL_TAG, "%u: %dx%d-%dx%d", id, r.x, r.y, r.w, r.h); + } + } + break; + default: + break; + } + } + break; + + case SDL_RENDER_TARGETS_RESET: + sdl_redraw(sdl); + break; + case SDL_RENDER_DEVICE_RESET: + sdl_redraw(sdl); + break; + case SDL_APP_WILLENTERFOREGROUND: + sdl_redraw(sdl); + break; + case SDL_USEREVENT_CERT_DIALOG: + { + auto title = static_cast(windowEvent.user.data1); + auto msg = static_cast(windowEvent.user.data2); + sdl_cert_dialog_show(title, msg); + } + break; + case SDL_USEREVENT_SHOW_DIALOG: + { + auto title = static_cast(windowEvent.user.data1); + auto msg = static_cast(windowEvent.user.data2); + sdl_message_dialog_show(title, msg, windowEvent.user.code); + } + break; + case SDL_USEREVENT_SCARD_DIALOG: + { + auto title = static_cast(windowEvent.user.data1); + auto msg = static_cast(windowEvent.user.data2); + sdl_scard_dialog_show(title, windowEvent.user.code, msg); + } + break; + case SDL_USEREVENT_AUTH_DIALOG: + sdl_auth_dialog_show( + reinterpret_cast(windowEvent.padding)); + break; + case SDL_USEREVENT_UPDATE: + { + auto context = static_cast(windowEvent.user.data1); + sdl_end_paint_process(context); + } + break; + case SDL_USEREVENT_CREATE_WINDOWS: + { + auto ctx = static_cast(windowEvent.user.data1); + sdl_create_windows(ctx); + } + break; + case SDL_USEREVENT_WINDOW_RESIZEABLE: + { + auto window = static_cast(windowEvent.user.data1); + const SDL_bool use = windowEvent.user.code != 0 ? SDL_TRUE : SDL_FALSE; + if (window) + window->resizeable(use); + } + break; + case SDL_USEREVENT_WINDOW_FULLSCREEN: + { + auto window = static_cast(windowEvent.user.data1); + const SDL_bool enter = windowEvent.user.code != 0 ? SDL_TRUE : SDL_FALSE; + if (window) + window->fullscreen(enter); + } + break; + case SDL_USEREVENT_WINDOW_MINIMIZE: + { + for (auto& window : sdl->windows) + { + window.second.minimize(); + } + } + break; + case SDL_USEREVENT_POINTER_NULL: + SDL_ShowCursor(SDL_DISABLE); + break; + case SDL_USEREVENT_POINTER_DEFAULT: + { + SDL_Cursor* def = SDL_GetDefaultCursor(); + SDL_SetCursor(def); + SDL_ShowCursor(SDL_ENABLE); + } + break; + case SDL_USEREVENT_POINTER_POSITION: + { + const auto x = + static_cast(reinterpret_cast(windowEvent.user.data1)); + const auto y = + static_cast(reinterpret_cast(windowEvent.user.data2)); + + SDL_Window* window = SDL_GetMouseFocus(); + if (window) + { + const Uint32 id = SDL_GetWindowID(window); + + INT32 sx = x; + INT32 sy = y; + if (sdl_scale_coordinates(sdl, id, &sx, &sy, FALSE, FALSE)) + SDL_WarpMouseInWindow(window, sx, sy); + } + } + break; + case SDL_USEREVENT_POINTER_SET: + sdl_Pointer_Set_Process(&windowEvent.user); + break; + case SDL_USEREVENT_QUIT: + default: + break; + } + } + } + + rc = 1; + + sdl_cleanup_sdl(sdl); + return rc; +} + +/* Called after a RDP connection was successfully established. + * Settings might have changed during negotiation of client / server feature + * support. + * + * Set up local framebuffers and paing callbacks. + * If required, register pointer callbacks to change the local mouse cursor + * when hovering over the RDP window + */ +static BOOL sdl_post_connect(freerdp* instance) +{ + WINPR_ASSERT(instance); + + auto context = instance->context; + WINPR_ASSERT(context); + + auto sdl = get_context(context); + + // Retry was successful, discard dialog + sdl_hide_connection_dialog(sdl); + + if (freerdp_settings_get_bool(context->settings, FreeRDP_AuthenticationOnly)) + { + /* Check +auth-only has a username and password. */ + if (!freerdp_settings_get_string(context->settings, FreeRDP_Password)) + { + WLog_Print(sdl->log, WLOG_INFO, "auth-only, but no password set. Please provide one."); + return FALSE; + } + + WLog_Print(sdl->log, WLOG_INFO, "Authentication only. Don't connect to X."); + return TRUE; + } + + if (!sdl_wait_create_windows(sdl)) + return FALSE; + + sdl->sdl_pixel_format = SDL_PIXELFORMAT_BGRA32; + if (!gdi_init(instance, PIXEL_FORMAT_BGRA32)) + return FALSE; + + if (!sdl_create_primary(sdl)) + return FALSE; + + if (!sdl_register_pointer(instance->context->graphics)) + return FALSE; + + WINPR_ASSERT(context->update); + + context->update->BeginPaint = sdl_begin_paint; + context->update->EndPaint = sdl_end_paint; + context->update->PlaySound = sdl_play_sound; + context->update->DesktopResize = sdl_desktop_resize; + context->update->SetKeyboardIndicators = sdlInput::keyboard_set_indicators; + context->update->SetKeyboardImeStatus = sdlInput::keyboard_set_ime_status; + + sdl->update_resizeable(FALSE); + sdl->update_fullscreen(freerdp_settings_get_bool(context->settings, FreeRDP_Fullscreen) || + freerdp_settings_get_bool(context->settings, FreeRDP_UseMultimon)); + return TRUE; +} + +/* This function is called whether a session ends by failure or success. + * Clean up everything allocated by pre_connect and post_connect. + */ +static void sdl_post_disconnect(freerdp* instance) +{ + if (!instance) + return; + + if (!instance->context) + return; + + PubSub_UnsubscribeChannelConnected(instance->context->pubSub, + sdl_OnChannelConnectedEventHandler); + PubSub_UnsubscribeChannelDisconnected(instance->context->pubSub, + sdl_OnChannelDisconnectedEventHandler); + gdi_free(instance); +} + +static void sdl_post_final_disconnect(freerdp* instance) +{ + if (!instance) + return; + + if (!instance->context) + return; +} + +static void sdl_client_cleanup(SdlContext* sdl, int exit_code, const std::string& error_msg) +{ + WINPR_ASSERT(sdl); + + rdpContext* context = sdl->context(); + WINPR_ASSERT(context); + + rdpSettings* settings = context->settings; + WINPR_ASSERT(settings); + + sdl->rdp_thread_running = false; + bool showError = false; + if (freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly)) + WLog_Print(sdl->log, WLOG_INFO, "Authentication only, exit status %s [%" PRId32 "]", + sdl_map_to_code_tag(exit_code), exit_code); + else + { + switch (exit_code) + { + case SDL_EXIT_SUCCESS: + case SDL_EXIT_DISCONNECT: + case SDL_EXIT_LOGOFF: + case SDL_EXIT_DISCONNECT_BY_USER: + case SDL_EXIT_CONNECT_CANCELLED: + break; + default: + { + std::scoped_lock lock(sdl->critical); + if (sdl->connection_dialog && !error_msg.empty()) + { + sdl->connection_dialog->showError(error_msg.c_str()); + showError = true; + } + } + break; + } + } + + if (!showError) + sdl_hide_connection_dialog(sdl); + + sdl->exit_code = exit_code; + sdl_push_user_event(SDL_USEREVENT_QUIT); +#if SDL_VERSION_ATLEAST(2, 0, 16) + SDL_TLSCleanup(); +#endif +} + +static int sdl_client_thread_connect(SdlContext* sdl, std::string& error_msg) +{ + WINPR_ASSERT(sdl); + + auto instance = sdl->context()->instance; + WINPR_ASSERT(instance); + + sdl->rdp_thread_running = true; + BOOL rc = freerdp_connect(instance); + + rdpContext* context = sdl->context(); + WINPR_ASSERT(context); + + rdpSettings* settings = context->settings; + WINPR_ASSERT(settings); + + int exit_code = SDL_EXIT_SUCCESS; + if (!rc) + { + UINT32 error = freerdp_get_last_error(context); + exit_code = sdl_map_error_to_exit_code(error); + } + + if (freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly)) + { + DWORD code = freerdp_get_last_error(context); + freerdp_abort_connect_context(context); + WLog_Print(sdl->log, WLOG_ERROR, "Authentication only, %s [0x%08" PRIx32 "] %s", + freerdp_get_last_error_name(code), code, freerdp_get_last_error_string(code)); + return exit_code; + } + + if (!rc) + { + DWORD code = freerdp_error_info(instance); + if (exit_code == SDL_EXIT_SUCCESS) + { + char* msg = nullptr; + size_t len = 0; + exit_code = error_info_to_error(instance, &code, &msg, &len); + if (msg) + error_msg = msg; + free(msg); + } + + auto last = freerdp_get_last_error(context); + if (error_msg.empty()) + { + char* msg = nullptr; + size_t len = 0; + winpr_asprintf(&msg, &len, "%s [0x%08" PRIx32 "]\n%s", + freerdp_get_last_error_name(last), last, + freerdp_get_last_error_string(last)); + if (msg) + error_msg = msg; + free(msg); + } + + if (exit_code == SDL_EXIT_SUCCESS) + { + if (last == FREERDP_ERROR_AUTHENTICATION_FAILED) + exit_code = SDL_EXIT_AUTH_FAILURE; + else if (code == ERRINFO_SUCCESS) + exit_code = SDL_EXIT_CONN_FAILED; + } + + sdl_hide_connection_dialog(sdl); + } + return exit_code; +} + +static int sdl_client_thread_run(SdlContext* sdl, std::string& error_msg) +{ + WINPR_ASSERT(sdl); + + auto context = sdl->context(); + WINPR_ASSERT(context); + + auto instance = context->instance; + WINPR_ASSERT(instance); + + int exit_code = SDL_EXIT_SUCCESS; + while (!freerdp_shall_disconnect_context(context)) + { + HANDLE handles[MAXIMUM_WAIT_OBJECTS] = {}; + /* + * win8 and server 2k12 seem to have some timing issue/race condition + * when a initial sync request is send to sync the keyboard indicators + * sending the sync event twice fixed this problem + */ + if (freerdp_focus_required(instance)) + { + auto ctx = get_context(context); + WINPR_ASSERT(ctx); + if (!ctx->input.keyboard_focus_in()) + break; + if (!ctx->input.keyboard_focus_in()) + break; + } + + const DWORD nCount = freerdp_get_event_handles(context, handles, ARRAYSIZE(handles)); + + if (nCount == 0) + { + WLog_Print(sdl->log, WLOG_ERROR, "freerdp_get_event_handles failed"); + break; + } + + const DWORD status = WaitForMultipleObjects(nCount, handles, FALSE, INFINITE); + + if (status == WAIT_FAILED) + break; + + if (!freerdp_check_event_handles(context)) + { + if (client_auto_reconnect(instance)) + { + // Retry was successful, discard dialog + sdl_hide_connection_dialog(sdl); + continue; + } + else + { + /* + * Indicate an unsuccessful connection attempt if reconnect + * did not succeed and no other error was specified. + */ + if (freerdp_error_info(instance) == 0) + exit_code = SDL_EXIT_CONN_FAILED; + } + + if (freerdp_get_last_error(context) == FREERDP_ERROR_SUCCESS) + WLog_Print(sdl->log, WLOG_ERROR, "WaitForMultipleObjects failed with %" PRIu32 "", + status); + if (freerdp_get_last_error(context) == FREERDP_ERROR_SUCCESS) + WLog_Print(sdl->log, WLOG_ERROR, "Failed to check FreeRDP event handles"); + break; + } + } + + if (exit_code == SDL_EXIT_SUCCESS) + { + DWORD code = 0; + { + char* msg = nullptr; + size_t len = 0; + exit_code = error_info_to_error(instance, &code, &msg, &len); + if (msg) + error_msg = msg; + free(msg); + } + + if ((code == ERRINFO_LOGOFF_BY_USER) && + (freerdp_get_disconnect_ultimatum(context) == Disconnect_Ultimatum_user_requested)) + { + const char* msg = "Error info says user did not initiate but disconnect ultimatum says " + "they did; treat this as a user logoff"; + char* emsg = nullptr; + size_t len = 0; + winpr_asprintf(&emsg, &len, "%s", msg); + if (emsg) + error_msg = emsg; + free(emsg); + /* This situation might be limited to Windows XP. */ + WLog_Print(sdl->log, WLOG_INFO, "%s", msg); + exit_code = SDL_EXIT_LOGOFF; + } + } + + freerdp_disconnect(instance); + return exit_code; +} + +/* RDP main loop. + * Connects RDP, loops while running and handles event and dispatch, cleans up + * after the connection ends. */ +static DWORD WINAPI sdl_client_thread_proc(SdlContext* sdl) +{ + WINPR_ASSERT(sdl); + + std::string error_msg; + int exit_code = sdl_client_thread_connect(sdl, error_msg); + if (exit_code == SDL_EXIT_SUCCESS) + exit_code = sdl_client_thread_run(sdl, error_msg); + sdl_client_cleanup(sdl, exit_code, error_msg); + + return static_cast(exit_code); +} + +/* Optional global initializer. + * Here we just register a signal handler to print out stack traces + * if available. */ +static BOOL sdl_client_global_init() +{ +#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 +} + +static BOOL sdl_client_new(freerdp* instance, rdpContext* context) +{ + auto sdl = reinterpret_cast(context); + + if (!instance || !context) + return FALSE; + + sdl->sdl = new SdlContext(context); + if (!sdl->sdl) + return FALSE; + + instance->PreConnect = sdl_pre_connect; + instance->PostConnect = sdl_post_connect; + instance->PostDisconnect = sdl_post_disconnect; + instance->PostFinalDisconnect = sdl_post_final_disconnect; + instance->AuthenticateEx = sdl_authenticate_ex; + instance->VerifyCertificateEx = sdl_verify_certificate_ex; + instance->VerifyChangedCertificateEx = sdl_verify_changed_certificate_ex; + instance->LogonErrorInfo = sdl_logon_error_info; + instance->PresentGatewayMessage = sdl_present_gateway_message; + instance->ChooseSmartcard = sdl_choose_smartcard; + instance->RetryDialog = sdl_retry_dialog; + +#if defined(WITH_WEBVIEW) + instance->GetAccessToken = sdl_webview_get_access_token; +#else + instance->GetAccessToken = client_cli_get_access_token; +#endif + /* TODO: Client display set up */ + + return TRUE; +} + +static void sdl_client_free([[maybe_unused]] freerdp* instance, rdpContext* context) +{ + auto sdl = reinterpret_cast(context); + + if (!context) + return; + + delete sdl->sdl; +} + +static int sdl_client_start(rdpContext* context) +{ + auto sdl = get_context(context); + WINPR_ASSERT(sdl); + + sdl->thread = std::thread(sdl_client_thread_proc, sdl); + return 0; +} + +static int sdl_client_stop(rdpContext* context) +{ + auto sdl = get_context(context); + WINPR_ASSERT(sdl); + + /* We do not want to use freerdp_abort_connect_context here. + * It would change the exit code and we do not want that. */ + HANDLE event = freerdp_abort_event(context); + if (!SetEvent(event)) + return -1; + + sdl->thread.join(); + return 0; +} + +static int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints) +{ + WINPR_ASSERT(pEntryPoints); + + ZeroMemory(pEntryPoints, sizeof(RDP_CLIENT_ENTRY_POINTS)); + pEntryPoints->Version = RDP_CLIENT_INTERFACE_VERSION; + pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1); + pEntryPoints->GlobalInit = sdl_client_global_init; + pEntryPoints->GlobalUninit = sdl_client_global_uninit; + pEntryPoints->ContextSize = sizeof(sdl_rdp_context); + pEntryPoints->ClientNew = sdl_client_new; + pEntryPoints->ClientFree = sdl_client_free; + pEntryPoints->ClientStart = sdl_client_start; + pEntryPoints->ClientStop = sdl_client_stop; + return 0; +} + +static void context_free(sdl_rdp_context* sdl) +{ + if (sdl) + freerdp_client_context_free(&sdl->common.context); +} + +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_RESERVED1: + return "SDL_LOG_CATEGORY_RESERVED1"; + 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"; + } +} + +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; + } +} + +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(userdata); + WINPR_ASSERT(sdl); + + const DWORD level = sdlpriority2wlog(priority); + auto log = sdl->log; + if (!WLog_IsLevelActive(log, level)) + return; + + WLog_PrintTextMessage(log, level, __LINE__, __FILE__, __func__, "[%s] %s", + category2str(category), message); +} + +int main(int argc, char* argv[]) +{ + int rc = -1; + int status = 0; + RDP_CLIENT_ENTRY_POINTS clientEntryPoints = {}; + + freerdp_client_warn_experimental(argc, argv); + freerdp_client_warn_deprecated(argc, argv); + WLog_WARN(SDL_TAG, + "SDL2 client does not support clipboard! Only SDL3 client has (partial) support"); + + RdpClientEntry(&clientEntryPoints); + std::unique_ptr sdl_rdp( + reinterpret_cast(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); + + status = freerdp_client_settings_parse_command_line(settings, argc, argv, FALSE); + if (status) + { + rc = freerdp_client_settings_command_line_status_print(settings, status, argc, argv); + if (freerdp_settings_get_bool(settings, FreeRDP_ListMonitors)) + sdl_list_monitors(sdl); + 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(2); + break; + } + } + return rc; + } + + SDL_LogSetOutputFunction(winpr_LogOutputFunction, sdl); + auto level = WLog_GetLogLevel(sdl->log); + SDL_LogSetAllPriority(wloglevel2dl(level)); + + 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->exit_code != 0) + rc = sdl->exit_code; + + return rc; +} + +BOOL SdlContext::update_fullscreen(BOOL enter) +{ + std::scoped_lock lock(critical); + for (const auto& window : windows) + { + if (!sdl_push_user_event(SDL_USEREVENT_WINDOW_FULLSCREEN, &window.second, enter)) + return FALSE; + } + fullscreen = enter; + return TRUE; +} + +BOOL SdlContext::update_minimize() +{ + std::scoped_lock lock(critical); + return sdl_push_user_event(SDL_USEREVENT_WINDOW_MINIMIZE); +} + +BOOL SdlContext::update_resizeable(BOOL enable) +{ + std::scoped_lock lock(critical); + + const auto settings = context()->settings; + const BOOL dyn = freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate); + const BOOL smart = freerdp_settings_get_bool(settings, FreeRDP_SmartSizing); + BOOL use = (dyn && enable) || smart; + + for (const auto& window : windows) + { + if (!sdl_push_user_event(SDL_USEREVENT_WINDOW_RESIZEABLE, &window.second, use)) + return FALSE; + } + resizeable = use; + + return TRUE; +} + +SdlContext::SdlContext(rdpContext* context) + : _context(context), log(WLog_Get(SDL_TAG)), update_complete(true), disp(this), input(this), + primary(nullptr, SDL_FreeSurface), primary_format(nullptr, SDL_FreeFormat), + rdp_thread_running(false) +{ + WINPR_ASSERT(context); + grab_kbd_enabled = freerdp_settings_get_bool(context->settings, FreeRDP_GrabKeyboard); +} + +rdpContext* SdlContext::context() const +{ + return _context; +} + +rdpClientContext* SdlContext::common() const +{ + return reinterpret_cast(_context); +} diff --git a/third_party/FreeRDP/client/SDL/SDL2/sdl_freerdp.hpp b/third_party/FreeRDP/client/SDL/SDL2/sdl_freerdp.hpp new file mode 100644 index 0000000..3142e98 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/sdl_freerdp.hpp @@ -0,0 +1,98 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client + * + * Copyright 2022 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "sdl_types.hpp" +#include "sdl_disp.hpp" +#include "sdl_kbd.hpp" +#include "sdl_utils.hpp" +#include "sdl_window.hpp" +#include "dialogs/sdl_connection_dialog.hpp" + +using SDLSurfacePtr = std::unique_ptr; +using SDLPixelFormatPtr = std::unique_ptr; + +class SdlContext +{ + public: + explicit SdlContext(rdpContext* context); + SdlContext(const SdlContext& other) = delete; + SdlContext(SdlContext&& other) = delete; + ~SdlContext() = default; + + SdlContext& operator=(const SdlContext& other) = delete; + SdlContext& operator=(SdlContext&& other) = delete; + + private: + rdpContext* _context; + + public: + wLog* log; + + /* SDL */ + bool fullscreen = false; + bool resizeable = false; + bool grab_mouse = false; + bool grab_kbd = false; + bool grab_kbd_enabled = true; + + std::map windows; + + CriticalSection critical; + std::thread thread; + WinPREvent initialize; + WinPREvent initialized; + WinPREvent update_complete; + WinPREvent windows_created; + int exit_code = -1; + + sdlDispContext disp; + sdlInput input; + + SDLSurfacePtr primary; + SDLPixelFormatPtr primary_format; + + Uint32 sdl_pixel_format = 0; + + std::unique_ptr connection_dialog; + + std::atomic rdp_thread_running; + + BOOL update_resizeable(BOOL enable); + BOOL update_fullscreen(BOOL enter); + BOOL update_minimize(); + + [[nodiscard]] rdpContext* context() const; + [[nodiscard]] rdpClientContext* common() const; +}; diff --git a/third_party/FreeRDP/client/SDL/SDL2/sdl_kbd.cpp b/third_party/FreeRDP/client/SDL/SDL2/sdl_kbd.cpp new file mode 100644 index 0000000..25159e8 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/sdl_kbd.cpp @@ -0,0 +1,612 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP SDL keyboard helper + * + * Copyright 2022 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "sdl_kbd.hpp" +#include "sdl_disp.hpp" +#include "sdl_freerdp.hpp" +#include "sdl_utils.hpp" +#include "sdl_prefs.hpp" +#include "sdl_touch.hpp" + +#include + +#include +#include + +#include +#define TAG CLIENT_TAG("SDL.kbd") + +using scancode_entry_t = struct +{ + Uint32 sdl; + const char* sdl_name; + UINT32 rdp; + const char* rdp_name; +}; + +#define STR(x) #x +#define ENTRY(x, y) { x, STR(x), y, #y } + +// clang-format off +static const scancode_entry_t map[] = { + ENTRY(SDL_SCANCODE_UNKNOWN, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_A, RDP_SCANCODE_KEY_A), + ENTRY(SDL_SCANCODE_B, RDP_SCANCODE_KEY_B), + ENTRY(SDL_SCANCODE_C, RDP_SCANCODE_KEY_C), + ENTRY(SDL_SCANCODE_D, RDP_SCANCODE_KEY_D), + ENTRY(SDL_SCANCODE_E, RDP_SCANCODE_KEY_E), + ENTRY(SDL_SCANCODE_F, RDP_SCANCODE_KEY_F), + ENTRY(SDL_SCANCODE_G, RDP_SCANCODE_KEY_G), + ENTRY(SDL_SCANCODE_H, RDP_SCANCODE_KEY_H), + ENTRY(SDL_SCANCODE_I, RDP_SCANCODE_KEY_I), + ENTRY(SDL_SCANCODE_J, RDP_SCANCODE_KEY_J), + ENTRY(SDL_SCANCODE_K, RDP_SCANCODE_KEY_K), + ENTRY(SDL_SCANCODE_L, RDP_SCANCODE_KEY_L), + ENTRY(SDL_SCANCODE_M, RDP_SCANCODE_KEY_M), + ENTRY(SDL_SCANCODE_N, RDP_SCANCODE_KEY_N), + ENTRY(SDL_SCANCODE_O, RDP_SCANCODE_KEY_O), + ENTRY(SDL_SCANCODE_P, RDP_SCANCODE_KEY_P), + ENTRY(SDL_SCANCODE_Q, RDP_SCANCODE_KEY_Q), + ENTRY(SDL_SCANCODE_R, RDP_SCANCODE_KEY_R), + ENTRY(SDL_SCANCODE_S, RDP_SCANCODE_KEY_S), + ENTRY(SDL_SCANCODE_T, RDP_SCANCODE_KEY_T), + ENTRY(SDL_SCANCODE_U, RDP_SCANCODE_KEY_U), + ENTRY(SDL_SCANCODE_V, RDP_SCANCODE_KEY_V), + ENTRY(SDL_SCANCODE_W, RDP_SCANCODE_KEY_W), + ENTRY(SDL_SCANCODE_X, RDP_SCANCODE_KEY_X), + ENTRY(SDL_SCANCODE_Y, RDP_SCANCODE_KEY_Y), + ENTRY(SDL_SCANCODE_Z, RDP_SCANCODE_KEY_Z), + ENTRY(SDL_SCANCODE_1, RDP_SCANCODE_KEY_1), + ENTRY(SDL_SCANCODE_2, RDP_SCANCODE_KEY_2), + ENTRY(SDL_SCANCODE_3, RDP_SCANCODE_KEY_3), + ENTRY(SDL_SCANCODE_4, RDP_SCANCODE_KEY_4), + ENTRY(SDL_SCANCODE_5, RDP_SCANCODE_KEY_5), + ENTRY(SDL_SCANCODE_6, RDP_SCANCODE_KEY_6), + ENTRY(SDL_SCANCODE_7, RDP_SCANCODE_KEY_7), + ENTRY(SDL_SCANCODE_8, RDP_SCANCODE_KEY_8), + ENTRY(SDL_SCANCODE_9, RDP_SCANCODE_KEY_9), + ENTRY(SDL_SCANCODE_0, RDP_SCANCODE_KEY_0), + ENTRY(SDL_SCANCODE_RETURN, RDP_SCANCODE_RETURN), + ENTRY(SDL_SCANCODE_ESCAPE, RDP_SCANCODE_ESCAPE), + ENTRY(SDL_SCANCODE_BACKSPACE, RDP_SCANCODE_BACKSPACE), + ENTRY(SDL_SCANCODE_TAB, RDP_SCANCODE_TAB), + ENTRY(SDL_SCANCODE_SPACE, RDP_SCANCODE_SPACE), + ENTRY(SDL_SCANCODE_MINUS, RDP_SCANCODE_OEM_MINUS), + ENTRY(SDL_SCANCODE_CAPSLOCK, RDP_SCANCODE_CAPSLOCK), + ENTRY(SDL_SCANCODE_F1, RDP_SCANCODE_F1), + ENTRY(SDL_SCANCODE_F2, RDP_SCANCODE_F2), + ENTRY(SDL_SCANCODE_F3, RDP_SCANCODE_F3), + ENTRY(SDL_SCANCODE_F4, RDP_SCANCODE_F4), + ENTRY(SDL_SCANCODE_F5, RDP_SCANCODE_F5), + ENTRY(SDL_SCANCODE_F6, RDP_SCANCODE_F6), + ENTRY(SDL_SCANCODE_F7, RDP_SCANCODE_F7), + ENTRY(SDL_SCANCODE_F8, RDP_SCANCODE_F8), + ENTRY(SDL_SCANCODE_F9, RDP_SCANCODE_F9), + ENTRY(SDL_SCANCODE_F10, RDP_SCANCODE_F10), + ENTRY(SDL_SCANCODE_F11, RDP_SCANCODE_F11), + ENTRY(SDL_SCANCODE_F12, RDP_SCANCODE_F12), + ENTRY(SDL_SCANCODE_F13, RDP_SCANCODE_F13), + ENTRY(SDL_SCANCODE_F14, RDP_SCANCODE_F14), + ENTRY(SDL_SCANCODE_F15, RDP_SCANCODE_F15), + ENTRY(SDL_SCANCODE_F16, RDP_SCANCODE_F16), + ENTRY(SDL_SCANCODE_F17, RDP_SCANCODE_F17), + ENTRY(SDL_SCANCODE_F18, RDP_SCANCODE_F18), + ENTRY(SDL_SCANCODE_F19, RDP_SCANCODE_F19), + ENTRY(SDL_SCANCODE_F20, RDP_SCANCODE_F20), + ENTRY(SDL_SCANCODE_F21, RDP_SCANCODE_F21), + ENTRY(SDL_SCANCODE_F22, RDP_SCANCODE_F22), + ENTRY(SDL_SCANCODE_F23, RDP_SCANCODE_F23), + ENTRY(SDL_SCANCODE_F24, RDP_SCANCODE_F24), + ENTRY(SDL_SCANCODE_NUMLOCKCLEAR, RDP_SCANCODE_NUMLOCK), + ENTRY(SDL_SCANCODE_KP_DIVIDE, RDP_SCANCODE_DIVIDE), + ENTRY(SDL_SCANCODE_KP_MULTIPLY, RDP_SCANCODE_MULTIPLY), + ENTRY(SDL_SCANCODE_KP_MINUS, RDP_SCANCODE_SUBTRACT), + ENTRY(SDL_SCANCODE_KP_PLUS, RDP_SCANCODE_ADD), + ENTRY(SDL_SCANCODE_KP_ENTER, RDP_SCANCODE_RETURN_KP), + ENTRY(SDL_SCANCODE_KP_1, RDP_SCANCODE_NUMPAD1), + ENTRY(SDL_SCANCODE_KP_2, RDP_SCANCODE_NUMPAD2), + ENTRY(SDL_SCANCODE_KP_3, RDP_SCANCODE_NUMPAD3), + ENTRY(SDL_SCANCODE_KP_4, RDP_SCANCODE_NUMPAD4), + ENTRY(SDL_SCANCODE_KP_5, RDP_SCANCODE_NUMPAD5), + ENTRY(SDL_SCANCODE_KP_6, RDP_SCANCODE_NUMPAD6), + ENTRY(SDL_SCANCODE_KP_7, RDP_SCANCODE_NUMPAD7), + ENTRY(SDL_SCANCODE_KP_8, RDP_SCANCODE_NUMPAD8), + ENTRY(SDL_SCANCODE_KP_9, RDP_SCANCODE_NUMPAD9), + ENTRY(SDL_SCANCODE_KP_0, RDP_SCANCODE_NUMPAD0), + ENTRY(SDL_SCANCODE_KP_PERIOD, RDP_SCANCODE_OEM_PERIOD), + ENTRY(SDL_SCANCODE_LCTRL, RDP_SCANCODE_LCONTROL), + ENTRY(SDL_SCANCODE_LSHIFT, RDP_SCANCODE_LSHIFT), + ENTRY(SDL_SCANCODE_LALT, RDP_SCANCODE_LMENU), + ENTRY(SDL_SCANCODE_LGUI, RDP_SCANCODE_LWIN), + ENTRY(SDL_SCANCODE_RCTRL, RDP_SCANCODE_RCONTROL), + ENTRY(SDL_SCANCODE_RSHIFT, RDP_SCANCODE_RSHIFT), + ENTRY(SDL_SCANCODE_RALT, RDP_SCANCODE_RMENU), + ENTRY(SDL_SCANCODE_RGUI, RDP_SCANCODE_RWIN), + ENTRY(SDL_SCANCODE_MODE, RDP_SCANCODE_APPS), + ENTRY(SDL_SCANCODE_MUTE, RDP_SCANCODE_VOLUME_MUTE), + ENTRY(SDL_SCANCODE_VOLUMEUP, RDP_SCANCODE_VOLUME_UP), + ENTRY(SDL_SCANCODE_VOLUMEDOWN, RDP_SCANCODE_VOLUME_DOWN), + ENTRY(SDL_SCANCODE_GRAVE, RDP_SCANCODE_OEM_3), + ENTRY(SDL_SCANCODE_COMMA, RDP_SCANCODE_OEM_COMMA), + ENTRY(SDL_SCANCODE_PERIOD, RDP_SCANCODE_OEM_PERIOD), + ENTRY(SDL_SCANCODE_SLASH, RDP_SCANCODE_OEM_2), + ENTRY(SDL_SCANCODE_BACKSLASH, RDP_SCANCODE_OEM_5), + ENTRY(SDL_SCANCODE_SCROLLLOCK, RDP_SCANCODE_SCROLLLOCK), + ENTRY(SDL_SCANCODE_INSERT, RDP_SCANCODE_INSERT), + ENTRY(SDL_SCANCODE_PRINTSCREEN, RDP_SCANCODE_PRINTSCREEN), + ENTRY(SDL_SCANCODE_HOME, RDP_SCANCODE_HOME), + ENTRY(SDL_SCANCODE_DELETE, RDP_SCANCODE_DELETE), + ENTRY(SDL_SCANCODE_RIGHT, RDP_SCANCODE_RIGHT), + ENTRY(SDL_SCANCODE_LEFT, RDP_SCANCODE_LEFT), + ENTRY(SDL_SCANCODE_DOWN, RDP_SCANCODE_DOWN), + ENTRY(SDL_SCANCODE_UP, RDP_SCANCODE_UP), + ENTRY(SDL_SCANCODE_SEMICOLON, RDP_SCANCODE_OEM_1), + ENTRY(SDL_SCANCODE_PAUSE, RDP_SCANCODE_PAUSE), + ENTRY(SDL_SCANCODE_PAGEUP, RDP_SCANCODE_PRIOR), + ENTRY(SDL_SCANCODE_END, RDP_SCANCODE_END), + ENTRY(SDL_SCANCODE_PAGEDOWN, RDP_SCANCODE_NEXT), + ENTRY(SDL_SCANCODE_AUDIONEXT, RDP_SCANCODE_MEDIA_NEXT_TRACK), + ENTRY(SDL_SCANCODE_AUDIOPREV, RDP_SCANCODE_MEDIA_PREV_TRACK), + ENTRY(SDL_SCANCODE_AUDIOSTOP, RDP_SCANCODE_MEDIA_STOP), + ENTRY(SDL_SCANCODE_AUDIOPLAY, RDP_SCANCODE_MEDIA_PLAY_PAUSE), + ENTRY(SDL_SCANCODE_AUDIOMUTE, RDP_SCANCODE_VOLUME_MUTE), + ENTRY(SDL_SCANCODE_MEDIASELECT, RDP_SCANCODE_LAUNCH_MEDIA_SELECT), + ENTRY(SDL_SCANCODE_MAIL, RDP_SCANCODE_LAUNCH_MAIL), + ENTRY(SDL_SCANCODE_APP1, RDP_SCANCODE_LAUNCH_APP1), + ENTRY(SDL_SCANCODE_APP2, RDP_SCANCODE_LAUNCH_APP2), + ENTRY(SDL_SCANCODE_SYSREQ, RDP_SCANCODE_SYSREQ), + ENTRY(SDL_SCANCODE_WWW, RDP_SCANCODE_BROWSER_HOME), + ENTRY(SDL_SCANCODE_LEFTBRACKET, RDP_SCANCODE_OEM_4), + ENTRY(SDL_SCANCODE_RIGHTBRACKET, RDP_SCANCODE_OEM_6), + ENTRY(SDL_SCANCODE_APOSTROPHE, RDP_SCANCODE_OEM_7), + ENTRY(SDL_SCANCODE_NONUSBACKSLASH, RDP_SCANCODE_OEM_102), + ENTRY(SDL_SCANCODE_SLEEP, RDP_SCANCODE_SLEEP), + ENTRY(SDL_SCANCODE_EQUALS, RDP_SCANCODE_OEM_PLUS), + ENTRY(SDL_SCANCODE_KP_COMMA, RDP_SCANCODE_DECIMAL), + ENTRY(SDL_SCANCODE_FIND, RDP_SCANCODE_BROWSER_SEARCH), + ENTRY(SDL_SCANCODE_RETURN2, RDP_SCANCODE_RETURN_KP), + ENTRY(SDL_SCANCODE_AC_SEARCH, RDP_SCANCODE_BROWSER_SEARCH), + ENTRY(SDL_SCANCODE_AC_HOME, RDP_SCANCODE_BROWSER_HOME), + ENTRY(SDL_SCANCODE_AC_BACK, RDP_SCANCODE_BROWSER_BACK), + ENTRY(SDL_SCANCODE_AC_FORWARD, RDP_SCANCODE_BROWSER_FORWARD), + ENTRY(SDL_SCANCODE_AC_STOP, RDP_SCANCODE_BROWSER_STOP), + + // TODO: unmapped + ENTRY(SDL_SCANCODE_NONUSHASH, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_APPLICATION, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_POWER, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_EQUALS, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_EXECUTE, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_HELP, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_MENU, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_SELECT, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_STOP, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_AGAIN, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_UNDO, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_CUT, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_COPY, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_PASTE, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_EQUALSAS400, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_INTERNATIONAL1, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_INTERNATIONAL2, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_INTERNATIONAL3, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_INTERNATIONAL4, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_INTERNATIONAL5, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_INTERNATIONAL6, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_INTERNATIONAL7, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_INTERNATIONAL8, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_INTERNATIONAL9, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_LANG1, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_LANG2, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_LANG3, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_LANG4, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_LANG5, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_LANG6, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_LANG7, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_LANG8, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_LANG9, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_ALTERASE, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_CANCEL, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_CLEAR, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_PRIOR, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_SEPARATOR, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_OUT, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_OPER, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_CLEARAGAIN, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_CRSEL, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_EXSEL, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_00, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_000, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_THOUSANDSSEPARATOR, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_DECIMALSEPARATOR, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_CURRENCYUNIT, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_CURRENCYSUBUNIT, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_LEFTPAREN, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_RIGHTPAREN, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_LEFTBRACE, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_RIGHTBRACE, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_TAB, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_BACKSPACE, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_A, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_B, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_C, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_D, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_E, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_F, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_XOR, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_POWER, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_PERCENT, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_LESS, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_GREATER, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_AMPERSAND, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_DBLAMPERSAND, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_VERTICALBAR, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_DBLVERTICALBAR, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_COLON, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_HASH, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_SPACE, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_AT, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_EXCLAM, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_MEMSTORE, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_MEMRECALL, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_MEMCLEAR, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_MEMADD, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_MEMSUBTRACT, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_MEMMULTIPLY, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_MEMDIVIDE, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_PLUSMINUS, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_CLEAR, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_CLEARENTRY, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_BINARY, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_OCTAL, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_DECIMAL, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KP_HEXADECIMAL, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_CALCULATOR, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_COMPUTER, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_AC_REFRESH, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_AC_BOOKMARKS, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_BRIGHTNESSDOWN, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_BRIGHTNESSUP, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_DISPLAYSWITCH, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KBDILLUMTOGGLE, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KBDILLUMDOWN, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_KBDILLUMUP, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_EJECT, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_AUDIOREWIND, RDP_SCANCODE_UNKNOWN), + ENTRY(SDL_SCANCODE_AUDIOFASTFORWARD, RDP_SCANCODE_UNKNOWN) +}; +// clang-format on + +static UINT16 sdl_get_kbd_flags() +{ + UINT16 flags = 0; + + SDL_Keymod mod = SDL_GetModState(); + if ((mod & KMOD_NUM) != 0) + flags |= KBD_SYNC_NUM_LOCK; + if ((mod & KMOD_CAPS) != 0) + flags |= KBD_SYNC_CAPS_LOCK; +#if SDL_VERSION_ATLEAST(2, 0, 18) + if ((mod & KMOD_SCROLL) != 0) + flags |= KBD_SYNC_SCROLL_LOCK; +#endif + + // TODO: KBD_SYNC_KANA_LOCK + + return flags; +} + +BOOL sdlInput::keyboard_sync_state() +{ + const auto syncFlags = sdl_get_kbd_flags(); + return freerdp_input_send_synchronize_event(_sdl->context()->input, syncFlags); +} + +BOOL sdlInput::keyboard_focus_in() +{ + auto input = _sdl->context()->input; + WINPR_ASSERT(input); + + auto syncFlags = sdl_get_kbd_flags(); + freerdp_input_send_focus_in_event(input, WINPR_ASSERTING_INT_CAST(UINT16, syncFlags)); + + /* finish with a mouse pointer position like mstsc.exe if required */ + // TODO: fullscreen/remote app + int x = 0; + int y = 0; + if (_sdl->fullscreen) + { + SDL_GetGlobalMouseState(&x, &y); + } + else + { + SDL_GetMouseState(&x, &y); + } + auto w = SDL_GetMouseFocus(); + if (w) + { + auto id = SDL_GetWindowID(w); + sdl_scale_coordinates(_sdl, id, &x, &y, TRUE, TRUE); + } + return freerdp_client_send_button_event(_sdl->common(), FALSE, PTR_FLAGS_MOVE, x, y); +} + +/* This function is called to update the keyboard indicator LED */ +BOOL sdlInput::keyboard_set_indicators(rdpContext* context, UINT16 led_flags) +{ + WINPR_UNUSED(context); + + int state = KMOD_NONE; + + if ((led_flags & KBD_SYNC_NUM_LOCK) != 0) + state |= KMOD_NUM; + if ((led_flags & KBD_SYNC_CAPS_LOCK) != 0) + state |= KMOD_CAPS; +#if SDL_VERSION_ATLEAST(2, 0, 18) + if ((led_flags & KBD_SYNC_SCROLL_LOCK) != 0) + state |= KMOD_SCROLL; +#endif + + // TODO: KBD_SYNC_KANA_LOCK + + SDL_SetModState(static_cast(state)); + + return TRUE; +} + +/* This function is called to set the IME state */ +BOOL sdlInput::keyboard_set_ime_status(rdpContext* context, UINT16 imeId, UINT32 imeState, + UINT32 imeConvMode) +{ + if (!context) + return FALSE; + + WLog_WARN(TAG, + "KeyboardSetImeStatus(unitId=%04" PRIx16 ", imeState=%08" PRIx32 + ", imeConvMode=%08" PRIx32 ") ignored", + imeId, imeState, imeConvMode); + return TRUE; +} + +static const std::map& getSdlMap() +{ + static std::map s_map = { + { "KMOD_LSHIFT", KMOD_LSHIFT }, { "KMOD_RSHIFT", KMOD_RSHIFT }, + { "KMOD_LCTRL", KMOD_LCTRL }, { "KMOD_RCTRL", KMOD_RCTRL }, + { "KMOD_LALT", KMOD_LALT }, { "KMOD_RALT", KMOD_RALT }, + { "KMOD_LGUI", KMOD_LGUI }, { "KMOD_RGUI", KMOD_RGUI }, + { "KMOD_NUM", KMOD_NUM }, { "KMOD_CAPS", KMOD_CAPS }, + { "KMOD_MODE", KMOD_MODE }, +#if SDL_VERSION_ATLEAST(2, 0, 18) + { "KMOD_SCROLL", KMOD_SCROLL }, +#endif + { "KMOD_CTRL", KMOD_CTRL }, { "KMOD_SHIFT", KMOD_SHIFT }, + { "KMOD_ALT", KMOD_ALT }, { "KMOD_GUI", KMOD_GUI }, + { "KMOD_NONE", KMOD_NONE }, { "SDL_KMOD_LSHIFT", KMOD_LSHIFT }, + { "SDL_KMOD_RSHIFT", KMOD_RSHIFT }, { "SDL_KMOD_LCTRL", KMOD_LCTRL }, + { "SDL_KMOD_RCTRL", KMOD_RCTRL }, { "SDL_KMOD_LALT", KMOD_LALT }, + { "SDL_KMOD_RALT", KMOD_RALT }, { "SDL_KMOD_LGUI", KMOD_LGUI }, + { "SDL_KMOD_RGUI", KMOD_RGUI }, { "SDL_KMOD_NUM", KMOD_NUM }, + { "SDL_KMOD_CAPS", KMOD_CAPS }, { "SDL_KMOD_MODE", KMOD_MODE }, + { "SDL_KMOD_SCROLL", KMOD_SCROLL }, { "SDL_KMOD_CTRL", KMOD_CTRL }, + { "SDL_KMOD_SHIFT", KMOD_SHIFT }, { "SDL_KMOD_ALT", KMOD_ALT }, + { "SDL_KMOD_GUI", KMOD_GUI }, { "SDL_KMOD_NONE", KMOD_NONE } + }; + + return s_map; +} + +bool sdlInput::prefToEnabled() +{ + bool enable = true; + const auto& m = getSdlMap(); + for (const auto& val : SdlPref::instance()->get_array("SDL_KeyModMask", { "KMOD_RSHIFT" })) + { + auto it = m.find(val); + if (it != m.end()) + { + if (it->second == KMOD_NONE) + enable = false; + } + else + { + WLog_WARN(TAG, "Invalid config::SDL_KeyModMask entry value '%s', disabling hotkeys", + val.c_str()); + enable = false; + } + } + return enable; +} + +uint32_t sdlInput::prefToMask() +{ + const auto& mapping = getSdlMap(); + uint32_t mod = KMOD_NONE; + for (const auto& val : SdlPref::instance()->get_array("SDL_KeyModMask", { "KMOD_RSHIFT" })) + { + auto it = mapping.find(val); + if (it != mapping.end()) + mod |= it->second; + } + return mod; +} + +static Uint32 sdl_scancode_val(const char* scancodeName) +{ + for (const auto& cur : map) + { + if (strcmp(cur.sdl_name, scancodeName) == 0) + return cur.sdl; + } + + return SDL_SCANCODE_UNKNOWN; +} + +static UINT32 sdl_scancode_to_rdp(Uint32 scancode) +{ + UINT32 rdp = RDP_SCANCODE_UNKNOWN; + + for (const auto& cur : map) + { + if (cur.sdl == scancode) + { + rdp = cur.rdp; + break; + } + } + +#if defined(WITH_DEBUG_SDL_KBD_EVENTS) + auto code = static_cast(scancode); + WLog_DBG(TAG, "got %s [0x%08" PRIx32 "] -> [%s]", SDL_GetScancodeName(code), scancode, + freerdp_keyboard_scancode_name(rdp)); +#endif + return rdp; +} + +uint32_t sdlInput::prefKeyValue(const std::string& key, uint32_t fallback) +{ + auto item = SdlPref::instance()->get_string(key); + if (item.empty()) + return fallback; + auto val = sdl_scancode_val(item.c_str()); + if (val == SDL_SCANCODE_UNKNOWN) + return fallback; + return val; +} + +std::list sdlInput::tokenize(const std::string& data, const std::string& delimiter) +{ + size_t lastpos = 0; + size_t pos = 0; + std::list list; + while ((pos = data.find(delimiter, lastpos)) != std::string::npos) + { + auto token = data.substr(lastpos, pos); + lastpos = pos + 1; + list.push_back(std::move(token)); + } + auto token = data.substr(lastpos); + list.push_back(std::move(token)); + return list; +} + +bool sdlInput::extract(const std::string& token, uint32_t& key, uint32_t& value) +{ + return freerdp_extract_key_value(token.c_str(), &key, &value); +} + +BOOL sdlInput::keyboard_handle_event(const SDL_KeyboardEvent* ev) +{ + WINPR_ASSERT(ev); + const UINT32 rdp_scancode = sdl_scancode_to_rdp(ev->keysym.scancode); + const SDL_Keymod mods = SDL_GetModState(); + + if (_hotkeysEnabled && (mods & _hotkeyModmask) == _hotkeyModmask) + { + if (ev->type == SDL_KEYDOWN) + { + if (ev->keysym.scancode == _hotkeyFullscreen) + { + _sdl->update_fullscreen(!_sdl->fullscreen); + return TRUE; + } + if (ev->keysym.scancode == _hotkeyResizable) + { + _sdl->update_resizeable(!_sdl->resizeable); + return TRUE; + } + + if (ev->keysym.scancode == _hotkeyGrab) + { + keyboard_grab(ev->windowID, !_sdl->grab_kbd); + return TRUE; + } + if (ev->keysym.scancode == _hotkeyDisconnect) + { + freerdp_abort_connect_context(_sdl->context()); + return TRUE; + } + if (ev->keysym.scancode == _hotkeyMinimize) + { + _sdl->update_minimize(); + return TRUE; + } + } + } + + auto scancode = freerdp_keyboard_remap_key(_remapTable, rdp_scancode); + return freerdp_input_send_keyboard_event_ex(_sdl->context()->input, ev->type == SDL_KEYDOWN, + ev->repeat, scancode); +} + +BOOL sdlInput::keyboard_grab(Uint32 windowID, bool enable) +{ + auto it = _sdl->windows.find(windowID); + if (it == _sdl->windows.end()) + return FALSE; + + auto status = enable && _sdl->grab_kbd_enabled; + _sdl->grab_kbd = status; + return it->second.grabKeyboard(status); +} + +BOOL sdlInput::mouse_focus(Uint32 windowID) +{ + if (_lastWindowID != windowID) + { + _lastWindowID = windowID; + auto it = _sdl->windows.find(windowID); + if (it == _sdl->windows.end()) + return FALSE; + + it->second.raise(); + } + return TRUE; +} + +BOOL sdlInput::mouse_grab(Uint32 windowID, SDL_bool enable) +{ + auto it = _sdl->windows.find(windowID); + if (it == _sdl->windows.end()) + return FALSE; + _sdl->grab_mouse = enable; + return it->second.grabMouse(enable); +} + +sdlInput::sdlInput(SdlContext* sdl) + : _sdl(sdl), _lastWindowID(UINT32_MAX), _hotkeysEnabled(prefToEnabled()), + _hotkeyModmask(prefToMask()) +{ + auto list = + freerdp_settings_get_string(_sdl->context()->settings, FreeRDP_KeyboardRemappingList); + _remapTable = freerdp_keyboard_remap_string_to_list(list); + assert(_remapTable); + _hotkeyFullscreen = prefKeyValue("SDL_Fullscreen", SDL_SCANCODE_RETURN); + _hotkeyResizable = prefKeyValue("SDL_Resizeable", SDL_SCANCODE_R); + _hotkeyGrab = prefKeyValue("SDL_Grab", SDL_SCANCODE_G); + _hotkeyDisconnect = prefKeyValue("SDL_Disconnect", SDL_SCANCODE_D); + _hotkeyMinimize = prefKeyValue("SDL_Minimize", SDL_SCANCODE_M); +} + +sdlInput::~sdlInput() +{ + freerdp_keyboard_remap_free(_remapTable); +} diff --git a/third_party/FreeRDP/client/SDL/SDL2/sdl_kbd.hpp b/third_party/FreeRDP/client/SDL/SDL2/sdl_kbd.hpp new file mode 100644 index 0000000..e65e8cc --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/sdl_kbd.hpp @@ -0,0 +1,79 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client keyboard helper + * + * Copyright 2022 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "sdl_types.hpp" + +class sdlInput +{ + public: + explicit sdlInput(SdlContext* sdl); + sdlInput(const sdlInput& other) = delete; + sdlInput(sdlInput&& other) = delete; + ~sdlInput(); + + sdlInput& operator=(const sdlInput& other) = delete; + sdlInput& operator=(sdlInput&& other) = delete; + + BOOL keyboard_sync_state(); + BOOL keyboard_focus_in(); + + BOOL keyboard_handle_event(const SDL_KeyboardEvent* ev); + + BOOL keyboard_grab(Uint32 windowID, bool enable); + BOOL mouse_focus(Uint32 windowID); + BOOL mouse_grab(Uint32 windowID, SDL_bool enable); + + static BOOL keyboard_set_indicators(rdpContext* context, UINT16 led_flags); + static BOOL keyboard_set_ime_status(rdpContext* context, UINT16 imeId, UINT32 imeState, + UINT32 imeConvMode); + + static bool prefToEnabled(); + static uint32_t prefToMask(); + static uint32_t prefKeyValue(const std::string& key, uint32_t fallback = SDL_SCANCODE_UNKNOWN); + + private: + static std::list tokenize(const std::string& data, + const std::string& delimiter = ","); + static bool extract(const std::string& token, uint32_t& key, uint32_t& value); + + SdlContext* _sdl; + Uint32 _lastWindowID; + + // hotkey handling + bool _hotkeysEnabled; + uint32_t _hotkeyModmask; // modifier keys mask + uint32_t _hotkeyFullscreen; + uint32_t _hotkeyResizable; + uint32_t _hotkeyGrab; + uint32_t _hotkeyDisconnect; + uint32_t _hotkeyMinimize; + FREERDP_REMAP_TABLE* _remapTable; +}; diff --git a/third_party/FreeRDP/client/SDL/SDL2/sdl_monitor.cpp b/third_party/FreeRDP/client/SDL/SDL2/sdl_monitor.cpp new file mode 100644 index 0000000..46c842a --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/sdl_monitor.cpp @@ -0,0 +1,393 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Monitor Handling + * + * Copyright 2011 Marc-Andre Moreau + * Copyright 2017 David Fort + * Copyright 2018 Kai Harms + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#define TAG CLIENT_TAG("sdl") + +#include "sdl_monitor.hpp" +#include "sdl_freerdp.hpp" + +using MONITOR_INFO = struct +{ + RECTANGLE_16 area; + RECTANGLE_16 workarea; + BOOL primary; +}; + +using VIRTUAL_SCREEN = struct +{ + int nmonitors; + RECTANGLE_16 area; + RECTANGLE_16 workarea; + MONITOR_INFO* monitors; +}; + +/* See MSDN Section on Multiple Display Monitors: http://msdn.microsoft.com/en-us/library/dd145071 + */ + +int sdl_list_monitors([[maybe_unused]] SdlContext* sdl) +{ + SDL_Init(SDL_INIT_VIDEO); + const int nmonitors = SDL_GetNumVideoDisplays(); + + printf("listing %d monitors:\n", nmonitors); + for (int i = 0; i < nmonitors; i++) + { + SDL_Rect rect = {}; + const int brc = SDL_GetDisplayBounds(i, &rect); + const char* name = SDL_GetDisplayName(i); + + if (brc != 0) + continue; + printf(" %s [%d] [%s] %dx%d\t+%d+%d\n", (i == 0) ? "*" : " ", i, name, rect.w, rect.h, + rect.x, rect.y); + } + + SDL_Quit(); + return 0; +} + +static BOOL sdl_apply_max_size(SdlContext* sdl, UINT32* pMaxWidth, UINT32* pMaxHeight) +{ + WINPR_ASSERT(sdl); + WINPR_ASSERT(pMaxWidth); + WINPR_ASSERT(pMaxHeight); + + auto settings = sdl->context()->settings; + WINPR_ASSERT(settings); + + *pMaxWidth = 0; + *pMaxHeight = 0; + + for (size_t x = 0; x < freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount); x++) + { + auto monitor = static_cast( + freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorDefArray, x)); + + if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)) + { + *pMaxWidth = WINPR_ASSERTING_INT_CAST(uint32_t, monitor->width); + *pMaxHeight = WINPR_ASSERTING_INT_CAST(uint32_t, monitor->height); + } + else if (freerdp_settings_get_bool(settings, FreeRDP_Workarea)) + { + SDL_Rect rect = {}; + SDL_GetDisplayUsableBounds(WINPR_ASSERTING_INT_CAST(int, monitor->orig_screen), &rect); + *pMaxWidth = WINPR_ASSERTING_INT_CAST(uint32_t, rect.w); + *pMaxHeight = WINPR_ASSERTING_INT_CAST(uint32_t, rect.h); + } + else if (freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen) > 0) + { + SDL_Rect rect = {}; + SDL_GetDisplayUsableBounds(WINPR_ASSERTING_INT_CAST(int, monitor->orig_screen), &rect); + + *pMaxWidth = WINPR_ASSERTING_INT_CAST(uint32_t, rect.w); + *pMaxHeight = WINPR_ASSERTING_INT_CAST(uint32_t, rect.h); + + if (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseWidth)) + *pMaxWidth = (WINPR_ASSERTING_INT_CAST(uint32_t, rect.w) * + freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)) / + 100; + + if (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseHeight)) + *pMaxHeight = (WINPR_ASSERTING_INT_CAST(uint32_t, rect.h) * + freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)) / + 100; + } + else if (freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) && + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)) + { + *pMaxWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + *pMaxHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + } + } + return TRUE; +} + +#if SDL_VERSION_ATLEAST(2, 0, 10) +static UINT32 sdl_orientaion_to_rdp(SDL_DisplayOrientation orientation) +{ + switch (orientation) + { + case SDL_ORIENTATION_LANDSCAPE: + return ORIENTATION_LANDSCAPE; + case SDL_ORIENTATION_LANDSCAPE_FLIPPED: + return ORIENTATION_LANDSCAPE_FLIPPED; + case SDL_ORIENTATION_PORTRAIT_FLIPPED: + return ORIENTATION_PORTRAIT_FLIPPED; + case SDL_ORIENTATION_PORTRAIT: + default: + return ORIENTATION_PORTRAIT; + } +} +#endif + +static Uint32 scale(Uint32 val, float scale) +{ + const auto dval = static_cast(val); + const auto sval = dval / scale; + return static_cast(sval); +} + +static BOOL sdl_apply_display_properties(SdlContext* sdl) +{ + WINPR_ASSERT(sdl); + + rdpSettings* settings = sdl->context()->settings; + WINPR_ASSERT(settings); + + if (!freerdp_settings_get_bool(settings, FreeRDP_Fullscreen) && + !freerdp_settings_get_bool(settings, FreeRDP_UseMultimon)) + return TRUE; + + const UINT32 numIds = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds); + std::vector monitors; + + for (UINT32 x = 0; x < numIds; x++) + { + auto id = static_cast( + freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorIds, x)); + WINPR_ASSERT(id); + + float ddpi = 1.0f; + float hdpi = 1.0f; + float vdpi = 1.0f; + SDL_Rect rect = {}; + + if (SDL_GetDisplayBounds(*id, &rect) < 0) + return FALSE; + + if (SDL_GetDisplayDPI(*id, &ddpi, &hdpi, &vdpi) < 0) + return FALSE; + + WINPR_ASSERT(rect.w > 0); + WINPR_ASSERT(rect.h > 0); + WINPR_ASSERT(ddpi > 0); + WINPR_ASSERT(hdpi > 0); + WINPR_ASSERT(vdpi > 0); + + bool highDpi = hdpi > 100; + + if (highDpi) + { + // HighDPI is problematic with SDL: We can only get native resolution by creating a + // window. Work around this by checking the supported resolutions (and keep maximum) + // Also scale the DPI + const SDL_Rect scaleRect = rect; + for (int i = 0; i < SDL_GetNumDisplayModes(*id); i++) + { + SDL_DisplayMode mode = {}; + SDL_GetDisplayMode(WINPR_ASSERTING_INT_CAST(int, x), i, &mode); + + if (mode.w > rect.w) + { + rect.w = mode.w; + rect.h = mode.h; + } + else if (mode.w == rect.w) + { + if (mode.h > rect.h) + { + rect.w = mode.w; + rect.h = mode.h; + } + } + } + + const float dw = 1.0f * static_cast(rect.w) / static_cast(scaleRect.w); + const float dh = 1.0f * static_cast(rect.h) / static_cast(scaleRect.h); + hdpi /= dw; + vdpi /= dh; + } + +#if SDL_VERSION_ATLEAST(2, 0, 10) + const SDL_DisplayOrientation orientation = SDL_GetDisplayOrientation(*id); + const UINT32 rdp_orientation = sdl_orientaion_to_rdp(orientation); +#else + const UINT32 rdp_orientation = ORIENTATION_LANDSCAPE; +#endif + + rdpMonitor monitor = {}; + + /* windows uses 96 dpi as 'default' and the scale factors are in percent. */ + const auto factor = ddpi / 96.0f * 100.0f; + monitor.orig_screen = x; + monitor.x = rect.x; + monitor.y = rect.y; + monitor.width = rect.w; + monitor.height = rect.h; + monitor.is_primary = x == 0; + monitor.attributes.desktopScaleFactor = static_cast(factor); + monitor.attributes.deviceScaleFactor = 100; + monitor.attributes.orientation = rdp_orientation; + monitor.attributes.physicalWidth = scale(WINPR_ASSERTING_INT_CAST(uint32_t, rect.w), hdpi); + monitor.attributes.physicalHeight = scale(WINPR_ASSERTING_INT_CAST(uint32_t, rect.h), vdpi); + monitors.emplace_back(monitor); + } + return freerdp_settings_set_monitor_def_array_sorted(settings, monitors.data(), + monitors.size()); +} + +static BOOL sdl_detect_single_window(SdlContext* sdl, UINT32* pMaxWidth, UINT32* pMaxHeight) +{ + WINPR_ASSERT(sdl); + WINPR_ASSERT(pMaxWidth); + WINPR_ASSERT(pMaxHeight); + + rdpSettings* settings = sdl->context()->settings; + WINPR_ASSERT(settings); + + if ((!freerdp_settings_get_bool(settings, FreeRDP_UseMultimon) && + !freerdp_settings_get_bool(settings, FreeRDP_SpanMonitors)) || + (freerdp_settings_get_bool(settings, FreeRDP_Workarea) && + !freerdp_settings_get_bool(settings, FreeRDP_RemoteApplicationMode))) + { + /* If no monitors were specified on the command-line then set the current monitor as active + */ + if (freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds) == 0) + { + const size_t id = (!sdl->windows.empty()) + ? WINPR_ASSERTING_INT_CAST( + uint32_t, sdl->windows.begin()->second.displayIndex()) + : 0; + if (!freerdp_settings_set_pointer_len(settings, FreeRDP_MonitorIds, &id, 1)) + return FALSE; + } + else + { + + /* Always sets number of monitors from command-line to just 1. + * If the monitor is invalid then we will default back to current monitor + * later as a fallback. So, there is no need to validate command-line entry here. + */ + if (!freerdp_settings_set_uint32(settings, FreeRDP_NumMonitorIds, 1)) + return FALSE; + } + + // TODO: Fill monitor struct + if (!sdl_apply_display_properties(sdl)) + return FALSE; + return sdl_apply_max_size(sdl, pMaxWidth, pMaxHeight); + } + return TRUE; +} + +BOOL sdl_detect_monitors(SdlContext* sdl, UINT32* pMaxWidth, UINT32* pMaxHeight) +{ + WINPR_ASSERT(sdl); + WINPR_ASSERT(pMaxWidth); + WINPR_ASSERT(pMaxHeight); + + rdpSettings* settings = sdl->context()->settings; + WINPR_ASSERT(settings); + + const int numDisplays = SDL_GetNumVideoDisplays(); + auto nr = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds); + if (numDisplays < 0) + return FALSE; + + if (nr == 0) + { + if (!freerdp_settings_set_pointer_len(settings, FreeRDP_MonitorIds, nullptr, + static_cast(numDisplays))) + return FALSE; + for (size_t x = 0; x < static_cast(numDisplays); x++) + { + if (!freerdp_settings_set_pointer_array(settings, FreeRDP_MonitorIds, x, &x)) + return FALSE; + } + } + else + { + + /* There were more IDs supplied than there are monitors */ + if (nr > static_cast(numDisplays)) + { + WLog_ERR(TAG, "Found %" PRIu32 " monitor IDs, but only have %d monitors connected", nr, + numDisplays); + return FALSE; + } + + std::vector used; + for (size_t x = 0; x < nr; x++) + { + auto cur = static_cast( + freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorIds, x)); + WINPR_ASSERT(cur); + + auto id = *cur; + + /* the ID is no valid monitor index */ + if (id >= nr) + { + WLog_ERR(TAG, + "Supplied monitor ID[%" PRIuz "]=%" PRIu32 " is invalid, only [0-%" PRIu32 + "] are allowed", + x, id, nr - 1); + return FALSE; + } + + /* The ID is already taken */ + if (std::find(used.begin(), used.end(), id) != used.end()) + { + WLog_ERR(TAG, "Duplicate monitor ID[%" PRIuz "]=%" PRIu32 " detected", x, id); + return FALSE; + } + used.push_back(*cur); + } + } + + if (!sdl_apply_display_properties(sdl)) + return FALSE; + + return sdl_detect_single_window(sdl, pMaxWidth, pMaxHeight); +} + +INT64 sdl_monitor_id_for_index(SdlContext* sdl, UINT32 index) +{ + WINPR_ASSERT(sdl); + auto settings = sdl->context()->settings; + + auto nr = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds); + if (nr == 0) + return index; + + if (nr <= index) + return -1; + + auto cur = static_cast( + freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorIds, index)); + WINPR_ASSERT(cur); + return *cur; +} diff --git a/third_party/FreeRDP/client/SDL/SDL2/sdl_monitor.hpp b/third_party/FreeRDP/client/SDL/SDL2/sdl_monitor.hpp new file mode 100644 index 0000000..56be9b9 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/sdl_monitor.hpp @@ -0,0 +1,29 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Monitor Handling + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include "sdl_types.hpp" + +int sdl_list_monitors(SdlContext* sdl); +BOOL sdl_detect_monitors(SdlContext* sdl, UINT32* pMaxWidth, UINT32* pMaxHeight); +INT64 sdl_monitor_id_for_index(SdlContext* sdl, UINT32 index); diff --git a/third_party/FreeRDP/client/SDL/SDL2/sdl_pointer.cpp b/third_party/FreeRDP/client/SDL/SDL2/sdl_pointer.cpp new file mode 100644 index 0000000..3cc8bdb --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/sdl_pointer.cpp @@ -0,0 +1,210 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Mouse Pointer + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include "sdl_pointer.hpp" +#include "sdl_freerdp.hpp" +#include "sdl_touch.hpp" +#include "sdl_utils.hpp" + +#include + +using sdlPointer = struct +{ + rdpPointer pointer; + SDL_Cursor* cursor; + SDL_Surface* image; + size_t size; + void* data; +}; + +static BOOL sdl_Pointer_New(rdpContext* context, rdpPointer* pointer) +{ + auto ptr = reinterpret_cast(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(ptr->data); + if (!freerdp_image_copy_from_pointer_data( + data, gdi->dstFormat, 0, 0, 0, pointer->width, pointer->height, pointer->xorMaskData, + pointer->lengthXorMask, pointer->andMaskData, pointer->lengthAndMask, pointer->xorBpp, + &context->gdi->palette)) + { + winpr_aligned_free(ptr->data); + ptr->data = nullptr; + return FALSE; + } + + return TRUE; +} + +static void sdl_Pointer_Clear(sdlPointer* ptr) +{ + WINPR_ASSERT(ptr); + SDL_FreeCursor(ptr->cursor); + SDL_FreeSurface(ptr->image); + ptr->cursor = nullptr; + ptr->image = nullptr; +} + +static void sdl_Pointer_Free(rdpContext* context, rdpPointer* pointer) +{ + auto ptr = reinterpret_cast(pointer); + WINPR_UNUSED(context); + + if (ptr) + { + sdl_Pointer_Clear(ptr); + winpr_aligned_free(ptr->data); + ptr->data = nullptr; + } +} + +static BOOL sdl_Pointer_SetDefault(rdpContext* context) +{ + WINPR_UNUSED(context); + + return sdl_push_user_event(SDL_USEREVENT_POINTER_DEFAULT); +} + +static BOOL sdl_Pointer_Set(rdpContext* context, rdpPointer* pointer) +{ + auto sdl = get_context(context); + + return sdl_push_user_event(SDL_USEREVENT_POINTER_SET, pointer, sdl); +} + +BOOL sdl_Pointer_Set_Process(SDL_UserEvent* uptr) +{ + INT32 w = 0; + INT32 h = 0; + INT32 x = 0; + INT32 y = 0; + INT32 sw = 0; + INT32 sh = 0; + + WINPR_ASSERT(uptr); + + auto sdl = static_cast(uptr->data2); + WINPR_ASSERT(sdl); + + auto context = sdl->context(); + auto ptr = static_cast(uptr->data1); + WINPR_ASSERT(ptr); + + rdpPointer* pointer = &ptr->pointer; + + rdpGdi* gdi = context->gdi; + WINPR_ASSERT(gdi); + + x = static_cast(pointer->xPos); + y = static_cast(pointer->yPos); + sw = w = static_cast(pointer->width); + sh = h = static_cast(pointer->height); + + SDL_Window* window = SDL_GetMouseFocus(); + if (!window) + return sdl_Pointer_SetDefault(context); + + const Uint32 id = SDL_GetWindowID(window); + + if (!sdl_scale_coordinates(sdl, id, &x, &y, FALSE, FALSE) || + !sdl_scale_coordinates(sdl, id, &sw, &sh, FALSE, FALSE)) + return FALSE; + + sdl_Pointer_Clear(ptr); + + const DWORD bpp = FreeRDPGetBitsPerPixel(gdi->dstFormat); + ptr->image = + SDL_CreateRGBSurfaceWithFormat(0, sw, sh, static_cast(bpp), sdl->sdl_pixel_format); + if (!ptr->image) + return FALSE; + + SDL_LockSurface(ptr->image); + auto pixels = static_cast(ptr->image->pixels); + auto data = static_cast(ptr->data); + const BOOL rc = freerdp_image_scale( + pixels, gdi->dstFormat, static_cast(ptr->image->pitch), 0, 0, + static_cast(ptr->image->w), static_cast(ptr->image->h), data, + gdi->dstFormat, 0, 0, 0, static_cast(w), static_cast(h)); + SDL_UnlockSurface(ptr->image); + if (!rc) + return FALSE; + + ptr->cursor = SDL_CreateColorCursor(ptr->image, x, y); + if (!ptr->cursor) + return FALSE; + + SDL_SetCursor(ptr->cursor); + SDL_ShowCursor(SDL_ENABLE); + return TRUE; +} + +static BOOL sdl_Pointer_SetNull(rdpContext* context) +{ + WINPR_UNUSED(context); + + return sdl_push_user_event(SDL_USEREVENT_POINTER_NULL); +} + +static BOOL sdl_Pointer_SetPosition(rdpContext* context, UINT32 x, UINT32 y) +{ + WINPR_UNUSED(context); + WINPR_ASSERT(context); + + return sdl_push_user_event(SDL_USEREVENT_POINTER_POSITION, x, y); +} + +BOOL sdl_register_pointer(rdpGraphics* graphics) +{ + const rdpPointer pointer = { sizeof(sdlPointer), + sdl_Pointer_New, + sdl_Pointer_Free, + sdl_Pointer_Set, + sdl_Pointer_SetNull, + sdl_Pointer_SetDefault, + sdl_Pointer_SetPosition, + {}, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + nullptr, + nullptr, + {} }; + graphics_register_pointer(graphics, &pointer); + return TRUE; +} diff --git a/third_party/FreeRDP/client/SDL/SDL2/sdl_pointer.hpp b/third_party/FreeRDP/client/SDL/SDL2/sdl_pointer.hpp new file mode 100644 index 0000000..006e962 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/sdl_pointer.hpp @@ -0,0 +1,27 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Mouse Pointer + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +BOOL sdl_register_pointer(rdpGraphics* graphics); + +BOOL sdl_Pointer_Set_Process(SDL_UserEvent* uptr); diff --git a/third_party/FreeRDP/client/SDL/SDL2/sdl_touch.cpp b/third_party/FreeRDP/client/SDL/SDL2/sdl_touch.cpp new file mode 100644 index 0000000..d9e4fd2 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/sdl_touch.cpp @@ -0,0 +1,283 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP SDL touch/mouse input + * + * Copyright 2022 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "sdl_touch.hpp" +#include "sdl_freerdp.hpp" + +#include +#include + +#include +#include + +#include + +BOOL sdl_scale_coordinates(SdlContext* sdl, Uint32 windowId, INT32* px, INT32* py, + BOOL fromLocalToRDP, BOOL applyOffset) +{ + rdpGdi* gdi = nullptr; + double sx = 1.0; + double sy = 1.0; + + if (!sdl || !px || !py || !sdl->context()->gdi) + return FALSE; + + WINPR_ASSERT(sdl->context()->gdi); + WINPR_ASSERT(sdl->context()->settings); + + gdi = sdl->context()->gdi; + + // TODO: Make this multimonitor ready! + // TODO: Need to find the primary monitor, get the scale + // TODO: Need to find the destination monitor, get the scale + // TODO: All intermediate monitors, get the scale + + int offset_x = 0; + int offset_y = 0; + for (const auto& it : sdl->windows) + { + auto& window = it.second; + const auto id = window.id(); + if (id != windowId) + { + continue; + } + + auto size = window.rect(); + + sx = size.w / static_cast(gdi->width); + sy = size.h / static_cast(gdi->height); + offset_x = window.offsetX(); + offset_y = window.offsetY(); + break; + } + + if (freerdp_settings_get_bool(sdl->context()->settings, FreeRDP_SmartSizing)) + { + if (!fromLocalToRDP) + { + *px = static_cast(*px * sx); + *py = static_cast(*py * sy); + } + else + { + *px = static_cast(*px / sx); + *py = static_cast(*py / sy); + } + } + else if (applyOffset) + { + *px -= offset_x; + *py -= offset_y; + } + + return TRUE; +} + +static BOOL sdl_get_touch_scaled(SdlContext* sdl, const SDL_TouchFingerEvent* ev, INT32* px, + INT32* py, BOOL local) +{ + Uint32 windowID = 0; + + WINPR_ASSERT(sdl); + WINPR_ASSERT(ev); + WINPR_ASSERT(px); + WINPR_ASSERT(py); + +#if SDL_VERSION_ATLEAST(2, 0, 12) + SDL_Window* window = SDL_GetWindowFromID(ev->windowID); +#else + SDL_Window* window = SDL_GetMouseFocus(); +#endif + + if (!window) + return FALSE; + + windowID = SDL_GetWindowID(window); + SDL_Surface* surface = SDL_GetWindowSurface(window); + if (!surface) + return FALSE; + + // TODO: Add the offset of the surface in the global coordinates + *px = static_cast(ev->x * static_cast(surface->w)); + *py = static_cast(ev->y * static_cast(surface->h)); + return sdl_scale_coordinates(sdl, windowID, px, py, local, TRUE); +} + +static BOOL send_mouse_wheel(SdlContext* sdl, UINT16 flags, INT32 avalue) +{ + WINPR_ASSERT(sdl); + if (avalue < 0) + { + flags |= PTR_FLAGS_WHEEL_NEGATIVE; + avalue = -avalue; + } + + while (avalue > 0) + { + const UINT16 cval = (avalue > 0xFF) ? 0xFF : static_cast(avalue); + UINT16 cflags = flags | cval; + /* Convert negative values to 9bit twos complement */ + if (flags & PTR_FLAGS_WHEEL_NEGATIVE) + cflags = (flags & 0xFF00) | (0x100 - cval); + if (!freerdp_client_send_wheel_event(sdl->common(), cflags)) + return FALSE; + + avalue -= cval; + } + return TRUE; +} + +static UINT32 sdl_scale_pressure(const float pressure) +{ + const float val = pressure * 0x400; /* [MS-RDPEI] 2.2.3.3.1.1 RDPINPUT_TOUCH_CONTACT */ + if (val < 0.0f) + return 0; + if (val > 0x400) + return 0x400; + return static_cast(val); +} + +BOOL sdl_handle_touch_up(SdlContext* sdl, const SDL_TouchFingerEvent* ev) +{ + WINPR_ASSERT(sdl); + WINPR_ASSERT(ev); + + INT32 x = 0; + INT32 y = 0; + if (!sdl_get_touch_scaled(sdl, ev, &x, &y, TRUE)) + return FALSE; + return freerdp_client_handle_touch(sdl->common(), FREERDP_TOUCH_UP | FREERDP_TOUCH_HAS_PRESSURE, + static_cast(ev->fingerId), + sdl_scale_pressure(ev->pressure), x, y); +} + +BOOL sdl_handle_touch_down(SdlContext* sdl, const SDL_TouchFingerEvent* ev) +{ + WINPR_ASSERT(sdl); + WINPR_ASSERT(ev); + + INT32 x = 0; + INT32 y = 0; + if (!sdl_get_touch_scaled(sdl, ev, &x, &y, TRUE)) + return FALSE; + return freerdp_client_handle_touch( + sdl->common(), FREERDP_TOUCH_DOWN | FREERDP_TOUCH_HAS_PRESSURE, + static_cast(ev->fingerId), sdl_scale_pressure(ev->pressure), x, y); +} + +BOOL sdl_handle_touch_motion(SdlContext* sdl, const SDL_TouchFingerEvent* ev) +{ + WINPR_ASSERT(sdl); + WINPR_ASSERT(ev); + + INT32 x = 0; + INT32 y = 0; + if (!sdl_get_touch_scaled(sdl, ev, &x, &y, TRUE)) + return FALSE; + return freerdp_client_handle_touch( + sdl->common(), FREERDP_TOUCH_MOTION | FREERDP_TOUCH_HAS_PRESSURE, + static_cast(ev->fingerId), sdl_scale_pressure(ev->pressure), x, y); +} + +BOOL sdl_handle_mouse_motion(SdlContext* sdl, const SDL_MouseMotionEvent* ev) +{ + WINPR_ASSERT(sdl); + WINPR_ASSERT(ev); + + sdl->input.mouse_focus(ev->windowID); + const BOOL relative = freerdp_client_use_relative_mouse_events(sdl->common()); + INT32 x = relative ? ev->xrel : ev->x; + INT32 y = relative ? ev->yrel : ev->y; + sdl_scale_coordinates(sdl, ev->windowID, &x, &y, TRUE, TRUE); + return freerdp_client_send_button_event(sdl->common(), relative, PTR_FLAGS_MOVE, x, y); +} + +BOOL sdl_handle_mouse_wheel(SdlContext* sdl, const SDL_MouseWheelEvent* ev) +{ + WINPR_ASSERT(sdl); + WINPR_ASSERT(ev); + + const BOOL flipped = (ev->direction == SDL_MOUSEWHEEL_FLIPPED); + const INT32 x = ev->x * (flipped ? -1 : 1) * 0x78; + const INT32 y = ev->y * (flipped ? -1 : 1) * 0x78; + UINT16 flags = 0; + + if (y != 0) + { + flags |= PTR_FLAGS_WHEEL; + send_mouse_wheel(sdl, flags, y); + } + + if (x != 0) + { + flags |= PTR_FLAGS_HWHEEL; + send_mouse_wheel(sdl, flags, x); + } + return TRUE; +} + +BOOL sdl_handle_mouse_button(SdlContext* sdl, const SDL_MouseButtonEvent* ev) +{ + UINT16 flags = 0; + UINT16 xflags = 0; + + WINPR_ASSERT(sdl); + WINPR_ASSERT(ev); + + if (ev->state == SDL_PRESSED) + { + flags |= PTR_FLAGS_DOWN; + xflags |= PTR_XFLAGS_DOWN; + } + + switch (ev->button) + { + case 1: + flags |= PTR_FLAGS_BUTTON1; + break; + case 2: + flags |= PTR_FLAGS_BUTTON3; + break; + case 3: + flags |= PTR_FLAGS_BUTTON2; + break; + case 4: + xflags |= PTR_XFLAGS_BUTTON1; + break; + case 5: + xflags |= PTR_XFLAGS_BUTTON2; + break; + default: + break; + } + + const BOOL relative = freerdp_client_use_relative_mouse_events(sdl->common()); + INT32 x = relative ? 0 : ev->x; + INT32 y = relative ? 0 : ev->y; + sdl_scale_coordinates(sdl, ev->windowID, &x, &y, TRUE, TRUE); + if ((flags & (~PTR_FLAGS_DOWN)) != 0) + return freerdp_client_send_button_event(sdl->common(), relative, flags, x, y); + else if ((xflags & (~PTR_XFLAGS_DOWN)) != 0) + return freerdp_client_send_extended_button_event(sdl->common(), relative, xflags, x, y); + else + return FALSE; +} diff --git a/third_party/FreeRDP/client/SDL/SDL2/sdl_touch.hpp b/third_party/FreeRDP/client/SDL/SDL2/sdl_touch.hpp new file mode 100644 index 0000000..395fddb --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/sdl_touch.hpp @@ -0,0 +1,36 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP SDL touch/mouse input + * + * Copyright 2022 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include "sdl_types.hpp" + +BOOL sdl_scale_coordinates(SdlContext* sdl, Uint32 windowId, INT32* px, INT32* py, + BOOL fromLocalToRDP, BOOL applyOffset); + +BOOL sdl_handle_mouse_motion(SdlContext* sdl, const SDL_MouseMotionEvent* ev); +BOOL sdl_handle_mouse_wheel(SdlContext* sdl, const SDL_MouseWheelEvent* ev); +BOOL sdl_handle_mouse_button(SdlContext* sdl, const SDL_MouseButtonEvent* ev); + +BOOL sdl_handle_touch_down(SdlContext* sdl, const SDL_TouchFingerEvent* ev); +BOOL sdl_handle_touch_up(SdlContext* sdl, const SDL_TouchFingerEvent* ev); +BOOL sdl_handle_touch_motion(SdlContext* sdl, const SDL_TouchFingerEvent* ev); diff --git a/third_party/FreeRDP/client/SDL/SDL2/sdl_types.hpp b/third_party/FreeRDP/client/SDL/SDL2/sdl_types.hpp new file mode 100644 index 0000000..831472c --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/sdl_types.hpp @@ -0,0 +1,46 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client + * + * Copyright 2022 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +class SdlContext; + +typedef struct +{ + rdpClientContext common; + SdlContext* sdl; +} sdl_rdp_context; + +static inline SdlContext* get_context(void* ctx) +{ + if (!ctx) + return nullptr; + auto sdl = static_cast(ctx); + return sdl->sdl; +} + +static inline SdlContext* get_context(rdpContext* ctx) +{ + if (!ctx) + return nullptr; + auto sdl = reinterpret_cast(ctx); + return sdl->sdl; +} diff --git a/third_party/FreeRDP/client/SDL/SDL2/sdl_utils.cpp b/third_party/FreeRDP/client/SDL/SDL2/sdl_utils.cpp new file mode 100644 index 0000000..9b24bb9 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/sdl_utils.cpp @@ -0,0 +1,293 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client + * + * Copyright 2022 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "sdl_utils.hpp" + +#include "sdl_freerdp.hpp" + +#include + +#include + +const char* sdl_event_type_str(Uint32 type) +{ +#define STR(x) #x +#define EV_CASE_STR(x) \ + case x: \ + return STR(x) + + switch (type) + { + EV_CASE_STR(SDL_FIRSTEVENT); + EV_CASE_STR(SDL_QUIT); + EV_CASE_STR(SDL_APP_TERMINATING); + EV_CASE_STR(SDL_APP_LOWMEMORY); + EV_CASE_STR(SDL_APP_WILLENTERBACKGROUND); + EV_CASE_STR(SDL_APP_DIDENTERBACKGROUND); + EV_CASE_STR(SDL_APP_WILLENTERFOREGROUND); + EV_CASE_STR(SDL_APP_DIDENTERFOREGROUND); +#if SDL_VERSION_ATLEAST(2, 0, 10) + EV_CASE_STR(SDL_DISPLAYEVENT); +#endif + EV_CASE_STR(SDL_WINDOWEVENT); + EV_CASE_STR(SDL_SYSWMEVENT); + EV_CASE_STR(SDL_KEYDOWN); + EV_CASE_STR(SDL_KEYUP); + EV_CASE_STR(SDL_TEXTEDITING); + EV_CASE_STR(SDL_TEXTINPUT); + EV_CASE_STR(SDL_KEYMAPCHANGED); + EV_CASE_STR(SDL_MOUSEMOTION); + EV_CASE_STR(SDL_MOUSEBUTTONDOWN); + EV_CASE_STR(SDL_MOUSEBUTTONUP); + EV_CASE_STR(SDL_MOUSEWHEEL); + EV_CASE_STR(SDL_JOYAXISMOTION); + EV_CASE_STR(SDL_JOYBALLMOTION); + EV_CASE_STR(SDL_JOYHATMOTION); + EV_CASE_STR(SDL_JOYBUTTONDOWN); + EV_CASE_STR(SDL_JOYBUTTONUP); + EV_CASE_STR(SDL_JOYDEVICEADDED); + EV_CASE_STR(SDL_JOYDEVICEREMOVED); + EV_CASE_STR(SDL_CONTROLLERAXISMOTION); + EV_CASE_STR(SDL_CONTROLLERBUTTONDOWN); + EV_CASE_STR(SDL_CONTROLLERBUTTONUP); + EV_CASE_STR(SDL_CONTROLLERDEVICEADDED); + EV_CASE_STR(SDL_CONTROLLERDEVICEREMOVED); + EV_CASE_STR(SDL_CONTROLLERDEVICEREMAPPED); +#if SDL_VERSION_ATLEAST(2, 0, 14) + EV_CASE_STR(SDL_LOCALECHANGED); + EV_CASE_STR(SDL_CONTROLLERTOUCHPADDOWN); + EV_CASE_STR(SDL_CONTROLLERTOUCHPADMOTION); + EV_CASE_STR(SDL_CONTROLLERTOUCHPADUP); + EV_CASE_STR(SDL_CONTROLLERSENSORUPDATE); +#endif + EV_CASE_STR(SDL_FINGERDOWN); + EV_CASE_STR(SDL_FINGERUP); + EV_CASE_STR(SDL_FINGERMOTION); + EV_CASE_STR(SDL_DOLLARGESTURE); + EV_CASE_STR(SDL_DOLLARRECORD); + EV_CASE_STR(SDL_MULTIGESTURE); + EV_CASE_STR(SDL_CLIPBOARDUPDATE); + EV_CASE_STR(SDL_DROPFILE); + EV_CASE_STR(SDL_DROPTEXT); + EV_CASE_STR(SDL_DROPBEGIN); + EV_CASE_STR(SDL_DROPCOMPLETE); + EV_CASE_STR(SDL_AUDIODEVICEADDED); + EV_CASE_STR(SDL_AUDIODEVICEREMOVED); +#if SDL_VERSION_ATLEAST(2, 0, 9) + EV_CASE_STR(SDL_SENSORUPDATE); +#endif + EV_CASE_STR(SDL_RENDER_TARGETS_RESET); + EV_CASE_STR(SDL_RENDER_DEVICE_RESET); + EV_CASE_STR(SDL_USEREVENT); + + EV_CASE_STR(SDL_USEREVENT_CERT_DIALOG); + EV_CASE_STR(SDL_USEREVENT_CERT_RESULT); + EV_CASE_STR(SDL_USEREVENT_SHOW_DIALOG); + EV_CASE_STR(SDL_USEREVENT_SHOW_RESULT); + EV_CASE_STR(SDL_USEREVENT_AUTH_DIALOG); + EV_CASE_STR(SDL_USEREVENT_AUTH_RESULT); + EV_CASE_STR(SDL_USEREVENT_SCARD_DIALOG); + EV_CASE_STR(SDL_USEREVENT_RETRY_DIALOG); + EV_CASE_STR(SDL_USEREVENT_SCARD_RESULT); + EV_CASE_STR(SDL_USEREVENT_UPDATE); + EV_CASE_STR(SDL_USEREVENT_CREATE_WINDOWS); + EV_CASE_STR(SDL_USEREVENT_WINDOW_RESIZEABLE); + EV_CASE_STR(SDL_USEREVENT_WINDOW_FULLSCREEN); + EV_CASE_STR(SDL_USEREVENT_WINDOW_MINIMIZE); + EV_CASE_STR(SDL_USEREVENT_POINTER_NULL); + EV_CASE_STR(SDL_USEREVENT_POINTER_DEFAULT); + EV_CASE_STR(SDL_USEREVENT_POINTER_POSITION); + EV_CASE_STR(SDL_USEREVENT_POINTER_SET); + EV_CASE_STR(SDL_USEREVENT_QUIT); + + EV_CASE_STR(SDL_LASTEVENT); + default: + return "SDL_UNKNOWNEVENT"; + } +#undef EV_CASE_STR +#undef STR +} + +const char* sdl_error_string(Sint32 res) +{ + if (res == 0) + return nullptr; + + return SDL_GetError(); +} + +BOOL sdl_log_error_ex(Sint32 res, wLog* log, const char* what, const char* file, size_t line, + const char* fkt) +{ + const char* msg = sdl_error_string(res); + + WINPR_UNUSED(file); + + if (!msg) + return FALSE; + + WLog_Print(log, WLOG_ERROR, "[%s:%" PRIuz "][%s]: %s", fkt, line, what, msg); + return TRUE; +} + +BOOL sdl_push_user_event(Uint32 type, ...) +{ + SDL_Event ev = {}; + SDL_UserEvent* event = &ev.user; + + va_list ap = {}; + va_start(ap, type); + event->type = type; + switch (type) + { + case SDL_USEREVENT_AUTH_RESULT: + { + auto arg = reinterpret_cast(ev.padding); + arg->user = va_arg(ap, char*); + arg->domain = va_arg(ap, char*); + arg->password = va_arg(ap, char*); + arg->result = va_arg(ap, Sint32); + } + break; + case SDL_USEREVENT_AUTH_DIALOG: + { + auto arg = reinterpret_cast(ev.padding); + + arg->title = va_arg(ap, char*); + arg->user = va_arg(ap, char*); + arg->domain = va_arg(ap, char*); + arg->password = va_arg(ap, char*); + arg->result = va_arg(ap, Sint32); + } + break; + case SDL_USEREVENT_SCARD_DIALOG: + { + event->data1 = va_arg(ap, char*); + event->data2 = va_arg(ap, char**); + event->code = va_arg(ap, Sint32); + } + break; + case SDL_USEREVENT_RETRY_DIALOG: + break; + case SDL_USEREVENT_SCARD_RESULT: + case SDL_USEREVENT_SHOW_RESULT: + case SDL_USEREVENT_CERT_RESULT: + event->code = va_arg(ap, Sint32); + break; + + case SDL_USEREVENT_SHOW_DIALOG: + event->data1 = va_arg(ap, char*); + event->data2 = va_arg(ap, char*); + event->code = va_arg(ap, Sint32); + break; + case SDL_USEREVENT_CERT_DIALOG: + event->data1 = va_arg(ap, char*); + event->data2 = va_arg(ap, char*); + break; + case SDL_USEREVENT_UPDATE: + event->data1 = va_arg(ap, void*); + break; + case SDL_USEREVENT_POINTER_POSITION: + event->data1 = reinterpret_cast(static_cast(va_arg(ap, UINT32))); + event->data2 = reinterpret_cast(static_cast(va_arg(ap, UINT32))); + break; + case SDL_USEREVENT_POINTER_SET: + event->data1 = va_arg(ap, void*); + event->data2 = va_arg(ap, void*); + break; + case SDL_USEREVENT_CREATE_WINDOWS: + event->data1 = va_arg(ap, void*); + break; + case SDL_USEREVENT_WINDOW_FULLSCREEN: + case SDL_USEREVENT_WINDOW_RESIZEABLE: + event->data1 = va_arg(ap, void*); + event->code = va_arg(ap, int); + break; + case SDL_USEREVENT_WINDOW_MINIMIZE: + case SDL_USEREVENT_QUIT: + case SDL_USEREVENT_POINTER_NULL: + case SDL_USEREVENT_POINTER_DEFAULT: + break; + default: + va_end(ap); + return FALSE; + } + va_end(ap); + return SDL_PushEvent(&ev) == 1; +} + +bool sdl_push_quit() +{ + SDL_Event ev = {}; + ev.type = SDL_QUIT; + SDL_PushEvent(&ev); + return true; +} + +std::string sdl_window_event_str(Uint8 ev) +{ + switch (ev) + { + case SDL_WINDOWEVENT_NONE: + return "SDL_WINDOWEVENT_NONE"; + case SDL_WINDOWEVENT_SHOWN: + return "SDL_WINDOWEVENT_SHOWN"; + case SDL_WINDOWEVENT_HIDDEN: + return "SDL_WINDOWEVENT_HIDDEN"; + case SDL_WINDOWEVENT_EXPOSED: + return "SDL_WINDOWEVENT_EXPOSED"; + case SDL_WINDOWEVENT_MOVED: + return "SDL_WINDOWEVENT_MOVED"; + case SDL_WINDOWEVENT_RESIZED: + return "SDL_WINDOWEVENT_RESIZED"; + case SDL_WINDOWEVENT_SIZE_CHANGED: + return "SDL_WINDOWEVENT_SIZE_CHANGED"; + case SDL_WINDOWEVENT_MINIMIZED: + return "SDL_WINDOWEVENT_MINIMIZED"; + case SDL_WINDOWEVENT_MAXIMIZED: + return "SDL_WINDOWEVENT_MAXIMIZED"; + case SDL_WINDOWEVENT_RESTORED: + return "SDL_WINDOWEVENT_RESTORED"; + case SDL_WINDOWEVENT_ENTER: + return "SDL_WINDOWEVENT_ENTER"; + case SDL_WINDOWEVENT_LEAVE: + return "SDL_WINDOWEVENT_LEAVE"; + case SDL_WINDOWEVENT_FOCUS_GAINED: + return "SDL_WINDOWEVENT_FOCUS_GAINED"; + case SDL_WINDOWEVENT_FOCUS_LOST: + return "SDL_WINDOWEVENT_FOCUS_LOST"; + case SDL_WINDOWEVENT_CLOSE: + return "SDL_WINDOWEVENT_CLOSE"; +#if SDL_VERSION_ATLEAST(2, 0, 5) + case SDL_WINDOWEVENT_TAKE_FOCUS: + return "SDL_WINDOWEVENT_TAKE_FOCUS"; + case SDL_WINDOWEVENT_HIT_TEST: + return "SDL_WINDOWEVENT_HIT_TEST"; +#endif +#if SDL_VERSION_ATLEAST(2, 0, 18) + case SDL_WINDOWEVENT_ICCPROF_CHANGED: + return "SDL_WINDOWEVENT_ICCPROF_CHANGED"; + case SDL_WINDOWEVENT_DISPLAY_CHANGED: + return "SDL_WINDOWEVENT_DISPLAY_CHANGED"; +#endif + default: + return "SDL_WINDOWEVENT_UNKNOWN"; + } +} diff --git a/third_party/FreeRDP/client/SDL/SDL2/sdl_utils.hpp b/third_party/FreeRDP/client/SDL/SDL2/sdl_utils.hpp new file mode 100644 index 0000000..586d0e1 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/sdl_utils.hpp @@ -0,0 +1,76 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client + * + * Copyright 2022 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include +#include +#include + +#include + +enum +{ + SDL_USEREVENT_UPDATE = SDL_USEREVENT + 1, + SDL_USEREVENT_CREATE_WINDOWS, + SDL_USEREVENT_WINDOW_RESIZEABLE, + SDL_USEREVENT_WINDOW_FULLSCREEN, + SDL_USEREVENT_WINDOW_MINIMIZE, + SDL_USEREVENT_POINTER_NULL, + SDL_USEREVENT_POINTER_DEFAULT, + SDL_USEREVENT_POINTER_POSITION, + SDL_USEREVENT_POINTER_SET, + SDL_USEREVENT_QUIT, + SDL_USEREVENT_CERT_DIALOG, + SDL_USEREVENT_SHOW_DIALOG, + SDL_USEREVENT_AUTH_DIALOG, + SDL_USEREVENT_SCARD_DIALOG, + SDL_USEREVENT_RETRY_DIALOG, + + SDL_USEREVENT_CERT_RESULT, + SDL_USEREVENT_SHOW_RESULT, + SDL_USEREVENT_AUTH_RESULT, + SDL_USEREVENT_SCARD_RESULT +}; + +typedef struct +{ + Uint32 type; + Uint32 timestamp; + char* title; + char* user; + char* domain; + char* password; + Sint32 result; +} SDL_UserAuthArg; + +BOOL sdl_push_user_event(Uint32 type, ...); + +bool sdl_push_quit(); + +std::string sdl_window_event_str(Uint8 ev); +const char* sdl_event_type_str(Uint32 type); +const char* sdl_error_string(Sint32 res); + +#define sdl_log_error(res, log, what) sdl_log_error_ex(res, log, what, __FILE__, __LINE__, __func__) +BOOL sdl_log_error_ex(Sint32 res, wLog* log, const char* what, const char* file, size_t line, + const char* fkt); diff --git a/third_party/FreeRDP/client/SDL/SDL2/sdl_window.cpp b/third_party/FreeRDP/client/SDL/SDL2/sdl_window.cpp new file mode 100644 index 0000000..5ecfae9 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/sdl_window.cpp @@ -0,0 +1,207 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client + * + * Copyright 2023 Armin Novak + * Copyright 2023 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "sdl_window.hpp" +#include "sdl_utils.hpp" + +SdlWindow::SdlWindow(const std::string& title, Sint32 startupX, Sint32 startupY, Sint32 width, + Sint32 height, Uint32 flags) + : _window(SDL_CreateWindow(title.c_str(), startupX, startupY, width, height, flags)) +{ +} + +SdlWindow::SdlWindow(SdlWindow&& other) noexcept + : _window(other._window), _offset_x(other._offset_x), _offset_y(other._offset_y) +{ + other._window = nullptr; +} + +SdlWindow::~SdlWindow() +{ + SDL_DestroyWindow(_window); +} + +Uint32 SdlWindow::id() const +{ + if (!_window) + return 0; + return SDL_GetWindowID(_window); +} + +int SdlWindow::displayIndex() const +{ + if (!_window) + return 0; + return SDL_GetWindowDisplayIndex(_window); +} + +SDL_Rect SdlWindow::rect() const +{ + SDL_Rect rect = {}; + if (_window) + { + SDL_GetWindowPosition(_window, &rect.x, &rect.y); + SDL_GetWindowSize(_window, &rect.w, &rect.h); + } + return rect; +} + +SDL_Window* SdlWindow::window() const +{ + return _window; +} + +Sint32 SdlWindow::offsetX() const +{ + return _offset_x; +} + +void SdlWindow::setOffsetX(Sint32 x) +{ + _offset_x = x; +} + +void SdlWindow::setOffsetY(Sint32 y) +{ + _offset_y = y; +} + +Sint32 SdlWindow::offsetY() const +{ + return _offset_y; +} + +bool SdlWindow::grabKeyboard(bool enable) +{ + if (!_window) + return false; +#if SDL_VERSION_ATLEAST(2, 0, 16) + SDL_SetWindowKeyboardGrab(_window, enable ? SDL_TRUE : SDL_FALSE); + return true; +#else + SDL_LogError(SDL_LOG_CATEGORY_INPUT, "Keyboard grabbing not supported by SDL2 < 2.0.16"); + return false; +#endif +} + +bool SdlWindow::grabMouse(bool enable) +{ + if (!_window) + return false; +#if SDL_VERSION_ATLEAST(2, 0, 16) + SDL_SetWindowMouseGrab(_window, enable ? SDL_TRUE : SDL_FALSE); +#else + SDL_SetWindowGrab(_window, enable ? SDL_TRUE : SDL_FALSE); +#endif + return true; +} + +void SdlWindow::setBordered(bool bordered) +{ + if (_window) + SDL_SetWindowBordered(_window, bordered ? SDL_TRUE : SDL_FALSE); +} + +void SdlWindow::raise() +{ + SDL_RaiseWindow(_window); +} + +void SdlWindow::resizeable(bool use) +{ + SDL_SetWindowResizable(_window, use ? SDL_TRUE : SDL_FALSE); +} + +void SdlWindow::fullscreen(bool enter) +{ + auto curFlags = SDL_GetWindowFlags(_window); + + if (enter) + { + if (!(curFlags & SDL_WINDOW_BORDERLESS)) + { + auto idx = SDL_GetWindowDisplayIndex(_window); + SDL_DisplayMode mode = {}; + SDL_GetCurrentDisplayMode(idx, &mode); + + SDL_RestoreWindow(_window); // Maximize so we can see the caption and + // bits + SDL_SetWindowBordered(_window, SDL_FALSE); + SDL_SetWindowPosition(_window, 0, 0); +#if SDL_VERSION_ATLEAST(2, 0, 16) + SDL_SetWindowAlwaysOnTop(_window, SDL_TRUE); +#endif + SDL_RaiseWindow(_window); + SDL_SetWindowSize(_window, mode.w, mode.h); + } + } + else + { + if (curFlags & SDL_WINDOW_BORDERLESS) + { + + SDL_SetWindowBordered(_window, SDL_TRUE); +#if SDL_VERSION_ATLEAST(2, 0, 16) + SDL_SetWindowAlwaysOnTop(_window, SDL_FALSE); +#endif + SDL_RaiseWindow(_window); + SDL_MinimizeWindow(_window); // Maximize so we can see the caption and bits + SDL_MaximizeWindow(_window); // Maximize so we can see the caption and bits + } + } +} + +void SdlWindow::minimize() +{ + SDL_MinimizeWindow(_window); +} + +bool SdlWindow::fill(Uint8 r, Uint8 g, Uint8 b, Uint8 a) +{ + auto surface = SDL_GetWindowSurface(_window); + if (!surface) + return false; + SDL_Rect rect = { 0, 0, surface->w, surface->h }; + auto color = SDL_MapRGBA(surface->format, r, g, b, a); + + SDL_FillRect(surface, &rect, color); + return true; +} + +bool SdlWindow::blit(SDL_Surface* surface, SDL_Rect srcRect, SDL_Rect& dstRect) +{ + auto screen = SDL_GetWindowSurface(_window); + if (!screen || !surface) + return false; + if (!SDL_SetClipRect(surface, &srcRect)) + return true; + if (!SDL_SetClipRect(screen, &dstRect)) + return true; + auto rc = SDL_BlitScaled(surface, &srcRect, screen, &dstRect); + if (rc != 0) + { + SDL_LogError(SDL_LOG_CATEGORY_RENDER, "SDL_BlitScaled: %s [%d]", sdl_error_string(rc), rc); + } + return rc == 0; +} + +void SdlWindow::updateSurface() +{ + SDL_UpdateWindowSurface(_window); +} diff --git a/third_party/FreeRDP/client/SDL/SDL2/sdl_window.hpp b/third_party/FreeRDP/client/SDL/SDL2/sdl_window.hpp new file mode 100644 index 0000000..93f7104 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL2/sdl_window.hpp @@ -0,0 +1,64 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client + * + * Copyright 2023 Armin Novak + * 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 +#include + +class SdlWindow +{ + public: + SdlWindow(const std::string& title, Sint32 startupX, Sint32 startupY, Sint32 width, + Sint32 height, Uint32 flags); + SdlWindow(const SdlWindow& other) = delete; + SdlWindow(SdlWindow&& other) noexcept; + ~SdlWindow(); + + SdlWindow& operator=(const SdlWindow& other) = delete; + SdlWindow& operator=(SdlWindow&& other) = delete; + + [[nodiscard]] Uint32 id() const; + [[nodiscard]] int displayIndex() const; + [[nodiscard]] SDL_Rect rect() const; + [[nodiscard]] SDL_Window* window() const; + + [[nodiscard]] Sint32 offsetX() const; + void setOffsetX(Sint32 x); + + void setOffsetY(Sint32 y); + [[nodiscard]] Sint32 offsetY() const; + + bool grabKeyboard(bool enable); + bool grabMouse(bool enable); + void setBordered(bool bordered); + void raise(); + void resizeable(bool use); + void fullscreen(bool enter); + void minimize(); + + bool fill(Uint8 r = 0x00, Uint8 g = 0x00, Uint8 b = 0x00, Uint8 a = 0xff); + bool blit(SDL_Surface* surface, SDL_Rect src, SDL_Rect& dst); + void updateSurface(); + + private: + SDL_Window* _window = nullptr; + Sint32 _offset_x = 0; + Sint32 _offset_y = 0; +}; diff --git a/third_party/FreeRDP/client/SDL/SDL3/CMakeLists.txt b/third_party/FreeRDP/client/SDL/SDL3/CMakeLists.txt new file mode 100644 index 0000000..3ccff87 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/CMakeLists.txt @@ -0,0 +1,92 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP SDL Client +# +# Copyright 2022 Armin Novak +# Copyright 2024 Armin Novak +# 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) diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/CMakeLists.txt b/third_party/FreeRDP/client/SDL/SDL3/dialogs/CMakeLists.txt new file mode 100644 index 0000000..5d15882 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/CMakeLists.txt @@ -0,0 +1,87 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP SDL Client +# +# Copyright 2024 Armin Novak +# 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() diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/font/OFL.txt b/third_party/FreeRDP/client/SDL/SDL3/dialogs/font/OFL.txt new file mode 100644 index 0000000..9b448d4 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/font/OFL.txt @@ -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. diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/font/OpenSans-Italic-VariableFont_wdth,wght.ttf b/third_party/FreeRDP/client/SDL/SDL3/dialogs/font/OpenSans-Italic-VariableFont_wdth,wght.ttf new file mode 100644 index 0000000..5bda9cc Binary files /dev/null and b/third_party/FreeRDP/client/SDL/SDL3/dialogs/font/OpenSans-Italic-VariableFont_wdth,wght.ttf differ diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/font/OpenSans-VariableFont_wdth,wght.ttf b/third_party/FreeRDP/client/SDL/SDL3/dialogs/font/OpenSans-VariableFont_wdth,wght.ttf new file mode 100644 index 0000000..e4142bf Binary files /dev/null and b/third_party/FreeRDP/client/SDL/SDL3/dialogs/font/OpenSans-VariableFont_wdth,wght.ttf differ diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/font/README.txt b/third_party/FreeRDP/client/SDL/SDL3/dialogs/font/README.txt new file mode 100644 index 0000000..2548322 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/font/README.txt @@ -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 aren’t 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. diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/res/CMakeLists.txt b/third_party/FreeRDP/client/SDL/SDL3/dialogs/res/CMakeLists.txt new file mode 100644 index 0000000..370ee1e --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/res/CMakeLists.txt @@ -0,0 +1,31 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP SDL Client +# +# Copyright 2024 Armin Novak +# 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) diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/res/sdl3_resource_manager.cpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/res/sdl3_resource_manager.cpp new file mode 100644 index 0000000..0f1bd46 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/res/sdl3_resource_manager.cpp @@ -0,0 +1,43 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "sdl3_resource_manager.hpp" +#include +#if __has_include() +#include +namespace fs = std::filesystem; +#elif __has_include() +#include +namespace fs = std::experimental::filesystem; +#else +#error Could not find system header "" or "" +#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"); +} diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/res/sdl3_resource_manager.hpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/res/sdl3_resource_manager.hpp new file mode 100644 index 0000000..93970ae --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/res/sdl3_resource_manager.hpp @@ -0,0 +1,38 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include +#include + +#include + +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); +}; diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_blend_mode_guard.cpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_blend_mode_guard.cpp new file mode 100644 index 0000000..17ee589 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_blend_mode_guard.cpp @@ -0,0 +1,45 @@ +#include "sdl_blend_mode_guard.hpp" + +SdlBlendModeGuard::SdlBlendModeGuard(const std::shared_ptr& 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()); +} diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_blend_mode_guard.hpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_blend_mode_guard.hpp new file mode 100644 index 0000000..2057e7e --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_blend_mode_guard.hpp @@ -0,0 +1,46 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2025 Armin Novak + * 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 + +#include + +class SdlBlendModeGuard +{ + public: + explicit SdlBlendModeGuard(const std::shared_ptr& 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 _renderer; +}; diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_button.cpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_button.cpp new file mode 100644 index 0000000..da33c94 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_button.cpp @@ -0,0 +1,45 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client Channels + * + * Copyright 2023 Armin Novak + * 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 +#include + +#include "sdl_button.hpp" + +SdlButton::SdlButton(std::shared_ptr& 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; +} diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_button.hpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_button.hpp new file mode 100644 index 0000000..febbf9f --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_button.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +#include "sdl_selectable_widget.hpp" + +class SdlButton : public SdlSelectableWidget +{ + public: + SdlButton(std::shared_ptr& 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; +}; diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_buttons.cpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_buttons.cpp new file mode 100644 index 0000000..e586c5f --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_buttons.cpp @@ -0,0 +1,108 @@ +#include +#include + +#include "sdl_buttons.hpp" + +static const Uint32 hpadding = 10; + +SdlButtonList::~SdlButtonList() = default; + +bool SdlButtonList::populate(std::shared_ptr& renderer, + const std::vector& labels, const std::vector& 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(width) + hpadding) + hpadding; + size_t offsetX = static_cast(total_width) - + std::min(static_cast(total_width), button_width); + for (size_t x = 0; x < ids.size(); x++) + { + const size_t curOffsetX = offsetX + x * (static_cast(width) + hpadding); + const SDL_FRect rect = { static_cast(curOffsetX), static_cast(offsetY), + static_cast(width), static_cast(height) }; + auto button = std::make_shared(renderer, labels.at(x), ids.at(x), rect); + _list.emplace_back(button); + } + return true; +} + +std::shared_ptr 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 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; +} diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_buttons.hpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_buttons.hpp new file mode 100644 index 0000000..3d43c54 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_buttons.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include + +#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& renderer, + const std::vector& labels, const std::vector& ids, + Sint32 total_width, Sint32 offsetY, Sint32 width, Sint32 height); + + [[nodiscard]] bool update(); + [[nodiscard]] std::shared_ptr get_selected(const SDL_MouseButtonEvent& button); + [[nodiscard]] std::shared_ptr 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> _list; + std::shared_ptr _highlighted = nullptr; + size_t _highlight_index = 0; + std::shared_ptr _mouseover = nullptr; +}; diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_connection_dialog.cpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_connection_dialog.cpp new file mode 100644 index 0000000..8fd501c --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_connection_dialog.cpp @@ -0,0 +1,479 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include + +#include "sdl_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(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(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(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(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(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(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(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 buttonids = { 1 }; + const std::vector buttonlabels = { "cancel" }; + if (!_buttons.populate(_renderer, buttonlabels, buttonids, widget_width, + total_height - widget_height - vpadding, + static_cast(widget_width / 2), + static_cast(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) > 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(pvthis); + std::ignore = self->hide(); + self->_running = false; + return 0; +} diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_connection_dialog.hpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_connection_dialog.hpp new file mode 100644 index 0000000..051da48 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_connection_dialog.hpp @@ -0,0 +1,95 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +#include + +#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 _list; +}; diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_connection_dialog_hider.cpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_connection_dialog_hider.cpp new file mode 100644 index 0000000..e745527 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_connection_dialog_hider.cpp @@ -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); +} diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_connection_dialog_hider.hpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_connection_dialog_hider.hpp new file mode 100644 index 0000000..ad72792 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_connection_dialog_hider.hpp @@ -0,0 +1,39 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#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; +}; diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_connection_dialog_wrapper.cpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_connection_dialog_wrapper.cpp new file mode 100644 index 0000000..e04b0bf --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_connection_dialog_wrapper.cpp @@ -0,0 +1,287 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2025 Armin Novak + * 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 + +#include +#include +#include + +#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(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) + 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(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(); +} diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_connection_dialog_wrapper.hpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_connection_dialog_wrapper.hpp new file mode 100644 index 0000000..ed4665e --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_connection_dialog_wrapper.hpp @@ -0,0 +1,122 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2025 Armin Novak + * 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 +#include +#include +#include + +#include +#include +#include + +#include + +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 _connection_dialog; + std::queue _queue; + wLog* _log = nullptr; +}; diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_dialogs.cpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_dialogs.cpp new file mode 100644 index 0000000..e5ca434 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_dialogs.cpp @@ -0,0 +1,648 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include +#include + +#include + +#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(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 strlist; + std::vector 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(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(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 auth = { "Username: ", "Domain: ", + "Password: " }; + const std::vector authPin = { "Device: ", "PIN: " }; + const std::vector gw = { "GatewayUsername: ", "GatewayDomain: ", + "GatewayPassword: " }; + std::vector 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 result; + + if (!prompt.empty()) + { + std::vector initial{ args->user ? args->user : "Smartcard", "" }; + std::vector 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 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(); +} diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_dialogs.hpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_dialogs.hpp new file mode 100644 index 0000000..627b369 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_dialogs.hpp @@ -0,0 +1,59 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#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(); diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_input_widget.cpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_input_widget.cpp new file mode 100644 index 0000000..33b995e --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_input_widget.cpp @@ -0,0 +1,52 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2025 Armin Novak + * 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& 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& renderer, const SDL_FRect& rect, + SDL_IOStream* ops) + : SdlSelectableWidget(renderer, rect, ops) +{ + init(); +} +#endif + +SdlInputWidget::SdlInputWidget(SdlInputWidget&& other) noexcept = default; diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_input_widget.hpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_input_widget.hpp new file mode 100644 index 0000000..26235e7 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_input_widget.hpp @@ -0,0 +1,43 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2025 Armin Novak + * 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& renderer, const SDL_FRect& rect); + SdlInputWidget(std::shared_ptr& 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(); +}; diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_input_widget_pair.cpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_input_widget_pair.cpp new file mode 100644 index 0000000..887564b --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_input_widget_pair.cpp @@ -0,0 +1,129 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "sdl_input_widget_pair.hpp" + +#include +#include +#include +#include + +#include +#include + +#include "sdl_widget.hpp" +#include "sdl_button.hpp" +#include "sdl_buttons.hpp" + +SdlInputWidgetPair::SdlInputWidgetPair(std::shared_ptr& 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(offset * (height + _vpadding)), + static_cast(width), static_cast(height) }), + _input(renderer, { static_cast(width + _hpadding), + static_cast(offset * (height + _vpadding)), + static_cast(width), static_cast(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(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); +} diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_input_widget_pair.hpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_input_widget_pair.hpp new file mode 100644 index 0000000..892fdb9 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_input_widget_pair.hpp @@ -0,0 +1,73 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include +#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& 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; +}; diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_input_widget_pair_list.cpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_input_widget_pair_list.cpp new file mode 100644 index 0000000..8597f48 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_input_widget_pair_list.cpp @@ -0,0 +1,313 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2025 Armin Novak + * 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 +#include + +#include + +#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& labels, + const std::vector& initial, + const std::vector& flags, ssize_t selected) +{ + assert(labels.size() == initial.size()); + assert(labels.size() == flags.size()); + const std::vector buttonids = { INPUT_BUTTON_ACCEPT, INPUT_BUTTON_CANCEL }; + const std::vector 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(_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(input_height), + static_cast(widget_width), static_cast(widget_heigth)); + _buttons.set_highlight(0); + m_currentActiveTextInput = selected; + } +} + +ssize_t SdlInputWidgetPairList::next(ssize_t current) +{ + size_t iteration = 0; + auto val = static_cast(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(val))); + return static_cast(val); +} + +bool SdlInputWidgetPairList::valid(ssize_t current) const +{ + if (current < 0) + return false; + auto s = static_cast(current); + if (s >= m_list.size()) + return false; + return !m_list.at(s)->readonly(); +} + +std::shared_ptr SdlInputWidgetPairList::get(ssize_t index) +{ + if (index < 0) + return nullptr; + auto s = static_cast(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& 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(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; +} diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_input_widget_pair_list.hpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_input_widget_pair_list.hpp new file mode 100644 index 0000000..5de4762 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_input_widget_pair_list.hpp @@ -0,0 +1,63 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2025 Armin Novak + * 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 +#include +#include + +#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& labels, + const std::vector& initial, + const std::vector& 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& 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 get(ssize_t index); + + std::vector> m_list; + ssize_t m_currentActiveTextInput = -1; +}; diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_select.cpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_select.cpp new file mode 100644 index 0000000..c295a85 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_select.cpp @@ -0,0 +1,45 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +#include +#include + +#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& 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; diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_select.hpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_select.hpp new file mode 100644 index 0000000..4870198 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_select.hpp @@ -0,0 +1,39 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include +#include "sdl_selectable_widget.hpp" + +class SdlSelectWidget : public SdlSelectableWidget +{ + public: + SdlSelectWidget(std::shared_ptr& 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; +}; diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_select_list.cpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_select_list.cpp new file mode 100644 index 0000000..d58bf80 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_select_list.cpp @@ -0,0 +1,200 @@ +#include +#include +#include "sdl_select_list.hpp" +#include "../sdl_utils.hpp" + +static const Uint32 vpadding = 5; + +SdlSelectList::SdlSelectList(const std::string& title, const std::vector& 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 buttonids = { INPUT_BUTTON_ACCEPT, INPUT_BUTTON_CANCEL }; + const std::vector buttonlabels = { "accept", "cancel" }; + std::ignore = _buttons.populate( + _renderer, buttonlabels, buttonids, widget_width, static_cast(total_height), + static_cast(widget_width / 2), static_cast(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(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(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); + } +} diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_select_list.hpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_select_list.hpp new file mode 100644 index 0000000..53d406f --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_select_list.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include +#include + +#include + +#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& 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 _list; +}; diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_selectable_widget.cpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_selectable_widget.cpp new file mode 100644 index 0000000..12a95a9 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_selectable_widget.cpp @@ -0,0 +1,68 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2025 Armin Novak + * 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& renderer, + const SDL_FRect& rect) + : SdlWidget(renderer, rect) +{ +} + +#if defined(WITH_SDL_IMAGE_DIALOGS) +SdlSelectableWidget::SdlSelectableWidget(std::shared_ptr& 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 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(); +} diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_selectable_widget.hpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_selectable_widget.hpp new file mode 100644 index 0000000..f9706b4 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_selectable_widget.hpp @@ -0,0 +1,50 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2025 Armin Novak + * 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& renderer, const SDL_FRect& rect); +#if defined(WITH_SDL_IMAGE_DIALOGS) + SdlSelectableWidget(std::shared_ptr& 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; +}; diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_widget.cpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_widget.cpp new file mode 100644 index 0000000..843f7a0 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_widget.cpp @@ -0,0 +1,332 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include +#include + +#include "sdl_widget.hpp" +#include "sdl_blend_mode_guard.hpp" +#include "../sdl_utils.hpp" + +#include "res/sdl3_resource_manager.hpp" + +#include + +#if defined(WITH_SDL_IMAGE_DIALOGS) +#include +#endif + +#define TAG CLIENT_TAG("SDL.widget") + +static const Uint32 hpadding = 10; + +SdlWidget::SdlWidget(std::shared_ptr& 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_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& 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(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 SdlWidget::render_text(const std::string& text, SDL_Color fgcolor, + SDL_FRect& src, SDL_FRect& dst) const +{ + auto surface = std::shared_ptr( + 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_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 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(w); + src.h = static_cast(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 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( + TTF_RenderText_Blended_Wrapped(_font.get(), text.c_str(), 0, fgcolor, + static_cast(_text_width)), + SDL_DestroySurface); + if (!surface) + { + widget_log_error(false, "TTF_RenderText_Blended"); + return nullptr; + } + + src.w = static_cast(surface->w); + src.h = static_cast(surface->h); + + auto texture = std::shared_ptr( + 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(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 colors = { color }; + return fill(colors); +} + +bool SdlWidget::fill(const std::vector& 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 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(w); + src.h = static_cast(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"); +} diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_widget.hpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_widget.hpp new file mode 100644 index 0000000..1e13326 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_widget.hpp @@ -0,0 +1,100 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client helper dialogs + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) +#include +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& renderer, const SDL_FRect& rect); +#if defined(WITH_SDL_IMAGE_DIALOGS) + SdlWidget(std::shared_ptr& 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& 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 _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 + render_text(const std::string& text, SDL_Color fgcolor, SDL_FRect& src, SDL_FRect& dst) const; + [[nodiscard]] std::shared_ptr render_text_wrapped(const std::string& text, + SDL_Color fgcolor, + SDL_FRect& src, + SDL_FRect& dst) const; + + std::shared_ptr _font = nullptr; + std::shared_ptr _image = nullptr; + std::shared_ptr _engine = nullptr; + SDL_FRect _rect = {}; + bool _wrap = false; + size_t _text_width = 0; +}; diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_widget_list.cpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_widget_list.cpp new file mode 100644 index 0000000..e7be4b6 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_widget_list.cpp @@ -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(renderer, SDL_DestroyRenderer); + _window = std::shared_ptr(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; +} diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_widget_list.hpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_widget_list.hpp new file mode 100644 index 0000000..ab3a582 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/sdl_widget_list.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include +#include + +#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 _window; + std::shared_ptr _renderer; + SdlButtonList _buttons; + SDL_Color _backgroundcolor{ 0x38, 0x36, 0x35, 0xff }; +}; diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/test/CMakeLists.txt b/third_party/FreeRDP/client/SDL/SDL3/dialogs/test/CMakeLists.txt new file mode 100644 index 0000000..0130efa --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/test/CMakeLists.txt @@ -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() diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/test/TestSDLDialogInput.cpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/test/TestSDLDialogInput.cpp new file mode 100644 index 0000000..28ad4a1 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/test/TestSDLDialogInput.cpp @@ -0,0 +1,59 @@ +#include "../sdl_dialogs.hpp" +#include "../sdl_input_widget_pair_list.hpp" + +#include +#include + +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 result; + + std::vector initial{ "Smartcard", "abc", "def" }; + std::vector flags = { SdlInputWidgetPair::SDL_INPUT_READONLY, + SdlInputWidgetPair::SDL_INPUT_MASK, 0 }; + + const std::vector& 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); +} diff --git a/third_party/FreeRDP/client/SDL/SDL3/dialogs/test/TestSDLDialogSelectList.cpp b/third_party/FreeRDP/client/SDL/SDL3/dialogs/test/TestSDLDialogSelectList.cpp new file mode 100644 index 0000000..206c2da --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/dialogs/test/TestSDLDialogSelectList.cpp @@ -0,0 +1,47 @@ +#include "../sdl_dialogs.hpp" +#include "../sdl_select_list.hpp" + +#include +#include + +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 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); +} diff --git a/third_party/FreeRDP/client/SDL/SDL3/man/CMakeLists.txt b/third_party/FreeRDP/client/SDL/SDL3/man/CMakeLists.txt new file mode 100644 index 0000000..22756aa --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/man/CMakeLists.txt @@ -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}") diff --git a/third_party/FreeRDP/client/SDL/SDL3/man/sdl3-freerdp.1.in b/third_party/FreeRDP/client/SDL/SDL3/man/sdl3-freerdp.1.in new file mode 100644 index 0000000..6fb12b2 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/man/sdl3-freerdp.1.in @@ -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)\&. diff --git a/third_party/FreeRDP/client/SDL/SDL3/sdl_channels.cpp b/third_party/FreeRDP/client/SDL/SDL3/sdl_channels.cpp new file mode 100644 index 0000000..210d3b1 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/sdl_channels.cpp @@ -0,0 +1,93 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client Channels + * + * Copyright 2022 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include +#include +#include + +#include "sdl_channels.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(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(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(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(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); +} diff --git a/third_party/FreeRDP/client/SDL/SDL3/sdl_channels.hpp b/third_party/FreeRDP/client/SDL/SDL3/sdl_channels.hpp new file mode 100644 index 0000000..19800ca --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/sdl_channels.hpp @@ -0,0 +1,26 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client Channels + * + * Copyright 2022 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +void sdl_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e); +void sdl_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e); diff --git a/third_party/FreeRDP/client/SDL/SDL3/sdl_clip.cpp b/third_party/FreeRDP/client/SDL/SDL3/sdl_clip.cpp new file mode 100644 index 0000000..248ca0f --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/sdl_clip.cpp @@ -0,0 +1,1008 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client keyboard helper + * + * Copyright 2024 Armin Novak + * 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. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "sdl_clip.hpp" +#include "sdl_context.hpp" + +#define TAG CLIENT_TAG("sdl.cliprdr") + +#define mime_text_plain "text/plain" +// NOLINTNEXTLINE(bugprone-suspicious-missing-comma) +const char mime_text_utf8[] = mime_text_plain ";charset=utf-8"; + +[[nodiscard]] static const std::vector& s_mime_text() +{ + static std::vector values; + if (values.empty()) + { + values = std::vector( + { mime_text_plain, mime_text_utf8, "UTF8_STRING", "COMPOUND_TEXT", "TEXT", "STRING" }); + } + return values; +} + +static const char s_mime_png[] = "image/png"; +static const char s_mime_webp[] = "image/webp"; +static const char s_mime_jpg[] = "image/jpeg"; + +static const char s_mime_tiff[] = "image/tiff"; +static const char s_mime_uri_list[] = "text/uri-list"; +static const char s_mime_html[] = "text/html"; + +#define BMP_MIME_LIST "image/bmp", "image/x-bmp", "image/x-MS-bmp", "image/x-win-bitmap" + +[[nodiscard]] static const std::vector& s_mime_bitmap() +{ + static std::vector values; + if (values.empty()) + { + values = std::vector({ BMP_MIME_LIST }); + } + return values; +} + +[[nodiscard]] static const std::vector& s_mime_image() +{ + static std::vector values; + if (values.empty()) + { + if (winpr_image_format_is_supported(WINPR_IMAGE_WEBP)) + values.push_back(s_mime_webp); + + if (winpr_image_format_is_supported(WINPR_IMAGE_PNG)) + values.push_back(s_mime_png); + + if (winpr_image_format_is_supported(WINPR_IMAGE_JPEG)) + values.push_back(s_mime_jpg); + + auto bmp = std::vector({ s_mime_tiff, BMP_MIME_LIST }); + values.insert(values.end(), bmp.begin(), bmp.end()); + } + return values; +} + +static const char s_mime_gnome_copied_files[] = "x-special/gnome-copied-files"; +static const char s_mime_mate_copied_files[] = "x-special/mate-copied-files"; + +static const char s_mime_freerdp_update[] = "x-special/freerdp-clipboard-update"; + +static const char* s_type_HtmlFormat = "HTML Format"; +static const char* s_type_FileGroupDescriptorW = "FileGroupDescriptorW"; + +class ClipboardLockGuard +{ + public: + explicit ClipboardLockGuard(wClipboard* clipboard) : _clipboard(clipboard) + { + ClipboardLock(_clipboard); + } + ClipboardLockGuard(const ClipboardLockGuard& other) = delete; + ClipboardLockGuard(ClipboardLockGuard&& other) = delete; + + ClipboardLockGuard& operator=(const ClipboardLockGuard& rhs) = delete; + ClipboardLockGuard& operator=(ClipboardLockGuard&& rhs) = delete; + + ~ClipboardLockGuard() + { + ClipboardUnlock(_clipboard); + } + + private: + wClipboard* _clipboard; +}; + +static bool operator<(const CLIPRDR_FORMAT& lhs, const CLIPRDR_FORMAT& rhs) +{ + return (lhs.formatId < rhs.formatId); +} + +static bool operator==(const CLIPRDR_FORMAT& lhs, const CLIPRDR_FORMAT& rhs) +{ + return (lhs.formatId == rhs.formatId); +} + +sdlClip::sdlClip(SdlContext* sdl) + : _sdl(sdl), _file(cliprdr_file_context_new(this)), _log(WLog_Get(TAG)), + _system(ClipboardCreate()), _event(CreateEventA(nullptr, TRUE, FALSE, nullptr)), + _uuid(sdl::utils::generate_uuid_v4()) +{ + WINPR_ASSERT(sdl); + + std::stringstream ss; + ss << s_mime_freerdp_update << "-" << _uuid; + _mime_uuid = ss.str(); +} + +sdlClip::~sdlClip() +{ + cliprdr_file_context_free(_file); + ClipboardDestroy(_system); + std::ignore = CloseHandle(_event); +} + +bool sdlClip::init(CliprdrClientContext* clip) +{ + WINPR_ASSERT(clip); + _ctx = clip; + clip->custom = this; + _ctx->MonitorReady = sdlClip::MonitorReady; + _ctx->ServerCapabilities = sdlClip::ReceiveServerCapabilities; + _ctx->ServerFormatList = sdlClip::ReceiveServerFormatList; + _ctx->ServerFormatListResponse = sdlClip::ReceiveFormatListResponse; + _ctx->ServerFormatDataRequest = sdlClip::ReceiveFormatDataRequest; + _ctx->ServerFormatDataResponse = sdlClip::ReceiveFormatDataResponse; + + return cliprdr_file_context_init(_file, _ctx); +} + +bool sdlClip::uninit(CliprdrClientContext* clip) +{ + WINPR_ASSERT(clip); + if (!cliprdr_file_context_uninit(_file, _ctx)) + return false; + _ctx = nullptr; + clip->custom = nullptr; + return true; +} + +bool sdlClip::contains(const char** mime_types, Sint32 count) +{ + for (Sint32 x = 0; x < count; x++) + { + const auto mime = mime_types[x]; + if (mime && (strcmp(_mime_uuid.c_str(), mime) == 0)) + return true; + } + return false; +} + +bool sdlClip::handleEvent(const SDL_ClipboardEvent& ev) +{ + if (!_ctx || !_sync || ev.owner) + { + _last_timestamp = ev.timestamp; + if (!_current_mimetypes.empty()) + { + _cache_data.clear(); + auto rc = + SDL_SetClipboardData(sdlClip::ClipDataCb, sdlClip::ClipCleanCb, this, ev.mime_types, + WINPR_ASSERTING_INT_CAST(size_t, ev.num_mime_types)); + _current_mimetypes.clear(); + return rc; + } + return true; + } + + if (ev.timestamp == _last_timestamp) + { + return true; + } + + if (contains(ev.mime_types, ev.num_mime_types)) + { + return true; + } + + clearServerFormats(); + + std::string mime_html = s_mime_html; + + std::vector mime_bitmap = { BMP_MIME_LIST }; + std::string mime_webp = s_mime_webp; + std::string mime_png = s_mime_png; + std::string mime_jpeg = s_mime_jpg; + std::string mime_tiff = s_mime_tiff; + std::vector mime_images = { mime_webp, mime_png, mime_jpeg, mime_tiff }; + + std::vector clientFormatNames; + std::vector clientFormats; + + size_t nformats = WINPR_ASSERTING_INT_CAST(size_t, ev.num_mime_types); + const char** clipboard_mime_formats = ev.mime_types; + + WLog_Print(_log, WLOG_TRACE, "SDL has %" PRIuz " formats", nformats); + + bool textPushed = false; + bool imgPushed = false; + + for (size_t i = 0; i < nformats; i++) + { + std::string local_mime = clipboard_mime_formats[i]; + WLog_Print(_log, WLOG_TRACE, " - %s", local_mime.c_str()); + + if (std::find(s_mime_text().begin(), s_mime_text().end(), local_mime) != + s_mime_text().end()) + { + /* text formats */ + if (!textPushed) + { + clientFormats.push_back({ CF_TEXT, nullptr }); + clientFormats.push_back({ CF_OEMTEXT, nullptr }); + clientFormats.push_back({ CF_UNICODETEXT, nullptr }); + textPushed = true; + } + } + else if (local_mime == mime_html) + /* html */ + clientFormatNames.emplace_back(s_type_HtmlFormat); + else if ((std::find(mime_bitmap.begin(), mime_bitmap.end(), local_mime) != + mime_bitmap.end()) || + (std::find(mime_images.begin(), mime_images.end(), local_mime) != + mime_images.end())) + { + /* image formats */ + if (!imgPushed) + { + clientFormats.push_back({ CF_DIB, nullptr }); +#if defined(WINPR_UTILS_IMAGE_DIBv5) + clientFormats.push_back({ CF_DIBV5, nullptr }); +#endif + + for (auto& bmp : mime_bitmap) + clientFormatNames.push_back(bmp); + + for (auto& img : mime_images) + clientFormatNames.push_back(img); + + clientFormatNames.emplace_back(s_type_HtmlFormat); + imgPushed = true; + } + } + } + + for (auto& name : clientFormatNames) + { + clientFormats.push_back({ ClipboardRegisterFormat(_system, name.c_str()), name.data() }); + } + + std::sort(clientFormats.begin(), clientFormats.end(), + [](const auto& a, const auto& b) { return a < b; }); + auto u = std::unique(clientFormats.begin(), clientFormats.end()); + clientFormats.erase(u, clientFormats.end()); + + const CLIPRDR_FORMAT_LIST formatList = { + { CB_FORMAT_LIST, 0, 0 }, + static_cast(clientFormats.size()), + clientFormats.data(), + }; + + WLog_Print(_log, WLOG_TRACE, + "-------------- client format list [%" PRIu32 "] ------------------", + formatList.numFormats); + for (UINT32 x = 0; x < formatList.numFormats; x++) + { + auto format = &formatList.formats[x]; + WLog_Print(_log, WLOG_TRACE, "client announces %" PRIu32 " [%s][%s]", format->formatId, + ClipboardGetFormatIdString(format->formatId), format->formatName); + } + + WINPR_ASSERT(_ctx); + WINPR_ASSERT(_ctx->ClientFormatList); + return _ctx->ClientFormatList(_ctx, &formatList) == CHANNEL_RC_OK; +} + +UINT sdlClip::MonitorReady(CliprdrClientContext* context, const CLIPRDR_MONITOR_READY* monitorReady) +{ + WINPR_UNUSED(monitorReady); + WINPR_ASSERT(context); + WINPR_ASSERT(monitorReady); + + auto clipboard = static_cast( + cliprdr_file_context_get_context(static_cast(context->custom))); + WINPR_ASSERT(clipboard); + + auto ret = clipboard->SendClientCapabilities(); + if (ret != CHANNEL_RC_OK) + return ret; + + clipboard->_sync = true; + if (!sdl_push_user_event(SDL_EVENT_CLIPBOARD_UPDATE)) + return ERROR_INTERNAL_ERROR; + + return CHANNEL_RC_OK; +} + +UINT sdlClip::SendClientCapabilities() +{ + CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet = { + CB_CAPSTYPE_GENERAL, 12, CB_CAPS_VERSION_2, + CB_USE_LONG_FORMAT_NAMES | cliprdr_file_context_current_flags(_file) + }; + const CLIPRDR_CAPABILITIES capabilities = { + { CB_TYPE_NONE, 0, 0 }, 1, reinterpret_cast(&generalCapabilitySet) + }; + + WINPR_ASSERT(_ctx); + WINPR_ASSERT(_ctx->ClientCapabilities); + return _ctx->ClientCapabilities(_ctx, &capabilities); +} + +void sdlClip::clearServerFormats() +{ + _serverFormats.clear(); + _cache_data.clear(); + cliprdr_file_context_clear(_file); +} + +UINT sdlClip::SendFormatListResponse(BOOL status) +{ + const CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse = { + { CB_FORMAT_LIST_RESPONSE, static_cast(status ? CB_RESPONSE_OK : CB_RESPONSE_FAIL), + 0 } + }; + WINPR_ASSERT(_ctx); + WINPR_ASSERT(_ctx->ClientFormatListResponse); + return _ctx->ClientFormatListResponse(_ctx, &formatListResponse); +} + +UINT sdlClip::SendDataResponse(const BYTE* data, size_t size) +{ + CLIPRDR_FORMAT_DATA_RESPONSE response = {}; + + if (size > UINT32_MAX) + return ERROR_INVALID_PARAMETER; + + response.common.msgFlags = (data) ? CB_RESPONSE_OK : CB_RESPONSE_FAIL; + response.common.dataLen = static_cast(size); + response.requestedFormatData = data; + + WINPR_ASSERT(_ctx); + WINPR_ASSERT(_ctx->ClientFormatDataResponse); + return _ctx->ClientFormatDataResponse(_ctx, &response); +} + +UINT sdlClip::SendDataRequest(uint32_t formatID, const std::string& mime) +{ + const CLIPRDR_FORMAT_DATA_REQUEST request = { { CB_TYPE_NONE, 0, 0 }, formatID }; + + _request_queue.emplace(formatID, mime); + + WINPR_ASSERT(_ctx); + WINPR_ASSERT(_ctx->ClientFormatDataRequest); + UINT ret = _ctx->ClientFormatDataRequest(_ctx, &request); + if (ret != CHANNEL_RC_OK) + { + WLog_Print(_log, WLOG_ERROR, "error sending ClientFormatDataRequest, cancelling request"); + _request_queue.pop(); + } + + return ret; +} + +std::string sdlClip::getServerFormat(uint32_t id) +{ + for (auto& fmt : _serverFormats) + { + if (fmt.formatId() == id) + { + if (fmt.formatName()) + return fmt.formatName(); + break; + } + } + + return ""; +} + +uint32_t sdlClip::serverIdForMime(const std::string& mime) +{ + std::string cmp = mime; + if (mime_is_html(mime)) + cmp = s_type_HtmlFormat; + if (mime_is_file(mime)) + cmp = s_type_FileGroupDescriptorW; + + for (auto& format : _serverFormats) + { + if (!format.formatName()) + continue; + if (cmp == format.formatName()) + return format.formatId(); + } + + if (mime_is_image(mime)) + return CF_DIB; + if (mime_is_text(mime)) + return CF_UNICODETEXT; + + return 0; +} + +UINT sdlClip::ReceiveServerCapabilities(CliprdrClientContext* context, + const CLIPRDR_CAPABILITIES* capabilities) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(capabilities); + + auto capsPtr = reinterpret_cast(capabilities->capabilitySets); + WINPR_ASSERT(capsPtr); + + auto clipboard = static_cast( + cliprdr_file_context_get_context(static_cast(context->custom))); + WINPR_ASSERT(clipboard); + + if (!cliprdr_file_context_remote_set_flags(clipboard->_file, 0)) + return ERROR_INTERNAL_ERROR; + + for (UINT32 i = 0; i < capabilities->cCapabilitiesSets; i++) + { + auto caps = reinterpret_cast(capsPtr); + + if (caps->capabilitySetType == CB_CAPSTYPE_GENERAL) + { + auto generalCaps = reinterpret_cast(caps); + + if (!cliprdr_file_context_remote_set_flags(clipboard->_file, generalCaps->generalFlags)) + return ERROR_INTERNAL_ERROR; + } + + capsPtr += caps->capabilitySetLength; + } + + return CHANNEL_RC_OK; +} + +UINT sdlClip::ReceiveServerFormatList(CliprdrClientContext* context, + const CLIPRDR_FORMAT_LIST* formatList) +{ + BOOL html = FALSE; + BOOL text = FALSE; + BOOL image = FALSE; + BOOL file = FALSE; + + if (!context || !context->custom) + return ERROR_INVALID_PARAMETER; + + auto clipboard = static_cast( + cliprdr_file_context_get_context(static_cast(context->custom))); + WINPR_ASSERT(clipboard); + + clipboard->clearServerFormats(); + + for (UINT32 i = 0; i < formatList->numFormats; i++) + { + const CLIPRDR_FORMAT* format = &formatList->formats[i]; + + clipboard->_serverFormats.emplace_back(format->formatId, format->formatName); + + if (format->formatName) + { + if (strcmp(format->formatName, s_type_HtmlFormat) == 0) + { + text = TRUE; + html = TRUE; + } + else if (strcmp(format->formatName, s_type_FileGroupDescriptorW) == 0) + { + file = TRUE; + text = TRUE; + } + } + else + { + switch (format->formatId) + { + case CF_TEXT: + case CF_OEMTEXT: + case CF_UNICODETEXT: + text = TRUE; + break; + + case CF_DIB: + image = TRUE; + break; + + default: + break; + } + } + } + + clipboard->_current_mimetypes.clear(); + if (text) + { + clipboard->_current_mimetypes.insert(clipboard->_current_mimetypes.end(), + s_mime_text().begin(), s_mime_text().end()); + } + if (image) + { + clipboard->_current_mimetypes.insert(clipboard->_current_mimetypes.end(), + s_mime_bitmap().begin(), s_mime_bitmap().end()); + clipboard->_current_mimetypes.insert(clipboard->_current_mimetypes.end(), + s_mime_image().begin(), s_mime_image().end()); + } + if (html) + { + clipboard->_current_mimetypes.push_back(s_mime_html); + } + if (file) + { + clipboard->_current_mimetypes.push_back(s_mime_uri_list); + clipboard->_current_mimetypes.push_back(s_mime_gnome_copied_files); + clipboard->_current_mimetypes.push_back(s_mime_mate_copied_files); + } + clipboard->_current_mimetypes.push_back(clipboard->_mime_uuid.c_str()); + + auto s = clipboard->_current_mimetypes.size(); + SDL_Event ev = { SDL_EVENT_CLIPBOARD_UPDATE }; + ev.clipboard.owner = true; + ev.clipboard.timestamp = SDL_GetTicksNS(); + ev.clipboard.num_mime_types = WINPR_ASSERTING_INT_CAST(Sint32, s); + ev.clipboard.mime_types = clipboard->_current_mimetypes.data(); + + auto rc = (SDL_PushEvent(&ev) == 1); + return clipboard->SendFormatListResponse(rc); +} + +UINT sdlClip::ReceiveFormatListResponse(WINPR_ATTR_UNUSED CliprdrClientContext* context, + const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(formatListResponse); + + if (formatListResponse->common.msgFlags & CB_RESPONSE_FAIL) + WLog_WARN(TAG, "format list update failed"); + return CHANNEL_RC_OK; +} + +std::shared_ptr sdlClip::ReceiveFormatDataRequestHandle( + sdlClip* clipboard, const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest, uint32_t& len) +{ + const char* mime = nullptr; + UINT32 formatId = 0; + + BOOL res = FALSE; + + std::shared_ptr data; + + WINPR_ASSERT(clipboard); + WINPR_ASSERT(formatDataRequest); + + len = 0; + auto localFormatId = formatId = formatDataRequest->requestedFormatId; + WLog_Print(clipboard->_log, WLOG_DEBUG, "Requesting format %s [0x%08" PRIx32 "] [%s]", + ClipboardGetFormatIdString(localFormatId), localFormatId, + ClipboardGetFormatName(clipboard->_system, localFormatId)); + + ClipboardLockGuard systemlock(clipboard->_system); + std::scoped_lock lock(clipboard->_lock); + + const UINT32 fileFormatId = + ClipboardGetFormatId(clipboard->_system, s_type_FileGroupDescriptorW); + const UINT32 htmlFormatId = ClipboardGetFormatId(clipboard->_system, s_type_HtmlFormat); + + switch (formatId) + { + case CF_TEXT: + case CF_OEMTEXT: + case CF_UNICODETEXT: + localFormatId = ClipboardGetFormatId(clipboard->_system, mime_text_plain); + mime = mime_text_utf8; + break; + + case CF_DIB: + case CF_DIBV5: + mime = s_mime_bitmap().at(0); + localFormatId = ClipboardGetFormatId(clipboard->_system, mime); + break; + + case CF_TIFF: + mime = s_mime_tiff; + break; + + default: + if (formatId == fileFormatId) + { + localFormatId = ClipboardGetFormatId(clipboard->_system, s_mime_uri_list); + mime = s_mime_uri_list; + } + else if (formatId == htmlFormatId) + { + /* In case HTML format was requested but we only have images in local clipboard */ + if (!SDL_HasClipboardData(s_mime_html)) + { + for (const auto& cmime : s_mime_image()) + { + if (SDL_HasClipboardData(cmime)) + { + localFormatId = ClipboardGetFormatId(clipboard->_system, cmime); + mime = cmime; + break; + } + } + } + else + { + localFormatId = ClipboardGetFormatId(clipboard->_system, s_mime_html); + mime = s_mime_html; + } + } + else + return data; + } + + { + size_t size = 0; + auto sdldata = std::shared_ptr(SDL_GetClipboardData(mime, &size), SDL_free); + if (!sdldata) + return data; + + if (fileFormatId == formatId) + { + auto bdata = static_cast(sdldata.get()); + if (!cliprdr_file_context_update_client_data(clipboard->_file, bdata, size)) + return data; + } + + res = ClipboardSetData(clipboard->_system, localFormatId, sdldata.get(), + static_cast(size)); + } + + if (!res) + return data; + + uint32_t ptrlen = 0; + auto ptr = static_cast(ClipboardGetData(clipboard->_system, formatId, &ptrlen)); + data = std::shared_ptr(ptr, free); + + if (!data) + return data; + + if (fileFormatId == formatId) + { + BYTE* ddata = nullptr; + UINT32 dsize = 0; + const UINT32 flags = cliprdr_file_context_remote_get_flags(clipboard->_file); + const UINT32 error = cliprdr_serialize_file_list_ex( + flags, reinterpret_cast(data.get()), + ptrlen / sizeof(FILEDESCRIPTORW), &ddata, &dsize); + data.reset(); + auto tmp = std::shared_ptr(ddata, free); + if (error) + return data; + + data = tmp; + len = dsize; + } + else + len = ptrlen; + return data; +} + +UINT sdlClip::ReceiveFormatDataRequest(CliprdrClientContext* context, + const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(formatDataRequest); + + auto clipboard = static_cast( + cliprdr_file_context_get_context(static_cast(context->custom))); + WINPR_ASSERT(clipboard); + + uint32_t len = 0; + auto rc = ReceiveFormatDataRequestHandle(clipboard, formatDataRequest, len); + return clipboard->SendDataResponse(rc.get(), len); +} + +UINT sdlClip::ReceiveFormatDataResponse(CliprdrClientContext* context, + const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(formatDataResponse); + + const UINT32 size = formatDataResponse->common.dataLen; + const BYTE* data = formatDataResponse->requestedFormatData; + + auto clipboard = static_cast( + cliprdr_file_context_get_context(static_cast(context->custom))); + WINPR_ASSERT(clipboard); + + ClipboardLockGuard systemlock(clipboard->_system); + std::scoped_lock lock(clipboard->_lock); + if (clipboard->_request_queue.empty()) + { + WLog_Print(clipboard->_log, WLOG_ERROR, "no pending format request"); + return ERROR_INTERNAL_ERROR; + } + + do + { + UINT32 srcFormatId = 0; + auto& request = clipboard->_request_queue.front(); + bool success = (formatDataResponse->common.msgFlags & CB_RESPONSE_OK) && + !(formatDataResponse->common.msgFlags & CB_RESPONSE_FAIL); + request.setSuccess(success); + + if (!success) + { + WLog_Print(clipboard->_log, WLOG_WARN, + "clipboard data request for format %" PRIu32 " [%s], mime %s failed", + request.format(), request.formatstr().c_str(), request.mime().c_str()); + break; + } + + switch (request.format()) + { + case CF_TEXT: + case CF_OEMTEXT: + case CF_UNICODETEXT: + srcFormatId = request.format(); + break; + + case CF_DIB: + case CF_DIBV5: + srcFormatId = request.format(); + break; + + default: + { + auto name = clipboard->getServerFormat(request.format()); + if (!name.empty()) + { + if (name == s_type_FileGroupDescriptorW) + { + srcFormatId = + ClipboardGetFormatId(clipboard->_system, s_type_FileGroupDescriptorW); + + if (!cliprdr_file_context_update_server_data( + clipboard->_file, clipboard->_system, data, size)) + return ERROR_INTERNAL_ERROR; + } + else if (name == s_type_HtmlFormat) + { + srcFormatId = ClipboardGetFormatId(clipboard->_system, s_type_HtmlFormat); + } + } + } + break; + } + + if (!ClipboardSetData(clipboard->_system, srcFormatId, data, size)) + { + WLog_Print(clipboard->_log, WLOG_ERROR, "error when setting clipboard data"); + return ERROR_INTERNAL_ERROR; + } + WLog_Print(clipboard->_log, WLOG_DEBUG, "updated clipboard data %s [0x%08" PRIx32 "]", + ClipboardGetFormatName(clipboard->_system, srcFormatId), srcFormatId); + } while (false); + + if (!SetEvent(clipboard->_event)) + { + WLog_Print(clipboard->_log, WLOG_ERROR, "error when setting clipboard event"); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +const void* sdlClip::ClipDataCb(void* userdata, const char* mime_type, size_t* size) +{ + auto clip = static_cast(userdata); + WINPR_ASSERT(clip); + WINPR_ASSERT(size); + WINPR_ASSERT(mime_type); + + *size = 0; + uint32_t len = 0; + + if (mime_is_text(mime_type)) + mime_type = "text/plain"; + + { + ClipboardLockGuard systemlock(clip->_system); + std::scoped_lock lock(clip->_lock); + + /* check if we already used this mime type */ + auto cache = clip->_cache_data.find(mime_type); + if (cache != clip->_cache_data.end()) + { + *size = cache->second.size; + return cache->second.ptr.get(); + } + + auto formatID = clip->serverIdForMime(mime_type); + + /* Can we convert the data from existing formats in the clibpard? */ + uint32_t fsize = 0; + auto mimeFormatID = ClipboardRegisterFormat(clip->_system, mime_type); + auto fptr = ClipboardGetData(clip->_system, mimeFormatID, &fsize); + if (fptr) + { + auto ptr = std::shared_ptr(fptr, free); + clip->_cache_data.insert({ mime_type, { fsize, ptr } }); + + auto fcache = clip->_cache_data.find(mime_type); + if (fcache != clip->_cache_data.end()) + { + *size = fcache->second.size; + return fcache->second.ptr.get(); + } + } + + WLog_Print(clip->_log, WLOG_DEBUG, "requesting format %s [%s 0x%08" PRIx32 "]", mime_type, + ClipboardGetFormatName(clip->_system, formatID), formatID); + if (clip->SendDataRequest(formatID, mime_type)) + return nullptr; + } + { + HANDLE hdl[2] = { freerdp_abort_event(clip->_sdl->context()), clip->_event }; + + DWORD status = WaitForMultipleObjects(ARRAYSIZE(hdl), hdl, FALSE, 10 * 1000); + + if (status != WAIT_OBJECT_0 + 1) + { + std::scoped_lock lock(clip->_lock); + clip->_request_queue.pop(); + + if (status == WAIT_TIMEOUT) + WLog_Print(clip->_log, WLOG_ERROR, + "no reply in 10 seconds, returning empty content"); + + return nullptr; + } + } + + { + ClipboardLockGuard systemlock(clip->_system); + std::scoped_lock lock(clip->_lock); + auto request = clip->_request_queue.front(); + clip->_request_queue.pop(); + + if (clip->_request_queue.empty()) + std::ignore = ResetEvent(clip->_event); + + if (request.success()) + { + auto formatID = ClipboardRegisterFormat(clip->_system, mime_type); + auto data = ClipboardGetData(clip->_system, formatID, &len); + if (!data) + { + WLog_Print(clip->_log, WLOG_ERROR, "error retrieving clipboard data"); + return nullptr; + } + + auto ptr = std::shared_ptr(data, free); + clip->_cache_data.insert({ mime_type, { len, ptr } }); + *size = len; + return ptr.get(); + } + + return nullptr; + } +} + +void sdlClip::ClipCleanCb(void* userdata) +{ + auto clip = static_cast(userdata); + WINPR_ASSERT(clip); + ClipboardLockGuard give_me_a_name(clip->_system); + std::scoped_lock lock(clip->_lock); + ClipboardEmpty(clip->_system); +} + +bool sdlClip::mime_is_file(const std::string& mime) +{ + if (strncmp(s_mime_uri_list, mime.c_str(), sizeof(s_mime_uri_list)) == 0) + return true; + if (strncmp(s_mime_gnome_copied_files, mime.c_str(), sizeof(s_mime_gnome_copied_files)) == 0) + return true; + if (strncmp(s_mime_mate_copied_files, mime.c_str(), sizeof(s_mime_mate_copied_files)) == 0) + return true; + return false; +} + +bool sdlClip::mime_is_text(const std::string& mime) +{ + for (const auto& tmime : s_mime_text()) + { + assert(tmime != nullptr); + if (mime == tmime) + return true; + } + + return false; +} + +bool sdlClip::mime_is_image(const std::string& mime) +{ + for (const auto& imime : s_mime_image()) + { + assert(imime != nullptr); + if (mime == imime) + return true; + } + + return false; +} + +bool sdlClip::mime_is_bmp(const std::string& mime) +{ + for (const auto& imime : s_mime_bitmap()) + { + assert(imime != nullptr); + if (mime == imime) + return true; + } + + return false; +} + +bool sdlClip::mime_is_html(const std::string& mime) +{ + return mime.compare(s_mime_html) == 0; +} + +ClipRequest::ClipRequest(UINT32 format, const std::string& mime) + : _format(format), _mime(mime), _success(false) +{ +} + +uint32_t ClipRequest::format() const +{ + return _format; +} + +std::string ClipRequest::formatstr() const +{ + return ClipboardGetFormatIdString(_format); +} + +std::string ClipRequest::mime() const +{ + return _mime; +} + +bool ClipRequest::success() const +{ + return _success; +} + +void ClipRequest::setSuccess(bool status) +{ + _success = status; +} + +CliprdrFormat::CliprdrFormat(uint32_t formatID, const char* formatName) : _formatID(formatID) +{ + if (formatName) + _formatName = formatName; +} + +uint32_t CliprdrFormat::formatId() const +{ + return _formatID; +} + +const char* CliprdrFormat::formatName() const +{ + if (_formatName.empty()) + return nullptr; + return _formatName.c_str(); +} diff --git a/third_party/FreeRDP/client/SDL/SDL3/sdl_clip.hpp b/third_party/FreeRDP/client/SDL/SDL3/sdl_clip.hpp new file mode 100644 index 0000000..b753fe0 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/sdl_clip.hpp @@ -0,0 +1,160 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client keyboard helper + * + * Copyright 2024 Armin Novak + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 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 _sync = false; + HANDLE _event = nullptr; + Uint64 _last_timestamp = 0; + + std::vector _serverFormats; + CriticalSection _lock; + + std::queue _request_queue; + + struct cache_entry + { + cache_entry(size_t len, std::shared_ptr p) : size(len), ptr(std::move(p)) + { + } + + size_t size; + std::shared_ptr ptr; + }; + std::map _cache_data; + std::vector _current_mimetypes; + std::string _uuid; + std::string _mime_uuid; +}; diff --git a/third_party/FreeRDP/client/SDL/SDL3/sdl_config.hpp.in b/third_party/FreeRDP/client/SDL/SDL3/sdl_config.hpp.in new file mode 100644 index 0000000..97ee81a --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/sdl_config.hpp.in @@ -0,0 +1,30 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL config template + * + * Copyright 2025 Armin Novak + * 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"; diff --git a/third_party/FreeRDP/client/SDL/SDL3/sdl_context.cpp b/third_party/FreeRDP/client/SDL/SDL3/sdl_context.cpp new file mode 100644 index 0000000..b38537b --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/sdl_context.cpp @@ -0,0 +1,1651 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client + * + * Copyright 2022 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "sdl_context.hpp" +#include "sdl_config.hpp" +#include "sdl_channels.hpp" +#include "sdl_monitor.hpp" +#include "sdl_pointer.hpp" +#include "sdl_touch.hpp" + +#include +#include + +#include "dialogs/sdl_dialogs.hpp" + +#if defined(WITH_WEBVIEW) +#include +#endif + +SdlContext::SdlContext(rdpContext* context) + : _context(context), _log(WLog_Get(CLIENT_TAG("SDL"))), _rdpThreadRunning(false), + _primary(nullptr, SDL_DestroySurface), _disp(this), _input(this), _clip(this), _dialog(_log) +{ + WINPR_ASSERT(context); + setMetadata(); + + auto instance = _context->instance; + WINPR_ASSERT(instance); + + instance->PreConnect = preConnect; + instance->PostConnect = postConnect; + instance->PostDisconnect = postDisconnect; + instance->PostFinalDisconnect = postFinalDisconnect; + instance->AuthenticateEx = sdl_authenticate_ex; + instance->VerifyCertificateEx = sdl_verify_certificate_ex; + instance->VerifyChangedCertificateEx = sdl_verify_changed_certificate_ex; + instance->LogonErrorInfo = sdl_logon_error_info; + instance->PresentGatewayMessage = sdl_present_gateway_message; + instance->ChooseSmartcard = sdl_choose_smartcard; + instance->RetryDialog = sdl_retry_dialog; + +#ifdef WITH_WEBVIEW + instance->GetAccessToken = sdl_webview_get_access_token; +#else + instance->GetAccessToken = client_cli_get_access_token; +#endif + /* TODO: Client display set up */ +} + +void SdlContext::setHasCursor(bool val) +{ + this->_cursor_visible = val; +} + +bool SdlContext::hasCursor() const +{ + return _cursor_visible; +} + +void SdlContext::setMetadata() +{ + auto wmclass = freerdp_settings_get_string(_context->settings, FreeRDP_WmClass); + if (!wmclass || (strlen(wmclass) == 0)) + wmclass = SDL_CLIENT_UUID; + + SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_IDENTIFIER_STRING, wmclass); + SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING, SDL_CLIENT_NAME); + SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_VERSION_STRING, SDL_CLIENT_VERSION); + SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_CREATOR_STRING, SDL_CLIENT_VENDOR); + SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_COPYRIGHT_STRING, SDL_CLIENT_COPYRIGHT); + SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_URL_STRING, SDL_CLIENT_URL); + SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_TYPE_STRING, SDL_CLIENT_TYPE); +} + +int SdlContext::start() +{ + _thread = std::thread(rdpThreadRun, this); + return 0; +} + +int SdlContext::join() +{ + /* We do not want to use freerdp_abort_connect_context here. + * It would change the exit code and we do not want that. */ + HANDLE event = freerdp_abort_event(context()); + if (!SetEvent(event)) + return -1; + + _thread.join(); + return 0; +} + +void SdlContext::cleanup() +{ + std::unique_lock lock(_critical); + _windows.clear(); + _dialog.destroy(); + _primary.reset(); +} + +bool SdlContext::shallAbort(bool ignoreDialogs) +{ + std::unique_lock lock(_critical); + if (freerdp_shall_disconnect_context(context())) + { + if (ignoreDialogs) + return true; + if (_rdpThreadRunning) + return false; + return !getDialog().isRunning(); + } + return false; +} + +/* Called before a connection is established. + * Set all configuration options to support and load channels here. */ +BOOL SdlContext::preConnect(freerdp* instance) +{ + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->context); + + auto sdl = get_context(instance->context); + + auto settings = instance->context->settings; + WINPR_ASSERT(settings); + + if (!freerdp_settings_set_bool(settings, FreeRDP_CertificateCallbackPreferPEM, TRUE)) + return FALSE; + + /* Optional OS identifier sent to server */ + if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMajorType, OSMAJORTYPE_UNIX)) + return FALSE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMinorType, OSMINORTYPE_NATIVE_SDL)) + return FALSE; + /* OrderSupport is initialized at this point. + * Only override it if you plan to implement custom order + * callbacks or deactivate certain features. */ + /* Register the channel listeners. + * They are required to set up / tear down channels if they are loaded. */ + if (PubSub_SubscribeChannelConnected(instance->context->pubSub, + sdl_OnChannelConnectedEventHandler) < 0) + return FALSE; + if (PubSub_SubscribeChannelDisconnected(instance->context->pubSub, + sdl_OnChannelDisconnectedEventHandler) < 0) + return FALSE; + + if (!freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly)) + { + UINT32 maxWidth = 0; + UINT32 maxHeight = 0; + + if (!sdl_detect_monitors(sdl, &maxWidth, &maxHeight)) + return FALSE; + + if ((maxWidth != 0) && (maxHeight != 0) && + !freerdp_settings_get_bool(settings, FreeRDP_SmartSizing)) + { + WLog_Print(sdl->getWLog(), WLOG_INFO, "Update size to %ux%u", maxWidth, maxHeight); + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, maxWidth)) + return FALSE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, maxHeight)) + return FALSE; + } + + /** + * If /f is specified in combination with /smart-sizing:widthxheight then + * we run the session in the /smart-sizing dimensions scaled to full screen + */ + + const uint32_t sw = freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingWidth); + const uint32_t sh = freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingHeight); + const BOOL sm = freerdp_settings_get_bool(settings, FreeRDP_SmartSizing); + if (sm && (sw > 0) && (sh > 0)) + { + const BOOL mm = freerdp_settings_get_bool(settings, FreeRDP_UseMultimon); + if (mm) + WLog_Print(sdl->getWLog(), WLOG_WARN, + "/smart-sizing and /multimon are currently not supported, ignoring " + "/smart-sizing!"); + else + { + sdl->_windowWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + sdl->_windowHeigth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, sw)) + return FALSE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, sh)) + return FALSE; + } + } + } + else + { + /* Check +auth-only has a username and password. */ + if (!freerdp_settings_get_string(settings, FreeRDP_Password)) + { + WLog_Print(sdl->getWLog(), WLOG_INFO, + "auth-only, but no password set. Please provide one."); + return FALSE; + } + + if (!freerdp_settings_set_bool(settings, FreeRDP_DeactivateClientDecoding, TRUE)) + return FALSE; + + WLog_Print(sdl->getWLog(), WLOG_INFO, "Authentication only. Don't connect SDL."); + } + + if (!sdl->getInputChannelContext().initialize()) + return FALSE; + + /* TODO: Any code your client requires */ + return TRUE; +} + +/* Called after a RDP connection was successfully established. + * Settings might have changed during negotiation of client / server feature + * support. + * + * Set up local framebuffers and paing callbacks. + * If required, register pointer callbacks to change the local mouse cursor + * when hovering over the RDP window + */ +BOOL SdlContext::postConnect(freerdp* instance) +{ + WINPR_ASSERT(instance); + + auto context = instance->context; + WINPR_ASSERT(context); + + auto sdl = get_context(context); + + // Retry was successful, discard dialog + sdl->getDialog().show(false); + + if (freerdp_settings_get_bool(context->settings, FreeRDP_AuthenticationOnly)) + { + /* Check +auth-only has a username and password. */ + if (!freerdp_settings_get_string(context->settings, FreeRDP_Password)) + { + WLog_Print(sdl->getWLog(), WLOG_INFO, + "auth-only, but no password set. Please provide one."); + return FALSE; + } + + WLog_Print(sdl->getWLog(), WLOG_INFO, "Authentication only. Don't connect to X."); + return TRUE; + } + + if (!sdl->waitForWindowsCreated()) + return FALSE; + + sdl->_sdlPixelFormat = SDL_PIXELFORMAT_BGRA32; + if (!gdi_init(instance, PIXEL_FORMAT_BGRA32)) + return FALSE; + + if (!sdl->createPrimary()) + return FALSE; + + if (!sdl_register_pointer(instance->context->graphics)) + return FALSE; + + WINPR_ASSERT(context->update); + + context->update->BeginPaint = beginPaint; + context->update->EndPaint = endPaint; + context->update->PlaySound = playSound; + context->update->DesktopResize = desktopResize; + context->update->SetKeyboardIndicators = sdlInput::keyboard_set_indicators; + context->update->SetKeyboardImeStatus = sdlInput::keyboard_set_ime_status; + + if (!sdl->setResizeable(false)) + return FALSE; + if (!sdl->setFullscreen(freerdp_settings_get_bool(context->settings, FreeRDP_Fullscreen) || + freerdp_settings_get_bool(context->settings, FreeRDP_UseMultimon), + true)) + return FALSE; + sdl->setConnected(true); + return TRUE; +} + +/* This function is called whether a session ends by failure or success. + * Clean up everything allocated by pre_connect and post_connect. + */ +void SdlContext::postDisconnect(freerdp* instance) +{ + if (!instance) + return; + + if (!instance->context) + return; + + auto sdl = get_context(instance->context); + sdl->setConnected(false); + + gdi_free(instance); +} + +void SdlContext::postFinalDisconnect(freerdp* instance) +{ + if (!instance) + return; + + if (!instance->context) + return; + + PubSub_UnsubscribeChannelConnected(instance->context->pubSub, + sdl_OnChannelConnectedEventHandler); + PubSub_UnsubscribeChannelDisconnected(instance->context->pubSub, + sdl_OnChannelDisconnectedEventHandler); +} + +/* Create a SDL surface from the GDI buffer */ +bool SdlContext::createPrimary() +{ + auto gdi = context()->gdi; + WINPR_ASSERT(gdi); + + _primary = SDLSurfacePtr( + SDL_CreateSurfaceFrom(static_cast(gdi->width), static_cast(gdi->height), + pixelFormat(), gdi->primary_buffer, static_cast(gdi->stride)), + SDL_DestroySurface); + if (!_primary) + return false; + + SDL_SetSurfaceBlendMode(_primary.get(), SDL_BLENDMODE_NONE); + SDL_Rect surfaceRect = { 0, 0, gdi->width, gdi->height }; + SDL_FillSurfaceRect(_primary.get(), &surfaceRect, + SDL_MapSurfaceRGBA(_primary.get(), 0, 0, 0, 0xff)); + + return true; +} + +bool SdlContext::createWindows() +{ + auto settings = context()->settings; + const auto& title = windowTitle(); + + ScopeGuard guard1([&]() { _windowsCreatedEvent.set(); }); + + UINT32 windowCount = freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount); + + Sint32 originX = 0; + Sint32 originY = 0; + for (UINT32 x = 0; x < windowCount; x++) + { + auto id = monitorId(x); + if (id < 0) + return false; + + auto monitor = static_cast( + freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorDefArray, x)); + + originX = std::min(monitor->x, originX); + originY = std::min(monitor->y, originY); + } + + for (UINT32 x = 0; x < windowCount; x++) + { + auto id = monitorId(x); + if (id < 0) + return false; + + auto monitor = static_cast( + freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorDefArray, x)); + + Uint32 w = WINPR_ASSERTING_INT_CAST(Uint32, monitor->width); + Uint32 h = WINPR_ASSERTING_INT_CAST(Uint32, monitor->height); + if (!(freerdp_settings_get_bool(settings, FreeRDP_UseMultimon) || + freerdp_settings_get_bool(settings, FreeRDP_Fullscreen))) + { + if (_windowWidth > 0) + w = _windowWidth; + else + w = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + + if (_windowHeigth > 0) + h = _windowHeigth; + else + h = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + } + + Uint32 flags = SDL_WINDOW_HIGH_PIXEL_DENSITY; + + if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen) && + !freerdp_settings_get_bool(settings, FreeRDP_UseMultimon)) + { + flags |= SDL_WINDOW_FULLSCREEN; + } + + if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon)) + { + flags |= SDL_WINDOW_BORDERLESS; + } + + if (!freerdp_settings_get_bool(settings, FreeRDP_Decorations)) + flags |= SDL_WINDOW_BORDERLESS; + + auto did = WINPR_ASSERTING_INT_CAST(SDL_DisplayID, id); + auto window = SdlWindow::create(did, title, flags, w, h); + + if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon)) + { + window.setOffsetX(originX - monitor->x); + window.setOffsetY(originY - monitor->y); + } + + _windows.insert({ window.id(), std::move(window) }); + } + + return true; +} + +bool SdlContext::updateWindowList() +{ + std::vector list; + list.reserve(_windows.size()); + for (const auto& win : _windows) + list.push_back(win.second.monitor(_windows.size() == 1)); + + return freerdp_settings_set_monitor_def_array_sorted(context()->settings, list.data(), + list.size()); +} + +bool SdlContext::updateWindow(SDL_WindowID id) +{ + if (freerdp_settings_get_bool(_context->settings, FreeRDP_Fullscreen) || + freerdp_settings_get_bool(_context->settings, FreeRDP_UseMultimon)) + return true; + + auto& w = _windows.at(id); + auto m = w.monitor(true); + auto r = w.rect(); + m.width = r.w; + m.height = r.h; + m.attributes.physicalWidth = static_cast(r.w); + m.attributes.physicalHeight = static_cast(r.h); + w.setMonitor(m); + return true; +} + +std::string SdlContext::windowTitle() const +{ + const char* prefix = "FreeRDP:"; + + const auto windowTitle = freerdp_settings_get_string(context()->settings, FreeRDP_WindowTitle); + if (windowTitle) + return windowTitle; + + const auto name = freerdp_settings_get_server_name(context()->settings); + const auto port = freerdp_settings_get_uint32(context()->settings, FreeRDP_ServerPort); + const auto addPort = (port != 3389); + + std::stringstream ss; + ss << prefix << " " << name; + + if (addPort) + ss << ":" << port; + + return ss.str(); +} + +bool SdlContext::waitForWindowsCreated() +{ + { + std::unique_lock lock(_critical); + _windowsCreatedEvent.clear(); + if (!sdl_push_user_event(SDL_EVENT_USER_CREATE_WINDOWS, this)) + return false; + } + + HANDLE handles[] = { _windowsCreatedEvent.handle(), freerdp_abort_event(context()) }; + + const DWORD rc = WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, INFINITE); + switch (rc) + { + case WAIT_OBJECT_0: + return true; + default: + return false; + } +} + +/* This function is called when the library completed composing a new + * frame. Read out the changed areas and blit them to your output device. + * The image buffer will have the format specified by gdi_init + */ +BOOL SdlContext::endPaint(rdpContext* context) +{ + auto sdl = get_context(context); + WINPR_ASSERT(sdl); + + auto gdi = context->gdi; + WINPR_ASSERT(gdi); + WINPR_ASSERT(gdi->primary); + + HGDI_DC hdc = gdi->primary->hdc; + WINPR_ASSERT(hdc); + if (!hdc->hwnd) + return TRUE; + + HGDI_WND hwnd = hdc->hwnd; + WINPR_ASSERT(hwnd->invalid || (hwnd->ninvalid == 0)); + + if (hwnd->invalid->null) + return TRUE; + + WINPR_ASSERT(hwnd->invalid); + if (gdi->suppressOutput || hwnd->invalid->null) + return TRUE; + + const INT32 ninvalid = hwnd->ninvalid; + const GDI_RGN* cinvalid = hwnd->cinvalid; + + if (ninvalid < 1) + return TRUE; + + std::vector rects; + for (INT32 x = 0; x < ninvalid; x++) + { + auto& rgn = cinvalid[x]; + rects.push_back({ rgn.x, rgn.y, rgn.w, rgn.h }); + } + + sdl->push(std::move(rects)); + return sdl_push_user_event(SDL_EVENT_USER_UPDATE); +} + +void SdlContext::sdl_client_cleanup(int exit_code, const std::string& error_msg) +{ + rdpSettings* settings = context()->settings; + WINPR_ASSERT(settings); + + _rdpThreadRunning = false; + bool showError = false; + if (freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly)) + WLog_Print(getWLog(), WLOG_INFO, "Authentication only, exit status %s [%" PRId32 "]", + sdl::error::exitCodeToTag(exit_code), exit_code); + else + { + switch (exit_code) + { + case sdl::error::SUCCESS: + case sdl::error::DISCONNECT: + case sdl::error::LOGOFF: + case sdl::error::DISCONNECT_BY_USER: + case sdl::error::CONNECT_CANCELLED: + break; + default: + { + getDialog().showError(error_msg); + } + break; + } + } + + if (!showError) + getDialog().show(false); + + _exitCode = exit_code; + std::ignore = sdl_push_user_event(SDL_EVENT_USER_QUIT); + SDL_CleanupTLS(); +} + +int SdlContext::sdl_client_thread_connect(std::string& error_msg) +{ + auto instance = context()->instance; + WINPR_ASSERT(instance); + + _rdpThreadRunning = true; + BOOL rc = freerdp_connect(instance); + + rdpSettings* settings = context()->settings; + WINPR_ASSERT(settings); + + int exit_code = sdl::error::SUCCESS; + if (!rc) + { + UINT32 error = freerdp_get_last_error(context()); + exit_code = sdl::error::errorToExitCode(error); + } + + if (freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly)) + { + DWORD code = freerdp_get_last_error(context()); + freerdp_abort_connect_context(context()); + WLog_Print(getWLog(), WLOG_ERROR, "Authentication only, %s [0x%08" PRIx32 "] %s", + freerdp_get_last_error_name(code), code, freerdp_get_last_error_string(code)); + return exit_code; + } + + if (!rc) + { + DWORD code = freerdp_error_info(instance); + if (exit_code == sdl::error::SUCCESS) + { + char* msg = nullptr; + size_t len = 0; + exit_code = error_info_to_error(&code, &msg, &len); + if (msg) + error_msg = msg; + free(msg); + } + + auto last = freerdp_get_last_error(context()); + if (error_msg.empty()) + { + char* msg = nullptr; + size_t len = 0; + winpr_asprintf(&msg, &len, "%s [0x%08" PRIx32 "]\n%s", + freerdp_get_last_error_name(last), last, + freerdp_get_last_error_string(last)); + if (msg) + error_msg = msg; + free(msg); + } + + if (exit_code == sdl::error::SUCCESS) + { + if (last == FREERDP_ERROR_AUTHENTICATION_FAILED) + exit_code = sdl::error::AUTH_FAILURE; + else if (code == ERRINFO_SUCCESS) + exit_code = sdl::error::CONN_FAILED; + } + + getDialog().show(false); + } + + return exit_code; +} + +int SdlContext::sdl_client_thread_run(std::string& error_msg) +{ + auto instance = context()->instance; + WINPR_ASSERT(instance); + + int exit_code = sdl::error::SUCCESS; + while (!freerdp_shall_disconnect_context(context())) + { + HANDLE handles[MAXIMUM_WAIT_OBJECTS] = {}; + /* + * win8 and server 2k12 seem to have some timing issue/race condition + * when a initial sync request is send to sync the keyboard indicators + * sending the sync event twice fixed this problem + */ + if (freerdp_focus_required(instance)) + { + auto ctx = get_context(context()); + WINPR_ASSERT(ctx); + + auto& input = ctx->getInputChannelContext(); + if (!input.keyboard_focus_in()) + break; + if (!input.keyboard_focus_in()) + break; + } + + const DWORD nCount = freerdp_get_event_handles(context(), handles, ARRAYSIZE(handles)); + + if (nCount == 0) + { + WLog_Print(getWLog(), WLOG_ERROR, "freerdp_get_event_handles failed"); + break; + } + + const DWORD status = WaitForMultipleObjects(nCount, handles, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + WLog_Print(getWLog(), WLOG_ERROR, "WaitForMultipleObjects WAIT_FAILED"); + break; + } + + if (!freerdp_check_event_handles(context())) + { + if (client_auto_reconnect(instance)) + { + // Retry was successful, discard dialog + getDialog().show(false); + continue; + } + else + { + /* + * Indicate an unsuccessful connection attempt if reconnect + * did not succeed and no other error was specified. + */ + if (freerdp_error_info(instance) == 0) + exit_code = sdl::error::CONN_FAILED; + } + + if (freerdp_get_last_error(context()) == FREERDP_ERROR_SUCCESS) + WLog_Print(getWLog(), WLOG_ERROR, "WaitForMultipleObjects failed with %" PRIu32 "", + status); + if (freerdp_get_last_error(context()) == FREERDP_ERROR_SUCCESS) + WLog_Print(getWLog(), WLOG_ERROR, "Failed to check FreeRDP event handles"); + break; + } + } + + if (exit_code == sdl::error::SUCCESS) + { + DWORD code = 0; + { + char* emsg = nullptr; + size_t elen = 0; + exit_code = error_info_to_error(&code, &emsg, &elen); + if (emsg) + error_msg = emsg; + free(emsg); + } + + if ((code == ERRINFO_LOGOFF_BY_USER) && + (freerdp_get_disconnect_ultimatum(context()) == Disconnect_Ultimatum_user_requested)) + { + const char* msg = "Error info says user did not initiate but disconnect ultimatum says " + "they did; treat this as a user logoff"; + + char* emsg = nullptr; + size_t elen = 0; + winpr_asprintf(&emsg, &elen, "%s", msg); + if (emsg) + error_msg = emsg; + free(emsg); + + /* This situation might be limited to Windows XP. */ + WLog_Print(getWLog(), WLOG_INFO, "%s", msg); + exit_code = sdl::error::LOGOFF; + } + } + + freerdp_disconnect(instance); + + return exit_code; +} + +/* RDP main loop. + * Connects RDP, loops while running and handles event and dispatch, cleans up + * after the connection ends. */ +DWORD SdlContext::rdpThreadRun(SdlContext* sdl) +{ + WINPR_ASSERT(sdl); + + std::string error_msg; + int exit_code = sdl->sdl_client_thread_connect(error_msg); + if (exit_code == sdl::error::SUCCESS) + exit_code = sdl->sdl_client_thread_run(error_msg); + sdl->sdl_client_cleanup(exit_code, error_msg); + + return static_cast(exit_code); +} + +int SdlContext::error_info_to_error(DWORD* pcode, char** msg, size_t* len) const +{ + const DWORD code = freerdp_error_info(context()->instance); + const char* name = freerdp_get_error_info_name(code); + const char* str = freerdp_get_error_info_string(code); + const int exit_code = sdl::error::errorToExitCode(code); + + winpr_asprintf(msg, len, "Terminate with %s due to ERROR_INFO %s [0x%08" PRIx32 "]: %s", + sdl::error::errorToExitCodeTag(code), name, code, str); + SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "%s", *msg); + if (pcode) + *pcode = code; + return exit_code; +} + +void SdlContext::applyMonitorOffset(SDL_WindowID window, float& x, float& y) const +{ + if (!freerdp_settings_get_bool(context()->settings, FreeRDP_UseMultimon)) + return; + + auto w = getWindowForId(window); + x -= static_cast(w->offsetX()); + y -= static_cast(w->offsetY()); +} + +static bool alignX(const SDL_Rect& a, const SDL_Rect& b) +{ + if (a.x + a.w == b.x) + return true; + if (b.x + b.w == a.x) + return true; + return false; +} + +static bool alignY(const SDL_Rect& a, const SDL_Rect& b) +{ + if (a.y + a.h == b.y) + return true; + if (b.y + b.h == a.y) + return true; + return false; +} + +std::vector +SdlContext::updateDisplayOffsetsForNeighbours(SDL_DisplayID id, + const std::vector& ignore) +{ + auto first = _offsets.at(id); + std::vector neighbours; + + for (auto& entry : _offsets) + { + if (entry.first == id) + continue; + if (std::find(ignore.begin(), ignore.end(), entry.first) != ignore.end()) + continue; + + bool neighbor = false; + if (alignX(entry.second.first, first.first)) + { + if (entry.second.first.x < first.first.x) + entry.second.second.x = first.second.x - entry.second.second.w; + else + entry.second.second.x = first.second.x + first.second.w; + neighbor = true; + } + if (alignY(entry.second.first, first.first)) + { + if (entry.second.first.y < first.first.y) + entry.second.second.y = first.second.y - entry.second.second.h; + else + entry.second.second.y = first.second.y + first.second.h; + neighbor = true; + } + + if (neighbor) + neighbours.push_back(entry.first); + } + return neighbours; +} + +void SdlContext::updateMonitorDataFromOffsets() +{ + for (auto& entry : _displays) + { + auto offsets = _offsets.at(entry.first); + entry.second.x = offsets.second.x; + entry.second.y = offsets.second.y; + } + + for (auto& entry : _windows) + { + const auto& monitor = _displays.at(entry.first); + entry.second.setMonitor(monitor); + } +} + +bool SdlContext::drawToWindow(SdlWindow& window, const std::vector& rects) +{ + if (!isConnected()) + return true; + + auto gdi = context()->gdi; + WINPR_ASSERT(gdi); + + auto size = window.rect(); + + std::unique_lock lock(_critical); + auto surface = _primary.get(); + if (freerdp_settings_get_bool(context()->settings, FreeRDP_SmartSizing)) + { + window.setOffsetX(0); + window.setOffsetY(0); + if (gdi->width < size.w) + { + window.setOffsetX((size.w - gdi->width) / 2); + } + if (gdi->height < size.h) + { + window.setOffsetY((size.h - gdi->height) / 2); + } + + _localScale = { static_cast(size.w) / static_cast(gdi->width), + static_cast(size.h) / static_cast(gdi->height) }; + if (!window.drawScaledRects(surface, _localScale, rects)) + return false; + } + else + { + SDL_Point offset{ 0, 0 }; + if (freerdp_settings_get_bool(context()->settings, FreeRDP_UseMultimon)) + offset = { window.offsetX(), window.offsetY() }; + if (!window.drawRects(surface, offset, rects)) + return false; + } + + window.updateSurface(); + return true; +} + +bool SdlContext::minimizeAllWindows() +{ + for (auto& w : _windows) + w.second.minimize(); + return true; +} + +int SdlContext::exitCode() const +{ + return _exitCode; +} + +SDL_PixelFormat SdlContext::pixelFormat() const +{ + return _sdlPixelFormat; +} + +bool SdlContext::addDisplayWindow(SDL_DisplayID id) +{ + const auto flags = + SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS; + auto title = sdl::utils::windowTitle(context()->settings); + auto w = SdlWindow::create(id, title, flags); + _windows.emplace(w.id(), std::move(w)); + return true; +} + +bool SdlContext::removeDisplayWindow(SDL_DisplayID id) +{ + for (auto& w : _windows) + { + if (w.second.displayIndex() == id) + _windows.erase(w.first); + } + return true; +} + +bool SdlContext::detectDisplays() +{ + int count = 0; + auto display = SDL_GetDisplays(&count); + if (!display) + return false; + for (int x = 0; x < count; x++) + { + const auto id = display[x]; + addOrUpdateDisplay(id); + } + SDL_free(display); + return true; +} + +rdpMonitor SdlContext::getDisplay(SDL_DisplayID id) const +{ + return _displays.at(id); +} + +std::vector SdlContext::getDisplayIds() const +{ + std::vector keys; + keys.reserve(_displays.size()); + for (const auto& entry : _displays) + { + keys.push_back(entry.first); + } + return keys; +} + +const SdlWindow* SdlContext::getWindowForId(SDL_WindowID id) const +{ + auto it = _windows.find(id); + if (it == _windows.end()) + return nullptr; + return &it->second; +} + +SdlWindow* SdlContext::getWindowForId(SDL_WindowID id) +{ + auto it = _windows.find(id); + if (it == _windows.end()) + return nullptr; + return &it->second; +} + +SdlWindow* SdlContext::getFirstWindow() +{ + if (_windows.empty()) + return nullptr; + return &_windows.begin()->second; +} + +sdlDispContext& SdlContext::getDisplayChannelContext() +{ + return _disp; +} + +sdlInput& SdlContext::getInputChannelContext() +{ + return _input; +} + +sdlClip& SdlContext::getClipboardChannelContext() +{ + return _clip; +} + +SdlConnectionDialogWrapper& SdlContext::getDialog() +{ + return _dialog; +} + +wLog* SdlContext::getWLog() +{ + return _log; +} + +bool SdlContext::moveMouseTo(const SDL_FPoint& pos) +{ + auto window = SDL_GetMouseFocus(); + if (!window) + return true; + + const auto id = SDL_GetWindowID(window); + const auto spos = pixelToScreen(id, pos); + SDL_WarpMouseInWindow(window, spos.x, spos.y); + return true; +} + +bool SdlContext::handleEvent(const SDL_MouseMotionEvent& ev) +{ + SDL_Event copy{}; + copy.motion = ev; + if (!eventToPixelCoordinates(ev.windowID, copy)) + return false; + removeLocalScaling(copy.motion.x, copy.motion.y); + removeLocalScaling(copy.motion.xrel, copy.motion.yrel); + applyMonitorOffset(copy.motion.windowID, copy.motion.x, copy.motion.y); + + return SdlTouch::handleEvent(this, copy.motion); +} + +bool SdlContext::handleEvent(const SDL_MouseWheelEvent& ev) +{ + SDL_Event copy{}; + copy.wheel = ev; + if (!eventToPixelCoordinates(ev.windowID, copy)) + return false; + removeLocalScaling(copy.wheel.mouse_x, copy.wheel.mouse_y); + return SdlTouch::handleEvent(this, copy.wheel); +} + +bool SdlContext::handleEvent(const SDL_WindowEvent& ev) +{ + if (!getDisplayChannelContext().handleEvent(ev)) + return false; + + auto window = getWindowForId(ev.windowID); + if (!window) + return true; + + { + const auto& r = window->rect(); + const auto& b = window->bounds(); + const auto& scale = window->scale(); + const auto& orientation = window->orientation(); + SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, + "%s: [%u] %dx%d-%dx%d {%dx%d-%dx%d}{scale=%f,orientation=%s}", + sdl::utils::toString(ev.type).c_str(), ev.windowID, r.x, r.y, r.w, r.h, b.x, + b.y, b.w, b.h, static_cast(scale), + sdl::utils::toString(orientation).c_str()); + } + + switch (ev.type) + { + case SDL_EVENT_WINDOW_MOUSE_ENTER: + return restoreCursor(); + case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED: + if (isConnected()) + { + if (!window->fill()) + return false; + if (!drawToWindow(*window)) + return false; + if (!restoreCursor()) + return false; + } + break; + case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: + if (!window->fill()) + return false; + if (!drawToWindow(*window)) + return false; + if (!restoreCursor()) + return false; + break; + case SDL_EVENT_WINDOW_MOVED: + { + auto r = window->rect(); + auto id = window->id(); + SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "%u: %dx%d-%dx%d", id, r.x, r.y, r.w, r.h); + } + break; + case SDL_EVENT_WINDOW_CLOSE_REQUESTED: + { + SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Window closed, terminating RDP session..."); + freerdp_abort_connect_context(context()); + } + break; + default: + break; + } + return true; +} + +bool SdlContext::handleEvent(const SDL_DisplayEvent& ev) +{ + if (!getDisplayChannelContext().handleEvent(ev)) + return false; + + switch (ev.type) + { + case SDL_EVENT_DISPLAY_REMOVED: // Can't show details for this one... + break; + default: + { + SDL_Rect r = {}; + if (!SDL_GetDisplayBounds(ev.displayID, &r)) + return false; + const auto name = SDL_GetDisplayName(ev.displayID); + if (!name) + return false; + const auto orientation = SDL_GetCurrentDisplayOrientation(ev.displayID); + const auto scale = SDL_GetDisplayContentScale(ev.displayID); + const auto mode = SDL_GetCurrentDisplayMode(ev.displayID); + if (!mode) + return false; + + SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, + "%s: [%u, %s] %dx%d-%dx%d {orientation=%s, scale=%f}%s", + sdl::utils::toString(ev.type).c_str(), ev.displayID, name, r.x, r.y, r.w, + r.h, sdl::utils::toString(orientation).c_str(), static_cast(scale), + sdl::utils::toString(mode).c_str()); + } + break; + } + return true; +} + +bool SdlContext::handleEvent(const SDL_MouseButtonEvent& ev) +{ + SDL_Event copy = {}; + copy.button = ev; + if (!eventToPixelCoordinates(ev.windowID, copy)) + return false; + removeLocalScaling(copy.button.x, copy.button.y); + applyMonitorOffset(copy.button.windowID, copy.button.x, copy.button.y); + return SdlTouch::handleEvent(this, copy.button); +} + +bool SdlContext::handleEvent(const SDL_TouchFingerEvent& ev) +{ + SDL_Event copy{}; + copy.tfinger = ev; + if (!eventToPixelCoordinates(ev.windowID, copy)) + return false; + removeLocalScaling(copy.tfinger.dx, copy.tfinger.dy); + removeLocalScaling(copy.tfinger.x, copy.tfinger.y); + applyMonitorOffset(copy.tfinger.windowID, copy.tfinger.x, copy.tfinger.y); + return SdlTouch::handleEvent(this, copy.tfinger); +} + +void SdlContext::addOrUpdateDisplay(SDL_DisplayID id) +{ + auto monitor = SdlWindow::query(id, false); + _displays.emplace(id, monitor); + + /* Update actual display rectangles: + * + * 1. Get logical display bounds + * 2. Use already known pixel width and height + * 3. Iterate over each display and update the x and y offsets by adding all monitor + * widths/heights from the primary + */ + _offsets.clear(); + for (auto& entry : _displays) + { + SDL_Rect bounds{}; + std::ignore = SDL_GetDisplayBounds(entry.first, &bounds); + + SDL_Rect pixel{}; + pixel.w = entry.second.width; + pixel.h = entry.second.height; + _offsets.emplace(entry.first, std::pair{ bounds, pixel }); + } + + /* 1. Find primary and update all neighbors + * 2. For each neighbor update all neighbors + * 3. repeat until all displays updated. + */ + const auto primary = SDL_GetPrimaryDisplay(); + std::vector handled; + handled.push_back(primary); + + auto neighbors = updateDisplayOffsetsForNeighbours(primary); + while (!neighbors.empty()) + { + auto neighbor = *neighbors.begin(); + neighbors.pop_back(); + + if (std::find(handled.begin(), handled.end(), neighbor) != handled.end()) + continue; + handled.push_back(neighbor); + + auto next = updateDisplayOffsetsForNeighbours(neighbor, handled); + neighbors.insert(neighbors.end(), next.begin(), next.end()); + } + updateMonitorDataFromOffsets(); +} + +void SdlContext::deleteDisplay(SDL_DisplayID id) +{ + _displays.erase(id); +} + +bool SdlContext::eventToPixelCoordinates(SDL_WindowID id, SDL_Event& ev) +{ + auto w = getWindowForId(id); + if (!w) + return false; + + /* Ignore errors here, sometimes SDL has no renderer */ + auto renderer = SDL_GetRenderer(w->window()); + if (!renderer) + return true; + return SDL_ConvertEventToRenderCoordinates(renderer, &ev); +} + +SDL_FPoint SdlContext::applyLocalScaling(const SDL_FPoint& val) const +{ + if (!freerdp_settings_get_bool(context()->settings, FreeRDP_SmartSizing)) + return val; + + auto rval = val; + rval.x *= _localScale.x; + rval.y *= _localScale.y; + return rval; +} + +void SdlContext::removeLocalScaling(float& x, float& y) const +{ + if (!freerdp_settings_get_bool(context()->settings, FreeRDP_SmartSizing)) + return; + x /= _localScale.x; + y /= _localScale.y; +} + +SDL_FPoint SdlContext::screenToPixel(SDL_WindowID id, const SDL_FPoint& pos) +{ + auto w = getWindowForId(id); + if (!w) + return {}; + + /* Ignore errors here, sometimes SDL has no renderer */ + auto renderer = SDL_GetRenderer(w->window()); + if (!renderer) + return pos; + + SDL_FPoint rpos{}; + if (!SDL_RenderCoordinatesFromWindow(renderer, pos.x, pos.y, &rpos.x, &rpos.y)) + return {}; + removeLocalScaling(rpos.x, rpos.y); + return rpos; +} + +SDL_FPoint SdlContext::pixelToScreen(SDL_WindowID id, const SDL_FPoint& pos) +{ + auto w = getWindowForId(id); + if (!w) + return {}; + + /* Ignore errors here, sometimes SDL has no renderer */ + auto renderer = SDL_GetRenderer(w->window()); + if (!renderer) + return pos; + + SDL_FPoint rpos{}; + if (!SDL_RenderCoordinatesToWindow(renderer, pos.x, pos.y, &rpos.x, &rpos.y)) + return {}; + return applyLocalScaling(rpos); +} + +SDL_FRect SdlContext::pixelToScreen(SDL_WindowID id, const SDL_FRect& pos) +{ + const auto fpos = pixelToScreen(id, SDL_FPoint{ pos.x, pos.y }); + const auto size = pixelToScreen(id, SDL_FPoint{ pos.w, pos.h }); + return { fpos.x, fpos.y, size.x, size.y }; +} + +bool SdlContext::handleEvent(const SDL_Event& ev) +{ + if ((ev.type >= SDL_EVENT_DISPLAY_FIRST) && (ev.type <= SDL_EVENT_DISPLAY_LAST)) + { + const auto& dev = ev.display; + return handleEvent(dev); + } + if ((ev.type >= SDL_EVENT_WINDOW_FIRST) && (ev.type <= SDL_EVENT_WINDOW_LAST)) + { + const auto& wev = ev.window; + return handleEvent(wev); + } + switch (ev.type) + { + case SDL_EVENT_RENDER_TARGETS_RESET: + case SDL_EVENT_RENDER_DEVICE_RESET: + case SDL_EVENT_WILL_ENTER_FOREGROUND: + return redraw(); + default: + break; + } + + if (!isConnected()) + return true; + + switch (ev.type) + { + case SDL_EVENT_FINGER_DOWN: + case SDL_EVENT_FINGER_UP: + case SDL_EVENT_FINGER_MOTION: + { + const auto& cev = ev.tfinger; + return handleEvent(cev); + } + case SDL_EVENT_MOUSE_MOTION: + + { + const auto& cev = ev.motion; + return handleEvent(cev); + } + case SDL_EVENT_MOUSE_BUTTON_DOWN: + case SDL_EVENT_MOUSE_BUTTON_UP: + { + const auto& cev = ev.button; + return handleEvent(cev); + } + case SDL_EVENT_MOUSE_WHEEL: + { + const auto& cev = ev.wheel; + return handleEvent(cev); + } + case SDL_EVENT_CLIPBOARD_UPDATE: + { + const auto& cev = ev.clipboard; + return getClipboardChannelContext().handleEvent(cev); + } + case SDL_EVENT_KEY_DOWN: + case SDL_EVENT_KEY_UP: + { + const auto& cev = ev.key; + return getInputChannelContext().handleEvent(cev); + } + default: + return true; + } +} + +bool SdlContext::drawToWindows(const std::vector& rects) +{ + for (auto& window : _windows) + { + if (!drawToWindow(window.second, rects)) + return FALSE; + } + + return TRUE; +} + +BOOL SdlContext::desktopResize(rdpContext* context) +{ + rdpGdi* gdi = nullptr; + rdpSettings* settings = nullptr; + auto sdl = get_context(context); + + WINPR_ASSERT(sdl); + WINPR_ASSERT(context); + + settings = context->settings; + WINPR_ASSERT(settings); + + std::unique_lock lock(sdl->_critical); + gdi = context->gdi; + if (!gdi_resize(gdi, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight))) + return FALSE; + return sdl->createPrimary(); +} + +/* This function is called to output a System BEEP */ +BOOL SdlContext::playSound(rdpContext* context, const PLAY_SOUND_UPDATE* play_sound) +{ + /* TODO: Implement */ + WINPR_UNUSED(context); + WINPR_UNUSED(play_sound); + return TRUE; +} + +/* This function is called whenever a new frame starts. + * It can be used to reset invalidated areas. */ +BOOL SdlContext::beginPaint(rdpContext* context) +{ + auto gdi = context->gdi; + WINPR_ASSERT(gdi); + WINPR_ASSERT(gdi->primary); + + HGDI_DC hdc = gdi->primary->hdc; + WINPR_ASSERT(hdc); + if (!hdc->hwnd) + return TRUE; + + HGDI_WND hwnd = hdc->hwnd; + WINPR_ASSERT(hwnd->invalid); + hwnd->invalid->null = TRUE; + hwnd->ninvalid = 0; + + return TRUE; +} + +bool SdlContext::redraw(bool suppress) const +{ + if (!_connected) + return true; + + auto gdi = context()->gdi; + WINPR_ASSERT(gdi); + return gdi_send_suppress_output(gdi, suppress); +} + +void SdlContext::setConnected(bool val) +{ + _connected = val; +} + +bool SdlContext::isConnected() const +{ + return _connected; +} + +rdpContext* SdlContext::context() const +{ + WINPR_ASSERT(_context); + return _context; +} + +rdpClientContext* SdlContext::common() const +{ + return reinterpret_cast(context()); +} + +bool SdlContext::setCursor(CursorType type) +{ + _cursorType = type; + return restoreCursor(); +} + +bool SdlContext::setCursor(rdpPointer* cursor) +{ + _cursor = cursor; + return setCursor(CURSOR_IMAGE); +} + +rdpPointer* SdlContext::cursor() const +{ + return _cursor; +} + +bool SdlContext::restoreCursor() +{ + WLog_Print(getWLog(), WLOG_DEBUG, "restore cursor: %d", _cursorType); + switch (_cursorType) + { + case CURSOR_NULL: + if (!SDL_HideCursor()) + { + WLog_Print(getWLog(), WLOG_ERROR, "SDL_HideCursor failed"); + return false; + } + + setHasCursor(false); + return true; + + case CURSOR_DEFAULT: + { + auto def = SDL_GetDefaultCursor(); + if (!SDL_SetCursor(def)) + { + WLog_Print(getWLog(), WLOG_ERROR, "SDL_SetCursor(default=%p) failed", + static_cast(def)); + return false; + } + if (!SDL_ShowCursor()) + { + WLog_Print(getWLog(), WLOG_ERROR, "SDL_ShowCursor failed"); + return false; + } + setHasCursor(true); + return true; + } + case CURSOR_IMAGE: + setHasCursor(true); + return sdl_Pointer_Set_Process(this); + default: + WLog_Print(getWLog(), WLOG_ERROR, "Unknown cursorType %s", + sdl::utils::toString(_cursorType).c_str()); + return false; + } +} + +void SdlContext::setMonitorIds(const std::vector& ids) +{ + _monitorIds.clear(); + for (auto id : ids) + { + _monitorIds.push_back(id); + } +} + +const std::vector& SdlContext::monitorIds() const +{ + return _monitorIds; +} + +int64_t SdlContext::monitorId(uint32_t index) const +{ + if (index >= _monitorIds.size()) + { + return -1; + } + return _monitorIds.at(index); +} + +void SdlContext::push(std::vector&& rects) +{ + std::unique_lock lock(_queue_mux); + _queue.emplace(std::move(rects)); +} + +std::vector SdlContext::pop() +{ + std::unique_lock lock(_queue_mux); + if (_queue.empty()) + { + return {}; + } + auto val = std::move(_queue.front()); + _queue.pop(); + return val; +} + +bool SdlContext::setFullscreen(bool enter, bool forceOriginalDisplay) +{ + for (const auto& window : _windows) + { + if (!sdl_push_user_event(SDL_EVENT_USER_WINDOW_FULLSCREEN, &window.second, enter, + forceOriginalDisplay)) + return false; + } + _fullscreen = enter; + return true; +} + +bool SdlContext::setMinimized() +{ + return sdl_push_user_event(SDL_EVENT_USER_WINDOW_MINIMIZE); +} + +bool SdlContext::grabMouse() const +{ + return _grabMouse; +} + +bool SdlContext::toggleGrabMouse() +{ + return setGrabMouse(!grabMouse()); +} + +bool SdlContext::setGrabMouse(bool enter) +{ + _grabMouse = enter; + return true; +} + +bool SdlContext::grabKeyboard() const +{ + return _grabKeyboard; +} + +bool SdlContext::toggleGrabKeyboard() +{ + return setGrabKeyboard(!grabKeyboard()); +} + +bool SdlContext::setGrabKeyboard(bool enter) +{ + _grabKeyboard = enter; + return true; +} + +bool SdlContext::setResizeable(bool enable) +{ + const auto settings = context()->settings; + const bool dyn = freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate); + const bool smart = freerdp_settings_get_bool(settings, FreeRDP_SmartSizing); + bool use = (dyn && enable) || smart; + + for (const auto& window : _windows) + { + if (!sdl_push_user_event(SDL_EVENT_USER_WINDOW_RESIZEABLE, &window.second, use)) + return false; + } + _resizeable = use; + + return true; +} + +bool SdlContext::resizeable() const +{ + return _resizeable; +} + +bool SdlContext::toggleResizeable() +{ + return setResizeable(!resizeable()); +} + +bool SdlContext::fullscreen() const +{ + return _fullscreen; +} + +bool SdlContext::toggleFullscreen() +{ + return setFullscreen(!fullscreen()); +} diff --git a/third_party/FreeRDP/client/SDL/SDL3/sdl_context.hpp b/third_party/FreeRDP/client/SDL/SDL3/sdl_context.hpp new file mode 100644 index 0000000..0ca9c25 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/sdl_context.hpp @@ -0,0 +1,229 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client + * + * Copyright 2022 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#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& ids); + [[nodiscard]] const std::vector& monitorIds() const; + [[nodiscard]] int64_t monitorId(uint32_t index) const; + + void push(std::vector&& rects); + [[nodiscard]] std::vector 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& rects = {}); + [[nodiscard]] bool drawToWindow(SdlWindow& window, const std::vector& 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 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 + updateDisplayOffsetsForNeighbours(SDL_DisplayID id, + const std::vector& ignore = {}); + void updateMonitorDataFromOffsets(); + + rdpContext* _context = nullptr; + wLog* _log = nullptr; + + std::atomic _connected = false; + bool _cursor_visible = true; + rdpPointer* _cursor = nullptr; + CursorType _cursorType = CURSOR_NULL; + std::vector _monitorIds; + std::mutex _queue_mux; + std::queue> _queue; + /* SDL */ + bool _fullscreen = false; + bool _resizeable = false; + bool _grabMouse = false; + bool _grabKeyboard = false; + int _exitCode = -1; + std::atomic _rdpThreadRunning = false; + SDL_PixelFormat _sdlPixelFormat = SDL_PIXELFORMAT_UNKNOWN; + + CriticalSection _critical; + + using SDLSurfacePtr = std::unique_ptr; + + SDLSurfacePtr _primary; + SDL_FPoint _localScale{ 1.0f, 1.0f }; + + sdlDispContext _disp; + sdlInput _input; + sdlClip _clip; + + SdlConnectionDialogWrapper _dialog; + + std::map _displays; + std::map _windows; + std::map> _offsets; + + uint32_t _windowWidth = 0; + uint32_t _windowHeigth = 0; + WinPREvent _windowsCreatedEvent; + std::thread _thread; +}; diff --git a/third_party/FreeRDP/client/SDL/SDL3/sdl_disp.cpp b/third_party/FreeRDP/client/SDL/SDL3/sdl_disp.cpp new file mode 100644 index 0000000..84671b0 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/sdl_disp.cpp @@ -0,0 +1,488 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Display Control Channel + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include + +#include + +#include "sdl_disp.hpp" +#include "sdl_input.hpp" +#include "sdl_context.hpp" + +#include +#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& 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( + 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(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 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(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(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); +} diff --git a/third_party/FreeRDP/client/SDL/SDL3/sdl_disp.hpp b/third_party/FreeRDP/client/SDL/SDL3/sdl_disp.hpp new file mode 100644 index 0000000..8dd12f6 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/sdl_disp.hpp @@ -0,0 +1,78 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Display Control Channel + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include + +#include +#include +#include + +#include "sdl_types.hpp" + +#include + +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& 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 _last_sent_layout; +}; diff --git a/third_party/FreeRDP/client/SDL/SDL3/sdl_freerdp.cpp b/third_party/FreeRDP/client/SDL/SDL3/sdl_freerdp.cpp new file mode 100644 index 0000000..1fae687 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/sdl_freerdp.cpp @@ -0,0 +1,657 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP SDL UI + * + * Copyright 2022 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#if !defined(__MINGW32__) +#include +#endif +#include +#include +#include + +#include + +#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(windowEvent.user.data1); + auto msg = static_cast(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(windowEvent.user.data1); + auto msg = static_cast(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(windowEvent.user.data1); + auto msg = static_cast(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(windowEvent.padding))) + throw ErrorMsg{ -1, windowEvent.type, "sdl_auth_dialog_show" }; + } + break; + case SDL_EVENT_USER_UPDATE: + { + std::vector 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(windowEvent.user.data1); + if (!ctx->createWindows()) + throw ErrorMsg{ -1, windowEvent.type, "sdl->createWindows" }; + } + break; + case SDL_EVENT_USER_WINDOW_RESIZEABLE: + { + auto window = static_cast(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(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(reinterpret_cast(windowEvent.user.data1)); + const auto y = + static_cast(reinterpret_cast(windowEvent.user.data2)); + if (!sdl->moveMouseTo( + { static_cast(x) * 1.0f, static_cast(y) * 1.0f })) + throw ErrorMsg{ -1, windowEvent.type, "sdl->moveMouseTo" }; + } + break; + case SDL_EVENT_USER_POINTER_SET: + if (!sdl->setCursor(static_cast(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(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(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(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(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( + reinterpret_cast(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 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; +} diff --git a/third_party/FreeRDP/client/SDL/SDL3/sdl_freerdp.hpp b/third_party/FreeRDP/client/SDL/SDL3/sdl_freerdp.hpp new file mode 100644 index 0000000..35af338 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/sdl_freerdp.hpp @@ -0,0 +1,22 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client + * + * Copyright 2022 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "sdl_context.hpp" diff --git a/third_party/FreeRDP/client/SDL/SDL3/sdl_input.cpp b/third_party/FreeRDP/client/SDL/SDL3/sdl_input.cpp new file mode 100644 index 0000000..6e7c3e7 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/sdl_input.cpp @@ -0,0 +1,717 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP SDL keyboard helper + * + * Copyright 2022 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "sdl_input.hpp" +#include "sdl_disp.hpp" +#include "sdl_context.hpp" +#include "sdl_utils.hpp" +#include "sdl_prefs.hpp" +#include "sdl_touch.hpp" + +#include + +#include + +#include +#include +#include +#include + +#include + +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(pos.x), static_cast(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(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& getSdlMap() +{ + static std::map 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 ""; + + 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(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 sdlInput::tokenize(const std::string& data, const std::string& delimiter) +{ + size_t lastpos = 0; + size_t pos = 0; + std::list 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; +} diff --git a/third_party/FreeRDP/client/SDL/SDL3/sdl_input.hpp b/third_party/FreeRDP/client/SDL/SDL3/sdl_input.hpp new file mode 100644 index 0000000..d1fdadb --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/sdl_input.hpp @@ -0,0 +1,84 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client keyboard helper + * + * Copyright 2022 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 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; +}; diff --git a/third_party/FreeRDP/client/SDL/SDL3/sdl_monitor.cpp b/third_party/FreeRDP/client/SDL/SDL3/sdl_monitor.cpp new file mode 100644 index 0000000..cd89076 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/sdl_monitor.cpp @@ -0,0 +1,311 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Monitor Handling + * + * Copyright 2011 Marc-Andre Moreau + * Copyright 2017 David Fort + * Copyright 2018 Kai Harms + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#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( + 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( + 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 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 used; + for (size_t x = 0; x < nr; x++) + { + auto cur = static_cast( + 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(sdl->monitorIds().size()); + if (!freerdp_settings_set_uint32(settings, FreeRDP_NumMonitorIds, size)) + return FALSE; + + return sdl_detect_single_window(sdl, pMaxWidth, pMaxHeight); +} diff --git a/third_party/FreeRDP/client/SDL/SDL3/sdl_monitor.hpp b/third_party/FreeRDP/client/SDL/SDL3/sdl_monitor.hpp new file mode 100644 index 0000000..a993c9f --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/sdl_monitor.hpp @@ -0,0 +1,28 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Monitor Handling + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include "sdl_types.hpp" + +[[nodiscard]] int sdl_list_monitors(SdlContext* sdl); +[[nodiscard]] BOOL sdl_detect_monitors(SdlContext* sdl, UINT32* pMaxWidth, UINT32* ppMaxHeight); diff --git a/third_party/FreeRDP/client/SDL/SDL3/sdl_pointer.cpp b/third_party/FreeRDP/client/SDL/SDL3/sdl_pointer.cpp new file mode 100644 index 0000000..c8a0e83 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/sdl_pointer.cpp @@ -0,0 +1,254 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Mouse Pointer + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include "sdl_pointer.hpp" +#include "sdl_context.hpp" +#include "sdl_touch.hpp" +#include "sdl_utils.hpp" + +#include + +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(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(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(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(pointer); + if (!ptr) + return true; + + rdpGdi* gdi = context->gdi; + WINPR_ASSERT(gdi); + + auto ix = static_cast(pointer->xPos); + auto iy = static_cast(pointer->yPos); + auto isw = static_cast(pointer->width); + auto ish = static_cast(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(orig.w), static_cast(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(ptr->image->pixels); + auto data = static_cast(ptr->data); + const BOOL rc = freerdp_image_scale( + pixels, gdi->dstFormat, static_cast(ptr->image->pitch), 0, 0, + static_cast(ptr->image->w), static_cast(ptr->image->h), data, + gdi->dstFormat, 0, 0, 0, static_cast(isw), static_cast(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(ptr->image->w), + static_cast(ptr->image->h) }); + std::unique_ptr normal{ + SDL_CreateSurface(static_cast(hidpi_scale.x), static_cast(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(pos.x), static_cast(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; +} diff --git a/third_party/FreeRDP/client/SDL/SDL3/sdl_pointer.hpp b/third_party/FreeRDP/client/SDL/SDL3/sdl_pointer.hpp new file mode 100644 index 0000000..fc134e7 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/sdl_pointer.hpp @@ -0,0 +1,30 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Mouse Pointer + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include "sdl_types.hpp" + +#include + +[[nodiscard]] bool sdl_register_pointer(rdpGraphics* graphics); + +[[nodiscard]] bool sdl_Pointer_Set_Process(SdlContext* sdl); diff --git a/third_party/FreeRDP/client/SDL/SDL3/sdl_touch.cpp b/third_party/FreeRDP/client/SDL/SDL3/sdl_touch.cpp new file mode 100644 index 0000000..2816426 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/sdl_touch.cpp @@ -0,0 +1,208 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP SDL touch/mouse input + * + * Copyright 2022 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "sdl_touch.hpp" +#include "sdl_context.hpp" + +#include +#include + +#include +#include + +#include + +[[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(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(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(ev.fingerID), + sdl_scale_pressure(ev.pressure), static_cast(ev.x), + static_cast(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(ev.fingerID), sdl_scale_pressure(ev.pressure), static_cast(ev.x), + static_cast(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(ev.fingerID), sdl_scale_pressure(ev.pressure), static_cast(ev.x), + static_cast(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(ev.fingerID), sdl_scale_pressure(ev.pressure), static_cast(ev.x), + static_cast(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(relative ? ev.xrel : ev.x); + auto y = static_cast(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(ev.x * (flipped ? -1.0f : 1.0f) * 120.0f); + const auto y = static_cast(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(relative ? 0 : ev.x); + auto y = static_cast(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; + } +} diff --git a/third_party/FreeRDP/client/SDL/SDL3/sdl_touch.hpp b/third_party/FreeRDP/client/SDL/SDL3/sdl_touch.hpp new file mode 100644 index 0000000..951a98e --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/sdl_touch.hpp @@ -0,0 +1,41 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP SDL touch/mouse input + * + * Copyright 2022 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#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); +}; diff --git a/third_party/FreeRDP/client/SDL/SDL3/sdl_types.hpp b/third_party/FreeRDP/client/SDL/SDL3/sdl_types.hpp new file mode 100644 index 0000000..b41c55a --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/sdl_types.hpp @@ -0,0 +1,46 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client + * + * Copyright 2022 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +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(ctx); + return sdl->sdl; +} + +[[nodiscard]] static inline SdlContext* get_context(rdpContext* ctx) +{ + if (!ctx) + return nullptr; + auto sdl = reinterpret_cast(ctx); + return sdl->sdl; +} diff --git a/third_party/FreeRDP/client/SDL/SDL3/sdl_utils.cpp b/third_party/FreeRDP/client/SDL/SDL3/sdl_utils.cpp new file mode 100644 index 0000000..49401d9 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/sdl_utils.cpp @@ -0,0 +1,610 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client + * + * Copyright 2022 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include "sdl_utils.hpp" + +#include "sdl_freerdp.hpp" + +#include + +#include +#include + +#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(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(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(static_cast(va_arg(ap, UINT32))); + event->data2 = reinterpret_cast(static_cast(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(static_cast(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 diff --git a/third_party/FreeRDP/client/SDL/SDL3/sdl_utils.hpp b/third_party/FreeRDP/client/SDL/SDL3/sdl_utils.hpp new file mode 100644 index 0000000..92ad4ec --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/sdl_utils.hpp @@ -0,0 +1,184 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client + * + * Copyright 2022 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +template using deleted_unique_ptr = std::unique_ptr>; + +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 diff --git a/third_party/FreeRDP/client/SDL/SDL3/sdl_window.cpp b/third_party/FreeRDP/client/SDL/SDL3/sdl_window.cpp new file mode 100644 index 0000000..1e49ef9 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/sdl_window.cpp @@ -0,0 +1,486 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client + * + * Copyright 2023 Armin Novak + * 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 +#include +#include + +#include "sdl_window.hpp" +#include "sdl_utils.hpp" + +#include + +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(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& 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(static_cast(dstRect.x) * scale.x); + dstRect.w = static_cast(static_cast(dstRect.w) * scale.x); + dstRect.y = static_cast(static_cast(dstRect.y) * scale.y); + dstRect.h = static_cast(static_cast(dstRect.h) * scale.y); + return blit(surface, srcRect, dstRect); +} + +bool SdlWindow::drawScaledRects(SDL_Surface* surface, const SDL_FPoint& scale, + const std::vector& 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(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(SDL_WINDOWPOS_CENTERED_DISPLAY(id)), + static_cast(SDL_WINDOWPOS_CENTERED_DISPLAY(id)), static_cast(width), + static_cast(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 window(createDummy(id), SDL_DestroyWindow); + if (!window) + return {}; + + std::unique_ptr 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 window(createDummy(id), SDL_DestroyWindow); + if (!window) + return {}; + + std::unique_ptr 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); +} diff --git a/third_party/FreeRDP/client/SDL/SDL3/sdl_window.hpp b/third_party/FreeRDP/client/SDL/SDL3/sdl_window.hpp new file mode 100644 index 0000000..0b4a4ec --- /dev/null +++ b/third_party/FreeRDP/client/SDL/SDL3/sdl_window.hpp @@ -0,0 +1,110 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Client + * + * Copyright 2023 Armin Novak + * 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 +#include + +#include + +#include + +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& 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& 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{}; +}; diff --git a/third_party/FreeRDP/client/SDL/common/CMakeLists.txt b/third_party/FreeRDP/client/SDL/common/CMakeLists.txt new file mode 100644 index 0000000..e579ecf --- /dev/null +++ b/third_party/FreeRDP/client/SDL/common/CMakeLists.txt @@ -0,0 +1,30 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP SDL Client +# +# Copyright 2024 Armin Novak +# 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. + +add_subdirectory(aad) +add_subdirectory(res) + +add_library( + sdl-common-prefs STATIC sdl_prefs.hpp sdl_prefs.cpp scoped_guard.hpp sdl_common_utils.hpp sdl_common_utils.cpp +) +target_link_libraries(sdl-common-prefs winpr freerdp) +set_property(TARGET sdl-common-prefs PROPERTY FOLDER "Client/Common") + +if(BUILD_TESTING_INTERNAL OR BUILD_TESTING) + add_subdirectory(test) +endif() diff --git a/third_party/FreeRDP/client/SDL/common/aad/CMakeLists.txt b/third_party/FreeRDP/client/SDL/common/aad/CMakeLists.txt new file mode 100644 index 0000000..e296f28 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/common/aad/CMakeLists.txt @@ -0,0 +1,55 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP SDL Client +# +# Copyright 2024 Armin Novak +# 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. + +option(WITH_WEBVIEW "Build with WebView support for AAD login popup browser" OFF) +if(WITH_WEBVIEW) + set(SRCS sdl_webview.hpp webview_impl.hpp sdl_webview.cpp) + set(LIBS winpr) + + include(FetchContent) + + set(FETCHCONTENT_SOURCE_DIR_WEBVIEW "${CMAKE_CURRENT_SOURCE_DIR}/../../../../external/webview") + if(IS_DIRECTORY "${FETCHCONTENT_SOURCE_DIR_WEBVIEW}") + message("Using existing source from ${FETCHCONTENT_SOURCE_DIR_WEBVIEW}") + else() + unset(FETCHCONTENT_SOURCE_DIR_WEBVIEW) + endif() + + set(WEBVIEW_BUILD_DOCS OFF CACHE INTERNAL "fetchcontent default") + set(WEBVIEW_BUILD_SHARED_LIBRARY OFF CACHE INTERNAL "fetchcontent default") + set(WEBVIEW_BUILD_STATIC_LIBRARY ON CACHE INTERNAL "fetchcontent default") + set(WEBVIEW_BUILD_TESTS OFF CACHE INTERNAL "fetchcontent default") + set(WEBVIEW_BUILD_EXAMPLES OFF CACHE INTERNAL "fetchcontent default") + FetchContent_Declare(webview GIT_REPOSITORY https://github.com/akallabeth/webview GIT_TAG navigation-listener SYSTEM) + FetchContent_MakeAvailable(webview) + + list(APPEND SRCS webview_impl.cpp) + + list(APPEND LIBS webview::core) + + include_directories(${CMAKE_CURRENT_BINARY_DIR}) + + add_library(sdl-common-aad-view STATIC ${SRCS}) + set_property(TARGET sdl-common-aad-view PROPERTY FOLDER "Client/SDL/Common") + + target_include_directories(sdl-common-aad-view PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) + target_link_libraries(sdl-common-aad-view PRIVATE ${LIBS}) + target_compile_definitions(sdl-common-aad-view PUBLIC ${DEFINITIONS}) +else() + add_library(sdl-common-aad-view STATIC dummy.cpp) +endif() diff --git a/third_party/FreeRDP/client/SDL/common/aad/dummy.cpp b/third_party/FreeRDP/client/SDL/common/aad/dummy.cpp new file mode 100644 index 0000000..e69de29 diff --git a/third_party/FreeRDP/client/SDL/common/aad/sdl_webview.cpp b/third_party/FreeRDP/client/SDL/common/aad/sdl_webview.cpp new file mode 100644 index 0000000..5940f56 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/common/aad/sdl_webview.cpp @@ -0,0 +1,155 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Popup browser for AAD authentication + * + * Copyright 2023 Isaac Klein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include "sdl_webview.hpp" +#include "webview_impl.hpp" + +#define TAG CLIENT_TAG("SDL.webview") + +static std::string from_settings(const rdpSettings* settings, FreeRDP_Settings_Keys_String id) +{ + auto val = freerdp_settings_get_string(settings, id); + if (!val) + { + WLog_WARN(TAG, "Settings key %s is nullptr", freerdp_settings_get_name_for_key(id)); + return ""; + } + return val; +} + +static std::string from_aad_wellknown(rdpContext* context, AAD_WELLKNOWN_VALUES which) +{ + auto val = freerdp_utils_aad_get_wellknown_string(context, which); + + if (!val) + { + WLog_WARN(TAG, "[wellknown] key %s is nullptr", + freerdp_utils_aad_wellknwon_value_name(which)); + return ""; + } + return val; +} + +static BOOL sdl_webview_get_rdsaad_access_token(freerdp* instance, const char* scope, + const char* req_cnf, char** token) +{ + WINPR_ASSERT(instance); + WINPR_ASSERT(scope); + WINPR_ASSERT(req_cnf); + WINPR_ASSERT(token); + + WINPR_UNUSED(instance); + + auto context = instance->context; + WINPR_UNUSED(context); + + auto settings = context->settings; + WINPR_ASSERT(settings); + + std::shared_ptr request(freerdp_client_get_aad_url((rdpClientContext*)instance->context, + FREERDP_CLIENT_AAD_AUTH_REQUEST, + scope), + free); + const std::string title = "FreeRDP WebView - AAD access token"; + std::string code; + auto rc = webview_impl_run(title, request.get(), code); + if (!rc || code.empty()) + return FALSE; + + std::shared_ptr token_request( + freerdp_client_get_aad_url((rdpClientContext*)instance->context, + FREERDP_CLIENT_AAD_TOKEN_REQUEST, scope, code.c_str(), req_cnf), + free); + return client_common_get_access_token(instance, token_request.get(), token); +} + +static BOOL sdl_webview_get_avd_access_token(freerdp* instance, char** token) +{ + WINPR_ASSERT(token); + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->context); + + std::shared_ptr request(freerdp_client_get_aad_url((rdpClientContext*)instance->context, + FREERDP_CLIENT_AAD_AVD_AUTH_REQUEST), + free); + + const std::string title = "FreeRDP WebView - AVD access token"; + std::string code; + auto rc = webview_impl_run(title, request.get(), code); + if (!rc || code.empty()) + return FALSE; + + std::shared_ptr token_request( + freerdp_client_get_aad_url((rdpClientContext*)instance->context, + FREERDP_CLIENT_AAD_AVD_TOKEN_REQUEST, code.c_str()), + free); + return client_common_get_access_token(instance, token_request.get(), token); +} + +BOOL sdl_webview_get_access_token(freerdp* instance, AccessTokenType tokenType, char** token, + size_t count, ...) +{ + WINPR_ASSERT(instance); + WINPR_ASSERT(token); + switch (tokenType) + { + case ACCESS_TOKEN_TYPE_AAD: + { + if (count < 2) + { + WLog_ERR(TAG, + "ACCESS_TOKEN_TYPE_AAD expected 2 additional arguments, but got %" PRIuz + ", aborting", + count); + return FALSE; + } + else if (count > 2) + WLog_WARN(TAG, + "ACCESS_TOKEN_TYPE_AAD expected 2 additional arguments, but got %" PRIuz + ", ignoring", + count); + va_list ap = {}; + va_start(ap, count); + const char* scope = va_arg(ap, const char*); + const char* req_cnf = va_arg(ap, const char*); + const BOOL rc = sdl_webview_get_rdsaad_access_token(instance, scope, req_cnf, token); + va_end(ap); + return rc; + } + case ACCESS_TOKEN_TYPE_AVD: + if (count != 0) + WLog_WARN(TAG, + "ACCESS_TOKEN_TYPE_AVD expected 0 additional arguments, but got %" PRIuz + ", ignoring", + count); + return sdl_webview_get_avd_access_token(instance, token); + default: + WLog_ERR(TAG, "Unexpected value for AccessTokenType [%" PRIuz "], aborting", tokenType); + return FALSE; + } +} diff --git a/third_party/FreeRDP/client/SDL/common/aad/sdl_webview.hpp b/third_party/FreeRDP/client/SDL/common/aad/sdl_webview.hpp new file mode 100644 index 0000000..80a7e82 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/common/aad/sdl_webview.hpp @@ -0,0 +1,34 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Popup browser for AAD authentication + * + * Copyright 2023 Isaac Klein + * + * 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 + +#ifdef __cplusplus +extern "C" +{ +#endif + + [[nodiscard]] BOOL sdl_webview_get_access_token(freerdp* instance, AccessTokenType tokenType, + char** token, size_t count, ...); + +#ifdef __cplusplus +} +#endif diff --git a/third_party/FreeRDP/client/SDL/common/aad/webview_impl.cpp b/third_party/FreeRDP/client/SDL/common/aad/webview_impl.cpp new file mode 100644 index 0000000..d4bfe4f --- /dev/null +++ b/third_party/FreeRDP/client/SDL/common/aad/webview_impl.cpp @@ -0,0 +1,179 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Popup browser for AAD authentication + * + * Copyright 2023 Isaac Klein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "webview_impl.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define TAG FREERDP_TAG("client.SDL.common.aad") + +class fkt_arg +{ + public: + fkt_arg(const std::string& url) + { + auto args = urlsplit(url); + auto redir = args.find("redirect_uri"); + if (redir == args.end()) + { + WLog_ERR(TAG, + "[Webview] url %s does not contain a redirect_uri parameter, " + "aborting.", + url.c_str()); + } + else + { + _redirect_uri = from_url_encoded_str(redir->second); + } + } + + bool valid() const + { + return !_redirect_uri.empty(); + } + + bool getCode(std::string& c) const + { + c = _code; + return !c.empty(); + } + + bool handle(const std::string& uri) const + { + std::string duri = from_url_encoded_str(uri); + if (duri.length() < _redirect_uri.length()) + return false; + auto rc = _strnicmp(duri.c_str(), _redirect_uri.c_str(), _redirect_uri.length()); + return rc == 0; + } + + bool parse(const std::string& uri) + { + _args = urlsplit(uri); + auto err = _args.find("error"); + if (err != _args.end()) + { + auto suberr = _args.find("error_subcode"); + WLog_ERR(TAG, "[Webview] %s: %s, %s: %s", err->first.c_str(), err->second.c_str(), + suberr->first.c_str(), suberr->second.c_str()); + return false; + } + auto val = _args.find("code"); + if (val == _args.end()) + { + WLog_ERR(TAG, "[Webview] no code parameter detected in redirect URI %s", uri.c_str()); + return false; + } + + _code = val->second; + return true; + } + + protected: + static std::string from_url_encoded_str(const std::string& str) + { + std::string cxxstr; + auto cstr = winpr_str_url_decode(str.c_str(), str.length()); + if (cstr) + { + cxxstr = std::string(cstr); + free(cstr); + } + return cxxstr; + } + + static std::vector split(const std::string& input, const std::string& regex) + { + // passing -1 as the submatch index parameter performs splitting + std::regex re(regex); + std::sregex_token_iterator first{ input.begin(), input.end(), re, -1 }; + std::sregex_token_iterator last; + return { first, last }; + } + + static std::map urlsplit(const std::string& url) + { + auto pos = url.find('?'); + if (pos == std::string::npos) + return {}; + + pos++; // skip '?' + auto surl = url.substr(pos); + auto args = split(surl, "&"); + + std::map argmap; + for (const auto& arg : args) + { + auto kv = split(arg, "="); + if (kv.size() == 2) + argmap.insert({ kv[0], kv[1] }); + } + + return argmap; + } + + private: + std::string _redirect_uri; + std::string _code; + std::map _args; +}; + +static void fkt(webview_t webview, const char* uri, webview_navigation_event_t type, void* arg) +{ + assert(arg); + auto rcode = static_cast(arg); + + if (type != WEBVIEW_LOAD_FINISHED) + return; + + if (!rcode->handle(uri)) + return; + + (void)rcode->parse(uri); + webview_terminate(webview); +} + +bool webview_impl_run(const std::string& title, const std::string& url, std::string& code) +{ + webview::webview w(false, nullptr); + + w.set_title(title); + w.set_size(800, 600, WEBVIEW_HINT_NONE); + + fkt_arg arg(url); + if (!arg.valid()) + { + return false; + } + w.add_navigation_listener(fkt, &arg); + w.navigate(url); + w.run(); + return arg.getCode(code); +} diff --git a/third_party/FreeRDP/client/SDL/common/aad/webview_impl.hpp b/third_party/FreeRDP/client/SDL/common/aad/webview_impl.hpp new file mode 100644 index 0000000..9e48de2 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/common/aad/webview_impl.hpp @@ -0,0 +1,25 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Popup browser for AAD authentication + * + * Copyright 2023 Isaac Klein + * + * 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 + +[[nodiscard]] bool webview_impl_run(const std::string& title, const std::string& url, + std::string& code); diff --git a/third_party/FreeRDP/client/SDL/common/man/sdl-freerdp-config.1.in b/third_party/FreeRDP/client/SDL/common/man/sdl-freerdp-config.1.in new file mode 100644 index 0000000..ca982b9 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/common/man/sdl-freerdp-config.1.in @@ -0,0 +1,130 @@ +.SH "CONFIGURATION FILE" +.PP +Format and Location: +.RS 4 +The configuration file is stored per user\&. +.br + +The +\fIXDG_CONFIG_HOME\fR +environment variable can be used to override the base directory\&. +.br + +This defaults to +\fI~/\&.config\fR +The location relative to +\fIXDG_CONFIG_HOME\fR +is +\fI$XDG_CONFIG_HOME/@VENDOR_PRODUCT@/sdl\-freerdp\&.json\fR +.br + +The configuration is stored in JSON format +.RE +.PP +Supported options: +.RS 4 +.PP +\fISDL_KeyModMask\fR +.RS 4 +.PP +.RS 4 +Defines the key combination required for SDL client shortcuts\&. +.br + +Default +\fIKMOD_RSHIFT\fR +.br + +An array of +\fISDL_Keymod\fR +strings as defined at +\fI@SDL_WIKI_BASE_URL@/SDL_Keymod\fR +.br +use \fRKEYMOD_NONE\fR to disable hotkeys +.RE +.RE +.PP +\fISDL_Fullscreen\fR +.RS 4 +.PP +.RS 4 +Toggles client fullscreen state\&. +.br + +Default +\fISDL_SCANCODE_RETURN\fR\&. +.br + +A string as defined at +\fI@SDL_WIKI_BASE_URL@/SDL_Scancode\fR +.RE +.RE +.PP +\fISDL_Minimize\fR +.RS 4 +.PP +.RS 4 +Minimizes the client window +.br + +Default +\fISDL_SCANCODE_M\fR\&. +.br + +A string as defined at +\fI@SDL_WIKI_BASE_URL@/SDL_Scancode\fR +.RE +.RE +.PP +\fISDL_Resizeable\fR +.RS 4 +.PP +.RS 4 +Toggles local window resizeable state\&. +.br + +Default +\fISDL_SCANCODE_R\fR\&. +.br + +A string as defined at +\fI@SDL_WIKI_BASE_URL@/SDL_Scancode\fR +.RE +.RE +.PP +\fISDL_Grab\fR +.RS 4 +.PP +.RS 4 +Toggles keyboard and mouse grab state. +.br +If keyboard is grabbed the local system shortcuts do no longer work and are sent to the remote system. +.br +If the Mouse is grabbed (optional) local gesture detection does not work and the mouse might not be able to leave the RDP window. Mouse events are not altered.\&. +.br + +Default +\fISDL_SCANCODE_G\fR\&. +.br + +A string as defined at +\fI@SDL_WIKI_BASE_URL@/SDL_Scancode\fR +.RE +.RE +.PP +\fISDL_Disconnect\fR +.RS 4 +.PP +.RS 4 +Disconnects from the RDP session\&. +.br + +Default +\fISDL_SCANCODE_D\fR\&. +.br + +A string as defined at +\fI@SDL_WIKI_BASE_URL@/SDL_Scancode\fR +.RE +.RE +.RE diff --git a/third_party/FreeRDP/client/SDL/common/man/sdl-freerdp-examples.1.in b/third_party/FreeRDP/client/SDL/common/man/sdl-freerdp-examples.1.in new file mode 100644 index 0000000..1f2fc6b --- /dev/null +++ b/third_party/FreeRDP/client/SDL/common/man/sdl-freerdp-examples.1.in @@ -0,0 +1,116 @@ +.SH "EXAMPLES" +.PP +\fB@MANPAGE_NAME@ connection\&.rdp /p:Pwd123! /f\fR +.RS 4 +Connect in fullscreen mode using a stored configuration +\fIconnection\&.rdp\fR +and the password +\fIPwd123!\fR +.RE +.PP +\fB@MANPAGE_NAME@ /u:USER /size:50%h /v:rdp\&.contoso\&.com\fR +.RS 4 +Connect to host +\fIrdp\&.contoso\&.com\fR +with user +\fIUSER\fR +and a size of +\fI50 percent of the height\fR\&. If width (w) is set instead of height (h) like /size:50%w\&. 50 percent of the width is used\&. +.RE +.PP +\fB@MANPAGE_NAME@ /u:CONTOSO\e\eJohnDoe /p:Pwd123! /v:rdp\&.contoso\&.com\fR +.RS 4 +Connect to host +\fIrdp\&.contoso\&.com\fR +with user +\fICONTOSO\e\eJohnDoe\fR +and password +\fIPwd123!\fR +.RE +.PP +\fB@MANPAGE_NAME@ /u:JohnDoe /p:Pwd123! /w:1366 /h:768 /v:192\&.168\&.1\&.100:4489\fR +.RS 4 +Connect to host +\fI192\&.168\&.1\&.100\fR +on port +\fI4489\fR +with user +\fIJohnDoe\fR, password +\fIPwd123!\fR\&. The screen width is set to +\fI1366\fR +and the height to +\fI768\fR +.RE +.PP +\fB@MANPAGE_NAME@ /u:JohnDoe /p:Pwd123! /vmconnect:C824F53E\-95D2\-46C6\-9A18\-23A5BB403532 /v:192\&.168\&.1\&.100\fR +.RS 4 +Establish a connection to host +\fI192\&.168\&.1\&.100\fR +with user +\fIJohnDoe\fR, password +\fIPwd123!\fR +and connect to Hyper\-V console (use port 2179, disable negotiation) with VMID +\fIC824F53E\-95D2\-46C6\-9A18\-23A5BB403532\fR +.RE +.PP +\fB+clipboard\fR +.RS 4 +Activate clipboard redirection +.RE +.PP +\fB/drive:home,/home/user\fR +.RS 4 +Activate drive redirection of +\fI/home/user\fR +as home drive +.RE +.PP +\fB/smartcard:\fR +.RS 4 +Activate smartcard redirection for device +\fIdevice\fR +.RE +.PP +\fB/printer:,\fR +.RS 4 +Activate printer redirection for printer +\fIdevice\fR +using driver +\fIdriver\fR +.RE +.PP +\fB/serial:\fR +.RS 4 +Activate serial port redirection for port +\fIdevice\fR +.RE +.PP +\fB/parallel:\fR +.RS 4 +Activate parallel port redirection for port +\fIdevice\fR +.RE +.PP +\fB/sound:sys:alsa\fR +.RS 4 +Activate audio output redirection using device +\fIsys:alsa\fR +.RE +.PP +\fB/microphone:sys:alsa\fR +.RS 4 +Activate audio input redirection using device +\fIsys:alsa\fR +.RE +.PP +\fB/multimedia:sys:alsa\fR +.RS 4 +Activate multimedia redirection using device +\fIsys:alsa\fR +.RE +.PP +\fB/usb:id,dev:054c:0268\fR +.RS 4 +Activate USB device redirection for the device identified by +\fI054c:0268\fR +.RE diff --git a/third_party/FreeRDP/client/SDL/common/man/sdl-global-config.1.in b/third_party/FreeRDP/client/SDL/common/man/sdl-global-config.1.in new file mode 100644 index 0000000..72704d8 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/common/man/sdl-global-config.1.in @@ -0,0 +1,24 @@ +.SH "GLOBAL CONFIGURATION (SDL3 client)" +.PP +The SDL3 client configuration location is +\fI@SYSCONF_DIR@/sdl-freerdp\&.json\fR +.br + +File format is JSON +.RE +.PP +Supported options: +.RS 4 +.PP +\fIisUserConfigEnabled\fR +.RS 4 +.PP +.RS 4 +\fIJSON boolean\fR +.br + +Allow or block per user configuration file support. +.RE +.RE +.PP +All user level options from \fBCONFIGURATION FILE\fR can be set here too diff --git a/third_party/FreeRDP/client/SDL/common/res/CMakeLists.txt b/third_party/FreeRDP/client/SDL/common/res/CMakeLists.txt new file mode 100644 index 0000000..0be4804 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/common/res/CMakeLists.txt @@ -0,0 +1,108 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP SDL Client +# +# Copyright 2024 Armin Novak +# 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(FACTORY_SRCS "") +set(FACTORY_HDR "") +set(FACTORY_CLASSES "") + +include(ConvertFileToHexArray) + +macro(convert_to_bin FILE FILE_TYPE) + get_filename_component(FILE_NAME ${FILE} NAME) + string(REGEX REPLACE "[^a-zA-Z0-9]" "_" TARGET_NAME ${FILE_NAME}) + + set(FILE_BIN_DIR ${CMAKE_CURRENT_BINARY_DIR}/bin) + + set(FILENAME ${FILE_NAME}) + set(CLASSNAME ${TARGET_NAME}) + set(CLASSTYPE ${FILE_TYPE}) + + file_to_hex_array("${FILE}" FILEDATA) + + cleaning_configure_file(resource.hpp.in ${FILE_BIN_DIR}/${TARGET_NAME}.hpp @ONLY) + + cleaning_configure_file(resource.cpp.in ${FILE_BIN_DIR}/${TARGET_NAME}.cpp @ONLY) + + list(APPEND FACTORY_HDR ${FILE_BIN_DIR}/${TARGET_NAME}.hpp) + list(APPEND FACTORY_SRCS ${FILE_BIN_DIR}/${TARGET_NAME}.cpp) + + list(APPEND FACTORY_CLASSES ${TARGET_NAME}) +endmacro() + +set(SRCS sdl_resource_manager.cpp sdl_resource_manager.hpp) + +set(RES_SVG_FILES ${CMAKE_SOURCE_DIR}/resources/FreeRDP_Icon.svg ${CMAKE_SOURCE_DIR}/resources/icon_info.svg + ${CMAKE_SOURCE_DIR}/resources/icon_warning.svg ${CMAKE_SOURCE_DIR}/resources/icon_error.svg +) + +set(RES_FONT_FILES ${CMAKE_SOURCE_DIR}/resources/font/OpenSans-VariableFont_wdth,wght.ttf) + +option(SDL_USE_COMPILED_RESOURCES "Compile in images/fonts" ON) + +if(SDL_USE_COMPILED_RESOURCES) + list(APPEND SRCS sdl_resource_file.cpp sdl_resource_file.hpp) + + include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + + if(WITH_SDL_IMAGE_DIALOGS) + foreach(FILE ${RES_SVG_FILES}) + convert_to_bin("${FILE}" "images") + endforeach() + endif() + + foreach(FILE ${RES_FONT_FILES}) + convert_to_bin("${FILE}" "fonts") + endforeach() + add_compile_definitions(SDL_USE_COMPILED_RESOURCES) + + set(FINIT ${CMAKE_CURRENT_BINARY_DIR}/resource-init.cpp) + list(APPEND FACTORY_SRCS ${FINIT}) + + write_file(${FINIT} "#include ") + foreach(HDR ${FACTORY_HDR}) + write_file(${FINIT} "#include <${HDR}>" APPEND) + endforeach() + + write_file(${FINIT} "void SDLResourceManager::init() {" APPEND) + foreach(CLASS ${FACTORY_CLASSES}) + write_file(${FINIT} "\t${CLASS}::init();" APPEND) + endforeach() + write_file(${FINIT} "}" APPEND) +else() + option(SDL_USE_VENDOR_PRODUCT_CONFIG_DIR "Use / path for resources" OFF) + set(SDL_RESOURCE_ROOT ${CMAKE_INSTALL_FULL_DATAROOTDIR}) + if(SDL_USE_VENDOR_PRODUCT_CONFIG_DIR) + string(APPEND SDL_RESOURCE_ROOT "/${VENDOR}") + endif() + string(APPEND SDL_RESOURCE_ROOT "/${PRODUCT}") + + if(WITH_BINARY_VERSIONING) + string(APPEND SDL_RESOURCE_ROOT "${FREERDP_VERSION_MAJOR}") + endif() + + add_compile_definitions(SDL_RESOURCE_ROOT="${SDL_RESOURCE_ROOT}") + + if(WITH_SDL_IMAGE_DIALOGS) + install(FILES ${RES_SVG_FILES} DESTINATION ${SDL_RESOURCE_ROOT}/images) + endif() + + install(FILES ${RES_FONT_FILES} DESTINATION ${SDL_RESOURCE_ROOT}/fonts) +endif() + +add_library(sdl-common-client-res STATIC ${RES_FILES} ${SRCS} ${FACTORY_HDR} ${FACTORY_SRCS}) +set_property(TARGET sdl-common-client-res PROPERTY POSITION_INDEPENDENT_CODE ON) +set_property(TARGET sdl-common-client-res PROPERTY FOLDER "Client/Common") diff --git a/third_party/FreeRDP/client/SDL/common/res/resource.cpp.in b/third_party/FreeRDP/client/SDL/common/res/resource.cpp.in new file mode 100644 index 0000000..d884f6b --- /dev/null +++ b/third_party/FreeRDP/client/SDL/common/res/resource.cpp.in @@ -0,0 +1,28 @@ +/* AUTOGENERATED file, do not edit + * + * part of @PROJECT_NAME@ + * generated by 'CMake' from common/res/resource.cpp.in + * + * contains the converted file '@FILENAME@' + */ + +#include +#include "@CLASSNAME@.hpp" + +std::string @CLASSNAME@::name() noexcept { + return "@FILENAME@"; +} +std::string @CLASSNAME@::type() noexcept { + return "@CLASSTYPE@"; +} + +// NOLINTNEXTLINE(clang-diagnostic-global-constructors) +const SDLResourceFile @CLASSNAME@::_initializer(type(), name(), init()); + +std::vector @CLASSNAME@::init() noexcept { + static const unsigned char data[] = { + @FILEDATA@ + }; + return std::vector(data, data + sizeof(data)); +} + diff --git a/third_party/FreeRDP/client/SDL/common/res/resource.hpp.in b/third_party/FreeRDP/client/SDL/common/res/resource.hpp.in new file mode 100644 index 0000000..cf32089 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/common/res/resource.hpp.in @@ -0,0 +1,34 @@ +/* AUTOGENERATED file, do not edit + * + * part of @PROJECT_NAME@ + * generated by 'CMake' from common/res/resource.hpp.in + * + * contains the converted file '@FILENAME@' + */ +#pragma once + +#include +#include +#include "sdl_resource_file.hpp" + +class @CLASSNAME@ +{ + friend class SDLResourceManager; + +public: + @CLASSNAME@() = delete; + ~@CLASSNAME@() = delete; + + @CLASSNAME@(const @CLASSNAME@&) = delete; + @CLASSNAME@(@CLASSNAME@&&) = delete; + + @CLASSNAME@& operator=(const @CLASSNAME@&) = delete; + @CLASSNAME@& operator=(@CLASSNAME@&&) = delete; + + static std::string name() noexcept; + static std::string type() noexcept; + +protected: + static std::vector init() noexcept; + static const SDLResourceFile _initializer; +}; diff --git a/third_party/FreeRDP/client/SDL/common/res/sdl_resource_file.cpp b/third_party/FreeRDP/client/SDL/common/res/sdl_resource_file.cpp new file mode 100644 index 0000000..c614fb8 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/common/res/sdl_resource_file.cpp @@ -0,0 +1,27 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "sdl_resource_file.hpp" +#include "sdl_resource_manager.hpp" + +SDLResourceFile::SDLResourceFile(const std::string& type, const std::string& id, + const std::vector& data) noexcept +{ + SDLResourceManager::insert(type, id, data); +} + +SDLResourceFile::~SDLResourceFile() = default; diff --git a/third_party/FreeRDP/client/SDL/common/res/sdl_resource_file.hpp b/third_party/FreeRDP/client/SDL/common/res/sdl_resource_file.hpp new file mode 100644 index 0000000..bad6af5 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/common/res/sdl_resource_file.hpp @@ -0,0 +1,34 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include + +class SDLResourceFile +{ + public: + SDLResourceFile(const std::string& type, const std::string& id, + const std::vector& data) noexcept; + virtual ~SDLResourceFile(); + + SDLResourceFile(const SDLResourceFile& other) = delete; + SDLResourceFile(SDLResourceFile&& other) = delete; + SDLResourceFile& operator=(const SDLResourceFile& other) = delete; + SDLResourceFile& operator=(SDLResourceFile&& other) = delete; +}; diff --git a/third_party/FreeRDP/client/SDL/common/res/sdl_resource_manager.cpp b/third_party/FreeRDP/client/SDL/common/res/sdl_resource_manager.cpp new file mode 100644 index 0000000..6873d36 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/common/res/sdl_resource_manager.cpp @@ -0,0 +1,102 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "sdl_resource_manager.hpp" +#include +#if __has_include() +#include +namespace fs = std::filesystem; +#elif __has_include() +#include +namespace fs = std::experimental::filesystem; +#else +#error Could not find system header "" or "" +#endif + +std::string SDLResourceManager::typeFonts() +{ + return "fonts"; +} + +std::string SDLResourceManager::typeImages() +{ + return "images"; +} + +void SDLResourceManager::insert(const std::string& type, const std::string& id, + const std::vector& data) +{ + std::string uuid = type + "/" + id; + resources().emplace(uuid, data); +} + +bool SDLResourceManager::useCompiledResources() +{ +#if defined(SDL_USE_COMPILED_RESOURCES) + return true; +#else + return false; +#endif +} + +const std::vector* SDLResourceManager::data(const std::string& type, + const std::string& id) +{ +#if defined(SDL_USE_COMPILED_RESOURCES) + std::string uuid = type + "/" + id; + auto val = resources().find(uuid); + if (val == resources().end()) + return nullptr; + + return &val->second; +#else + return nullptr; +#endif +} + +std::string SDLResourceManager::filename([[maybe_unused]] const std::string& type, + [[maybe_unused]] const std::string& id) +{ +#if defined(SDL_RESOURCE_ROOT) + std::string uuid = type + "/" + id; + fs::path path(SDL_RESOURCE_ROOT); + path /= type; + path /= id; + + if (!fs::exists(path)) + { + std::cerr << "sdl-freerdp expects resource '" << uuid << "' at location " + << fs::absolute(path) << std::endl; + std::cerr << "file not found, application will fail" << std::endl; + return ""; + } + return path.u8string(); +#else + return ""; +#endif +} + +std::map>& SDLResourceManager::resources() +{ + + static std::map> resources = {}; +#if defined(SDL_USE_COMPILED_RESOURCES) + if (resources.empty()) + init(); +#endif + return resources; +} diff --git a/third_party/FreeRDP/client/SDL/common/res/sdl_resource_manager.hpp b/third_party/FreeRDP/client/SDL/common/res/sdl_resource_manager.hpp new file mode 100644 index 0000000..a85b68b --- /dev/null +++ b/third_party/FreeRDP/client/SDL/common/res/sdl_resource_manager.hpp @@ -0,0 +1,54 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2023 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include + +class SDLResourceManager +{ + friend class SDLResourceFile; + + public: + SDLResourceManager() = delete; + SDLResourceManager(const SDLResourceManager& other) = delete; + SDLResourceManager(const SDLResourceManager&& other) = delete; + ~SDLResourceManager() = delete; + SDLResourceManager operator=(const SDLResourceManager& other) = delete; + SDLResourceManager& operator=(SDLResourceManager&& other) = delete; + + [[nodiscard]] static std::string typeFonts(); + [[nodiscard]] static std::string typeImages(); + + protected: + static void insert(const std::string& type, const std::string& id, + const std::vector& data); + + [[nodiscard]] static const std::vector* data(const std::string& type, + const std::string& id); + [[nodiscard]] static std::string filename(const std::string& type, const std::string& id); + + [[nodiscard]] static bool useCompiledResources(); + + private: + [[nodiscard]] static std::map>& resources(); +#if defined(SDL_USE_COMPILED_RESOURCES) + static void init(); // implemented in generated file +#endif +}; diff --git a/third_party/FreeRDP/client/SDL/common/scoped_guard.hpp b/third_party/FreeRDP/client/SDL/common/scoped_guard.hpp new file mode 100644 index 0000000..369cf40 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/common/scoped_guard.hpp @@ -0,0 +1,56 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL ScopeGuard + * + * Copyright 2024 Armin Novak + * 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 + +class ScopeGuard +{ + public: + template explicit ScopeGuard(Callable&& cleanupFunction) + try : f(std::forward(cleanupFunction)) + { + } + catch (...) + { + cleanupFunction(); + throw; + } + + ~ScopeGuard() + { + if (f) + f(); + } + + void dismiss() noexcept + { + f = nullptr; + } + + ScopeGuard(const ScopeGuard&) = delete; + ScopeGuard(ScopeGuard&& other) noexcept = delete; + ScopeGuard& operator=(const ScopeGuard&) = delete; + ScopeGuard& operator=(const ScopeGuard&&) = delete; + + private: + std::function f; +}; diff --git a/third_party/FreeRDP/client/SDL/common/sdl_common_utils.cpp b/third_party/FreeRDP/client/SDL/common/sdl_common_utils.cpp new file mode 100644 index 0000000..798c603 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/common/sdl_common_utils.cpp @@ -0,0 +1,103 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL common utilitis + * + * Copyright 2025 Armin Novak + * 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_common_utils.hpp" + +CriticalSection::CriticalSection() +{ + InitializeCriticalSection(&_section); +} + +CriticalSection::~CriticalSection() +{ + DeleteCriticalSection(&_section); +} + +void CriticalSection::lock() +{ + EnterCriticalSection(&_section); +} + +void CriticalSection::unlock() +{ + LeaveCriticalSection(&_section); +} + +WinPREvent::WinPREvent(bool initial) : _handle(CreateEventA(nullptr, TRUE, initial, nullptr)) +{ +} + +WinPREvent::~WinPREvent() +{ + (void)CloseHandle(_handle); +} + +void WinPREvent::set() +{ + (void)SetEvent(_handle); +} + +void WinPREvent::clear() +{ + (void)ResetEvent(_handle); +} + +bool WinPREvent::isSet() const +{ + return WaitForSingleObject(_handle, 0) == WAIT_OBJECT_0; +} + +HANDLE WinPREvent::handle() const +{ + return _handle; +} + +bool operator==(const rdpMonitor& l, const rdpMonitor& r) +{ + if (l.x != r.x) + return false; + if (l.y != r.y) + return false; + if (l.width != r.width) + return false; + if (l.height != r.height) + return false; + if (l.is_primary != r.is_primary) + return false; + if (l.orig_screen != r.orig_screen) + return false; + + return l.attributes == r.attributes; +} + +bool operator==(const MONITOR_ATTRIBUTES& l, const MONITOR_ATTRIBUTES& r) +{ + if (l.physicalWidth != r.physicalWidth) + return false; + if (l.physicalHeight != r.physicalHeight) + return false; + if (l.orientation != r.orientation) + return false; + if (l.desktopScaleFactor != r.desktopScaleFactor) + return false; + if (l.deviceScaleFactor != r.deviceScaleFactor) + return false; + return true; +} diff --git a/third_party/FreeRDP/client/SDL/common/sdl_common_utils.hpp b/third_party/FreeRDP/client/SDL/common/sdl_common_utils.hpp new file mode 100644 index 0000000..b20b69c --- /dev/null +++ b/third_party/FreeRDP/client/SDL/common/sdl_common_utils.hpp @@ -0,0 +1,69 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL common utilitis + * + * Copyright 2025 Armin Novak + * 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 + +#include + +using CStringPtr = std::unique_ptr; + +bool operator==(const MONITOR_ATTRIBUTES& l, const MONITOR_ATTRIBUTES& r); +bool operator==(const rdpMonitor& l, const rdpMonitor& r); + +class WinPREvent +{ + public: + explicit WinPREvent(bool initial = false); + WinPREvent(const WinPREvent& other) = delete; + WinPREvent(WinPREvent&& other) = delete; + + WinPREvent& operator=(const WinPREvent& other) = delete; + WinPREvent& operator=(WinPREvent&& other) = delete; + + ~WinPREvent(); + + void set(); + void clear(); + [[nodiscard]] bool isSet() const; + + [[nodiscard]] HANDLE handle() const; + + private: + HANDLE _handle; +}; + +class CriticalSection +{ + public: + CriticalSection(); + CriticalSection(const CriticalSection& other) = delete; + CriticalSection(CriticalSection&& other) = delete; + ~CriticalSection(); + + CriticalSection& operator=(const CriticalSection& other) = delete; + CriticalSection& operator=(const CriticalSection&& other) = delete; + + void lock(); + void unlock(); + + private: + CRITICAL_SECTION _section{}; +}; diff --git a/third_party/FreeRDP/client/SDL/common/sdl_prefs.cpp b/third_party/FreeRDP/client/SDL/common/sdl_prefs.cpp new file mode 100644 index 0000000..fc27e1d --- /dev/null +++ b/third_party/FreeRDP/client/SDL/common/sdl_prefs.cpp @@ -0,0 +1,243 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Prefs + * + * Copyright 2022 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#if __has_include() +#include +#include +namespace fs = std::filesystem; +#elif __has_include() +#include +namespace fs = std::experimental::filesystem; +#else +#error Could not find system header "" or "" +#endif + +#include "sdl_prefs.hpp" +#include "sdl_common_utils.hpp" + +#include +#include +#include + +#include +#include +#include + +SdlPref::WINPR_JSONPtr SdlPref::get(bool systemConfigOnly) const +{ + auto config = get_pref_file(systemConfigOnly); + return { WINPR_JSON_ParseFromFile(config.c_str()), WINPR_JSON_Delete }; +} + +WINPR_JSON* SdlPref::get_item(const std::string& key, bool systemConfigOnly) const +{ + /* If we request a system setting or user settings are disabled */ + if (systemConfigOnly || !is_user_config_enabled()) + return get_item(_system_config, key); + + /* Get the user setting */ + auto res = get_item(_config, key); + + /* User setting does not exist, fall back to system setting */ + if (!res) + res = get_item(_system_config, key); + return res; +} + +WINPR_JSON* SdlPref::get_item(const WINPR_JSONPtr& config, const std::string& key) const +{ + if (!config) + return nullptr; + return WINPR_JSON_GetObjectItemCaseSensitive(config.get(), key.c_str()); +} + +bool SdlPref::get_bool(const WINPR_JSONPtr& config, const std::string& key, bool fallback) const +{ + auto item = get_item(config, key); + if (!item || !WINPR_JSON_IsBool(item)) + return fallback; + return WINPR_JSON_IsTrue(item); +} + +bool SdlPref::is_user_config_enabled() const +{ + auto& config = _system_config; + if (!config) + return true; + return get_bool(config, "isUserConfigEnabled", true); +} + +std::string SdlPref::item_to_str(WINPR_JSON* item, const std::string& fallback) +{ + if (!item || !WINPR_JSON_IsString(item)) + return fallback; + auto str = WINPR_JSON_GetStringValue(item); + if (!str) + return {}; + return str; +} + +std::string SdlPref::get_string(const std::string& key, const std::string& fallback, + bool systemConfigOnly) const +{ + auto item = get_item(key, systemConfigOnly); + return item_to_str(item, fallback); +} + +bool SdlPref::get_bool(const std::string& key, bool fallback, bool systemConfigOnly) const +{ + auto& config = systemConfigOnly ? _system_config : _config; + return get_bool(config, key, fallback); +} + +int64_t SdlPref::get_int(const std::string& key, int64_t fallback, bool systemConfigOnly) const +{ + auto item = get_item(key, systemConfigOnly); + if (!item || !WINPR_JSON_IsNumber(item)) + return fallback; + auto val = WINPR_JSON_GetNumberValue(item); + return static_cast(val); +} + +std::vector SdlPref::get_array(const std::string& key, + const std::vector& fallback, + bool systemConfigOnly) const +{ + auto item = get_item(key, systemConfigOnly); + if (!item || !WINPR_JSON_IsArray(item)) + return fallback; + + std::vector values; + for (size_t x = 0; x < WINPR_JSON_GetArraySize(item); x++) + { + auto cur = WINPR_JSON_GetArrayItem(item, x); + values.push_back(item_to_str(cur)); + } + + return values; +} + +void SdlPref::print_config_file_help(int version) +{ +#if defined(WITH_WINPR_JSON) + const std::string url = "https://wiki.libsdl.org/SDL" + std::to_string(version); + std::cout << "GLOBAL CONFIGURATION FILE" << std::endl; + std::cout << std::endl; + std::cout << " The SDL client supports some system defined configuration options." + << std::endl; + std::cout << " Settings are stored in JSON format" << std::endl; + std::cout << " The location is a system configuration file. Location for current machine is " + << SdlPref::instance()->get_pref_file(true) << std::endl; + std::cout << std::endl; + std::cout << " The following configuration options are supported:" << std::endl; + std::cout << std::endl; + std::cout << " isUserConfigEnabled" << std::endl; + std::cout << " Allows to enable/disable user specific configuration files." << std::endl; + std::cout << " Default enabled" << std::endl; + std::cout << std::endl; + std::cout << " All options of the following user configuration file are also supported here." + << std::endl; + std::cout << std::endl; + + std::cout << "CONFIGURATION FILE" << std::endl; + std::cout << std::endl; + std::cout << " The SDL client supports some user defined configuration options." << std::endl; + std::cout << " Settings are stored in JSON format" << std::endl; + std::cout << " The location is a per user file. Location for current user is " + << SdlPref::instance()->get_pref_file() << std::endl; + std::cout + << " The XDG_CONFIG_HOME environment variable can be used to override the base directory." + << std::endl; + std::cout << std::endl; + std::cout << " The following configuration options are supported:" << std::endl; + std::cout << std::endl; + std::cout << " SDL_KeyModMask" << std::endl; + std::cout << " Defines the key combination required for SDL client shortcuts." + << std::endl; + std::cout << " Default KMOD_RSHIFT" << std::endl; + std::cout << " An array of SDL_Keymod strings as defined at " + "" + << url << "/SDL_Keymod" << std::endl; + std::cout << std::endl; + std::cout << " SDL_Fullscreen" << std::endl; + std::cout << " Toggles client fullscreen state." << std::endl; + std::cout << " Default SDL_SCANCODE_RETURN." << std::endl; + std::cout << " A string as " + "defined at " + << url << "/SDLScancodeLookup" << std::endl; + std::cout << std::endl; + std::cout << " SDL_Minimize" << std::endl; + std::cout << " Minimizes client windows." << std::endl; + std::cout << " Default SDL_SCANCODE_M." << std::endl; + std::cout << " A string as " + "defined at " + << url << "/SDLScancodeLookup" << std::endl; + std::cout << std::endl; + std::cout << " SDL_Resizeable" << std::endl; + std::cout << " Toggles local window resizeable state." << std::endl; + std::cout << " Default SDL_SCANCODE_R." << std::endl; + std::cout << " A string as " + "defined at " + << url << "/SDLScancodeLookup" << std::endl; + std::cout << std::endl; + std::cout << " SDL_Grab" << std::endl; + std::cout << " Toggles keyboard and mouse grab state." << std::endl; + std::cout << " Default SDL_SCANCODE_G." << std::endl; + std::cout << " A string as " + "defined at " + << url << "/SDLScancodeLookup" << std::endl; + std::cout << std::endl; + std::cout << " SDL_Disconnect" << std::endl; + std::cout << " Disconnects from the RDP session." << std::endl; + std::cout << " Default SDL_SCANCODE_D." << std::endl; + std::cout << " A string as defined at " << url << "/SDLScancodeLookup" << std::endl; + +#endif +} + +SdlPref::SdlPref(std::string file) + : _name(std::move(file)), _system_name(get_default_file(true)), _config(get(false)), + _system_config(get(true)) +{ +} + +std::string SdlPref::get_default_file(bool systemConfigOnly) +{ + CStringPtr name(freerdp_GetConfigFilePath(systemConfigOnly, "sdl-freerdp.json"), free); + fs::path config{ name.get() }; + return config.string(); +} + +std::shared_ptr SdlPref::instance(const std::string& name) +{ + static std::shared_ptr _instance; + if (!_instance || (_instance->get_pref_file() != name)) + _instance.reset(new SdlPref(name)); + return _instance; +} + +std::string SdlPref::get_pref_file(bool systemConfigOnly) const +{ + if (systemConfigOnly) + return _system_name; + + return _name; +} diff --git a/third_party/FreeRDP/client/SDL/common/sdl_prefs.hpp b/third_party/FreeRDP/client/SDL/common/sdl_prefs.hpp new file mode 100644 index 0000000..aba0b42 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/common/sdl_prefs.hpp @@ -0,0 +1,69 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SDL Prefs + * + * Copyright 2022 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +class SdlPref +{ + public: + [[nodiscard]] static std::shared_ptr + instance(const std::string& name = SdlPref::get_default_file(false)); + + [[nodiscard]] std::string get_pref_file(bool systemConfigOnly = false) const; + + [[nodiscard]] std::string get_string(const std::string& key, const std::string& fallback = "", + bool systemConfigOnly = false) const; + [[nodiscard]] int64_t get_int(const std::string& key, int64_t fallback = 0, + bool systemConfigOnly = false) const; + [[nodiscard]] bool get_bool(const std::string& key, bool fallback = false, + bool systemConfigOnly = false) const; + [[nodiscard]] std::vector get_array(const std::string& key, + const std::vector& fallback = {}, + bool systemConfigOnly = false) const; + + static void print_config_file_help(int version); + + private: + using WINPR_JSONPtr = std::unique_ptr; + + std::string _name; + std::string _system_name; + WINPR_JSONPtr _config; + WINPR_JSONPtr _system_config; + + explicit SdlPref(std::string file); + + [[nodiscard]] WINPR_JSON* get_item(const std::string& key, bool systemConfigOnly) const; + [[nodiscard]] WINPR_JSON* get_item(const WINPR_JSONPtr& config, const std::string& key) const; + + [[nodiscard]] WINPR_JSONPtr get(bool systemConfigOnly) const; + [[nodiscard]] bool get_bool(const WINPR_JSONPtr& config, const std::string& key, + bool fallback = false) const; + + [[nodiscard]] bool is_user_config_enabled() const; + + [[nodiscard]] static std::string get_default_file(bool systemConfigOnly); + [[nodiscard]] static std::string item_to_str(WINPR_JSON* item, + const std::string& fallback = ""); +}; diff --git a/third_party/FreeRDP/client/SDL/common/test/CMakeLists.txt b/third_party/FreeRDP/client/SDL/common/test/CMakeLists.txt new file mode 100644 index 0000000..ffb510c --- /dev/null +++ b/third_party/FreeRDP/client/SDL/common/test/CMakeLists.txt @@ -0,0 +1,39 @@ +set(MODULE_NAME "TestSDL2") +set(MODULE_PREFIX "TEST_SDL") + +set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.cpp) + +set(${MODULE_PREFIX}_TESTS TestSDLPrefs.cpp TestSDLWebview.cpp) + +disable_warnings_for_directory(${CMAKE_CURRENT_BINARY_DIR}) + +create_test_sourcelist(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_DRIVER} ${${MODULE_PREFIX}_TESTS}) + +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../aad") +include_directories("${CMAKE_CURRENT_BINARY_DIR}/../aad") + +add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS}) + +set(${MODULE_PREFIX}_LIBS freerdp freerdp-client winpr sdl-common-prefs sdl-common-aad-view) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + +set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}") + +set(TEST_SRC_AREA "${CMAKE_CURRENT_SOURCE_DIR}") +set(TEST_BIN_AREA "${CMAKE_CURRENT_BINARY_DIR}") + +if(WIN32) + string(REPLACE "\\" "\\\\" TEST_SRC_AREA "${TEST_SRC_AREA}") + string(REPLACE "\\" "\\\\" TEST_BIN_AREA "${TEST_BIN_AREA}") +endif() + +add_compile_definitions(TEST_SRC_AREA="${TEST_SRC_AREA}") +add_compile_definitions(TEST_BIN_AREA="${TEST_BIN_AREA}") + +foreach(test ${${MODULE_PREFIX}_TESTS}) + get_filename_component(TestName ${test} NAME_WE) + add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName}) +endforeach() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "FreeRDP/Client/Test") diff --git a/third_party/FreeRDP/client/SDL/common/test/TestSDLPrefs.cpp b/third_party/FreeRDP/client/SDL/common/test/TestSDLPrefs.cpp new file mode 100644 index 0000000..b89b8c6 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/common/test/TestSDLPrefs.cpp @@ -0,0 +1,121 @@ +#include "../sdl_prefs.hpp" + +#include +#include + +#include +#include + +#if __has_include() +#include +namespace fs = std::filesystem; +#elif __has_include() +#include +namespace fs = std::experimental::filesystem; +#else +#error Could not find system header "" or "" +#endif + +static std::shared_ptr instance() +{ +#if !defined(TEST_SRC_AREA) +#error "Please define TEST_SRC_AREA to the source directory of this test case" +#endif + fs::path src(TEST_SRC_AREA); + src /= "sdl-freerdp.json"; + if (!fs::exists(src)) + { + std::cerr << "[ERROR] test configuration file '" << src << "' does not exist" << std::endl; + return nullptr; + } + + return SdlPref::instance(src.string()); +} + +int TestSDLPrefs(int argc, char* argv[]) +{ + WINPR_UNUSED(argc); + WINPR_UNUSED(argv); + +#if defined(WITH_WINPR_JSON) + printf("implementation: json\n"); +#else + printf("implementation: fallback\n"); +#endif + +#if defined(WITH_WINPR_JSON) + printf("config: %s\n", instance()->get_pref_file().c_str()); +#endif + + auto string_value = instance()->get_string("string_key", "cba"); +#if defined(WITH_WINPR_JSON) + if (string_value != "abc") + return -1; +#else + if (string_value != "cba") + return -1; +#endif + + auto string_value_nonexistent = instance()->get_string("string_key_nonexistent", "cba"); + if (string_value_nonexistent != "cba") + return -1; + + auto int_value = instance()->get_int("int_key", 321); +#if defined(WITH_WINPR_JSON) + if (int_value != 123) + return -1; +#else + if (int_value != 321) + return -1; +#endif + + auto int_value_nonexistent = instance()->get_int("int_key_nonexistent", 321); + if (int_value_nonexistent != 321) + return -1; + + auto bool_value = instance()->get_bool("bool_key", false); +#if defined(WITH_WINPR_JSON) + if (!bool_value) + return -1; +#else + if (bool_value) + return -1; +#endif + + auto bool_value_nonexistent = instance()->get_bool("bool_key_nonexistent", false); + if (bool_value_nonexistent) + return -1; + + auto array_value = instance()->get_array("array_key", { "c", "b", "a" }); + if (array_value.size() != 3) + return -1; +#if defined(WITH_WINPR_JSON) + if (array_value.at(0) != "a") + return -1; + if (array_value.at(1) != "b") + return -1; + if (array_value.at(2) != "c") + return -1; +#else + if (array_value.at(0) != "c") + return -1; + if (array_value.at(1) != "b") + return -1; + if (array_value.at(2) != "a") + return -1; +#endif + + auto array_value_nonexistent = + instance()->get_array("array_key_nonexistent", { "c", "b", "a" }); + if (array_value_nonexistent.size() != 3) + return -1; + + if (array_value_nonexistent.at(0) != "c") + return -1; + if (array_value_nonexistent.at(1) != "b") + return -1; + if (array_value_nonexistent.at(2) != "a") + return -1; + + return 0; +} diff --git a/third_party/FreeRDP/client/SDL/common/test/TestSDLWebview.cpp b/third_party/FreeRDP/client/SDL/common/test/TestSDLWebview.cpp new file mode 100644 index 0000000..4e8076f --- /dev/null +++ b/third_party/FreeRDP/client/SDL/common/test/TestSDLWebview.cpp @@ -0,0 +1,30 @@ +#include +#include +#include + +#include +#include + +#include + +int TestSDLWebview(WINPR_ATTR_UNUSED int argc, WINPR_ATTR_UNUSED char* argv[]) +{ +#if 0 + RDP_CLIENT_ENTRY_POINTS entry = {}; + entry.Version = RDP_CLIENT_INTERFACE_VERSION; + entry.Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1); + entry.ContextSize = sizeof(rdpContext); + + std::shared_ptr context(freerdp_client_context_new(&entry), + [](rdpContext* ptr) { freerdp_client_context_free(ptr); }); + + char* token = nullptr; + if (!sdl_webview_get_access_token(context->instance, ACCESS_TOKEN_TYPE_AAD, &token, 2, "scope", + "foobar")) + { + std::cerr << "test failed!" << std::endl; + return -1; + } +#endif + return 0; +} diff --git a/third_party/FreeRDP/client/SDL/common/test/sdl-freerdp.json b/third_party/FreeRDP/client/SDL/common/test/sdl-freerdp.json new file mode 100644 index 0000000..17facb3 --- /dev/null +++ b/third_party/FreeRDP/client/SDL/common/test/sdl-freerdp.json @@ -0,0 +1,10 @@ +{ + "string_key": "abc", + "int_key": 123, + "bool_key": true, + "array_key": [ + "a", + "b", + "c" + ] +} \ No newline at end of file diff --git a/third_party/FreeRDP/client/Sample/CMakeLists.txt b/third_party/FreeRDP/client/Sample/CMakeLists.txt new file mode 100644 index 0000000..39d1dc7 --- /dev/null +++ b/third_party/FreeRDP/client/Sample/CMakeLists.txt @@ -0,0 +1,45 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP Sample UI cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +cmake_minimum_required(VERSION 3.13) + +if(POLICY CMP0091) + cmake_policy(SET CMP0091 NEW) +endif() +if(NOT FREERDP_DEFAULT_PROJECT_VERSION) + set(FREERDP_DEFAULT_PROJECT_VERSION "1.0.0.0") +endif() + +project(sfreerdp LANGUAGES C VERSION ${FREERDP_DEFAULT_PROJECT_VERSION}) + +message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}") + +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/) +include(ProjectCStandard) +include(CommonConfigOptions) + +include(ConfigureFreeRDP) + +set(SRCS tf_channels.c tf_channels.h tf_freerdp.h tf_freerdp.c) + +addtargetwithresourcefile(${PROJECT_NAME} TRUE "${PROJECT_VERSION}" SRCS) + +set(LIBS freerdp-client freerdp winpr) +target_link_libraries(${PROJECT_NAME} PRIVATE ${LIBS}) + +set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER "Client/Sample") +installwithrpath(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT client) +install_freerdp_desktop("${PROJECT_NAME}") diff --git a/third_party/FreeRDP/client/Sample/ModuleOptions.cmake b/third_party/FreeRDP/client/Sample/ModuleOptions.cmake new file mode 100644 index 0000000..1594cfe --- /dev/null +++ b/third_party/FreeRDP/client/Sample/ModuleOptions.cmake @@ -0,0 +1,3 @@ +set(FREERDP_CLIENT_NAME "sfreerdp") +set(FREERDP_CLIENT_PLATFORM "Sample") +set(FREERDP_CLIENT_VENDOR "FreeRDP") diff --git a/third_party/FreeRDP/client/Sample/tf_channels.c b/third_party/FreeRDP/client/Sample/tf_channels.c new file mode 100644 index 0000000..1655609 --- /dev/null +++ b/third_party/FreeRDP/client/Sample/tf_channels.c @@ -0,0 +1,73 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Sample Client Channels + * + * Copyright 2018 Armin Novak + * Copyright 2018 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 + +#include +#include + +#include +#include +#include +#include +#include + +#include "tf_channels.h" +#include "tf_freerdp.h" + +void tf_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e) +{ + tfContext* tf = (tfContext*)context; + + WINPR_ASSERT(tf); + WINPR_ASSERT(e); + + if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0) + { + } + else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) + { + CliprdrClientContext* clip = (CliprdrClientContext*)e->pInterface; + WINPR_ASSERT(clip); + clip->custom = context; + } + else + freerdp_client_OnChannelConnectedEventHandler(&tf->common, e); +} + +void tf_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e) +{ + tfContext* tf = (tfContext*)context; + + WINPR_ASSERT(tf); + WINPR_ASSERT(e); + + if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0) + { + } + else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) + { + CliprdrClientContext* clip = (CliprdrClientContext*)e->pInterface; + WINPR_ASSERT(clip); + clip->custom = nullptr; + } + else + freerdp_client_OnChannelDisconnectedEventHandler(&tf->common, e); +} diff --git a/third_party/FreeRDP/client/Sample/tf_channels.h b/third_party/FreeRDP/client/Sample/tf_channels.h new file mode 100644 index 0000000..d00a5c2 --- /dev/null +++ b/third_party/FreeRDP/client/Sample/tf_channels.h @@ -0,0 +1,33 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Sample Client Channels + * + * Copyright 2018 Armin Novak + * Copyright 2018 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. + */ + +#ifndef FREERDP_CLIENT_SAMPLE_CHANNELS_H +#define FREERDP_CLIENT_SAMPLE_CHANNELS_H + +#include +#include + +int tf_on_channel_connected(freerdp* instance, const char* name, void* pInterface); +int tf_on_channel_disconnected(freerdp* instance, const char* name, void* pInterface); + +void tf_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e); +void tf_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e); + +#endif /* FREERDP_CLIENT_SAMPLE_CHANNELS_H */ diff --git a/third_party/FreeRDP/client/Sample/tf_freerdp.c b/third_party/FreeRDP/client/Sample/tf_freerdp.c new file mode 100644 index 0000000..a42972a --- /dev/null +++ b/third_party/FreeRDP/client/Sample/tf_freerdp.c @@ -0,0 +1,426 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Test UI + * + * Copyright 2011 Marc-Andre Moreau + * Copyright 2016,2018 Armin Novak + * Copyright 2016,2018 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 + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "tf_channels.h" +#include "tf_freerdp.h" + +#define TAG CLIENT_TAG("sample") + +/* This function is called whenever a new frame starts. + * It can be used to reset invalidated areas. */ +static BOOL tf_begin_paint(rdpContext* context) +{ + rdpGdi* gdi = nullptr; + + WINPR_ASSERT(context); + + gdi = context->gdi; + WINPR_ASSERT(gdi); + WINPR_ASSERT(gdi->primary); + WINPR_ASSERT(gdi->primary->hdc); + WINPR_ASSERT(gdi->primary->hdc->hwnd); + WINPR_ASSERT(gdi->primary->hdc->hwnd->invalid); + gdi->primary->hdc->hwnd->invalid->null = TRUE; + return TRUE; +} + +/* This function is called when the library completed composing a new + * frame. Read out the changed areas and blit them to your output device. + * The image buffer will have the format specified by gdi_init + */ +static BOOL tf_end_paint(rdpContext* context) +{ + rdpGdi* gdi = nullptr; + + WINPR_ASSERT(context); + + gdi = context->gdi; + WINPR_ASSERT(gdi); + WINPR_ASSERT(gdi->primary); + + HGDI_DC hdc = gdi->primary->hdc; + WINPR_ASSERT(hdc); + if (!hdc->hwnd) + return TRUE; + + HGDI_WND hwnd = hdc->hwnd; + WINPR_ASSERT(hwnd->invalid || (hwnd->ninvalid == 0)); + + if (hwnd->invalid->null) + return TRUE; + + return TRUE; +} + +static BOOL tf_desktop_resize(rdpContext* context) +{ + rdpGdi* gdi = nullptr; + rdpSettings* settings = nullptr; + + WINPR_ASSERT(context); + + settings = context->settings; + WINPR_ASSERT(settings); + + gdi = context->gdi; + return gdi_resize(gdi, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)); +} + +/* This function is called to output a System BEEP */ +static BOOL tf_play_sound(rdpContext* context, const PLAY_SOUND_UPDATE* play_sound) +{ + /* TODO: Implement */ + WINPR_UNUSED(context); + WINPR_UNUSED(play_sound); + return TRUE; +} + +/* This function is called to update the keyboard indocator LED */ +static BOOL tf_keyboard_set_indicators(rdpContext* context, UINT16 led_flags) +{ + /* TODO: Set local keyboard indicator LED status */ + WINPR_UNUSED(context); + WINPR_UNUSED(led_flags); + return TRUE; +} + +/* This function is called to set the IME state */ +static BOOL tf_keyboard_set_ime_status(rdpContext* context, UINT16 imeId, UINT32 imeState, + UINT32 imeConvMode) +{ + if (!context) + return FALSE; + + WLog_WARN(TAG, + "KeyboardSetImeStatus(unitId=%04" PRIx16 ", imeState=%08" PRIx32 + ", imeConvMode=%08" PRIx32 ") ignored", + imeId, imeState, imeConvMode); + return TRUE; +} + +/* Called before a connection is established. + * Set all configuration options to support and load channels here. */ +static BOOL tf_pre_connect(freerdp* instance) +{ + rdpSettings* settings = nullptr; + + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->context); + + settings = instance->context->settings; + WINPR_ASSERT(settings); + + /* If the callbacks provide the PEM all certificate options can be extracted, otherwise + * only the certificate fingerprint is available. */ + if (!freerdp_settings_set_bool(settings, FreeRDP_CertificateCallbackPreferPEM, TRUE)) + return FALSE; + + /* Optional OS identifier sent to server */ + if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMajorType, OSMAJORTYPE_UNIX)) + return FALSE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMinorType, OSMINORTYPE_NATIVE_XSERVER)) + return FALSE; + /* OrderSupport is initialized at this point. + * Only override it if you plan to implement custom order + * callbacks or deactivate certain features. */ + /* Register the channel listeners. + * They are required to set up / tear down channels if they are loaded. */ + if (PubSub_SubscribeChannelConnected(instance->context->pubSub, + tf_OnChannelConnectedEventHandler) < 0) + return FALSE; + if (PubSub_SubscribeChannelDisconnected(instance->context->pubSub, + tf_OnChannelDisconnectedEventHandler) < 0) + return FALSE; + + /* TODO: Any code your client requires */ + return TRUE; +} + +/* Called after a RDP connection was successfully established. + * Settings might have changed during negotiation of client / server feature + * support. + * + * Set up local framebuffers and paing callbacks. + * If required, register pointer callbacks to change the local mouse cursor + * when hovering over the RDP window + */ +static BOOL tf_post_connect(freerdp* instance) +{ + rdpContext* context = nullptr; + + if (!gdi_init(instance, PIXEL_FORMAT_XRGB32)) + return FALSE; + + context = instance->context; + WINPR_ASSERT(context); + WINPR_ASSERT(context->update); + + /* With this setting we disable all graphics processing in the library. + * + * This allows low resource (client) protocol parsing. + */ + if (!freerdp_settings_set_bool(context->settings, FreeRDP_DeactivateClientDecoding, TRUE)) + return FALSE; + + context->update->BeginPaint = tf_begin_paint; + context->update->EndPaint = tf_end_paint; + context->update->PlaySound = tf_play_sound; + context->update->DesktopResize = tf_desktop_resize; + context->update->SetKeyboardIndicators = tf_keyboard_set_indicators; + context->update->SetKeyboardImeStatus = tf_keyboard_set_ime_status; + return TRUE; +} + +/* This function is called whether a session ends by failure or success. + * Clean up everything allocated by pre_connect and post_connect. + */ +static void tf_post_disconnect(freerdp* instance) +{ + tfContext* context = nullptr; + + if (!instance) + return; + + if (!instance->context) + return; + + context = (tfContext*)instance->context; + PubSub_UnsubscribeChannelConnected(instance->context->pubSub, + tf_OnChannelConnectedEventHandler); + PubSub_UnsubscribeChannelDisconnected(instance->context->pubSub, + tf_OnChannelDisconnectedEventHandler); + gdi_free(instance); + /* TODO : Clean up custom stuff */ + WINPR_UNUSED(context); +} + +/* RDP main loop. + * Connects RDP, loops while running and handles event and dispatch, cleans up + * after the connection ends. */ +static DWORD WINAPI tf_client_thread_proc(LPVOID arg) +{ + freerdp* instance = (freerdp*)arg; + DWORD nCount = 0; + DWORD status = 0; + DWORD result = 0; + HANDLE handles[MAXIMUM_WAIT_OBJECTS] = WINPR_C_ARRAY_INIT; + BOOL rc = freerdp_connect(instance); + + WINPR_ASSERT(instance->context); + WINPR_ASSERT(instance->context->settings); + if (freerdp_settings_get_bool(instance->context->settings, FreeRDP_AuthenticationOnly)) + { + result = freerdp_get_last_error(instance->context); + freerdp_abort_connect_context(instance->context); + WLog_ERR(TAG, "Authentication only, exit status 0x%08" PRIx32 "", result); + goto disconnect; + } + + if (!rc) + { + result = freerdp_get_last_error(instance->context); + WLog_ERR(TAG, "connection failure 0x%08" PRIx32, result); + return result; + } + + while (!freerdp_shall_disconnect_context(instance->context)) + { + nCount = freerdp_get_event_handles(instance->context, handles, ARRAYSIZE(handles)); + + if (nCount == 0) + { + WLog_ERR(TAG, "freerdp_get_event_handles failed"); + break; + } + + status = WaitForMultipleObjects(nCount, handles, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + WLog_ERR(TAG, "WaitForMultipleObjects failed with %" PRIu32 "", status); + break; + } + + if (!freerdp_check_event_handles(instance->context)) + { + if (freerdp_get_last_error(instance->context) == FREERDP_ERROR_SUCCESS) + WLog_ERR(TAG, "Failed to check FreeRDP event handles"); + + break; + } + } + +disconnect: + freerdp_disconnect(instance); + return result; +} + +/* Optional global initializer. + * Here we just register a signal handler to print out stack traces + * if available. */ +static BOOL tf_client_global_init(void) +{ + return freerdp_handle_signals() == 0; +} + +/* Optional global tear down */ +static void tf_client_global_uninit(void) +{ +} + +static int tf_logon_error_info(freerdp* instance, UINT32 data, UINT32 type) +{ + tfContext* tf = nullptr; + const char* str_data = freerdp_get_logon_error_info_data(data); + const char* str_type = freerdp_get_logon_error_info_type(type); + + if (!instance || !instance->context) + return -1; + + tf = (tfContext*)instance->context; + WLog_INFO(TAG, "Logon Error Info %s [%s]", str_data, str_type); + WINPR_UNUSED(tf); + + return 1; +} + +static BOOL tf_client_new(freerdp* instance, rdpContext* context) +{ + tfContext* tf = (tfContext*)context; + + if (!instance || !context) + return FALSE; + + instance->PreConnect = tf_pre_connect; + instance->PostConnect = tf_post_connect; + instance->PostDisconnect = tf_post_disconnect; + instance->LogonErrorInfo = tf_logon_error_info; + /* TODO: Client display set up */ + WINPR_UNUSED(tf); + return TRUE; +} + +static void tf_client_free(freerdp* instance, rdpContext* context) +{ + tfContext* tf = (tfContext*)instance->context; + + if (!context) + return; + + /* TODO: Client display tear down */ + WINPR_UNUSED(tf); +} + +static int tf_client_start(rdpContext* context) +{ + /* TODO: Start client related stuff */ + WINPR_UNUSED(context); + return 0; +} + +static int tf_client_stop(rdpContext* context) +{ + /* TODO: Stop client related stuff */ + WINPR_UNUSED(context); + return 0; +} + +static int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints) +{ + WINPR_ASSERT(pEntryPoints); + + ZeroMemory(pEntryPoints, sizeof(RDP_CLIENT_ENTRY_POINTS)); + pEntryPoints->Version = RDP_CLIENT_INTERFACE_VERSION; + pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1); + pEntryPoints->GlobalInit = tf_client_global_init; + pEntryPoints->GlobalUninit = tf_client_global_uninit; + pEntryPoints->ContextSize = sizeof(tfContext); + pEntryPoints->ClientNew = tf_client_new; + pEntryPoints->ClientFree = tf_client_free; + pEntryPoints->ClientStart = tf_client_start; + pEntryPoints->ClientStop = tf_client_stop; + return 0; +} + +int main(int argc, char* argv[]) +{ + int rc = -1; + RDP_CLIENT_ENTRY_POINTS clientEntryPoints = WINPR_C_ARRAY_INIT; + + RdpClientEntry(&clientEntryPoints); + rdpContext* context = freerdp_client_context_new(&clientEntryPoints); + + if (!context) + goto fail; + + { + const int status = + freerdp_client_settings_parse_command_line(context->settings, argc, argv, FALSE); + if (status) + { + rc = freerdp_client_settings_command_line_status_print(context->settings, status, argc, + argv); + goto fail; + } + } + + if (!stream_dump_register_handlers(context, CONNECTION_STATE_MCS_CREATE_REQUEST, FALSE)) + goto fail; + + if (freerdp_client_start(context) != 0) + goto fail; + + { + const DWORD res = tf_client_thread_proc(context->instance); + rc = (int)res; + } + + if (freerdp_client_stop(context) != 0) + rc = -1; + +fail: + freerdp_client_context_free(context); + return rc; +} diff --git a/third_party/FreeRDP/client/Sample/tf_freerdp.h b/third_party/FreeRDP/client/Sample/tf_freerdp.h new file mode 100644 index 0000000..c9b5295 --- /dev/null +++ b/third_party/FreeRDP/client/Sample/tf_freerdp.h @@ -0,0 +1,37 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Sample Client + * + * Copyright 2018 Armin Novak + * Copyright 2018 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. + */ + +#ifndef FREERDP_CLIENT_SAMPLE_H +#define FREERDP_CLIENT_SAMPLE_H + +#include +#include +#include +#include +#include + +typedef struct +{ + rdpClientContext common; + + /* Channels */ +} tfContext; + +#endif /* FREERDP_CLIENT_SAMPLE_H */ diff --git a/third_party/FreeRDP/client/Wayland/CMakeLists.txt b/third_party/FreeRDP/client/Wayland/CMakeLists.txt new file mode 100644 index 0000000..f036fae --- /dev/null +++ b/third_party/FreeRDP/client/Wayland/CMakeLists.txt @@ -0,0 +1,59 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP Wayland Client cmake build script +# +# Copyright 2014 Manuel Bachmann +# Copyright 2015 David Fort +# +# 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 "wlfreerdp") +set(MODULE_PREFIX "FREERDP_CLIENT_WAYLAND") + +include(WarnUnmaintained) +warn_unmaintained(${MODULE_NAME} "-DWITH_CLIENT_WAYLAND=OFF") + +include_directories(SYSTEM ${WAYLAND_INCLUDE_DIR}) + +set(${MODULE_PREFIX}_SRCS + wlfreerdp.c + wlfreerdp.h + wlf_disp.c + wlf_disp.h + wlf_pointer.c + wlf_pointer.h + wlf_input.c + wlf_input.h + wlf_cliprdr.c + wlf_cliprdr.h + wlf_channels.c + wlf_channels.h +) + +if(FREERDP_UNIFIED_BUILD) + include_directories(${PROJECT_SOURCE_DIR}/uwac/include) + include_directories(${PROJECT_BINARY_DIR}/uwac/include) +else() + find_package(uwac 0 REQUIRED) + include_directories(SYSTEM ${UWAC_INCLUDE_DIR}) +endif() + +list(APPEND ${MODULE_PREFIX}_LIBS freerdp-client freerdp uwac) + +addtargetwithresourcefile(${MODULE_NAME} TRUE ${FREERDP_VERSION} ${MODULE_PREFIX}_SRCS) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + +install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT client) + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Client/Wayland") +generate_and_install_freerdp_man_from_template(${MODULE_NAME} "1" "${FREERDP_API_VERSION}") diff --git a/third_party/FreeRDP/client/Wayland/wlf_channels.c b/third_party/FreeRDP/client/Wayland/wlf_channels.c new file mode 100644 index 0000000..3a11407 --- /dev/null +++ b/third_party/FreeRDP/client/Wayland/wlf_channels.c @@ -0,0 +1,79 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Client Channels + * + * Copyright 2013 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include + +#include "wlf_channels.h" +#include "wlf_cliprdr.h" +#include "wlf_disp.h" +#include "wlfreerdp.h" + +void wlf_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e) +{ + wlfContext* wlf = (wlfContext*)context; + + WINPR_ASSERT(wlf); + WINPR_ASSERT(e); + + if (FALSE) + { + } + else if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0) + { + } + else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) + { + wlf_cliprdr_init(wlf->clipboard, (CliprdrClientContext*)e->pInterface); + } + else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0) + { + wlf_disp_init(wlf->disp, (DispClientContext*)e->pInterface); + } + else + freerdp_client_OnChannelConnectedEventHandler(context, e); +} + +void wlf_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e) +{ + wlfContext* wlf = (wlfContext*)context; + + WINPR_ASSERT(wlf); + WINPR_ASSERT(e); + + if (FALSE) + { + } + else if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0) + { + } + else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) + { + wlf_cliprdr_uninit(wlf->clipboard, (CliprdrClientContext*)e->pInterface); + } + else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0) + { + wlf_disp_uninit(wlf->disp, (DispClientContext*)e->pInterface); + } + else + freerdp_client_OnChannelDisconnectedEventHandler(context, e); +} diff --git a/third_party/FreeRDP/client/Wayland/wlf_channels.h b/third_party/FreeRDP/client/Wayland/wlf_channels.h new file mode 100644 index 0000000..e876031 --- /dev/null +++ b/third_party/FreeRDP/client/Wayland/wlf_channels.h @@ -0,0 +1,37 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Client Channels + * + * Copyright 2013 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_WAYLAND_CHANNELS_H +#define FREERDP_CLIENT_WAYLAND_CHANNELS_H + +#include +#include +#include +#include +#include +#include +#include + +int wlf_on_channel_connected(freerdp* instance, const char* name, void* pInterface); +int wlf_on_channel_disconnected(freerdp* instance, const char* name, void* pInterface); + +void wlf_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e); +void wlf_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e); + +#endif /* FREERDP_CLIENT_WAYLAND_CHANNELS_H */ diff --git a/third_party/FreeRDP/client/Wayland/wlf_cliprdr.c b/third_party/FreeRDP/client/Wayland/wlf_cliprdr.c new file mode 100644 index 0000000..88419a9 --- /dev/null +++ b/third_party/FreeRDP/client/Wayland/wlf_cliprdr.c @@ -0,0 +1,1025 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Clipboard Redirection + * + * Copyright 2018 Armin Novak + * Copyright 2018 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 + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "wlf_cliprdr.h" + +#define TAG CLIENT_TAG("wayland.cliprdr") + +#define mime_text_plain "text/plain" +// NOLINTNEXTLINE(bugprone-suspicious-missing-comma) +static const char mime_text_utf8[] = mime_text_plain ";charset=utf-8"; + +// NOLINTNEXTLINE(bugprone-suspicious-missing-comma) +static const char* mime_text[] = { mime_text_plain, mime_text_utf8, "UTF8_STRING", + "COMPOUND_TEXT", "TEXT", "STRING" }; + +static const char mime_png[] = "image/png"; +static const char mime_webp[] = "image/webp"; +static const char mime_jpg[] = "image/jpeg"; +static const char mime_tiff[] = "image/tiff"; +static const char mime_uri_list[] = "text/uri-list"; +static const char mime_html[] = "text/html"; + +#define BMP_MIME_LIST "image/bmp", "image/x-bmp", "image/x-MS-bmp", "image/x-win-bitmap" +static const char* mime_bitmap[] = { BMP_MIME_LIST }; +static const char* mime_image[] = { mime_png, mime_webp, mime_jpg, mime_tiff, BMP_MIME_LIST }; + +static const char mime_gnome_copied_files[] = "x-special/gnome-copied-files"; +static const char mime_mate_copied_files[] = "x-special/mate-copied-files"; + +static const char type_FileGroupDescriptorW[] = "FileGroupDescriptorW"; +static const char type_HtmlFormat[] = "HTML Format"; + +typedef struct +{ + FILE* responseFile; + UINT32 responseFormat; + char* responseMime; +} wlf_request; + +typedef struct +{ + const FILE* responseFile; + UINT32 responseFormat; + const char* responseMime; +} wlf_const_request; + +struct wlf_clipboard +{ + wlfContext* wfc; + rdpChannels* channels; + CliprdrClientContext* context; + wLog* log; + + UwacSeat* seat; + wClipboard* system; + + size_t numClientFormats; + CLIPRDR_FORMAT* clientFormats; + + size_t numServerFormats; + CLIPRDR_FORMAT* serverFormats; + + BOOL sync; + + CRITICAL_SECTION lock; + CliprdrFileContext* file; + + wQueue* request_queue; +}; + +static void wlf_request_free(void* rq) +{ + wlf_request* request = rq; + if (request) + { + free(request->responseMime); + if (request->responseFile) + (void)fclose(request->responseFile); + } + free(request); +} + +static wlf_request* wlf_request_new(void) +{ + return calloc(1, sizeof(wlf_request)); +} + +static void* wlf_request_clone(const void* oth) +{ + const wlf_request* other = (const wlf_request*)oth; + wlf_request* copy = wlf_request_new(); + if (!copy) + return nullptr; + *copy = *other; + if (other->responseMime) + { + copy->responseMime = _strdup(other->responseMime); + if (!copy->responseMime) + goto fail; + } + return copy; +fail: + wlf_request_free(copy); + return nullptr; +} + +static BOOL wlf_mime_is_file(const char* mime) +{ + if (strncmp(mime_uri_list, mime, sizeof(mime_uri_list)) == 0) + return TRUE; + if (strncmp(mime_gnome_copied_files, mime, sizeof(mime_gnome_copied_files)) == 0) + return TRUE; + if (strncmp(mime_mate_copied_files, mime, sizeof(mime_mate_copied_files)) == 0) + return TRUE; + return FALSE; +} + +static BOOL wlf_mime_is_text(const char* mime) +{ + for (size_t x = 0; x < ARRAYSIZE(mime_text); x++) + { + if (strcmp(mime, mime_text[x]) == 0) + return TRUE; + } + + return FALSE; +} + +static BOOL wlf_mime_is_image(const char* mime) +{ + for (size_t x = 0; x < ARRAYSIZE(mime_image); x++) + { + if (strcmp(mime, mime_image[x]) == 0) + return TRUE; + } + + return FALSE; +} + +static BOOL wlf_mime_is_html(const char* mime) +{ + return strcmp(mime, mime_html) == 0; +} + +static void wlf_cliprdr_free_server_formats(wfClipboard* clipboard) +{ + if (clipboard && clipboard->serverFormats) + { + for (size_t j = 0; j < clipboard->numServerFormats; j++) + { + CLIPRDR_FORMAT* format = &clipboard->serverFormats[j]; + free(format->formatName); + } + + free(clipboard->serverFormats); + clipboard->serverFormats = nullptr; + clipboard->numServerFormats = 0; + } + + if (clipboard) + UwacClipboardOfferDestroy(clipboard->seat); +} + +static void wlf_cliprdr_free_client_formats(wfClipboard* clipboard) +{ + if (clipboard && clipboard->numClientFormats) + { + for (size_t j = 0; j < clipboard->numClientFormats; j++) + { + CLIPRDR_FORMAT* format = &clipboard->clientFormats[j]; + free(format->formatName); + } + + free(clipboard->clientFormats); + clipboard->clientFormats = nullptr; + clipboard->numClientFormats = 0; + } + + if (clipboard) + UwacClipboardOfferDestroy(clipboard->seat); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wlf_cliprdr_send_client_format_list(wfClipboard* clipboard) +{ + WINPR_ASSERT(clipboard); + + const CLIPRDR_FORMAT_LIST formatList = { .common.msgFlags = 0, + .numFormats = (UINT32)clipboard->numClientFormats, + .formats = clipboard->clientFormats, + .common.msgType = CB_FORMAT_LIST }; + + if (!cliprdr_file_context_clear(clipboard->file)) + return ERROR_INTERNAL_ERROR; + + WLog_VRB(TAG, "-------------- client format list [%" PRIu32 "] ------------------", + formatList.numFormats); + for (UINT32 x = 0; x < formatList.numFormats; x++) + { + const CLIPRDR_FORMAT* format = &formatList.formats[x]; + WLog_VRB(TAG, "client announces %" PRIu32 " [%s][%s]", format->formatId, + ClipboardGetFormatIdString(format->formatId), format->formatName); + } + WINPR_ASSERT(clipboard->context); + WINPR_ASSERT(clipboard->context->ClientFormatList); + return clipboard->context->ClientFormatList(clipboard->context, &formatList); +} + +static void wfl_cliprdr_add_client_format_id(wfClipboard* clipboard, UINT32 formatId) +{ + CLIPRDR_FORMAT* format = nullptr; + const char* name = ClipboardGetFormatName(clipboard->system, formatId); + + for (size_t x = 0; x < clipboard->numClientFormats; x++) + { + format = &clipboard->clientFormats[x]; + + if (format->formatId == formatId) + return; + } + + format = realloc(clipboard->clientFormats, + (clipboard->numClientFormats + 1) * sizeof(CLIPRDR_FORMAT)); + + if (!format) + return; + + clipboard->clientFormats = format; + format = &clipboard->clientFormats[clipboard->numClientFormats++]; + format->formatId = formatId; + format->formatName = nullptr; + + if (name && (formatId >= CF_MAX)) + format->formatName = _strdup(name); +} + +static BOOL wlf_cliprdr_add_client_format(wfClipboard* clipboard, const char* mime) +{ + WINPR_ASSERT(mime); + ClipboardLock(clipboard->system); + if (wlf_mime_is_html(mime)) + { + UINT32 formatId = ClipboardGetFormatId(clipboard->system, type_HtmlFormat); + wfl_cliprdr_add_client_format_id(clipboard, formatId); + } + else if (wlf_mime_is_text(mime)) + { + wfl_cliprdr_add_client_format_id(clipboard, CF_TEXT); + wfl_cliprdr_add_client_format_id(clipboard, CF_OEMTEXT); + wfl_cliprdr_add_client_format_id(clipboard, CF_UNICODETEXT); + } + else if (wlf_mime_is_image(mime)) + { + for (size_t x = 0; x < ARRAYSIZE(mime_image); x++) + { + const char* mime_bmp = mime_image[x]; + UINT32 formatId = ClipboardGetFormatId(clipboard->system, mime_bmp); + if (formatId != 0) + wfl_cliprdr_add_client_format_id(clipboard, formatId); + } + wfl_cliprdr_add_client_format_id(clipboard, CF_DIB); + wfl_cliprdr_add_client_format_id(clipboard, CF_TIFF); + } + else if (wlf_mime_is_file(mime)) + { + const UINT32 fileFormatId = + ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW); + wfl_cliprdr_add_client_format_id(clipboard, fileFormatId); + } + + ClipboardUnlock(clipboard->system); + return (wlf_cliprdr_send_client_format_list(clipboard) == CHANNEL_RC_OK); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wlf_cliprdr_send_data_request(wfClipboard* clipboard, const wlf_const_request* rq) +{ + WINPR_ASSERT(rq); + + CLIPRDR_FORMAT_DATA_REQUEST request = { .requestedFormatId = rq->responseFormat }; + + if (!Queue_Enqueue(clipboard->request_queue, rq)) + return ERROR_INTERNAL_ERROR; + + WINPR_ASSERT(clipboard); + WINPR_ASSERT(clipboard->context); + WINPR_ASSERT(clipboard->context->ClientFormatDataRequest); + return clipboard->context->ClientFormatDataRequest(clipboard->context, &request); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wlf_cliprdr_send_data_response(wfClipboard* clipboard, const BYTE* data, size_t size) +{ + CLIPRDR_FORMAT_DATA_RESPONSE response = WINPR_C_ARRAY_INIT; + + if (size > UINT32_MAX) + return ERROR_INVALID_PARAMETER; + + response.common.msgFlags = (data) ? CB_RESPONSE_OK : CB_RESPONSE_FAIL; + response.common.dataLen = (UINT32)size; + response.requestedFormatData = data; + + WINPR_ASSERT(clipboard); + WINPR_ASSERT(clipboard->context); + WINPR_ASSERT(clipboard->context->ClientFormatDataResponse); + return clipboard->context->ClientFormatDataResponse(clipboard->context, &response); +} + +BOOL wlf_cliprdr_handle_event(wfClipboard* clipboard, const UwacClipboardEvent* event) +{ + if (!clipboard || !event) + return FALSE; + + if (!clipboard->context) + return TRUE; + + switch (event->type) + { + case UWAC_EVENT_CLIPBOARD_AVAILABLE: + clipboard->seat = event->seat; + return TRUE; + + case UWAC_EVENT_CLIPBOARD_OFFER: + WLog_Print(clipboard->log, WLOG_DEBUG, "client announces mime %s", event->mime); + return wlf_cliprdr_add_client_format(clipboard, event->mime); + + case UWAC_EVENT_CLIPBOARD_SELECT: + WLog_Print(clipboard->log, WLOG_DEBUG, "client announces new data"); + wlf_cliprdr_free_client_formats(clipboard); + return TRUE; + + default: + return FALSE; + } +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wlf_cliprdr_send_client_capabilities(wfClipboard* clipboard) +{ + WINPR_ASSERT(clipboard); + + CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet = { + .capabilitySetType = CB_CAPSTYPE_GENERAL, + .capabilitySetLength = 12, + .version = CB_CAPS_VERSION_2, + .generalFlags = + CB_USE_LONG_FORMAT_NAMES | cliprdr_file_context_current_flags(clipboard->file) + }; + CLIPRDR_CAPABILITIES capabilities = { .cCapabilitiesSets = 1, + .capabilitySets = + (CLIPRDR_CAPABILITY_SET*)&(generalCapabilitySet) }; + + WINPR_ASSERT(clipboard); + WINPR_ASSERT(clipboard->context); + WINPR_ASSERT(clipboard->context->ClientCapabilities); + return clipboard->context->ClientCapabilities(clipboard->context, &capabilities); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wlf_cliprdr_send_client_format_list_response(wfClipboard* clipboard, BOOL status) +{ + const CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse = { + .common.msgType = CB_FORMAT_LIST_RESPONSE, + .common.msgFlags = status ? CB_RESPONSE_OK : CB_RESPONSE_FAIL, + .common.dataLen = 0 + }; + WINPR_ASSERT(clipboard); + WINPR_ASSERT(clipboard->context); + WINPR_ASSERT(clipboard->context->ClientFormatListResponse); + return clipboard->context->ClientFormatListResponse(clipboard->context, &formatListResponse); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wlf_cliprdr_monitor_ready(CliprdrClientContext* context, + const CLIPRDR_MONITOR_READY* monitorReady) +{ + UINT ret = 0; + + WINPR_UNUSED(monitorReady); + WINPR_ASSERT(context); + WINPR_ASSERT(monitorReady); + + wfClipboard* clipboard = cliprdr_file_context_get_context(context->custom); + WINPR_ASSERT(clipboard); + + if ((ret = wlf_cliprdr_send_client_capabilities(clipboard)) != CHANNEL_RC_OK) + return ret; + + if ((ret = wlf_cliprdr_send_client_format_list(clipboard)) != CHANNEL_RC_OK) + return ret; + + clipboard->sync = TRUE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wlf_cliprdr_server_capabilities(CliprdrClientContext* context, + const CLIPRDR_CAPABILITIES* capabilities) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(capabilities); + + const BYTE* capsPtr = (const BYTE*)capabilities->capabilitySets; + WINPR_ASSERT(capsPtr); + + wfClipboard* clipboard = cliprdr_file_context_get_context(context->custom); + WINPR_ASSERT(clipboard); + + if (!cliprdr_file_context_remote_set_flags(clipboard->file, 0)) + return ERROR_INTERNAL_ERROR; + + for (UINT32 i = 0; i < capabilities->cCapabilitiesSets; i++) + { + const CLIPRDR_CAPABILITY_SET* caps = (const CLIPRDR_CAPABILITY_SET*)capsPtr; + + if (caps->capabilitySetType == CB_CAPSTYPE_GENERAL) + { + const CLIPRDR_GENERAL_CAPABILITY_SET* generalCaps = + (const CLIPRDR_GENERAL_CAPABILITY_SET*)caps; + + if (!cliprdr_file_context_remote_set_flags(clipboard->file, generalCaps->generalFlags)) + return ERROR_INTERNAL_ERROR; + } + + capsPtr += caps->capabilitySetLength; + } + + return CHANNEL_RC_OK; +} + +static UINT32 wlf_get_server_format_id(const wfClipboard* clipboard, const char* name) +{ + WINPR_ASSERT(clipboard); + WINPR_ASSERT(name); + + for (UINT32 x = 0; x < clipboard->numServerFormats; x++) + { + const CLIPRDR_FORMAT* format = &clipboard->serverFormats[x]; + if (!format->formatName) + continue; + if (strcmp(name, format->formatName) == 0) + return format->formatId; + } + return 0; +} + +static const char* wlf_get_server_format_name(const wfClipboard* clipboard, UINT32 formatId) +{ + WINPR_ASSERT(clipboard); + + for (UINT32 x = 0; x < clipboard->numServerFormats; x++) + { + const CLIPRDR_FORMAT* format = &clipboard->serverFormats[x]; + if (format->formatId == formatId) + return format->formatName; + } + return nullptr; +} + +static void wlf_cliprdr_transfer_data(UwacSeat* seat, void* context, const char* mime, int fd) +{ + wfClipboard* clipboard = (wfClipboard*)context; + WINPR_UNUSED(seat); + + EnterCriticalSection(&clipboard->lock); + + wlf_const_request request = WINPR_C_ARRAY_INIT; + if (wlf_mime_is_html(mime)) + { + request.responseMime = mime_html; + request.responseFormat = wlf_get_server_format_id(clipboard, type_HtmlFormat); + } + else if (wlf_mime_is_file(mime)) + { + request.responseMime = mime; + request.responseFormat = wlf_get_server_format_id(clipboard, type_FileGroupDescriptorW); + } + else if (wlf_mime_is_text(mime)) + { + request.responseMime = mime_text_plain; + request.responseFormat = CF_UNICODETEXT; + } + else if (wlf_mime_is_image(mime)) + { + request.responseMime = mime; + if (strcmp(mime, mime_tiff) == 0) + request.responseFormat = CF_TIFF; + else + request.responseFormat = CF_DIB; + } + + if (request.responseMime != nullptr) + { + request.responseFile = fdopen(fd, "w"); + + if (request.responseFile) + wlf_cliprdr_send_data_request(clipboard, &request); + else + WLog_Print(clipboard->log, WLOG_ERROR, + "failed to open clipboard file descriptor for MIME %s", + request.responseMime); + } + + LeaveCriticalSection(&clipboard->lock); +} + +static void wlf_cliprdr_cancel_data(UwacSeat* seat, void* context) +{ + wfClipboard* clipboard = (wfClipboard*)context; + + WINPR_UNUSED(seat); + WINPR_ASSERT(clipboard); + cliprdr_file_context_clear(clipboard->file); +} + +/** + * Called when the clipboard changes server side. + * + * Clear the local clipboard offer and replace it with a new one + * that announces the formats we get listed here. + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wlf_cliprdr_server_format_list(CliprdrClientContext* context, + const CLIPRDR_FORMAT_LIST* formatList) +{ + BOOL html = FALSE; + BOOL text = FALSE; + BOOL image = FALSE; + BOOL file = FALSE; + + if (!context || !context->custom) + return ERROR_INVALID_PARAMETER; + + wfClipboard* clipboard = cliprdr_file_context_get_context(context->custom); + WINPR_ASSERT(clipboard); + + wlf_cliprdr_free_server_formats(clipboard); + if (!cliprdr_file_context_clear(clipboard->file)) + return ERROR_INTERNAL_ERROR; + + if (!(clipboard->serverFormats = + (CLIPRDR_FORMAT*)calloc(formatList->numFormats, sizeof(CLIPRDR_FORMAT)))) + { + WLog_Print(clipboard->log, WLOG_ERROR, + "failed to allocate %" PRIuz " CLIPRDR_FORMAT structs", + clipboard->numServerFormats); + return CHANNEL_RC_NO_MEMORY; + } + + clipboard->numServerFormats = formatList->numFormats; + + if (!clipboard->seat) + { + WLog_Print(clipboard->log, WLOG_ERROR, + "clipboard->seat=nullptr, check your client implementation"); + return ERROR_INTERNAL_ERROR; + } + + for (UINT32 i = 0; i < formatList->numFormats; i++) + { + const CLIPRDR_FORMAT* format = &formatList->formats[i]; + CLIPRDR_FORMAT* srvFormat = &clipboard->serverFormats[i]; + srvFormat->formatId = format->formatId; + + if (format->formatName) + { + srvFormat->formatName = _strdup(format->formatName); + + if (!srvFormat->formatName) + { + wlf_cliprdr_free_server_formats(clipboard); + return CHANNEL_RC_NO_MEMORY; + } + } + + if (format->formatName) + { + if (strcmp(format->formatName, type_HtmlFormat) == 0) + { + text = TRUE; + html = TRUE; + } + else if (strcmp(format->formatName, type_FileGroupDescriptorW) == 0) + { + file = TRUE; + text = TRUE; + } + } + else + { + switch (format->formatId) + { + case CF_TEXT: + case CF_OEMTEXT: + case CF_UNICODETEXT: + text = TRUE; + break; + + case CF_DIB: + image = TRUE; + break; + + default: + break; + } + } + } + + if (html) + { + UwacClipboardOfferCreate(clipboard->seat, mime_html); + } + + if (file && cliprdr_file_context_has_local_support(clipboard->file)) + { + UwacClipboardOfferCreate(clipboard->seat, mime_uri_list); + UwacClipboardOfferCreate(clipboard->seat, mime_gnome_copied_files); + UwacClipboardOfferCreate(clipboard->seat, mime_mate_copied_files); + } + + if (text) + { + for (size_t x = 0; x < ARRAYSIZE(mime_text); x++) + UwacClipboardOfferCreate(clipboard->seat, mime_text[x]); + } + + if (image) + { + for (size_t x = 0; x < ARRAYSIZE(mime_image); x++) + UwacClipboardOfferCreate(clipboard->seat, mime_image[x]); + } + + UwacClipboardOfferAnnounce(clipboard->seat, clipboard, wlf_cliprdr_transfer_data, + wlf_cliprdr_cancel_data); + return wlf_cliprdr_send_client_format_list_response(clipboard, TRUE); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +wlf_cliprdr_server_format_list_response(WINPR_ATTR_UNUSED CliprdrClientContext* context, + const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(formatListResponse); + + if (formatListResponse->common.msgFlags & CB_RESPONSE_FAIL) + WLog_WARN(TAG, "format list update failed"); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +wlf_cliprdr_server_format_data_request(CliprdrClientContext* context, + const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest) +{ + UINT rc = CHANNEL_RC_OK; + char* data = nullptr; + size_t size = 0; + const char* mime = nullptr; + UINT32 formatId = 0; + UINT32 localFormatId = 0; + wfClipboard* clipboard = nullptr; + + UINT32 dsize = 0; + BYTE* ddata = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(formatDataRequest); + + localFormatId = formatId = formatDataRequest->requestedFormatId; + clipboard = cliprdr_file_context_get_context(context->custom); + WINPR_ASSERT(clipboard); + + ClipboardLock(clipboard->system); + const UINT32 fileFormatId = ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW); + const UINT32 htmlFormatId = ClipboardGetFormatId(clipboard->system, type_HtmlFormat); + + switch (formatId) + { + case CF_TEXT: + case CF_OEMTEXT: + case CF_UNICODETEXT: + localFormatId = ClipboardGetFormatId(clipboard->system, mime_text_plain); + mime = mime_text_utf8; + break; + + case CF_DIB: + case CF_DIBV5: + mime = mime_bitmap[0]; + break; + + case CF_TIFF: + mime = mime_tiff; + break; + + default: + if (formatId == fileFormatId) + { + localFormatId = ClipboardGetFormatId(clipboard->system, mime_uri_list); + mime = mime_uri_list; + } + else if (formatId == htmlFormatId) + { + localFormatId = ClipboardGetFormatId(clipboard->system, mime_html); + mime = mime_html; + } + else + goto fail; + break; + } + + data = UwacClipboardDataGet(clipboard->seat, mime, &size); + + if (!data || (size > UINT32_MAX)) + goto fail; + + if (fileFormatId == formatId) + { + if (!cliprdr_file_context_update_client_data(clipboard->file, data, size)) + goto fail; + } + + { + const BOOL res = ClipboardSetData(clipboard->system, localFormatId, data, (UINT32)size); + free(data); + + UINT32 len = 0; + data = nullptr; + if (res) + data = ClipboardGetData(clipboard->system, formatId, &len); + + if (!res || !data) + goto fail; + + if (fileFormatId == formatId) + { + const UINT32 flags = cliprdr_file_context_remote_get_flags(clipboard->file); + const UINT32 error = cliprdr_serialize_file_list_ex( + flags, (const FILEDESCRIPTORW*)data, len / sizeof(FILEDESCRIPTORW), &ddata, &dsize); + if (error) + goto fail; + } + } +fail: + ClipboardUnlock(clipboard->system); + rc = wlf_cliprdr_send_data_response(clipboard, ddata, dsize); + free(data); + return rc; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +wlf_cliprdr_server_format_data_response(CliprdrClientContext* context, + const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse) +{ + UINT rc = ERROR_INTERNAL_ERROR; + + WINPR_ASSERT(context); + WINPR_ASSERT(formatDataResponse); + + const UINT32 size = formatDataResponse->common.dataLen; + const BYTE* data = formatDataResponse->requestedFormatData; + + wfClipboard* clipboard = cliprdr_file_context_get_context(context->custom); + WINPR_ASSERT(clipboard); + + wlf_request* request = Queue_Dequeue(clipboard->request_queue); + if (!request) + goto fail; + + rc = CHANNEL_RC_OK; + if (formatDataResponse->common.msgFlags & CB_RESPONSE_FAIL) + { + WLog_WARN(TAG, "clipboard data request for format %" PRIu32 " [%s], mime %s failed", + request->responseFormat, ClipboardGetFormatIdString(request->responseFormat), + request->responseMime); + goto fail; + } + rc = ERROR_INTERNAL_ERROR; + + ClipboardLock(clipboard->system); + EnterCriticalSection(&clipboard->lock); + { + BYTE* cdata = nullptr; + UINT32 srcFormatId = 0; + UINT32 dstFormatId = 0; + switch (request->responseFormat) + { + case CF_TEXT: + case CF_OEMTEXT: + case CF_UNICODETEXT: + srcFormatId = request->responseFormat; + dstFormatId = ClipboardGetFormatId(clipboard->system, request->responseMime); + break; + + case CF_DIB: + case CF_DIBV5: + srcFormatId = request->responseFormat; + dstFormatId = ClipboardGetFormatId(clipboard->system, request->responseMime); + break; + + default: + { + const char* name = wlf_get_server_format_name(clipboard, request->responseFormat); + if (name) + { + if (strcmp(type_FileGroupDescriptorW, name) == 0) + { + srcFormatId = + ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW); + dstFormatId = + ClipboardGetFormatId(clipboard->system, request->responseMime); + + if (!cliprdr_file_context_update_server_data(clipboard->file, + clipboard->system, data, size)) + goto unlock; + } + else if (strcmp(type_HtmlFormat, name) == 0) + { + srcFormatId = ClipboardGetFormatId(clipboard->system, type_HtmlFormat); + dstFormatId = + ClipboardGetFormatId(clipboard->system, request->responseMime); + } + } + } + break; + } + { + UINT32 len = 0; + + { + const BOOL sres = ClipboardSetData(clipboard->system, srcFormatId, data, size); + if (sres) + cdata = ClipboardGetData(clipboard->system, dstFormatId, &len); + + if (!sres || !cdata) + goto unlock; + } + + if (request->responseFile) + { + const size_t res = fwrite(cdata, 1, len, request->responseFile); + if (res == len) + rc = CHANNEL_RC_OK; + } + else + rc = CHANNEL_RC_OK; + } + + unlock: + free(cdata); + } + ClipboardUnlock(clipboard->system); + LeaveCriticalSection(&clipboard->lock); +fail: + wlf_request_free(request); + return rc; +} + +wfClipboard* wlf_clipboard_new(wlfContext* wfc) +{ + rdpChannels* channels = nullptr; + wfClipboard* clipboard = nullptr; + + WINPR_ASSERT(wfc); + + clipboard = (wfClipboard*)calloc(1, sizeof(wfClipboard)); + + if (!clipboard) + goto fail; + + InitializeCriticalSection(&clipboard->lock); + clipboard->wfc = wfc; + channels = wfc->common.context.channels; + clipboard->log = WLog_Get(TAG); + clipboard->channels = channels; + clipboard->system = ClipboardCreate(); + if (!clipboard->system) + goto fail; + + clipboard->file = cliprdr_file_context_new(clipboard); + if (!clipboard->file) + goto fail; + + if (!cliprdr_file_context_set_locally_available(clipboard->file, TRUE)) + goto fail; + + clipboard->request_queue = Queue_New(TRUE, -1, -1); + if (!clipboard->request_queue) + goto fail; + + { + wObject* obj = Queue_Object(clipboard->request_queue); + WINPR_ASSERT(obj); + obj->fnObjectFree = wlf_request_free; + obj->fnObjectNew = wlf_request_clone; + } + + return clipboard; + +fail: + wlf_clipboard_free(clipboard); + return nullptr; +} + +void wlf_clipboard_free(wfClipboard* clipboard) +{ + if (!clipboard) + return; + + cliprdr_file_context_free(clipboard->file); + + wlf_cliprdr_free_server_formats(clipboard); + wlf_cliprdr_free_client_formats(clipboard); + ClipboardDestroy(clipboard->system); + + EnterCriticalSection(&clipboard->lock); + + Queue_Free(clipboard->request_queue); + LeaveCriticalSection(&clipboard->lock); + DeleteCriticalSection(&clipboard->lock); + free(clipboard); +} + +BOOL wlf_cliprdr_init(wfClipboard* clipboard, CliprdrClientContext* cliprdr) +{ + WINPR_ASSERT(clipboard); + WINPR_ASSERT(cliprdr); + + clipboard->context = cliprdr; + cliprdr->MonitorReady = wlf_cliprdr_monitor_ready; + cliprdr->ServerCapabilities = wlf_cliprdr_server_capabilities; + cliprdr->ServerFormatList = wlf_cliprdr_server_format_list; + cliprdr->ServerFormatListResponse = wlf_cliprdr_server_format_list_response; + cliprdr->ServerFormatDataRequest = wlf_cliprdr_server_format_data_request; + cliprdr->ServerFormatDataResponse = wlf_cliprdr_server_format_data_response; + + return cliprdr_file_context_init(clipboard->file, cliprdr); +} + +BOOL wlf_cliprdr_uninit(wfClipboard* clipboard, CliprdrClientContext* cliprdr) +{ + WINPR_ASSERT(clipboard); + if (!cliprdr_file_context_uninit(clipboard->file, cliprdr)) + return FALSE; + + if (cliprdr) + cliprdr->custom = nullptr; + + return TRUE; +} diff --git a/third_party/FreeRDP/client/Wayland/wlf_cliprdr.h b/third_party/FreeRDP/client/Wayland/wlf_cliprdr.h new file mode 100644 index 0000000..07d58bc --- /dev/null +++ b/third_party/FreeRDP/client/Wayland/wlf_cliprdr.h @@ -0,0 +1,36 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Clipboard Redirection + * + * Copyright 2018 Armin Novak + * Copyright 2018 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. + */ + +#ifndef FREERDP_CLIENT_WAYLAND_CLIPRDR_H +#define FREERDP_CLIENT_WAYLAND_CLIPRDR_H + +#include "wlfreerdp.h" + +#include + +wfClipboard* wlf_clipboard_new(wlfContext* wfc); +void wlf_clipboard_free(wfClipboard* clipboard); + +BOOL wlf_cliprdr_init(wfClipboard* clipboard, CliprdrClientContext* cliprdr); +BOOL wlf_cliprdr_uninit(wfClipboard* clipboard, CliprdrClientContext* cliprdr); + +BOOL wlf_cliprdr_handle_event(wfClipboard* clipboard, const UwacClipboardEvent* event); + +#endif /* FREERDP_CLIENT_WAYLAND_CLIPRDR_H */ diff --git a/third_party/FreeRDP/client/Wayland/wlf_disp.c b/third_party/FreeRDP/client/Wayland/wlf_disp.c new file mode 100644 index 0000000..40a369a --- /dev/null +++ b/third_party/FreeRDP/client/Wayland/wlf_disp.c @@ -0,0 +1,481 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Display Control Channel + * + * Copyright 2018 Armin Novak + * Copyright 2018 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 +#include + +#include + +#include "wlf_disp.h" + +#define TAG CLIENT_TAG("wayland.disp") + +#define RESIZE_MIN_DELAY_NS 200000000UL /* minimum delay in ns between two resizes */ + +struct s_wlfDispContext +{ + wlfContext* wlc; + DispClientContext* disp; + BOOL haveXRandr; + int eventBase, errorBase; + int lastSentWidth, lastSentHeight; + UINT64 lastSentDate; + int targetWidth, targetHeight; + BOOL activated; + BOOL waitingResize; + BOOL fullscreen; + UINT16 lastSentDesktopOrientation; + UINT32 lastSentDesktopScaleFactor; + UINT32 lastSentDeviceScaleFactor; + FreeRDP_TimerID timerID; +}; + +static BOOL wlf_disp_sendResize(wlfDispContext* wlfDisp, BOOL fromTimer); +static BOOL wlf_disp_check_context(void* context, wlfContext** ppwlc, wlfDispContext** ppwlfDisp, + rdpSettings** ppSettings); +static UINT wlf_disp_sendLayout(DispClientContext* disp, const rdpMonitor* monitors, + size_t nmonitors); + +static BOOL wlf_disp_settings_changed(wlfDispContext* wlfDisp) +{ + rdpSettings* settings = nullptr; + + WINPR_ASSERT(wlfDisp); + WINPR_ASSERT(wlfDisp->wlc); + + settings = wlfDisp->wlc->common.context.settings; + WINPR_ASSERT(settings); + + if (wlfDisp->lastSentWidth != wlfDisp->targetWidth) + return TRUE; + + if (wlfDisp->lastSentHeight != wlfDisp->targetHeight) + return TRUE; + + if (wlfDisp->lastSentDesktopOrientation != + freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation)) + return TRUE; + + if (wlfDisp->lastSentDesktopScaleFactor != + freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor)) + return TRUE; + + if (wlfDisp->lastSentDeviceScaleFactor != + freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor)) + return TRUE; + + if (wlfDisp->fullscreen != wlfDisp->wlc->fullscreen) + return TRUE; + + return FALSE; +} + +static BOOL wlf_update_last_sent(wlfDispContext* wlfDisp) +{ + rdpSettings* settings = nullptr; + + WINPR_ASSERT(wlfDisp); + WINPR_ASSERT(wlfDisp->wlc); + + settings = wlfDisp->wlc->common.context.settings; + WINPR_ASSERT(settings); + + wlfDisp->lastSentDate = winpr_GetTickCount64NS(); + wlfDisp->lastSentWidth = wlfDisp->targetWidth; + wlfDisp->lastSentHeight = wlfDisp->targetHeight; + wlfDisp->lastSentDesktopOrientation = + freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation); + wlfDisp->lastSentDesktopScaleFactor = + freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor); + wlfDisp->lastSentDeviceScaleFactor = + freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor); + wlfDisp->fullscreen = wlfDisp->wlc->fullscreen; + return TRUE; +} + +static uint64_t wlf_disp_OnTimer(rdpContext* context, WINPR_ATTR_UNUSED void* userdata, + WINPR_ATTR_UNUSED FreeRDP_TimerID timerID, + WINPR_ATTR_UNUSED uint64_t timestamp, uint64_t interval) +{ + wlfContext* wlc = nullptr; + wlfDispContext* wlfDisp = nullptr; + rdpSettings* settings = nullptr; + + if (!wlf_disp_check_context(context, &wlc, &wlfDisp, &settings)) + return interval; + + if (!wlfDisp->activated || freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)) + return interval; + + wlf_disp_sendResize(wlfDisp, TRUE); + wlfDisp->timerID = 0; + return 0; +} + +static BOOL update_timer(wlfDispContext* wlfDisp, uint64_t intervalNS) +{ + WINPR_ASSERT(wlfDisp); + + if (wlfDisp->timerID == 0) + { + rdpContext* context = &wlfDisp->wlc->common.context; + + wlfDisp->timerID = freerdp_timer_add(context, intervalNS, wlf_disp_OnTimer, nullptr, true); + } + return TRUE; +} + +BOOL wlf_disp_sendResize(wlfDispContext* wlfDisp, BOOL fromTimer) +{ + DISPLAY_CONTROL_MONITOR_LAYOUT layout; + wlfContext* wlc = nullptr; + rdpSettings* settings = nullptr; + + if (!wlfDisp || !wlfDisp->wlc) + return FALSE; + + wlc = wlfDisp->wlc; + settings = wlc->common.context.settings; + + if (!settings) + return FALSE; + + if (!wlfDisp->activated || !wlfDisp->disp) + return update_timer(wlfDisp, RESIZE_MIN_DELAY_NS); + + const uint64_t now = winpr_GetTickCount64NS(); + const uint64_t diff = now - wlfDisp->lastSentDate; + if (diff < RESIZE_MIN_DELAY_NS) + return update_timer(wlfDisp, RESIZE_MIN_DELAY_NS); + + if (!fromTimer && (wlfDisp->timerID != 0)) + return TRUE; + + if (!wlf_disp_settings_changed(wlfDisp)) + return TRUE; + + if (wlc->fullscreen && (freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount) > 0)) + { + const rdpMonitor* monitors = + freerdp_settings_get_pointer(settings, FreeRDP_MonitorDefArray); + const size_t nmonitors = freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount); + if (wlf_disp_sendLayout(wlfDisp->disp, monitors, nmonitors) != CHANNEL_RC_OK) + return FALSE; + } + else + { + wlfDisp->waitingResize = TRUE; + layout.Flags = DISPLAY_CONTROL_MONITOR_PRIMARY; + layout.Top = layout.Left = 0; + layout.Width = WINPR_ASSERTING_INT_CAST(uint32_t, wlfDisp->targetWidth); + layout.Height = WINPR_ASSERTING_INT_CAST(uint32_t, wlfDisp->targetHeight); + layout.Orientation = freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation); + layout.DesktopScaleFactor = + freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor); + layout.DeviceScaleFactor = freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor); + layout.PhysicalWidth = WINPR_ASSERTING_INT_CAST(uint32_t, wlfDisp->targetWidth); + layout.PhysicalHeight = WINPR_ASSERTING_INT_CAST(uint32_t, wlfDisp->targetHeight); + + if (IFCALLRESULT(CHANNEL_RC_OK, wlfDisp->disp->SendMonitorLayout, wlfDisp->disp, 1, + &layout) != CHANNEL_RC_OK) + return FALSE; + } + return wlf_update_last_sent(wlfDisp); +} + +static BOOL wlf_disp_set_window_resizable(WINPR_ATTR_UNUSED wlfDispContext* wlfDisp) +{ + WLog_ERR("TODO", "TODO: implement"); + return TRUE; +} + +BOOL wlf_disp_check_context(void* context, wlfContext** ppwlc, wlfDispContext** ppwlfDisp, + rdpSettings** ppSettings) +{ + wlfContext* wlc = nullptr; + + if (!context) + return FALSE; + + wlc = (wlfContext*)context; + + if (!(wlc->disp)) + return FALSE; + + if (!wlc->common.context.settings) + return FALSE; + + *ppwlc = wlc; + *ppwlfDisp = wlc->disp; + *ppSettings = wlc->common.context.settings; + return TRUE; +} + +static void wlf_disp_OnActivated(void* context, const ActivatedEventArgs* e) +{ + wlfContext* wlc = nullptr; + wlfDispContext* wlfDisp = nullptr; + rdpSettings* settings = nullptr; + + if (!wlf_disp_check_context(context, &wlc, &wlfDisp, &settings)) + return; + + wlfDisp->waitingResize = FALSE; + + if (wlfDisp->activated && !freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)) + { + wlf_disp_set_window_resizable(wlfDisp); + + if (e->firstActivation) + return; + + wlf_disp_sendResize(wlfDisp, FALSE); + } +} + +static void wlf_disp_OnGraphicsReset(void* context, const GraphicsResetEventArgs* e) +{ + wlfContext* wlc = nullptr; + wlfDispContext* wlfDisp = nullptr; + rdpSettings* settings = nullptr; + + WINPR_UNUSED(e); + if (!wlf_disp_check_context(context, &wlc, &wlfDisp, &settings)) + return; + + wlfDisp->waitingResize = FALSE; + + if (wlfDisp->activated && !freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)) + { + wlf_disp_set_window_resizable(wlfDisp); + wlf_disp_sendResize(wlfDisp, FALSE); + } +} + +wlfDispContext* wlf_disp_new(wlfContext* wlc) +{ + if (!wlc || !wlc->common.context.settings || !wlc->common.context.pubSub) + return nullptr; + + rdpSettings* settings = wlc->common.context.settings; + wPubSub* pubSub = wlc->common.context.pubSub; + + if (PubSub_SubscribeActivated(pubSub, wlf_disp_OnActivated) < 0) + return nullptr; + if (PubSub_SubscribeGraphicsReset(pubSub, wlf_disp_OnGraphicsReset) < 0) + return nullptr; + + wlfDispContext* ret = calloc(1, sizeof(wlfDispContext)); + + if (!ret) + return nullptr; + + ret->wlc = wlc; + ret->lastSentWidth = ret->targetWidth = + WINPR_ASSERTING_INT_CAST(int, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth)); + ret->lastSentHeight = ret->targetHeight = + WINPR_ASSERTING_INT_CAST(int, freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)); + return ret; +} + +void wlf_disp_free(wlfDispContext* disp) +{ + if (!disp) + return; + + if (disp->wlc) + { + wPubSub* pubSub = disp->wlc->common.context.pubSub; + PubSub_UnsubscribeActivated(pubSub, wlf_disp_OnActivated); + PubSub_UnsubscribeGraphicsReset(pubSub, wlf_disp_OnGraphicsReset); + } + + free(disp); +} + +UINT wlf_disp_sendLayout(DispClientContext* disp, const rdpMonitor* monitors, size_t nmonitors) +{ + UINT ret = CHANNEL_RC_OK; + DISPLAY_CONTROL_MONITOR_LAYOUT* layouts = nullptr; + wlfDispContext* wlfDisp = nullptr; + rdpSettings* settings = nullptr; + + WINPR_ASSERT(disp); + WINPR_ASSERT(monitors); + WINPR_ASSERT(nmonitors > 0); + WINPR_ASSERT(nmonitors <= UINT32_MAX); + + wlfDisp = (wlfDispContext*)disp->custom; + WINPR_ASSERT(wlfDisp); + WINPR_ASSERT(wlfDisp->wlc); + + settings = wlfDisp->wlc->common.context.settings; + WINPR_ASSERT(settings); + + layouts = calloc(nmonitors, sizeof(DISPLAY_CONTROL_MONITOR_LAYOUT)); + + if (!layouts) + return CHANNEL_RC_NO_MEMORY; + + for (size_t i = 0; i < nmonitors; i++) + { + const rdpMonitor* monitor = &monitors[i]; + DISPLAY_CONTROL_MONITOR_LAYOUT* layout = &layouts[i]; + + layout->Flags = (monitor->is_primary ? DISPLAY_CONTROL_MONITOR_PRIMARY : 0); + layout->Left = monitor->x; + layout->Top = monitor->y; + layout->Width = WINPR_ASSERTING_INT_CAST(UINT32, monitor->width); + layout->Height = WINPR_ASSERTING_INT_CAST(UINT32, monitor->height); + layout->Orientation = ORIENTATION_LANDSCAPE; + layout->PhysicalWidth = monitor->attributes.physicalWidth; + layout->PhysicalHeight = monitor->attributes.physicalHeight; + + switch (monitor->attributes.orientation) + { + case 90: + layout->Orientation = ORIENTATION_PORTRAIT; + break; + + case 180: + layout->Orientation = ORIENTATION_LANDSCAPE_FLIPPED; + break; + + case 270: + layout->Orientation = ORIENTATION_PORTRAIT_FLIPPED; + break; + + case 0: + default: + /* MS-RDPEDISP - 2.2.2.2.1: + * Orientation (4 bytes): A 32-bit unsigned integer that specifies the + * orientation of the monitor in degrees. Valid values are 0, 90, 180 + * or 270 + * + * So we default to ORIENTATION_LANDSCAPE + */ + layout->Orientation = ORIENTATION_LANDSCAPE; + break; + } + + layout->DesktopScaleFactor = + freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor); + layout->DeviceScaleFactor = + freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor); + } + + ret = IFCALLRESULT(CHANNEL_RC_OK, disp->SendMonitorLayout, disp, (UINT32)nmonitors, layouts); + free(layouts); + return ret; +} + +BOOL wlf_disp_handle_configure(wlfDispContext* disp, int32_t width, int32_t height) +{ + if (!disp) + return FALSE; + + disp->targetWidth = width; + disp->targetHeight = height; + return wlf_disp_sendResize(disp, FALSE); +} + +static UINT wlf_DisplayControlCaps(DispClientContext* disp, UINT32 maxNumMonitors, + UINT32 maxMonitorAreaFactorA, UINT32 maxMonitorAreaFactorB) +{ + /* we're called only if dynamic resolution update is activated */ + wlfDispContext* wlfDisp = nullptr; + rdpSettings* settings = nullptr; + + WINPR_ASSERT(disp); + + wlfDisp = (wlfDispContext*)disp->custom; + WINPR_ASSERT(wlfDisp); + WINPR_ASSERT(wlfDisp->wlc); + + settings = wlfDisp->wlc->common.context.settings; + WINPR_ASSERT(settings); + + WLog_DBG(TAG, + "DisplayControlCapsPdu: MaxNumMonitors: %" PRIu32 " MaxMonitorAreaFactorA: %" PRIu32 + " MaxMonitorAreaFactorB: %" PRIu32 "", + maxNumMonitors, maxMonitorAreaFactorA, maxMonitorAreaFactorB); + wlfDisp->activated = TRUE; + + if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)) + return CHANNEL_RC_OK; + + WLog_DBG(TAG, "DisplayControlCapsPdu: setting the window as resizable"); + return wlf_disp_set_window_resizable(wlfDisp) ? CHANNEL_RC_OK : CHANNEL_RC_NO_MEMORY; +} + +BOOL wlf_disp_init(wlfDispContext* wlfDisp, DispClientContext* disp) +{ + rdpSettings* settings = nullptr; + + if (!wlfDisp || !wlfDisp->wlc || !disp) + return FALSE; + + settings = wlfDisp->wlc->common.context.settings; + + if (!settings) + return FALSE; + + wlfDisp->disp = disp; + disp->custom = (void*)wlfDisp; + + if (freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate)) + { + disp->DisplayControlCaps = wlf_DisplayControlCaps; + } + + return TRUE; +} + +BOOL wlf_disp_uninit(wlfDispContext* wlfDisp, DispClientContext* disp) +{ + if (!wlfDisp || !disp) + return FALSE; + + wlfDisp->disp = nullptr; + return TRUE; +} + +int wlf_list_monitors(wlfContext* wlc) +{ + uint32_t nmonitors = UwacDisplayGetNbOutputs(wlc->display); + + for (uint32_t i = 0; i < nmonitors; i++) + { + const UwacOutput* monitor = + UwacDisplayGetOutput(wlc->display, WINPR_ASSERTING_INT_CAST(int, i)); + UwacSize resolution; + UwacPosition pos; + + if (!monitor) + continue; + UwacOutputGetPosition(monitor, &pos); + UwacOutputGetResolution(monitor, &resolution); + + printf(" %s [%u] %dx%d\t+%d+%d\n", (i == 0) ? "*" : " ", i, resolution.width, + resolution.height, pos.x, pos.y); + } + + return 0; +} diff --git a/third_party/FreeRDP/client/Wayland/wlf_disp.h b/third_party/FreeRDP/client/Wayland/wlf_disp.h new file mode 100644 index 0000000..d3c5d92 --- /dev/null +++ b/third_party/FreeRDP/client/Wayland/wlf_disp.h @@ -0,0 +1,38 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Display Control Channel + * + * Copyright 2018 Armin Novak + * Copyright 2018 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. + */ +#ifndef FREERDP_CLIENT_WAYLAND_DISP_H +#define FREERDP_CLIENT_WAYLAND_DISP_H + +#include +#include + +#include "wlfreerdp.h" + +FREERDP_API BOOL wlf_disp_init(wlfDispContext* wlfDisp, DispClientContext* disp); +FREERDP_API BOOL wlf_disp_uninit(wlfDispContext* wlfDisp, DispClientContext* disp); + +wlfDispContext* wlf_disp_new(wlfContext* wlc); +void wlf_disp_free(wlfDispContext* disp); +BOOL wlf_disp_handle_configure(wlfDispContext* disp, int32_t width, int32_t height); +void wlf_disp_resized(wlfDispContext* disp); + +int wlf_list_monitors(wlfContext* wlc); + +#endif /* FREERDP_CLIENT_WAYLAND_DISP_H */ diff --git a/third_party/FreeRDP/client/Wayland/wlf_input.c b/third_party/FreeRDP/client/Wayland/wlf_input.c new file mode 100644 index 0000000..27a8b70 --- /dev/null +++ b/third_party/FreeRDP/client/Wayland/wlf_input.c @@ -0,0 +1,452 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Input + * + * Copyright 2014 Manuel Bachmann + * Copyright 2015 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include + +#include +#include + +#include +#include +#include + +#include "wlfreerdp.h" +#include "wlf_input.h" + +static BOOL scale_signed_coordinates(rdpContext* context, int32_t* x, int32_t* y, + BOOL fromLocalToRDP) +{ + BOOL rc = 0; + UINT32 ux = 0; + UINT32 uy = 0; + WINPR_ASSERT(context); + WINPR_ASSERT(x); + WINPR_ASSERT(y); + WINPR_ASSERT(*x >= 0); + WINPR_ASSERT(*y >= 0); + + ux = (UINT32)*x; + uy = (UINT32)*y; + rc = wlf_scale_coordinates(context, &ux, &uy, fromLocalToRDP); + WINPR_ASSERT(ux < INT32_MAX); + WINPR_ASSERT(uy < INT32_MAX); + *x = (int32_t)ux; + *y = (int32_t)uy; + return rc; +} + +BOOL wlf_handle_pointer_enter(freerdp* instance, const UwacPointerEnterLeaveEvent* ev) +{ + uint32_t x = 0; + uint32_t y = 0; + rdpClientContext* cctx = nullptr; + + if (!instance || !ev) + return FALSE; + + x = ev->x; + y = ev->y; + + if (!wlf_scale_coordinates(instance->context, &x, &y, TRUE)) + return FALSE; + + cctx = (rdpClientContext*)instance->context; + return freerdp_client_send_button_event(cctx, FALSE, PTR_FLAGS_MOVE, + WINPR_ASSERTING_INT_CAST(int, x), + WINPR_ASSERTING_INT_CAST(int, y)); +} + +BOOL wlf_handle_pointer_motion(freerdp* instance, const UwacPointerMotionEvent* ev) +{ + rdpClientContext* cctx = nullptr; + + if (!instance || !ev) + return FALSE; + + cctx = (rdpClientContext*)instance->context; + WINPR_ASSERT(cctx); + + uint32_t x = ev->x; + uint32_t y = ev->y; + + if (!wlf_scale_coordinates(instance->context, &x, &y, TRUE)) + return FALSE; + + return freerdp_client_send_button_event(cctx, FALSE, PTR_FLAGS_MOVE, + WINPR_ASSERTING_INT_CAST(int32_t, x), + WINPR_ASSERTING_INT_CAST(int32_t, y)); +} + +BOOL wlf_handle_pointer_buttons(freerdp* instance, const UwacPointerButtonEvent* ev) +{ + rdpClientContext* cctx = nullptr; + UINT16 flags = 0; + UINT16 xflags = 0; + + if (!instance || !ev) + return FALSE; + + cctx = (rdpClientContext*)instance->context; + WINPR_ASSERT(cctx); + + uint32_t x = ev->x; + uint32_t y = ev->y; + + if (!wlf_scale_coordinates(instance->context, &x, &y, TRUE)) + return FALSE; + + if (ev->state == WL_POINTER_BUTTON_STATE_PRESSED) + { + flags |= PTR_FLAGS_DOWN; + xflags |= PTR_XFLAGS_DOWN; + } + + switch (ev->button) + { + case BTN_LEFT: + flags |= PTR_FLAGS_BUTTON1; + break; + + case BTN_RIGHT: + flags |= PTR_FLAGS_BUTTON2; + break; + + case BTN_MIDDLE: + flags |= PTR_FLAGS_BUTTON3; + break; + + case BTN_SIDE: + xflags |= PTR_XFLAGS_BUTTON1; + break; + + case BTN_EXTRA: + xflags |= PTR_XFLAGS_BUTTON2; + break; + + default: + return TRUE; + } + + const INT32 cx = WINPR_ASSERTING_INT_CAST(int32_t, x); + const INT32 cy = WINPR_ASSERTING_INT_CAST(int32_t, y); + + if ((flags & ~PTR_FLAGS_DOWN) != 0) + return freerdp_client_send_button_event(cctx, FALSE, flags, cx, cy); + + if ((xflags & ~PTR_XFLAGS_DOWN) != 0) + return freerdp_client_send_extended_button_event(cctx, FALSE, xflags, cx, cy); + + return FALSE; +} + +BOOL wlf_handle_pointer_axis(freerdp* instance, const UwacPointerAxisEvent* ev) +{ + wlfContext* context = nullptr; + if (!instance || !instance->context || !ev) + return FALSE; + + context = (wlfContext*)instance->context; + return ArrayList_Append(context->events, ev); +} + +BOOL wlf_handle_pointer_axis_discrete(freerdp* instance, const UwacPointerAxisEvent* ev) +{ + wlfContext* context = nullptr; + if (!instance || !instance->context || !ev) + return FALSE; + + context = (wlfContext*)instance->context; + return ArrayList_Append(context->events, ev); +} + +static BOOL wlf_handle_wheel(freerdp* instance, uint32_t x, uint32_t y, uint32_t axis, + int32_t value) +{ + rdpClientContext* cctx = nullptr; + UINT16 flags = 0; + int32_t direction = 0; + uint32_t avalue = (uint32_t)abs(value); + + WINPR_ASSERT(instance); + + cctx = (rdpClientContext*)instance->context; + WINPR_ASSERT(cctx); + + if (!wlf_scale_coordinates(instance->context, &x, &y, TRUE)) + return FALSE; + + direction = value; + switch (axis) + { + case WL_POINTER_AXIS_VERTICAL_SCROLL: + flags |= PTR_FLAGS_WHEEL; + if (direction > 0) + flags |= PTR_FLAGS_WHEEL_NEGATIVE; + break; + + case WL_POINTER_AXIS_HORIZONTAL_SCROLL: + flags |= PTR_FLAGS_HWHEEL; + if (direction < 0) + flags |= PTR_FLAGS_WHEEL_NEGATIVE; + break; + + default: + return FALSE; + } + + /* Wheel rotation steps: + * + * positive: 0 ... 0xFF -> slow ... fast + * negative: 0 ... 0xFF -> fast ... slow + */ + + while (avalue > 0) + { + const UINT16 cval = (avalue > 0xFF) ? 0xFF : (UINT16)avalue; + UINT16 cflags = flags | cval; + /* Convert negative values to 9bit twos complement */ + if (flags & PTR_FLAGS_WHEEL_NEGATIVE) + cflags = (flags & 0xFF00) | (0x100 - cval); + if (!freerdp_client_send_wheel_event(cctx, cflags)) + return FALSE; + + avalue -= cval; + } + return TRUE; +} + +BOOL wlf_handle_pointer_frame(freerdp* instance, const UwacPointerFrameEvent* ev) +{ + BOOL success = TRUE; + BOOL handle = FALSE; + wlfContext* context = nullptr; + enum wl_pointer_axis_source source = WL_POINTER_AXIS_SOURCE_CONTINUOUS; + + if (!instance || !ev || !instance->context) + return FALSE; + + context = (wlfContext*)instance->context; + + for (size_t x = 0; x < ArrayList_Count(context->events); x++) + { + UwacEvent* cev = ArrayList_GetItem(context->events, x); + if (!cev) + continue; + if (cev->type == UWAC_EVENT_POINTER_SOURCE) + { + handle = TRUE; + source = cev->mouse_source.axis_source; + } + } + + /* We need source events to determine how to interpret the data */ + if (handle) + { + for (size_t x = 0; x < ArrayList_Count(context->events); x++) + { + UwacEvent* cev = ArrayList_GetItem(context->events, x); + if (!cev) + continue; + + switch (source) + { + /* If we have a mouse wheel, just use discrete data */ + case WL_POINTER_AXIS_SOURCE_WHEEL: +#if defined(WL_POINTER_AXIS_SOURCE_WHEEL_TILT_SINCE_VERSION) + case WL_POINTER_AXIS_SOURCE_WHEEL_TILT: +#endif + if (cev->type == UWAC_EVENT_POINTER_AXIS_DISCRETE) + { + /* Get the number of steps, multiply by default step width of 120 */ + int32_t val = cev->mouse_axis.value * 0x78; + /* No wheel event received, success! */ + if (!wlf_handle_wheel(instance, cev->mouse_axis.x, cev->mouse_axis.y, + cev->mouse_axis.axis, val)) + success = FALSE; + } + break; + /* If we have a touch pad we get actual data, scale */ + case WL_POINTER_AXIS_SOURCE_FINGER: + case WL_POINTER_AXIS_SOURCE_CONTINUOUS: + if (cev->type == UWAC_EVENT_POINTER_AXIS) + { + double dval = wl_fixed_to_double(cev->mouse_axis.value); + int32_t val = (int32_t)(dval * 0x78 / 10.0); + if (!wlf_handle_wheel(instance, cev->mouse_axis.x, cev->mouse_axis.y, + cev->mouse_axis.axis, val)) + success = FALSE; + } + break; + default: + break; + } + } + } + ArrayList_Clear(context->events); + return success; +} + +BOOL wlf_handle_pointer_source(freerdp* instance, const UwacPointerSourceEvent* ev) +{ + wlfContext* context = nullptr; + if (!instance || !instance->context || !ev) + return FALSE; + + context = (wlfContext*)instance->context; + return ArrayList_Append(context->events, ev); +} + +BOOL wlf_handle_key(freerdp* instance, const UwacKeyEvent* ev) +{ + if (!instance || !ev) + return FALSE; + + WINPR_ASSERT(instance->context); + wlfContext* ctx = (wlfContext*)instance->context; + if (freerdp_settings_get_bool(instance->context->settings, FreeRDP_GrabKeyboard) && + ev->raw_key == KEY_RIGHTCTRL) + wlf_handle_ungrab_key(instance, ev); + + rdpInput* input = instance->context->input; + + const DWORD vc = GetVirtualKeyCodeFromKeycode(ev->raw_key, WINPR_KEYCODE_TYPE_EVDEV); + const DWORD sc = GetVirtualScanCodeFromVirtualKeyCode(vc, WINPR_KBD_TYPE_IBM_ENHANCED); + const DWORD rdp_scancode = freerdp_keyboard_remap_key(ctx->remap_table, sc); + + if (rdp_scancode == RDP_SCANCODE_UNKNOWN) + return TRUE; + + return freerdp_input_send_keyboard_event_ex(input, ev->pressed, ev->repeated, rdp_scancode); +} + +BOOL wlf_handle_ungrab_key(freerdp* instance, const UwacKeyEvent* ev) +{ + wlfContext* context = nullptr; + if (!instance || !instance->context || !ev) + return FALSE; + + context = (wlfContext*)instance->context; + + return UwacSeatInhibitShortcuts(context->seat, false) == UWAC_SUCCESS; +} + +BOOL wlf_keyboard_enter(freerdp* instance, const UwacKeyboardEnterLeaveEvent* ev) +{ + if (!instance || !ev) + return FALSE; + + ((wlfContext*)instance->context)->focusing = TRUE; + return TRUE; +} + +BOOL wlf_keyboard_modifiers(freerdp* instance, const UwacKeyboardModifiersEvent* ev) +{ + rdpInput* input = nullptr; + UINT16 syncFlags = 0; + wlfContext* wlf = nullptr; + + if (!instance || !ev) + return FALSE; + + wlf = (wlfContext*)instance->context; + WINPR_ASSERT(wlf); + + input = instance->context->input; + WINPR_ASSERT(input); + + syncFlags = 0; + + if (ev->modifiers & UWAC_MOD_CAPS_MASK) + syncFlags |= KBD_SYNC_CAPS_LOCK; + if (ev->modifiers & UWAC_MOD_NUM_MASK) + syncFlags |= KBD_SYNC_NUM_LOCK; + + if (!wlf->focusing) + return TRUE; + + ((wlfContext*)instance->context)->focusing = FALSE; + + return freerdp_input_send_focus_in_event(input, syncFlags) && + freerdp_client_send_button_event(&wlf->common, FALSE, PTR_FLAGS_MOVE, 0, 0); +} + +BOOL wlf_handle_touch_up(freerdp* instance, const UwacTouchUp* ev) +{ + int32_t x = 0; + int32_t y = 0; + + WINPR_ASSERT(instance); + WINPR_ASSERT(ev); + + wlfContext* wlf = (wlfContext*)instance->context; + WINPR_ASSERT(wlf); + + x = ev->x; + y = ev->y; + + if (!scale_signed_coordinates(instance->context, &x, &y, TRUE)) + return FALSE; + + return freerdp_client_handle_touch(&wlf->common, FREERDP_TOUCH_UP, ev->id, 0, x, y); +} + +BOOL wlf_handle_touch_down(freerdp* instance, const UwacTouchDown* ev) +{ + int32_t x = 0; + int32_t y = 0; + + WINPR_ASSERT(instance); + WINPR_ASSERT(ev); + + wlfContext* wlf = (wlfContext*)instance->context; + WINPR_ASSERT(wlf); + + x = ev->x; + y = ev->y; + + if (!scale_signed_coordinates(instance->context, &x, &y, TRUE)) + return FALSE; + + return freerdp_client_handle_touch(&wlf->common, FREERDP_TOUCH_DOWN, ev->id, 0, x, y); +} + +BOOL wlf_handle_touch_motion(freerdp* instance, const UwacTouchMotion* ev) +{ + int32_t x = 0; + int32_t y = 0; + + WINPR_ASSERT(instance); + WINPR_ASSERT(ev); + + wlfContext* wlf = (wlfContext*)instance->context; + WINPR_ASSERT(wlf); + + x = ev->x; + y = ev->y; + + if (!scale_signed_coordinates(instance->context, &x, &y, TRUE)) + return FALSE; + + return freerdp_client_handle_touch(&wlf->common, FREERDP_TOUCH_MOTION, 0, + WINPR_ASSERTING_INT_CAST(uint32_t, ev->id), x, y); +} diff --git a/third_party/FreeRDP/client/Wayland/wlf_input.h b/third_party/FreeRDP/client/Wayland/wlf_input.h new file mode 100644 index 0000000..0d4f5ef --- /dev/null +++ b/third_party/FreeRDP/client/Wayland/wlf_input.h @@ -0,0 +1,45 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Input + * + * Copyright 2014 Manuel Bachmann + * Copyright 2015 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_WAYLAND_INPUT_H +#define FREERDP_CLIENT_WAYLAND_INPUT_H + +#include +#include +#include +#include + +BOOL wlf_handle_pointer_enter(freerdp* instance, const UwacPointerEnterLeaveEvent* ev); +BOOL wlf_handle_pointer_motion(freerdp* instance, const UwacPointerMotionEvent* ev); +BOOL wlf_handle_pointer_buttons(freerdp* instance, const UwacPointerButtonEvent* ev); +BOOL wlf_handle_pointer_axis(freerdp* instance, const UwacPointerAxisEvent* ev); +BOOL wlf_handle_pointer_axis_discrete(freerdp* instance, const UwacPointerAxisEvent* ev); +BOOL wlf_handle_pointer_frame(freerdp* instance, const UwacPointerFrameEvent* ev); +BOOL wlf_handle_pointer_source(freerdp* instance, const UwacPointerSourceEvent* ev); +BOOL wlf_handle_touch_up(freerdp* instance, const UwacTouchUp* ev); +BOOL wlf_handle_touch_down(freerdp* instance, const UwacTouchDown* ev); +BOOL wlf_handle_touch_motion(freerdp* instance, const UwacTouchMotion* ev); + +BOOL wlf_handle_key(freerdp* instance, const UwacKeyEvent* ev); +BOOL wlf_handle_ungrab_key(freerdp* instance, const UwacKeyEvent* ev); +BOOL wlf_keyboard_enter(freerdp* instance, const UwacKeyboardEnterLeaveEvent* ev); +BOOL wlf_keyboard_modifiers(freerdp* instance, const UwacKeyboardModifiersEvent* ev); + +#endif /* FREERDP_CLIENT_WAYLAND_INPUT_H */ diff --git a/third_party/FreeRDP/client/Wayland/wlf_pointer.c b/third_party/FreeRDP/client/Wayland/wlf_pointer.c new file mode 100644 index 0000000..0bc414f --- /dev/null +++ b/third_party/FreeRDP/client/Wayland/wlf_pointer.c @@ -0,0 +1,164 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Mouse Pointer + * + * Copyright 2019 Armin Novak + * Copyright 2019 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 + +#include "wlf_pointer.h" +#include "wlfreerdp.h" + +#define TAG CLIENT_TAG("wayland.pointer") + +typedef struct +{ + rdpPointer pointer; + size_t size; + void* data; +} wlfPointer; + +static BOOL wlf_Pointer_New(rdpContext* context, rdpPointer* pointer) +{ + wlfPointer* ptr = (wlfPointer*)pointer; + + if (!ptr) + return FALSE; + + ptr->size = 4ULL * pointer->width * pointer->height; + ptr->data = winpr_aligned_malloc(ptr->size, 16); + + if (!ptr->data) + return FALSE; + + if (!freerdp_image_copy_from_pointer_data( + ptr->data, PIXEL_FORMAT_BGRA32, 0, 0, 0, pointer->width, pointer->height, + pointer->xorMaskData, pointer->lengthXorMask, pointer->andMaskData, + pointer->lengthAndMask, pointer->xorBpp, &context->gdi->palette)) + { + winpr_aligned_free(ptr->data); + return FALSE; + } + + return TRUE; +} + +static void wlf_Pointer_Free(rdpContext* context, rdpPointer* pointer) +{ + wlfPointer* ptr = (wlfPointer*)pointer; + WINPR_UNUSED(context); + + if (ptr) + winpr_aligned_free(ptr->data); +} + +static BOOL wlf_Pointer_Set(rdpContext* context, rdpPointer* pointer) +{ + wlfContext* wlf = (wlfContext*)context; + wlfPointer* ptr = (wlfPointer*)pointer; + void* data = nullptr; + size_t size = 0; + UwacReturnCode rc = UWAC_ERROR_INTERNAL; + BOOL res = FALSE; + RECTANGLE_16 area; + + if (!wlf || !wlf->seat) + return FALSE; + + UINT32 x = pointer->xPos; + UINT32 y = pointer->yPos; + UINT32 w = pointer->width; + UINT32 h = pointer->height; + + if (!wlf_scale_coordinates(context, &x, &y, FALSE) || + !wlf_scale_coordinates(context, &w, &h, FALSE)) + return FALSE; + + size = 4ULL * w * h; + data = malloc(size); + + if (!data) + return FALSE; + + area.top = 0; + area.left = 0; + area.right = (UINT16)pointer->width; + area.bottom = (UINT16)pointer->height; + + if (!wlf_copy_image(ptr->data, 4ULL * pointer->width, pointer->width, pointer->height, data, + 4ULL * w, w, h, &area, + freerdp_settings_get_bool(context->settings, FreeRDP_SmartSizing))) + goto fail; + + rc = UwacSeatSetMouseCursor(wlf->seat, data, size, w, h, x, y); + + if (rc == UWAC_SUCCESS) + res = TRUE; + +fail: + free(data); + return res; +} + +static BOOL wlf_Pointer_SetNull(rdpContext* context) +{ + wlfContext* wlf = (wlfContext*)context; + + if (!wlf || !wlf->seat) + return FALSE; + + if (UwacSeatSetMouseCursor(wlf->seat, nullptr, 0, 0, 0, 0, 0) != UWAC_SUCCESS) + return FALSE; + + return TRUE; +} + +static BOOL wlf_Pointer_SetDefault(rdpContext* context) +{ + wlfContext* wlf = (wlfContext*)context; + + if (!wlf || !wlf->seat) + return FALSE; + + if (UwacSeatSetMouseCursor(wlf->seat, nullptr, 1, 0, 0, 0, 0) != UWAC_SUCCESS) + return FALSE; + + return TRUE; +} + +static BOOL wlf_Pointer_SetPosition(WINPR_ATTR_UNUSED rdpContext* context, + WINPR_ATTR_UNUSED UINT32 x, WINPR_ATTR_UNUSED UINT32 y) +{ + // TODO + WLog_ERR(TAG, "TODO: implement"); + return TRUE; +} + +BOOL wlf_register_pointer(rdpGraphics* graphics) +{ + rdpPointer pointer = WINPR_C_ARRAY_INIT; + + pointer.size = sizeof(wlfPointer); + pointer.New = wlf_Pointer_New; + pointer.Free = wlf_Pointer_Free; + pointer.Set = wlf_Pointer_Set; + pointer.SetNull = wlf_Pointer_SetNull; + pointer.SetDefault = wlf_Pointer_SetDefault; + pointer.SetPosition = wlf_Pointer_SetPosition; + graphics_register_pointer(graphics, &pointer); + return TRUE; +} diff --git a/third_party/FreeRDP/client/Wayland/wlf_pointer.h b/third_party/FreeRDP/client/Wayland/wlf_pointer.h new file mode 100644 index 0000000..8ae82e1 --- /dev/null +++ b/third_party/FreeRDP/client/Wayland/wlf_pointer.h @@ -0,0 +1,28 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Mouse Pointer + * + * Copyright 2019 Armin Novak + * Copyright 2019 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. + */ + +#ifndef FREERDP_CLIENT_WAYLAND_POINTER_H +#define FREERDP_CLIENT_WAYLAND_POINTER_H + +#include + +BOOL wlf_register_pointer(rdpGraphics* graphics); + +#endif /* FREERDP_CLIENT_WAYLAND_POINTER_H */ diff --git a/third_party/FreeRDP/client/Wayland/wlfreerdp.1.in b/third_party/FreeRDP/client/Wayland/wlfreerdp.1.in new file mode 100644 index 0000000..ee40412 --- /dev/null +++ b/third_party/FreeRDP/client/Wayland/wlfreerdp.1.in @@ -0,0 +1,38 @@ +.de URL +\\$2 \(laURL: \\$1 \(ra\\$3 +.. +.if \n[.g] .mso www.tmac +.TH @MANPAGE_NAME@ 1 2017-01-12 "@FREERDP_VERSION_FULL@" "FreeRDP" +.SH NAME +@MANPAGE_NAME@ \- FreeRDP wayland client +.SH SYNOPSIS +.B @MANPAGE_NAME@ +[file] +[\fIdefault_client_options\fP] +[\fB/v\fP:[:port]] +[\fB/version\fP] +[\fB/help\fP] +.SH DESCRIPTION +.B @MANPAGE_NAME@ +is a wayland Remote Desktop Protocol (RDP) client which is part of the FreeRDP project. A RDP server is built-in to many editions of Windows.. Alternative servers included ogon, gnome-remote-desktop, xrdp and VRDP (VirtualBox). +.SH OPTIONS +The wayland client also supports a lot of the \fIdefault client options\fP which are not described here. For details on those see the xfreerdp(1) man page. +.IP \fB/v:\fP\fI[:port]\fP +The server hostname or IP, and optionally the port, to connect to. +.IP /version +Print the version and exit. +.IP /help +Print the help and exit. +.SH EXIT STATUS +.TP +.B 0 +Successful program execution. +.TP +.B not 0 +On failure. + +.SH SEE ALSO +xfreerdp(1) wlog(7) + +.SH AUTHOR +FreeRDP diff --git a/third_party/FreeRDP/client/Wayland/wlfreerdp.c b/third_party/FreeRDP/client/Wayland/wlfreerdp.c new file mode 100644 index 0000000..5aa0508 --- /dev/null +++ b/third_party/FreeRDP/client/Wayland/wlfreerdp.c @@ -0,0 +1,782 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Client + * + * Copyright 2014 Manuel Bachmann + * Copyright 2016 Thincast Technologies GmbH + * Copyright 2016 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "wlfreerdp.h" +#include "wlf_input.h" +#include "wlf_cliprdr.h" +#include "wlf_disp.h" +#include "wlf_channels.h" +#include "wlf_pointer.h" + +#define TAG CLIENT_TAG("wayland") + +static BOOL wl_update_buffer(wlfContext* context_w, INT32 ix, INT32 iy, INT32 iw, INT32 ih) +{ + BOOL res = FALSE; + rdpGdi* gdi = nullptr; + char* data = nullptr; + UwacSize geometry = WINPR_C_ARRAY_INIT; + size_t stride = 0; + UwacReturnCode rc = UWAC_ERROR_INTERNAL; + RECTANGLE_16 area = WINPR_C_ARRAY_INIT; + + if (!context_w) + return FALSE; + + if ((ix < 0) || (iy < 0) || (iw < 0) || (ih < 0)) + return FALSE; + + EnterCriticalSection(&context_w->critical); + UINT32 x = WINPR_ASSERTING_INT_CAST(UINT16, ix); + UINT32 y = WINPR_ASSERTING_INT_CAST(UINT16, iy); + UINT32 w = WINPR_ASSERTING_INT_CAST(UINT16, iw); + UINT32 h = WINPR_ASSERTING_INT_CAST(UINT16, ih); + rc = UwacWindowGetDrawingBufferGeometry(context_w->window, &geometry, &stride); + data = UwacWindowGetDrawingBuffer(context_w->window); + + if (!data || (rc != UWAC_SUCCESS)) + goto fail; + + gdi = context_w->common.context.gdi; + + if (!gdi) + goto fail; + + /* Ignore output if the surface size does not match. */ + if (((INT64)x > geometry.width) || ((INT64)y > geometry.height)) + { + res = TRUE; + goto fail; + } + + area.left = WINPR_ASSERTING_INT_CAST(UINT16, x); + area.top = WINPR_ASSERTING_INT_CAST(UINT16, y); + area.right = WINPR_ASSERTING_INT_CAST(UINT16, x + w); + area.bottom = WINPR_ASSERTING_INT_CAST(UINT16, y + h); + + if (!wlf_copy_image( + gdi->primary_buffer, gdi->stride, WINPR_ASSERTING_INT_CAST(size_t, gdi->width), + WINPR_ASSERTING_INT_CAST(size_t, gdi->height), data, stride, + WINPR_ASSERTING_INT_CAST(size_t, geometry.width), + WINPR_ASSERTING_INT_CAST(size_t, geometry.height), &area, + freerdp_settings_get_bool(context_w->common.context.settings, FreeRDP_SmartSizing))) + goto fail; + + if (!wlf_scale_coordinates(&context_w->common.context, &x, &y, FALSE)) + goto fail; + + if (!wlf_scale_coordinates(&context_w->common.context, &w, &h, FALSE)) + goto fail; + + if (UwacWindowAddDamage(context_w->window, x, y, w, h) != UWAC_SUCCESS) + goto fail; + + if (UwacWindowSubmitBuffer(context_w->window, false) != UWAC_SUCCESS) + goto fail; + + res = TRUE; +fail: + LeaveCriticalSection(&context_w->critical); + return res; +} + +static BOOL wl_end_paint(rdpContext* context) +{ + if (!context || !context->gdi || !context->gdi->primary) + return FALSE; + + rdpGdi* gdi = context->gdi; + HGDI_DC hdc = gdi->primary->hdc; + WINPR_ASSERT(hdc); + if (!hdc->hwnd) + return TRUE; + + HGDI_WND hwnd = hdc->hwnd; + WINPR_ASSERT(hwnd->invalid || (hwnd->ninvalid == 0)); + + if (hwnd->invalid->null) + return TRUE; + + const INT32 x = hwnd->invalid->x; + const INT32 y = hwnd->invalid->y; + const INT32 w = hwnd->invalid->w; + const INT32 h = hwnd->invalid->h; + wlfContext* context_w = (wlfContext*)context; + if (!wl_update_buffer(context_w, x, y, w, h)) + { + return FALSE; + } + + hwnd->invalid->null = TRUE; + hwnd->ninvalid = 0; + return TRUE; +} + +static BOOL wl_refresh_display(wlfContext* context) +{ + rdpGdi* gdi = nullptr; + + if (!context || !context->common.context.gdi) + return FALSE; + + gdi = context->common.context.gdi; + return wl_update_buffer(context, 0, 0, gdi->width, gdi->height); +} + +static BOOL wl_resize_display(rdpContext* context) +{ + wlfContext* wlc = (wlfContext*)context; + rdpGdi* gdi = context->gdi; + rdpSettings* settings = context->settings; + + if (!gdi_resize(gdi, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight))) + return FALSE; + + return wl_refresh_display(wlc); +} + +static BOOL wl_pre_connect(freerdp* instance) +{ + rdpSettings* settings = nullptr; + wlfContext* context = nullptr; + const UwacOutput* output = nullptr; + UwacSize resolution; + + if (!instance) + return FALSE; + + context = (wlfContext*)instance->context; + WINPR_ASSERT(context); + + settings = instance->context->settings; + WINPR_ASSERT(settings); + + if (!freerdp_settings_set_bool(settings, FreeRDP_CertificateCallbackPreferPEM, TRUE)) + return FALSE; + + if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMajorType, OSMAJORTYPE_UNIX)) + return FALSE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMinorType, OSMINORTYPE_NATIVE_WAYLAND)) + return FALSE; + if (PubSub_SubscribeChannelConnected(instance->context->pubSub, + wlf_OnChannelConnectedEventHandler) < 0) + return FALSE; + if (PubSub_SubscribeChannelDisconnected(instance->context->pubSub, + wlf_OnChannelDisconnectedEventHandler) < 0) + return FALSE; + + if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)) + { + // Use the resolution of the first display output + output = UwacDisplayGetOutput(context->display, 0); + + if ((output != nullptr) && (UwacOutputGetResolution(output, &resolution) == UWAC_SUCCESS)) + { + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, + (UINT32)resolution.width)) + return FALSE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, + (UINT32)resolution.height)) + return FALSE; + } + else + { + WLog_WARN(TAG, "Failed to get output resolution! Check your display settings"); + } + } + + return TRUE; +} + +static BOOL wl_post_connect(freerdp* instance) +{ + if (!instance || !instance->context) + return FALSE; + + wlfContext* context = (wlfContext*)instance->context; + WINPR_ASSERT(context); + + rdpSettings* settings = instance->context->settings; + WINPR_ASSERT(settings); + + const char* title = "FreeRDP"; + const char* wtitle = freerdp_settings_get_string(settings, FreeRDP_WindowTitle); + if (wtitle) + title = wtitle; + + const char* app_id = "wlfreerdp"; + const char* wmclass = freerdp_settings_get_string(settings, FreeRDP_WmClass); + if (wmclass) + app_id = wmclass; + + if (!gdi_init(instance, PIXEL_FORMAT_BGRA32)) + return FALSE; + + rdpGdi* gdi = instance->context->gdi; + + if (!gdi || (gdi->width < 0) || (gdi->height < 0)) + return FALSE; + + if (!wlf_register_pointer(instance->context->graphics)) + return FALSE; + + UINT32 w = (UINT32)gdi->width; + UINT32 h = (UINT32)gdi->height; + + if (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) && !context->fullscreen) + { + const UINT32 sw = freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingWidth); + if (sw > 0) + w = sw; + + const UINT32 sh = freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingHeight); + if (sh > 0) + h = sh; + } + + context->window = UwacCreateWindowShm(context->display, w, h, WL_SHM_FORMAT_XRGB8888); + + if (!context->window) + return FALSE; + + UwacWindowSetFullscreenState( + context->window, nullptr, + freerdp_settings_get_bool(instance->context->settings, FreeRDP_Fullscreen)); + UwacWindowSetTitle(context->window, title); + UwacWindowSetAppId(context->window, app_id); + UwacWindowSetOpaqueRegion(context->window, 0, 0, w, h); + instance->context->update->EndPaint = wl_end_paint; + instance->context->update->DesktopResize = wl_resize_display; + const char* KeyboardRemappingList = + freerdp_settings_get_string(instance->context->settings, FreeRDP_KeyboardRemappingList); + + context->remap_table = freerdp_keyboard_remap_string_to_list(KeyboardRemappingList); + if (!context->remap_table) + return FALSE; + + if (!(context->disp = wlf_disp_new(context))) + return FALSE; + + context->clipboard = wlf_clipboard_new(context); + + if (!context->clipboard) + return FALSE; + + return wl_refresh_display(context); +} + +static void wl_post_disconnect(freerdp* instance) +{ + if (!instance) + return; + + if (!instance->context) + return; + + wlfContext* context = (wlfContext*)instance->context; + gdi_free(instance); + wlf_clipboard_free(context->clipboard); + wlf_disp_free(context->disp); + + if (context->window) + UwacDestroyWindow(&context->window); + freerdp_keyboard_remap_free(context->remap_table); + context->remap_table = nullptr; +} + +static BOOL handle_uwac_events(freerdp* instance, UwacDisplay* display) +{ + UwacEvent event; + wlfContext* context = nullptr; + + if (UwacDisplayDispatch(display, 1) < 0) + return FALSE; + + context = (wlfContext*)instance->context; + + while (UwacHasEvent(display)) + { + if (UwacNextEvent(display, &event) != UWAC_SUCCESS) + return FALSE; + + /*printf("UWAC event type %d\n", event.type);*/ + switch (event.type) + { + case UWAC_EVENT_NEW_SEAT: + context->seat = event.seat_new.seat; + break; + + case UWAC_EVENT_REMOVED_SEAT: + context->seat = nullptr; + break; + + case UWAC_EVENT_FRAME_DONE: + { + EnterCriticalSection(&context->critical); + UwacReturnCode r = UwacWindowSubmitBuffer(context->window, false); + LeaveCriticalSection(&context->critical); + if (r != UWAC_SUCCESS) + return FALSE; + } + break; + + case UWAC_EVENT_POINTER_ENTER: + if (!wlf_handle_pointer_enter(instance, &event.mouse_enter_leave)) + return FALSE; + + break; + + case UWAC_EVENT_POINTER_MOTION: + if (!wlf_handle_pointer_motion(instance, &event.mouse_motion)) + return FALSE; + + break; + + case UWAC_EVENT_POINTER_BUTTONS: + if (!wlf_handle_pointer_buttons(instance, &event.mouse_button)) + return FALSE; + + break; + + case UWAC_EVENT_POINTER_AXIS: + if (!wlf_handle_pointer_axis(instance, &event.mouse_axis)) + return FALSE; + break; + + case UWAC_EVENT_POINTER_AXIS_DISCRETE: + if (!wlf_handle_pointer_axis_discrete(instance, &event.mouse_axis)) + return FALSE; + break; + + case UWAC_EVENT_POINTER_FRAME: + if (!wlf_handle_pointer_frame(instance, &event.mouse_frame)) + return FALSE; + break; + case UWAC_EVENT_POINTER_SOURCE: + if (!wlf_handle_pointer_source(instance, &event.mouse_source)) + return FALSE; + break; + + case UWAC_EVENT_KEY: + if (!wlf_handle_key(instance, &event.key)) + return FALSE; + + break; + + case UWAC_EVENT_TOUCH_UP: + if (!wlf_handle_touch_up(instance, &event.touchUp)) + return FALSE; + + break; + + case UWAC_EVENT_TOUCH_DOWN: + if (!wlf_handle_touch_down(instance, &event.touchDown)) + return FALSE; + + break; + + case UWAC_EVENT_TOUCH_MOTION: + if (!wlf_handle_touch_motion(instance, &event.touchMotion)) + return FALSE; + + break; + + case UWAC_EVENT_KEYBOARD_ENTER: + if (freerdp_settings_get_bool(instance->context->settings, FreeRDP_GrabKeyboard)) + UwacSeatInhibitShortcuts(event.keyboard_enter_leave.seat, true); + + if (!wlf_keyboard_enter(instance, &event.keyboard_enter_leave)) + return FALSE; + + break; + + case UWAC_EVENT_KEYBOARD_MODIFIERS: + if (!wlf_keyboard_modifiers(instance, &event.keyboard_modifiers)) + return FALSE; + + break; + + case UWAC_EVENT_CONFIGURE: + if (!wlf_disp_handle_configure(context->disp, event.configure.width, + event.configure.height)) + return FALSE; + + if (!wl_refresh_display(context)) + return FALSE; + + break; + + case UWAC_EVENT_CLIPBOARD_AVAILABLE: + case UWAC_EVENT_CLIPBOARD_OFFER: + case UWAC_EVENT_CLIPBOARD_SELECT: + if (!wlf_cliprdr_handle_event(context->clipboard, &event.clipboard)) + return FALSE; + + break; + + case UWAC_EVENT_CLOSE: + context->closed = TRUE; + + break; + + default: + break; + } + } + + return TRUE; +} + +static BOOL handle_window_events(freerdp* instance) +{ + return instance != nullptr; +} + +static int wlfreerdp_run(freerdp* instance) +{ + wlfContext* context = nullptr; + HANDLE handles[MAXIMUM_WAIT_OBJECTS] = WINPR_C_ARRAY_INIT; + DWORD status = WAIT_ABANDONED; + + if (!instance) + return -1; + + context = (wlfContext*)instance->context; + + if (!context) + return -1; + + if (!freerdp_connect(instance)) + { + WLog_Print(context->log, WLOG_ERROR, "Failed to connect"); + return -1; + } + + while (!freerdp_shall_disconnect_context(instance->context)) + { + DWORD count = 0; + handles[count++] = context->displayHandle; + count += freerdp_get_event_handles(instance->context, &handles[count], + ARRAYSIZE(handles) - count); + + if (count <= 2) + { + WLog_Print(context->log, WLOG_ERROR, "Failed to get FreeRDP file descriptor"); + break; + } + + status = WaitForMultipleObjects(count, handles, FALSE, INFINITE); + + if (WAIT_FAILED == status) + { + WLog_Print(context->log, WLOG_ERROR, "WaitForMultipleObjects failed"); + break; + } + + if (!handle_uwac_events(instance, context->display)) + { + WLog_Print(context->log, WLOG_ERROR, "error handling UWAC events"); + break; + } + + if (context->closed) + { + WLog_Print(context->log, WLOG_INFO, "Closed from Wayland"); + break; + } + + if (freerdp_check_event_handles(instance->context) != TRUE) + { + if (client_auto_reconnect_ex(instance, handle_window_events)) + continue; + else + { + /* + * Indicate an unsuccessful connection attempt if reconnect + * did not succeed and no other error was specified. + */ + if (freerdp_error_info(instance) == 0) + status = 42; + } + + if (freerdp_get_last_error(instance->context) == FREERDP_ERROR_SUCCESS) + WLog_Print(context->log, WLOG_ERROR, "Failed to check FreeRDP file descriptor"); + + break; + } + } + + freerdp_disconnect(instance); + return WINPR_ASSERTING_INT_CAST(int, status); +} + +static BOOL wlf_client_global_init(void) +{ + // NOLINTNEXTLINE(concurrency-mt-unsafe) + (void)setlocale(LC_ALL, ""); + + return (freerdp_handle_signals() == 0); +} + +static void wlf_client_global_uninit(void) +{ +} + +static int wlf_logon_error_info(freerdp* instance, UINT32 data, UINT32 type) +{ + wlfContext* wlf = nullptr; + 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; + + wlf = (wlfContext*)instance->context; + WLog_Print(wlf->log, WLOG_INFO, "Logon Error Info %s [%s]", str_data, str_type); + return 1; +} + +static void wlf_client_free(freerdp* instance, rdpContext* context) +{ + wlfContext* wlf = (wlfContext*)instance->context; + + if (!context) + return; + + if (wlf->display) + UwacCloseDisplay(&wlf->display); + + if (wlf->displayHandle) + (void)CloseHandle(wlf->displayHandle); + ArrayList_Free(wlf->events); + DeleteCriticalSection(&wlf->critical); +} + +static void* uwac_event_clone(const void* val) +{ + UwacEvent* copy = nullptr; + const UwacEvent* ev = (const UwacEvent*)val; + + copy = calloc(1, sizeof(UwacEvent)); + if (!copy) + return nullptr; + *copy = *ev; + return copy; +} + +static BOOL wlf_client_new(freerdp* instance, rdpContext* context) +{ + wObject* obj = nullptr; + UwacReturnCode status = UWAC_ERROR_INTERNAL; + wlfContext* wfl = (wlfContext*)context; + + if (!instance || !context) + return FALSE; + + instance->PreConnect = wl_pre_connect; + instance->PostConnect = wl_post_connect; + instance->PostDisconnect = wl_post_disconnect; + instance->LogonErrorInfo = wlf_logon_error_info; + wfl->log = WLog_Get(TAG); + wfl->display = UwacOpenDisplay(nullptr, &status); + + if (!wfl->display || (status != UWAC_SUCCESS) || !wfl->log) + return FALSE; + + wfl->displayHandle = CreateFileDescriptorEvent(nullptr, FALSE, FALSE, + UwacDisplayGetFd(wfl->display), WINPR_FD_READ); + + if (!wfl->displayHandle) + return FALSE; + + wfl->events = ArrayList_New(FALSE); + if (!wfl->events) + return FALSE; + + obj = ArrayList_Object(wfl->events); + obj->fnObjectNew = uwac_event_clone; + obj->fnObjectFree = free; + + InitializeCriticalSection(&wfl->critical); + + return TRUE; +} + +static int wfl_client_start(rdpContext* context) +{ + WINPR_UNUSED(context); + return 0; +} + +static int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints) +{ + WINPR_ASSERT(pEntryPoints); + ZeroMemory(pEntryPoints, sizeof(RDP_CLIENT_ENTRY_POINTS)); + pEntryPoints->Version = RDP_CLIENT_INTERFACE_VERSION; + pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1); + pEntryPoints->GlobalInit = wlf_client_global_init; + pEntryPoints->GlobalUninit = wlf_client_global_uninit; + pEntryPoints->ContextSize = sizeof(wlfContext); + pEntryPoints->ClientNew = wlf_client_new; + pEntryPoints->ClientFree = wlf_client_free; + pEntryPoints->ClientStart = wfl_client_start; + pEntryPoints->ClientStop = freerdp_client_common_stop; + return 0; +} + +int main(int argc, char* argv[]) +{ + int rc = -1; + int status = 0; + RDP_CLIENT_ENTRY_POINTS clientEntryPoints; + rdpContext* context = nullptr; + rdpSettings* settings = nullptr; + wlfContext* wlc = nullptr; + + freerdp_client_warn_deprecated(argc, argv); + + RdpClientEntry(&clientEntryPoints); + context = freerdp_client_context_new(&clientEntryPoints); + if (!context) + goto fail; + wlc = (wlfContext*)context; + settings = context->settings; + + status = freerdp_client_settings_parse_command_line(settings, argc, argv, FALSE); + if (status) + { + rc = freerdp_client_settings_command_line_status_print(settings, status, argc, argv); + + if (freerdp_settings_get_bool(settings, FreeRDP_ListMonitors)) + wlf_list_monitors(wlc); + + goto fail; + } + + if (freerdp_client_start(context) != 0) + goto fail; + + rc = wlfreerdp_run(context->instance); + + if (freerdp_client_stop(context) != 0) + rc = -1; + +fail: + freerdp_client_context_free(context); + return rc; +} + +BOOL wlf_copy_image(const void* src, size_t srcStride, size_t srcWidth, size_t srcHeight, void* dst, + size_t dstStride, size_t dstWidth, size_t dstHeight, const RECTANGLE_16* area, + BOOL scale) +{ + BOOL rc = FALSE; + + if (!src || !dst || !area) + return FALSE; + + if (scale) + { + WINPR_ASSERT(dstStride <= UINT32_MAX); + WINPR_ASSERT(dstWidth <= UINT32_MAX); + WINPR_ASSERT(dstHeight <= UINT32_MAX); + WINPR_ASSERT(srcStride <= UINT32_MAX); + WINPR_ASSERT(srcWidth <= UINT32_MAX); + WINPR_ASSERT(srcHeight <= UINT32_MAX); + return freerdp_image_scale(dst, PIXEL_FORMAT_BGRA32, (UINT32)dstStride, 0, 0, + (UINT32)dstWidth, (UINT32)dstHeight, src, PIXEL_FORMAT_BGRA32, + (UINT32)srcStride, 0, 0, (UINT32)srcWidth, (UINT32)srcHeight); + } + else + { + const size_t baseSrcOffset = 1ULL * area->top * srcStride + 4ULL * area->left; + const size_t baseDstOffset = 1ULL * area->top * dstStride + 4ULL * area->left; + const size_t width = MIN((size_t)area->right - area->left, dstWidth - area->left); + const size_t height = MIN((size_t)area->bottom - area->top, dstHeight - area->top); + const BYTE* psrc = (const BYTE*)src; + BYTE* pdst = (BYTE*)dst; + + for (size_t i = 0; i < height; i++) + { + const size_t srcOffset = i * srcStride + baseSrcOffset; + const size_t dstOffset = i * dstStride + baseDstOffset; + memcpy(&pdst[dstOffset], &psrc[srcOffset], width * 4); + } + + rc = TRUE; + } + + return rc; +} + +BOOL wlf_scale_coordinates(rdpContext* context, UINT32* px, UINT32* py, BOOL fromLocalToRDP) +{ + wlfContext* wlf = (wlfContext*)context; + UwacSize geometry = WINPR_C_ARRAY_INIT; + + if (!context || !px || !py || !context->gdi) + return FALSE; + + if (!freerdp_settings_get_bool(context->settings, FreeRDP_SmartSizing)) + return TRUE; + + rdpGdi* gdi = context->gdi; + + if (UwacWindowGetDrawingBufferGeometry(wlf->window, &geometry, nullptr) != UWAC_SUCCESS) + return FALSE; + + const double sx = 1.0 * geometry.width / (double)gdi->width; + const double sy = 1.0 * geometry.height / (double)gdi->height; + + if (!fromLocalToRDP) + { + *px *= (UINT32)lround(sx); + *py *= (UINT32)lround(sy); + } + else + { + *px /= (UINT32)lround(sx); + *py /= (UINT32)lround(sy); + } + + return TRUE; +} diff --git a/third_party/FreeRDP/client/Wayland/wlfreerdp.h b/third_party/FreeRDP/client/Wayland/wlfreerdp.h new file mode 100644 index 0000000..b472434 --- /dev/null +++ b/third_party/FreeRDP/client/Wayland/wlfreerdp.h @@ -0,0 +1,61 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Client + * + * Copyright 2014 Manuel Bachmann + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_WAYLAND_FREERDP_H +#define FREERDP_CLIENT_WAYLAND_FREERDP_H + +#include +#include +#include +#include +#include +#include +#include + +typedef struct wlf_clipboard wfClipboard; +typedef struct s_wlfDispContext wlfDispContext; + +typedef struct +{ + rdpClientContext common; + + UwacDisplay* display; + HANDLE displayHandle; + UwacWindow* window; + UwacSeat* seat; + + BOOL fullscreen; + BOOL closed; + BOOL focusing; + + /* Channels */ + wfClipboard* clipboard; + wlfDispContext* disp; + wLog* log; + CRITICAL_SECTION critical; + wArrayList* events; + FREERDP_REMAP_TABLE* remap_table; +} wlfContext; + +BOOL wlf_scale_coordinates(rdpContext* context, UINT32* px, UINT32* py, BOOL fromLocalToRDP); +BOOL wlf_copy_image(const void* src, size_t srcStride, size_t srcWidth, size_t srcHeight, void* dst, + size_t dstStride, size_t dstWidth, size_t dstHeight, const RECTANGLE_16* area, + BOOL scale); + +#endif /* FREERDP_CLIENT_WAYLAND_FREERDP_H */ diff --git a/third_party/FreeRDP/client/Windows/CMakeLists.txt b/third_party/FreeRDP/client/Windows/CMakeLists.txt new file mode 100644 index 0000000..d5dd95f --- /dev/null +++ b/third_party/FreeRDP/client/Windows/CMakeLists.txt @@ -0,0 +1,92 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP Windows cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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 "wfreerdp-client") +set(MODULE_PREFIX "FREERDP_CLIENT_WINDOWS_CONTROL") + +include(WarnUnmaintained) +warn_unmaintained(${MODULE_NAME} "-DWITH_CLIENT_WINDOWS=OFF") + +set(${MODULE_PREFIX}_SRCS + wf_gdi.c + wf_gdi.h + wf_event.c + wf_event.h + wf_channels.c + wf_channels.h + wf_graphics.c + wf_graphics.h + wf_cliprdr.c + wf_cliprdr.h + wf_rail.c + wf_rail.h + wf_client.c + wf_client.h + wf_floatbar.c + wf_floatbar.h + wf_defaults.h + wf_defaults.c + resource/wfreerdp.rc + resource/resource.h +) + +option(WITH_WINDOWS_CERT_STORE + "Build ${MODULE_NAME} with additional certificate validation against windows certificate store" ON +) +if(WITH_WINDOWS_CERT_STORE) + add_compile_definitions("WITH_WINDOWS_CERT_STORE") +endif() + +option(WITH_WIN_CONSOLE "Build ${MODULE_NAME} with console support" OFF) +if(WITH_WIN_CONSOLE) + add_compile_definitions("WITH_WIN_CONSOLE") + set(WIN32_GUI_FLAG "TRUE") +else() + set(WIN32_GUI_FLAG "WIN32") +endif() + +option(WITH_PROGRESS_BAR "Build ${MODULE_NAME} with connect progress bar (Windows 7+ or 2008 R2+)" ON) +if(WITH_PROGRESS_BAR) + add_compile_definitions("WITH_PROGRESS_BAR") +endif() + +if(CLIENT_INTERFACE_SHARED) + addtargetwithresourcefile(${MODULE_NAME} "SHARED" "${FREERDP_VERSION}" ${MODULE_PREFIX}_SRCS) +else() + addtargetwithresourcefile(${MODULE_NAME} FALSE "${FREERDP_VERSION}" ${MODULE_PREFIX}_SRCS) +endif() +target_include_directories(${MODULE_NAME} INTERFACE $) + +list(APPEND PUB_LIBS freerdp-client) +list(APPEND PUB_LIBS winpr freerdp) + +list(APPEND PRIV_LIBS msimg32.lib credui.lib) + +if(MINGW) + list(APPEND PRIV_LIBS ntdll.lib) # only required with MINGW +endif() +target_link_libraries(${MODULE_NAME} PUBLIC ${PUB_LIBS}) +target_link_libraries(${MODULE_NAME} PRIVATE ${PRIV_LIBS}) + +if(WITH_CLIENT_INTERFACE) + install(TARGETS ${MODULE_NAME} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT libraries + ) +endif() +add_subdirectory(cli) + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Client/Windows") diff --git a/third_party/FreeRDP/client/Windows/ModuleOptions.cmake b/third_party/FreeRDP/client/Windows/ModuleOptions.cmake new file mode 100644 index 0000000..ec9bf51 --- /dev/null +++ b/third_party/FreeRDP/client/Windows/ModuleOptions.cmake @@ -0,0 +1,3 @@ +set(FREERDP_CLIENT_NAME "wfreerdp") +set(FREERDP_CLIENT_PLATFORM "Windows") +set(FREERDP_CLIENT_VENDOR "FreeRDP") diff --git a/third_party/FreeRDP/client/Windows/cli/CMakeLists.txt b/third_party/FreeRDP/client/Windows/cli/CMakeLists.txt new file mode 100644 index 0000000..536ca3a --- /dev/null +++ b/third_party/FreeRDP/client/Windows/cli/CMakeLists.txt @@ -0,0 +1,32 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP Windows cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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 "wfreerdp") +set(MODULE_PREFIX "FREERDP_CLIENT_WINDOWS") + +include_directories(..) + +set(${MODULE_PREFIX}_SRCS wfreerdp.c wfreerdp.h ../resource/wfreerdp.rc) + +addtargetwithresourcefile(${MODULE_NAME} "${WIN32_GUI_FLAG}" "${FREERDP_VERSION}" ${MODULE_PREFIX}_SRCS) + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} wfreerdp-client) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + +install(TARGETS ${MODULE_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT client) +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Client/Windows") diff --git a/third_party/FreeRDP/client/Windows/cli/wfreerdp.c b/third_party/FreeRDP/client/Windows/cli/wfreerdp.c new file mode 100644 index 0000000..dfc6f66 --- /dev/null +++ b/third_party/FreeRDP/client/Windows/cli/wfreerdp.c @@ -0,0 +1,152 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Windows Client + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include + +#include +#include + +#include +#include +#include +#include + +#include "../resource/resource.h" + +#include +#include + +#include + +INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) +{ + int status; + HANDLE thread; + wfContext* wfc; + DWORD dwExitCode; + rdpContext* context; + rdpSettings* settings; + LPWSTR cmd; + char** argv = nullptr; + RDP_CLIENT_ENTRY_POINTS clientEntryPoints = WINPR_C_ARRAY_INIT; + int ret = 1; + int argc = 0; + LPWSTR* args = nullptr; + + WINPR_UNUSED(hInstance); + WINPR_UNUSED(hPrevInstance); + WINPR_UNUSED(lpCmdLine); + WINPR_UNUSED(nCmdShow); + + RdpClientEntry(&clientEntryPoints); + context = freerdp_client_context_new(&clientEntryPoints); + + if (!context) + return -1; + + cmd = GetCommandLineW(); + + if (!cmd) + goto out; + + args = CommandLineToArgvW(cmd, &argc); + + if (!args || (argc <= 0)) + goto out; + + argv = calloc((size_t)argc, sizeof(char*)); + + if (!argv) + goto out; + + for (int i = 0; i < argc; i++) + { + int size = WideCharToMultiByte(CP_UTF8, 0, args[i], -1, nullptr, 0, nullptr, nullptr); + if (size <= 0) + goto out; + argv[i] = calloc((size_t)size, sizeof(char)); + + if (!argv[i]) + goto out; + + if (WideCharToMultiByte(CP_UTF8, 0, args[i], -1, argv[i], size, nullptr, nullptr) != size) + goto out; + } + + freerdp_client_warn_deprecated(argc, argv); + + settings = context->settings; + wfc = (wfContext*)context; + + if (!settings || !wfc) + goto out; + + status = freerdp_client_settings_parse_command_line(settings, argc, argv, FALSE); + if (status) + { + ret = freerdp_client_settings_command_line_status_print(settings, status, argc, argv); + goto out; + } + + AddDefaultSettings(settings); + + if (freerdp_client_start(context) != 0) + goto out; + + thread = freerdp_client_get_thread(context); + + if (thread) + { + if (WaitForSingleObject(thread, INFINITE) == WAIT_OBJECT_0) + { + GetExitCodeThread(thread, &dwExitCode); + ret = (int)dwExitCode; + } + } + + if (freerdp_client_stop(context) != 0) + goto out; + +out: + freerdp_client_context_free(context); + + if (argv) + { + for (int i = 0; i < argc; i++) + free(argv[i]); + + free(argv); + } + + LocalFree(args); + return ret; +} + +#ifdef WITH_WIN_CONSOLE +int main() +{ + return WinMain(nullptr, nullptr, nullptr, 0); +} +#endif diff --git a/third_party/FreeRDP/client/Windows/cli/wfreerdp.h b/third_party/FreeRDP/client/Windows/cli/wfreerdp.h new file mode 100644 index 0000000..2bb57bc --- /dev/null +++ b/third_party/FreeRDP/client/Windows/cli/wfreerdp.h @@ -0,0 +1,27 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Windows Client + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_WIN_FREERDP_H +#define FREERDP_CLIENT_WIN_FREERDP_H + +#include "wf_interface.h" + +#endif /* FREERDP_CLIENT_WIN_FREERDP_H */ diff --git a/third_party/FreeRDP/client/Windows/resource/FreeRDP.ico b/third_party/FreeRDP/client/Windows/resource/FreeRDP.ico new file mode 100644 index 0000000..0f35c68 Binary files /dev/null and b/third_party/FreeRDP/client/Windows/resource/FreeRDP.ico differ diff --git a/third_party/FreeRDP/client/Windows/resource/close.bmp b/third_party/FreeRDP/client/Windows/resource/close.bmp new file mode 100644 index 0000000..bb17b28 Binary files /dev/null and b/third_party/FreeRDP/client/Windows/resource/close.bmp differ diff --git a/third_party/FreeRDP/client/Windows/resource/close_active.bmp b/third_party/FreeRDP/client/Windows/resource/close_active.bmp new file mode 100644 index 0000000..a59b2e3 Binary files /dev/null and b/third_party/FreeRDP/client/Windows/resource/close_active.bmp differ diff --git a/third_party/FreeRDP/client/Windows/resource/lock.bmp b/third_party/FreeRDP/client/Windows/resource/lock.bmp new file mode 100644 index 0000000..0442c02 Binary files /dev/null and b/third_party/FreeRDP/client/Windows/resource/lock.bmp differ diff --git a/third_party/FreeRDP/client/Windows/resource/lock_active.bmp b/third_party/FreeRDP/client/Windows/resource/lock_active.bmp new file mode 100644 index 0000000..2e37e36 Binary files /dev/null and b/third_party/FreeRDP/client/Windows/resource/lock_active.bmp differ diff --git a/third_party/FreeRDP/client/Windows/resource/minimize.bmp b/third_party/FreeRDP/client/Windows/resource/minimize.bmp new file mode 100644 index 0000000..485e170 Binary files /dev/null and b/third_party/FreeRDP/client/Windows/resource/minimize.bmp differ diff --git a/third_party/FreeRDP/client/Windows/resource/minimize_active.bmp b/third_party/FreeRDP/client/Windows/resource/minimize_active.bmp new file mode 100644 index 0000000..e16b252 Binary files /dev/null and b/third_party/FreeRDP/client/Windows/resource/minimize_active.bmp differ diff --git a/third_party/FreeRDP/client/Windows/resource/resource.h b/third_party/FreeRDP/client/Windows/resource/resource.h new file mode 100644 index 0000000..35991fc --- /dev/null +++ b/third_party/FreeRDP/client/Windows/resource/resource.h @@ -0,0 +1,12 @@ + +#define IDI_ICON1 101 +#define IDB_MINIMIZE 103 +#define IDB_MINIMIZE_ACT 104 +#define IDB_LOCK 105 +#define IDB_LOCK_ACT 106 +#define IDB_UNLOCK 107 +#define IDB_UNLOCK_ACT 108 +#define IDB_CLOSE 109 +#define IDB_CLOSE_ACT 100 +#define IDB_RESTORE 111 +#define IDB_RESTORE_ACT 112 diff --git a/third_party/FreeRDP/client/Windows/resource/restore.bmp b/third_party/FreeRDP/client/Windows/resource/restore.bmp new file mode 100644 index 0000000..26117b0 Binary files /dev/null and b/third_party/FreeRDP/client/Windows/resource/restore.bmp differ diff --git a/third_party/FreeRDP/client/Windows/resource/restore_active.bmp b/third_party/FreeRDP/client/Windows/resource/restore_active.bmp new file mode 100644 index 0000000..c2479ba Binary files /dev/null and b/third_party/FreeRDP/client/Windows/resource/restore_active.bmp differ diff --git a/third_party/FreeRDP/client/Windows/resource/unlock.bmp b/third_party/FreeRDP/client/Windows/resource/unlock.bmp new file mode 100644 index 0000000..fc1b0d3 Binary files /dev/null and b/third_party/FreeRDP/client/Windows/resource/unlock.bmp differ diff --git a/third_party/FreeRDP/client/Windows/resource/unlock_active.bmp b/third_party/FreeRDP/client/Windows/resource/unlock_active.bmp new file mode 100644 index 0000000..5a7f007 Binary files /dev/null and b/third_party/FreeRDP/client/Windows/resource/unlock_active.bmp differ diff --git a/third_party/FreeRDP/client/Windows/resource/wfreerdp.rc b/third_party/FreeRDP/client/Windows/resource/wfreerdp.rc new file mode 100644 index 0000000..71446c5 --- /dev/null +++ b/third_party/FreeRDP/client/Windows/resource/wfreerdp.rc @@ -0,0 +1,14 @@ + +#include "resource.h" + +IDI_ICON1 ICON "FreeRDP.ico" +IDB_MINIMIZE BITMAP "minimize.bmp" +IDB_MINIMIZE_ACT BITMAP "minimize_active.bmp" +IDB_LOCK BITMAP "lock.bmp" +IDB_LOCK_ACT BITMAP "lock_active.bmp" +IDB_UNLOCK BITMAP "unlock.bmp" +IDB_UNLOCK_ACT BITMAP "unlock_active.bmp" +IDB_CLOSE BITMAP "close.bmp" +IDB_CLOSE_ACT BITMAP "close_active.bmp" +IDB_RESTORE BITMAP "restore.bmp" +IDB_RESTORE_ACT BITMAP "restore_active.bmp" diff --git a/third_party/FreeRDP/client/Windows/wf_channels.c b/third_party/FreeRDP/client/Windows/wf_channels.c new file mode 100644 index 0000000..65e7154 --- /dev/null +++ b/third_party/FreeRDP/client/Windows/wf_channels.c @@ -0,0 +1,86 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include "wf_channels.h" + +#include "wf_rail.h" +#include "wf_cliprdr.h" + +#include +#include + +#include +#define TAG CLIENT_TAG("windows") + +void wf_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e) +{ + wfContext* wfc = (wfContext*)context; + rdpSettings* settings; + + WINPR_ASSERT(wfc); + WINPR_ASSERT(e); + + settings = wfc->common.context.settings; + WINPR_ASSERT(settings); + + if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0) + { + wf_rail_init(wfc, (RailClientContext*)e->pInterface); + } + else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) + { + wf_cliprdr_init(wfc, (CliprdrClientContext*)e->pInterface); + } + else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0) + { + wfc->disp = (DispClientContext*)e->pInterface; + } + else + freerdp_client_OnChannelConnectedEventHandler(context, e); +} + +void wf_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e) +{ + wfContext* wfc = (wfContext*)context; + rdpSettings* settings; + + WINPR_ASSERT(wfc); + WINPR_ASSERT(e); + + settings = wfc->common.context.settings; + WINPR_ASSERT(settings); + + if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0) + { + wf_rail_uninit(wfc, (RailClientContext*)e->pInterface); + } + else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) + { + wf_cliprdr_uninit(wfc, (CliprdrClientContext*)e->pInterface); + } + else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0) + { + wfc->disp = nullptr; + } + else + freerdp_client_OnChannelDisconnectedEventHandler(context, e); +} diff --git a/third_party/FreeRDP/client/Windows/wf_channels.h b/third_party/FreeRDP/client/Windows/wf_channels.h new file mode 100644 index 0000000..8676310 --- /dev/null +++ b/third_party/FreeRDP/client/Windows/wf_channels.h @@ -0,0 +1,36 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_WIN_CHANNELS_H +#define FREERDP_CLIENT_WIN_CHANNELS_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wf_client.h" + +void wf_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e); +void wf_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e); + +#endif /* FREERDP_CLIENT_WIN_CHANNELS_H */ diff --git a/third_party/FreeRDP/client/Windows/wf_client.c b/third_party/FreeRDP/client/Windows/wf_client.c new file mode 100644 index 0000000..e2459b9 --- /dev/null +++ b/third_party/FreeRDP/client/Windows/wf_client.c @@ -0,0 +1,1542 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Windows Client + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef WITH_PROGRESS_BAR +#include +#endif + +#ifdef WITH_WINDOWS_CERT_STORE +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "wf_gdi.h" +#include "wf_rail.h" +#include "wf_channels.h" +#include "wf_graphics.h" + +#include "resource/resource.h" + +#define TAG CLIENT_TAG("windows") + +#define WM_FREERDP_SHOWWINDOW (WM_USER + 100) + +static BOOL wf_has_console(void) +{ +#ifdef WITH_WIN_CONSOLE + int file = _fileno(stdin); + int tty = _isatty(file); +#else + int file = -1; + int tty = 0; +#endif + + WLog_INFO(TAG, "Detected stdin=%d -> %s mode", file, tty ? "console" : "gui"); + return tty; +} + +static BOOL wf_end_paint(rdpContext* context) +{ + RECT updateRect = WINPR_C_ARRAY_INIT; + REGION16 invalidRegion = WINPR_C_ARRAY_INIT; + RECTANGLE_16 invalidRect = WINPR_C_ARRAY_INIT; + const RECTANGLE_16* extents = nullptr; + + WINPR_ASSERT(context); + + wfContext* wfc = (wfContext*)context; + rdpGdi* gdi = context->gdi; + WINPR_ASSERT(gdi); + + HGDI_DC hdc = gdi->primary->hdc; + WINPR_ASSERT(hdc); + if (!hdc->hwnd) + return TRUE; + + HGDI_WND hwnd = hdc->hwnd; + WINPR_ASSERT(hwnd->invalid || (hwnd->ninvalid == 0)); + + if (hwnd->invalid->null) + return TRUE; + + const int ninvalid = hwnd->ninvalid; + const HGDI_RGN cinvalid = hwnd->cinvalid; + + if (ninvalid < 1) + return TRUE; + + region16_init(&invalidRegion); + + for (int i = 0; i < ninvalid; i++) + { + invalidRect.left = cinvalid[i].x; + invalidRect.top = cinvalid[i].y; + invalidRect.right = cinvalid[i].x + cinvalid[i].w; + invalidRect.bottom = cinvalid[i].y + cinvalid[i].h; + region16_union_rect(&invalidRegion, &invalidRegion, &invalidRect); + } + + if (!region16_is_empty(&invalidRegion)) + { + extents = region16_extents(&invalidRegion); + updateRect.left = extents->left; + updateRect.top = extents->top; + updateRect.right = extents->right; + updateRect.bottom = extents->bottom; + + wf_scale_rect(wfc, &updateRect); + + InvalidateRect(wfc->hwnd, &updateRect, FALSE); + + if (wfc->rail) + wf_rail_invalidate_region(wfc, &invalidRegion); + } + + region16_uninit(&invalidRegion); + + if (!wfc->is_shown) + { + wfc->is_shown = TRUE; + +#ifdef WITH_PROGRESS_BAR + if (wfc->taskBarList) + { + wfc->taskBarList->lpVtbl->SetProgressState(wfc->taskBarList, wfc->hwnd, + TBPF_NOPROGRESS); + } +#endif + + PostMessage(wfc->hwnd, WM_FREERDP_SHOWWINDOW, 0, 0); + WLog_INFO(TAG, "Window is shown!"); + } + return TRUE; +} + +static BOOL wf_begin_paint(rdpContext* context) +{ + HGDI_DC hdc; + + if (!context || !context->gdi || !context->gdi->primary || !context->gdi->primary->hdc) + return FALSE; + + hdc = context->gdi->primary->hdc; + + if (!hdc || !hdc->hwnd || !hdc->hwnd->invalid) + return FALSE; + + hdc->hwnd->invalid->null = TRUE; + hdc->hwnd->ninvalid = 0; + return TRUE; +} + +static BOOL wf_desktop_resize(rdpContext* context) +{ + BOOL same; + RECT rect; + rdpSettings* settings; + wfContext* wfc = (wfContext*)context; + + if (!context || !context->settings) + return FALSE; + + settings = context->settings; + + if (wfc->primary) + { + same = (wfc->primary == wfc->drawing) ? TRUE : FALSE; + wf_image_free(wfc->primary); + wfc->primary = + wf_image_new(wfc, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight), + context->gdi->dstFormat, nullptr); + } + + if (!gdi_resize_ex(context->gdi, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight), 0, + context->gdi->dstFormat, wfc->primary->pdata, nullptr)) + return FALSE; + + if (same) + wfc->drawing = wfc->primary; + + if (wfc->fullscreen != TRUE) + { + if (wfc->hwnd && !freerdp_settings_get_bool(settings, FreeRDP_SmartSizing)) + SetWindowPos(wfc->hwnd, HWND_TOP, -1, -1, + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) + wfc->diff.x, + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) + wfc->diff.y, + SWP_NOMOVE); + } + else + { + wf_update_offset(wfc); + GetWindowRect(wfc->hwnd, &rect); + InvalidateRect(wfc->hwnd, &rect, TRUE); + } + + return TRUE; +} + +static BOOL wf_pre_connect(freerdp* instance) +{ + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->context); + WINPR_ASSERT(instance->context->settings); + + rdpContext* context = instance->context; + wfContext* wfc = (wfContext*)instance->context; + rdpSettings* settings = context->settings; + if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMajorType, OSMAJORTYPE_WINDOWS)) + return FALSE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMinorType, OSMINORTYPE_WINDOWS_NT)) + return FALSE; + wfc->fullscreen = freerdp_settings_get_bool(settings, FreeRDP_Fullscreen); + wfc->fullscreen_toggle = freerdp_settings_get_bool(settings, FreeRDP_ToggleFullscreen); + UINT32 desktopWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + UINT32 desktopHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + + if (wfc->percentscreen > 0) + { + desktopWidth = (GetSystemMetrics(SM_CXSCREEN) * wfc->percentscreen) / 100; + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, desktopWidth)) + return FALSE; + desktopHeight = (GetSystemMetrics(SM_CYSCREEN) * wfc->percentscreen) / 100; + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, desktopHeight)) + return FALSE; + } + + if (wfc->fullscreen) + { + if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon)) + { + desktopWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN); + desktopHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN); + } + else + { + desktopWidth = GetSystemMetrics(SM_CXSCREEN); + desktopHeight = GetSystemMetrics(SM_CYSCREEN); + } + } + + /* FIXME: desktopWidth has a limitation that it should be divisible by 4, + * otherwise the screen will crash when connecting to an XP desktop.*/ + desktopWidth = (desktopWidth + 3) & (~3); + + if (desktopWidth != freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth)) + { + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, desktopWidth)) + return FALSE; + } + + if (desktopHeight != freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)) + { + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, desktopHeight)) + return FALSE; + } + + DWORD keyboardLayoutId = freerdp_settings_get_uint32(settings, FreeRDP_KeyboardLayout); + + { + CHAR name[KL_NAMELENGTH + 1] = WINPR_C_ARRAY_INIT; + if (GetKeyboardLayoutNameA(name)) + { + ULONG rc = 0; + + errno = 0; + rc = strtoul(name, nullptr, 16); + if (errno == 0) + keyboardLayoutId = rc; + } + + if (keyboardLayoutId == 0) + { + const HKL layout = GetKeyboardLayout(0); + const uint32_t masked = (uint32_t)(((uintptr_t)layout >> 16) & 0xFFFF); + keyboardLayoutId = masked; + } + } + + if (keyboardLayoutId == 0) + freerdp_detect_keyboard_layout_from_system_locale(&keyboardLayoutId); + if (keyboardLayoutId == 0) + keyboardLayoutId = ENGLISH_UNITED_STATES; + if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardLayout, keyboardLayoutId)) + return FALSE; + PubSub_SubscribeChannelConnected(instance->context->pubSub, wf_OnChannelConnectedEventHandler); + PubSub_SubscribeChannelDisconnected(instance->context->pubSub, + wf_OnChannelDisconnectedEventHandler); + return TRUE; +} + +static void wf_append_item_to_system_menu(HMENU hMenu, UINT fMask, UINT wID, const wchar_t* text, + wfContext* wfc) +{ + MENUITEMINFO item_info = WINPR_C_ARRAY_INIT; + item_info.fMask = fMask; + item_info.cbSize = sizeof(MENUITEMINFO); + item_info.wID = wID; + item_info.fType = MFT_STRING; + item_info.dwTypeData = _wcsdup(text); + item_info.cch = (UINT)_wcslen(text); + if (wfc) + item_info.dwItemData = (ULONG_PTR)wfc; + InsertMenuItem(hMenu, wfc->systemMenuInsertPosition++, TRUE, &item_info); +} + +static void wf_add_system_menu(wfContext* wfc) +{ + HMENU hMenu; + + if (wfc->fullscreen && !wfc->fullscreen_toggle) + { + return; + } + + if (freerdp_settings_get_bool(wfc->common.context.settings, FreeRDP_DynamicResolutionUpdate)) + { + return; + } + + hMenu = GetSystemMenu(wfc->hwnd, FALSE); + + wf_append_item_to_system_menu(hMenu, + MIIM_CHECKMARKS | MIIM_FTYPE | MIIM_ID | MIIM_STRING | MIIM_DATA, + SYSCOMMAND_ID_SMARTSIZING, L"Smart sizing", wfc); + + if (freerdp_settings_get_bool(wfc->common.context.settings, FreeRDP_SmartSizing)) + { + CheckMenuItem(hMenu, SYSCOMMAND_ID_SMARTSIZING, MF_CHECKED); + } + + if (freerdp_settings_get_bool(wfc->common.context.settings, FreeRDP_RemoteAssistanceMode)) + wf_append_item_to_system_menu(hMenu, MIIM_FTYPE | MIIM_ID | MIIM_STRING, + SYSCOMMAND_ID_REQUEST_CONTROL, L"Request control", wfc); +} + +static WCHAR* wf_window_get_title(rdpSettings* settings) +{ + BOOL port; + WCHAR* windowTitle = nullptr; + size_t size; + WCHAR prefix[] = L"FreeRDP:"; + + if (!settings) + return nullptr; + + const char* name = freerdp_settings_get_string(settings, FreeRDP_ServerHostname); + + if (freerdp_settings_get_string(settings, FreeRDP_WindowTitle)) + return ConvertUtf8ToWCharAlloc(freerdp_settings_get_string(settings, FreeRDP_WindowTitle), + nullptr); + + port = (freerdp_settings_get_uint32(settings, FreeRDP_ServerPort) != 3389); + size = strlen(name) + 16 + wcslen(prefix); + windowTitle = calloc(size, sizeof(WCHAR)); + + if (!windowTitle) + return nullptr; + + if (!port) + _snwprintf_s(windowTitle, size, _TRUNCATE, L"%s %S", prefix, name); + else + _snwprintf_s(windowTitle, size, _TRUNCATE, L"%s %S:%u", prefix, name, + freerdp_settings_get_uint32(settings, FreeRDP_ServerPort)); + + return windowTitle; +} + +static BOOL wf_post_connect(freerdp* instance) +{ + rdpGdi* gdi; + DWORD dwStyle; + rdpCache* cache; + wfContext* wfc; + rdpContext* context; + rdpSettings* settings; + EmbedWindowEventArgs e; + const UINT32 format = PIXEL_FORMAT_BGRX32; + + WINPR_ASSERT(instance); + + context = instance->context; + WINPR_ASSERT(context); + + settings = context->settings; + WINPR_ASSERT(settings); + + wfc = (wfContext*)instance->context; + WINPR_ASSERT(wfc); + + wfc->primary = + wf_image_new(wfc, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight), format, nullptr); + + if (!wfc->primary) + { + WLog_ERR(TAG, "Failed to allocate primary surface"); + return FALSE; + } + + if (!gdi_init_ex(instance, format, 0, wfc->primary->pdata, nullptr)) + return FALSE; + + cache = instance->context->cache; + WINPR_ASSERT(cache); + + gdi = instance->context->gdi; + + if (!freerdp_settings_get_bool(settings, FreeRDP_SoftwareGdi)) + { + wf_gdi_register_update_callbacks(context->update); + } + + wfc->window_title = wf_window_get_title(settings); + + if (!wfc->window_title) + return FALSE; + + if (freerdp_settings_get_bool(settings, FreeRDP_EmbeddedWindow)) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_Decorations, FALSE)) + return FALSE; + } + + if (wfc->fullscreen) + dwStyle = WS_POPUP; + else if (!freerdp_settings_get_bool(settings, FreeRDP_Decorations)) + dwStyle = WS_CHILD | WS_BORDER; + else + dwStyle = + WS_CAPTION | WS_OVERLAPPED | WS_SYSMENU | WS_MINIMIZEBOX | WS_SIZEBOX | WS_MAXIMIZEBOX; + + if (!wfc->hwnd) + { + wfc->hwnd = CreateWindowEx(0, wfc->wndClassName, wfc->window_title, dwStyle, 0, 0, 0, 0, + wfc->hWndParent, nullptr, wfc->hInstance, nullptr); + if (!wfc->hwnd) + { + WLog_ERR(TAG, "CreateWindowEx failed with error: %lu", GetLastError()); + return FALSE; + } + SetWindowLongPtr(wfc->hwnd, GWLP_USERDATA, (LONG_PTR)wfc); + } + + wf_resize_window(wfc); + wf_add_system_menu(wfc); + BitBlt(wfc->primary->hdc, 0, 0, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight), nullptr, 0, 0, BLACKNESS); + wfc->drawing = wfc->primary; + EventArgsInit(&e, "wfreerdp"); + e.embed = FALSE; + e.handle = (void*)wfc->hwnd; + PubSub_OnEmbedWindow(context->pubSub, context, &e); +#ifdef WITH_PROGRESS_BAR + if (wfc->taskBarList) + { + ShowWindow(wfc->hwnd, SW_SHOWMINIMIZED); + wfc->taskBarList->lpVtbl->SetProgressState(wfc->taskBarList, wfc->hwnd, TBPF_INDETERMINATE); + } +#endif + UpdateWindow(wfc->hwnd); + context->update->BeginPaint = wf_begin_paint; + context->update->DesktopResize = wf_desktop_resize; + context->update->EndPaint = wf_end_paint; + context->update->SetKeyboardIndicators = wf_keyboard_set_indicators; + wf_register_pointer(context->graphics); + + wfc->floatbar = wf_floatbar_new(wfc, wfc->hInstance, + freerdp_settings_get_uint32(settings, FreeRDP_Floatbar)); + + wf_event_focus_in(wfc); + + return TRUE; +} + +static void wf_post_disconnect(freerdp* instance) +{ + wfContext* wfc; + + if (!instance || !instance->context) + return; + + wfc = (wfContext*)instance->context; + free(wfc->window_title); +} + +static CREDUI_INFOW wfUiInfo = { sizeof(CREDUI_INFOW), nullptr, L"Enter your credentials", + L"Remote Desktop Security", nullptr }; + +static BOOL wf_authenticate_ex(freerdp* instance, char** username, char** password, char** domain, + rdp_auth_reason reason) +{ + wfContext* wfc; + BOOL fSave; + DWORD status; + DWORD dwFlags; + WCHAR UserNameW[CREDUI_MAX_USERNAME_LENGTH + 1] = WINPR_C_ARRAY_INIT; + WCHAR UserW[CREDUI_MAX_USERNAME_LENGTH + 1] = WINPR_C_ARRAY_INIT; + WCHAR DomainW[CREDUI_MAX_DOMAIN_TARGET_LENGTH + 1] = WINPR_C_ARRAY_INIT; + WCHAR PasswordW[CREDUI_MAX_PASSWORD_LENGTH + 1] = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->context); + WINPR_ASSERT(instance->context->settings); + + wfc = (wfContext*)instance->context; + WINPR_ASSERT(wfc); + + WINPR_ASSERT(username); + WINPR_ASSERT(domain); + WINPR_ASSERT(password); + + const WCHAR auth[] = L"Target credentials requested"; + const WCHAR authPin[] = L"PIN requested"; + const WCHAR gwAuth[] = L"Gateway credentials requested"; + const WCHAR* titleW = auth; + + fSave = FALSE; + dwFlags = CREDUI_FLAGS_DO_NOT_PERSIST | CREDUI_FLAGS_EXCLUDE_CERTIFICATES | + CREDUI_FLAGS_USERNAME_TARGET_CREDENTIALS; + switch (reason) + { + case AUTH_NLA: + break; + case AUTH_TLS: + case AUTH_RDP: + if ((*username) && (*password)) + return TRUE; + break; + case AUTH_SMARTCARD_PIN: + dwFlags &= ~CREDUI_FLAGS_USERNAME_TARGET_CREDENTIALS; + dwFlags |= CREDUI_FLAGS_PASSWORD_ONLY_OK | CREDUI_FLAGS_KEEP_USERNAME; + titleW = authPin; + if (*password) + return TRUE; + if (!(*username)) + *username = _strdup("PIN"); + break; + case GW_AUTH_HTTP: + case GW_AUTH_RDG: + case GW_AUTH_RPC: + titleW = gwAuth; + break; + default: + return FALSE; + } + + if (*username) + { + (void)ConvertUtf8ToWChar(*username, UserNameW, ARRAYSIZE(UserNameW)); + (void)ConvertUtf8ToWChar(*username, UserW, ARRAYSIZE(UserW)); + } + + if (*password) + (void)ConvertUtf8ToWChar(*password, PasswordW, ARRAYSIZE(PasswordW)); + + if (*domain) + (void)ConvertUtf8ToWChar(*domain, DomainW, ARRAYSIZE(DomainW)); + + if (_wcsnlen(PasswordW, ARRAYSIZE(PasswordW)) == 0) + { + if (!wfc->isConsole && + freerdp_settings_get_bool(wfc->common.context.settings, FreeRDP_CredentialsFromStdin)) + WLog_ERR(TAG, "Flag for stdin read present but stdin is redirected; using GUI"); + if (wfc->isConsole && + freerdp_settings_get_bool(wfc->common.context.settings, FreeRDP_CredentialsFromStdin)) + status = CredUICmdLinePromptForCredentialsW(titleW, nullptr, 0, UserNameW, + ARRAYSIZE(UserNameW), PasswordW, + ARRAYSIZE(PasswordW), &fSave, dwFlags); + else + status = CredUIPromptForCredentialsW(&wfUiInfo, titleW, nullptr, 0, UserNameW, + ARRAYSIZE(UserNameW), PasswordW, + ARRAYSIZE(PasswordW), &fSave, dwFlags); + if (status != NO_ERROR) + { + WLog_ERR(TAG, "CredUIPromptForCredentials unexpected status: 0x%08lX", status); + return FALSE; + } + + if ((dwFlags & CREDUI_FLAGS_KEEP_USERNAME) == 0) + { + status = CredUIParseUserNameW(UserNameW, UserW, ARRAYSIZE(UserW), DomainW, + ARRAYSIZE(DomainW)); + if (status != NO_ERROR) + { + CHAR User[CREDUI_MAX_USERNAME_LENGTH + 1] = WINPR_C_ARRAY_INIT; + CHAR UserName[CREDUI_MAX_USERNAME_LENGTH + 1] = WINPR_C_ARRAY_INIT; + CHAR Domain[CREDUI_MAX_DOMAIN_TARGET_LENGTH + 1] = WINPR_C_ARRAY_INIT; + + (void)ConvertWCharNToUtf8(UserNameW, ARRAYSIZE(UserNameW), UserName, + ARRAYSIZE(UserName)); + (void)ConvertWCharNToUtf8(UserW, ARRAYSIZE(UserW), User, ARRAYSIZE(User)); + (void)ConvertWCharNToUtf8(DomainW, ARRAYSIZE(DomainW), Domain, ARRAYSIZE(Domain)); + WLog_ERR(TAG, "Failed to parse UserName: %s into User: %s Domain: %s", UserName, + User, Domain); + return FALSE; + } + } + } + + *username = ConvertWCharNToUtf8Alloc(UserW, ARRAYSIZE(UserW), nullptr); + if (!(*username)) + { + WLog_ERR(TAG, "ConvertWCharNToUtf8Alloc failed", status); + return FALSE; + } + + if (_wcsnlen(DomainW, ARRAYSIZE(DomainW)) > 0) + *domain = ConvertWCharNToUtf8Alloc(DomainW, ARRAYSIZE(DomainW), nullptr); + else + *domain = _strdup("\0"); + + if (!(*domain)) + { + free(*username); + WLog_ERR(TAG, "strdup failed", status); + return FALSE; + } + + *password = ConvertWCharNToUtf8Alloc(PasswordW, ARRAYSIZE(PasswordW), nullptr); + if (!(*password)) + { + free(*username); + free(*domain); + return FALSE; + } + + return TRUE; +} + +static WCHAR* wf_format_text(const WCHAR* fmt, ...) +{ + int rc; + size_t size = 0; + WCHAR* buffer = nullptr; + + do + { + WCHAR* tmp = nullptr; + va_list ap = WINPR_C_ARRAY_INIT; + va_start(ap, fmt); + rc = _vsnwprintf(buffer, size, fmt, ap); + va_end(ap); + if (rc <= 0) + goto fail; + + if ((size_t)rc < size) + return buffer; + + size = (size_t)rc + 1; + tmp = realloc(buffer, size * sizeof(WCHAR)); + if (!tmp) + goto fail; + + buffer = tmp; + } while (TRUE); + +fail: + free(buffer); + return nullptr; +} + +#ifdef WITH_WINDOWS_CERT_STORE +/* https://stackoverflow.com/questions/1231178/load-an-pem-encoded-x-509-certificate-into-windows-cryptoapi/3803333#3803333 + */ +/* https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/Win7Samples/security/cryptoapi/peertrust/cpp/peertrust.cpp + */ +/* https://stackoverflow.com/questions/7340504/whats-the-correct-way-to-verify-an-ssl-certificate-in-win32 + */ + +static void wf_report_error(char* wszMessage, DWORD dwErrCode) +{ + LPSTR pwszMsgBuf = nullptr; + + if (nullptr != wszMessage && 0 != *wszMessage) + { + WLog_ERR(TAG, "%s", wszMessage); + } + + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + nullptr, // Location of message + // definition ignored + dwErrCode, // Message identifier for + // the requested message + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Language identifier for + // the requested message + (LPSTR)&pwszMsgBuf, // Buffer that receives + // the formatted message + 0, // Size of output buffer + // not needed as allocate + // buffer flag is set + nullptr // Array of insert values + ); + + if (nullptr != pwszMsgBuf) + { + WLog_ERR(TAG, "Error: 0x%08x (%d) %s", dwErrCode, dwErrCode, pwszMsgBuf); + LocalFree(pwszMsgBuf); + } + else + { + WLog_ERR(TAG, "Error: 0x%08x (%d)", dwErrCode, dwErrCode); + } +} + +static DWORD wf_is_x509_certificate_trusted(const char* common_name, const char* subject, + const char* issuer, const char* fingerprint) +{ + HRESULT hr = CRYPT_E_NOT_FOUND; + + DWORD dwChainFlags = CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT; + PCCERT_CONTEXT pCert = nullptr; + HCERTCHAINENGINE hChainEngine = nullptr; + PCCERT_CHAIN_CONTEXT pChainContext = nullptr; + + CERT_ENHKEY_USAGE EnhkeyUsage = WINPR_C_ARRAY_INIT; + CERT_USAGE_MATCH CertUsage = WINPR_C_ARRAY_INIT; + CERT_CHAIN_PARA ChainPara = WINPR_C_ARRAY_INIT; + CERT_CHAIN_POLICY_PARA ChainPolicy = WINPR_C_ARRAY_INIT; + CERT_CHAIN_POLICY_STATUS PolicyStatus = WINPR_C_ARRAY_INIT; + CERT_CHAIN_ENGINE_CONFIG EngineConfig = WINPR_C_ARRAY_INIT; + + DWORD derPubKeyLen = WINPR_ASSERTING_INT_CAST(uint32_t, strlen(fingerprint)); + char* derPubKey = calloc(derPubKeyLen, sizeof(char)); + if (nullptr == derPubKey) + { + WLog_ERR(TAG, "Could not allocate derPubKey"); + goto CleanUp; + } + + /* + * Convert from PEM format to DER format - removes header and footer and decodes from base64 + */ + if (!CryptStringToBinaryA(fingerprint, 0, CRYPT_STRING_BASE64HEADER, derPubKey, &derPubKeyLen, + nullptr, nullptr)) + { + WLog_ERR(TAG, "CryptStringToBinary failed. Err: %d", GetLastError()); + goto CleanUp; + } + + //--------------------------------------------------------- + // Initialize data structures for chain building. + + EnhkeyUsage.cUsageIdentifier = 0; + EnhkeyUsage.rgpszUsageIdentifier = nullptr; + + CertUsage.dwType = USAGE_MATCH_TYPE_AND; + CertUsage.Usage = EnhkeyUsage; + + ChainPara.cbSize = sizeof(ChainPara); + ChainPara.RequestedUsage = CertUsage; + + ChainPolicy.cbSize = sizeof(ChainPolicy); + + PolicyStatus.cbSize = sizeof(PolicyStatus); + + EngineConfig.cbSize = sizeof(EngineConfig); + EngineConfig.dwUrlRetrievalTimeout = 0; + + pCert = CertCreateCertificateContext(X509_ASN_ENCODING, derPubKey, derPubKeyLen); + if (nullptr == pCert) + { + WLog_ERR(TAG, "FAILED: Certificate could not be parsed."); + goto CleanUp; + } + + dwChainFlags |= CERT_CHAIN_ENABLE_PEER_TRUST; + + // When this flag is set, end entity certificates in the + // Trusted People store are trusted without doing any chain building + // This optimizes the chain building process. + + //--------------------------------------------------------- + // Create chain engine. + + if (!CertCreateCertificateChainEngine(&EngineConfig, &hChainEngine)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto CleanUp; + } + + //------------------------------------------------------------------- + // Build a chain using CertGetCertificateChain + + if (!CertGetCertificateChain(hChainEngine, // use the default chain engine + pCert, // pointer to the end certificate + nullptr, // use the default time + nullptr, // search no additional stores + &ChainPara, // use AND logic and enhanced key usage + // as indicated in the ChainPara + // data structure + dwChainFlags, + nullptr, // currently reserved + &pChainContext)) // return a pointer to the chain created + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto CleanUp; + } + + //--------------------------------------------------------------- + // Verify that the chain complies with policy + + if (!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_BASE, // use the base policy + pChainContext, // pointer to the chain + &ChainPolicy, + &PolicyStatus)) // return a pointer to the policy status + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto CleanUp; + } + + if (PolicyStatus.dwError != S_OK) + { + wf_report_error("CertVerifyCertificateChainPolicy: Chain Status", PolicyStatus.dwError); + hr = PolicyStatus.dwError; + // Instruction: If the PolicyStatus.dwError is CRYPT_E_NO_REVOCATION_CHECK or + // CRYPT_E_REVOCATION_OFFLINE, it indicates errors in obtaining + // revocation information. These can be ignored since the retrieval of + // revocation information depends on network availability + + if (PolicyStatus.dwError == CRYPT_E_NO_REVOCATION_CHECK || + PolicyStatus.dwError == CRYPT_E_REVOCATION_OFFLINE) + { + hr = S_OK; + } + + goto CleanUp; + } + + WLog_INFO(TAG, "CertVerifyCertificateChainPolicy succeeded for %s (%s) issued by %s", + common_name, subject, issuer); + + hr = S_OK; +CleanUp: + + if (FAILED(hr)) + { + WLog_INFO(TAG, "CertVerifyCertificateChainPolicy failed for %s (%s) issued by %s", + common_name, subject, issuer); + wf_report_error(nullptr, hr); + } + + free(derPubKey); + + if (nullptr != pChainContext) + { + CertFreeCertificateChain(pChainContext); + } + + if (nullptr != hChainEngine) + { + CertFreeCertificateChainEngine(hChainEngine); + } + + if (nullptr != pCert) + { + CertFreeCertificateContext(pCert); + } + + return (DWORD)hr; +} +#endif + +static DWORD wf_cli_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) +{ +#ifdef WITH_WINDOWS_CERT_STORE + if (flags & VERIFY_CERT_FLAG_FP_IS_PEM && !(flags & VERIFY_CERT_FLAG_MISMATCH)) + { + if (wf_is_x509_certificate_trusted(common_name, subject, issuer, fingerprint) == S_OK) + { + return 2; + } + } +#endif + + return client_cli_verify_certificate_ex(instance, host, port, common_name, subject, issuer, + fingerprint, flags); +} + +static DWORD wf_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) +{ + WCHAR* buffer; + WCHAR* caption; + int what = IDCANCEL; + +#ifdef WITH_WINDOWS_CERT_STORE + if (flags & VERIFY_CERT_FLAG_FP_IS_PEM && !(flags & VERIFY_CERT_FLAG_MISMATCH)) + { + if (wf_is_x509_certificate_trusted(common_name, subject, issuer, fingerprint) == S_OK) + { + return 2; + } + } +#endif + + buffer = wf_format_text( + L"Certificate details:\n" + L"\tCommonName: %S\n" + L"\tSubject: %S\n" + L"\tIssuer: %S\n" + L"\tThumbprint: %S\n" + L"\tHostMismatch: %S\n" + L"\n" + L"The above X.509 certificate could not be verified, possibly because you do not have " + L"the CA certificate in your certificate store, or the certificate has expired. " + L"Please look at the OpenSSL documentation on how to add a private CA to the store.\n" + L"\n" + L"YES\tAccept permanently\n" + L"NO\tAccept for this session only\n" + L"CANCEL\tAbort connection\n", + common_name, subject, issuer, fingerprint, + flags & VERIFY_CERT_FLAG_MISMATCH ? "Yes" : "No"); + caption = wf_format_text(L"Verify certificate for %S:%hu", host, port); + + WINPR_UNUSED(instance); + + if (!buffer || !caption) + goto fail; + + what = MessageBoxW(nullptr, buffer, caption, MB_YESNOCANCEL); +fail: + free(buffer); + free(caption); + + /* return 1 to accept and store a certificate, 2 to accept + * a certificate only for this session, 0 otherwise */ + switch (what) + { + case IDYES: + return 1; + case IDNO: + return 2; + default: + return 0; + } +} + +static DWORD wf_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) +{ + WCHAR* buffer; + WCHAR* caption; + int what = IDCANCEL; + + buffer = wf_format_text( + L"New Certificate details:\n" + L"\tCommonName: %S\n" + L"\tSubject: %S\n" + L"\tIssuer: %S\n" + L"\tThumbprint: %S\n" + L"\tHostMismatch: %S\n" + L"\n" + L"Old Certificate details:\n" + L"\tSubject: %S\n" + L"\tIssuer: %S\n" + L"\tThumbprint: %S" + L"The above X.509 certificate could not be verified, possibly because you do not have " + L"the CA certificate in your certificate store, or the certificate has expired. " + L"Please look at the OpenSSL documentation on how to add a private CA to the store.\n" + L"\n" + L"YES\tAccept permanently\n" + L"NO\tAccept for this session only\n" + L"CANCEL\tAbort connection\n", + common_name, subject, issuer, new_fingerprint, + flags & VERIFY_CERT_FLAG_MISMATCH ? "Yes" : "No", old_subject, old_issuer, old_fingerprint); + caption = wf_format_text(L"Verify certificate change for %S:%hu", host, port); + + WINPR_UNUSED(instance); + if (!buffer || !caption) + goto fail; + + what = MessageBoxW(nullptr, buffer, caption, MB_YESNOCANCEL); +fail: + free(buffer); + free(caption); + + /* return 1 to accept and store a certificate, 2 to accept + * a certificate only for this session, 0 otherwise */ + switch (what) + { + case IDYES: + return 1; + case IDNO: + return 2; + default: + return 0; + } +} + +static BOOL wf_present_gateway_message(freerdp* instance, UINT32 type, BOOL isDisplayMandatory, + BOOL isConsentMandatory, size_t length, const WCHAR* message) +{ + if (!isDisplayMandatory && !isConsentMandatory) + return TRUE; + + /* special handling for consent messages (show modal dialog) */ + if (type == GATEWAY_MESSAGE_CONSENT && isConsentMandatory) + { + int mbRes; + WCHAR* msg; + + msg = wf_format_text(L"%.*s\n\nI understand and agree to the terms of this policy", length, + message); + mbRes = MessageBoxW(nullptr, msg, L"Consent Message", MB_YESNO); + free(msg); + + if (mbRes != IDYES) + return FALSE; + } + else + return client_cli_present_gateway_message(instance, type, isDisplayMandatory, + isConsentMandatory, length, message); + + return TRUE; +} + +static DWORD WINAPI wf_client_thread(LPVOID lpParam) +{ + MSG msg = WINPR_C_ARRAY_INIT; + int width = 0; + int height = 0; + BOOL msg_ret = FALSE; + int quit_msg = 0; + DWORD error = 0; + + freerdp* instance = (freerdp*)lpParam; + WINPR_ASSERT(instance); + + if (!freerdp_connect(instance)) + goto end; + + rdpContext* context = instance->context; + WINPR_ASSERT(context); + + wfContext* wfc = (wfContext*)instance->context; + WINPR_ASSERT(wfc); + + rdpChannels* channels = context->channels; + WINPR_ASSERT(channels); + + rdpSettings* settings = context->settings; + WINPR_ASSERT(settings); + + while (!freerdp_shall_disconnect_context(instance->context)) + { + HANDLE handles[MAXIMUM_WAIT_OBJECTS] = WINPR_C_ARRAY_INIT; + DWORD nCount = 0; + + if (freerdp_focus_required(instance)) + { + wf_event_focus_in(wfc); + wf_event_focus_in(wfc); + } + + { + DWORD tmp = freerdp_get_event_handles(context, &handles[nCount], 64 - nCount); + + if (tmp == 0) + { + WLog_ERR(TAG, "freerdp_get_event_handles failed"); + break; + } + + nCount += tmp; + } + + DWORD status = MsgWaitForMultipleObjectsEx(nCount, handles, 5 * 1000, QS_ALLINPUT, + MWMO_ALERTABLE | MWMO_INPUTAVAILABLE); + if (status == WAIT_FAILED) + { + WLog_ERR(TAG, "wfreerdp_run: WaitForMultipleObjects failed: 0x%08lX", GetLastError()); + break; + } + + { + if (!freerdp_check_event_handles(context)) + { + if (client_auto_reconnect(instance)) + continue; + + WLog_ERR(TAG, "Failed to check FreeRDP file descriptor"); + break; + } + } + + if (freerdp_shall_disconnect_context(instance->context)) + break; + + quit_msg = FALSE; + + while (PeekMessage(&msg, nullptr, 0, 0, PM_NOREMOVE)) + { + msg_ret = GetMessage(&msg, nullptr, 0, 0); + + if (freerdp_settings_get_bool(settings, FreeRDP_EmbeddedWindow)) + { + if ((msg.message == WM_SETFOCUS) && (msg.lParam == 1)) + { + PostMessage(wfc->hwnd, WM_SETFOCUS, 0, 0); + } + else if ((msg.message == WM_KILLFOCUS) && (msg.lParam == 1)) + { + PostMessage(wfc->hwnd, WM_KILLFOCUS, 0, 0); + } + } + + switch (msg.message) + { + case WM_SIZE: + { + width = LOWORD(msg.lParam); + height = HIWORD(msg.lParam); + SetWindowPos(wfc->hwnd, HWND_TOP, 0, 0, width, height, SWP_FRAMECHANGED); + break; + } + case WM_FREERDP_SHOWWINDOW: + { + ShowWindow(wfc->hwnd, SW_NORMAL); + break; + } + default: + break; + } + + if ((msg_ret == 0) || (msg_ret == -1)) + { + quit_msg = TRUE; + break; + } + + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + if (quit_msg) + break; + } + + /* cleanup */ + freerdp_disconnect(instance); + +end: + error = freerdp_get_last_error(instance->context); + WLog_DBG(TAG, "Main thread exited with %" PRIu32, error); + ExitThread(error); + return error; +} + +static DWORD WINAPI wf_keyboard_thread(LPVOID lpParam) +{ + MSG msg; + BOOL status; + wfContext* wfc; + HHOOK hook_handle; + wfc = (wfContext*)lpParam; + WINPR_ASSERT(nullptr != wfc); + hook_handle = SetWindowsHookEx(WH_KEYBOARD_LL, wf_ll_kbd_proc, wfc->hInstance, 0); + + if (hook_handle) + { + while ((status = GetMessage(&msg, nullptr, 0, 0)) != 0) + { + if (status == -1) + { + WLog_ERR(TAG, "keyboard thread error getting message"); + break; + } + else + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + UnhookWindowsHookEx(hook_handle); + } + else + { + WLog_ERR(TAG, "failed to install keyboard hook"); + } + + WLog_DBG(TAG, "Keyboard thread exited."); + ExitThread(0); + return 0; +} + +int freerdp_client_set_window_size(wfContext* wfc, int width, int height) +{ + WLog_DBG(TAG, "freerdp_client_set_window_size %d, %d", width, height); + + if ((width != wfc->client_width) || (height != wfc->client_height)) + { + PostThreadMessage(wfc->mainThreadId, WM_SIZE, SIZE_RESTORED, + ((UINT)height << 16) | (UINT)width); + } + + return 0; +} + +void wf_size_scrollbars(wfContext* wfc, UINT32 client_width, UINT32 client_height) +{ + const rdpSettings* settings; + WINPR_ASSERT(wfc); + + settings = wfc->common.context.settings; + WINPR_ASSERT(settings); + + if (wfc->disablewindowtracking) + return; + + // prevent infinite message loop + wfc->disablewindowtracking = TRUE; + + if (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) || + freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate)) + { + wfc->xCurrentScroll = 0; + wfc->yCurrentScroll = 0; + + if (wfc->xScrollVisible || wfc->yScrollVisible) + { + if (ShowScrollBar(wfc->hwnd, SB_BOTH, FALSE)) + { + wfc->xScrollVisible = FALSE; + wfc->yScrollVisible = FALSE; + } + } + } + else + { + SCROLLINFO si; + BOOL horiz = wfc->xScrollVisible; + BOOL vert = wfc->yScrollVisible; + + if (!horiz && client_width < freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth)) + { + horiz = TRUE; + } + else if (horiz && + client_width >= + freerdp_settings_get_uint32( + settings, FreeRDP_DesktopWidth) /* - GetSystemMetrics(SM_CXVSCROLL)*/) + { + horiz = FALSE; + } + + if (!vert && client_height < freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)) + { + vert = TRUE; + } + else if (vert && + client_height >= + freerdp_settings_get_uint32( + settings, FreeRDP_DesktopHeight) /* - GetSystemMetrics(SM_CYHSCROLL)*/) + { + vert = FALSE; + } + + if (horiz == vert && (horiz != wfc->xScrollVisible && vert != wfc->yScrollVisible)) + { + if (ShowScrollBar(wfc->hwnd, SB_BOTH, horiz)) + { + wfc->xScrollVisible = horiz; + wfc->yScrollVisible = vert; + } + } + + if (horiz != wfc->xScrollVisible) + { + if (ShowScrollBar(wfc->hwnd, SB_HORZ, horiz)) + { + wfc->xScrollVisible = horiz; + } + } + + if (vert != wfc->yScrollVisible) + { + if (ShowScrollBar(wfc->hwnd, SB_VERT, vert)) + { + wfc->yScrollVisible = vert; + } + } + + if (horiz) + { + // The horizontal scrolling range is defined by + // (bitmap_width) - (client_width). The current horizontal + // scroll value remains within the horizontal scrolling range. + wfc->xMaxScroll = + MAX(freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) - client_width, 0); + wfc->xCurrentScroll = MIN(wfc->xCurrentScroll, wfc->xMaxScroll); + si.cbSize = sizeof(si); + si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS; + si.nMin = wfc->xMinScroll; + si.nMax = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + si.nPage = client_width; + si.nPos = wfc->xCurrentScroll; + SetScrollInfo(wfc->hwnd, SB_HORZ, &si, TRUE); + } + + if (vert) + { + // The vertical scrolling range is defined by + // (bitmap_height) - (client_height). The current vertical + // scroll value remains within the vertical scrolling range. + wfc->yMaxScroll = MAX( + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) - client_height, 0); + wfc->yCurrentScroll = MIN(wfc->yCurrentScroll, wfc->yMaxScroll); + si.cbSize = sizeof(si); + si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS; + si.nMin = wfc->yMinScroll; + si.nMax = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + si.nPage = client_height; + si.nPos = wfc->yCurrentScroll; + SetScrollInfo(wfc->hwnd, SB_VERT, &si, TRUE); + } + } + + wfc->disablewindowtracking = FALSE; + wf_update_canvas_diff(wfc); +} + +static BOOL wfreerdp_client_global_init(void) +{ + WSADATA wsaData; + + WSAStartup(0x101, &wsaData); + if (freerdp_handle_signals() != 0) + return FALSE; + + if (freerdp_register_addin_provider(freerdp_channels_load_static_addin_entry, 0) != + CHANNEL_RC_OK) + return FALSE; + + return TRUE; +} + +static void wfreerdp_client_global_uninit(void) +{ + WSACleanup(); +} + +static BOOL wfreerdp_client_new(freerdp* instance, rdpContext* context) +{ + wfContext* wfc = (wfContext*)context; + if (!wfc) + return FALSE; + + // AttachConsole and stdin do not work well. + // Use GUI input dialogs instead of command line ones. + wfc->isConsole = wf_has_console(); + + if (!(wfreerdp_client_global_init())) + return FALSE; + + WINPR_ASSERT(instance); + instance->PreConnect = wf_pre_connect; + instance->PostConnect = wf_post_connect; + instance->PostDisconnect = wf_post_disconnect; + instance->AuthenticateEx = wf_authenticate_ex; + +#ifdef WITH_WINDOWS_CERT_STORE + if (!freerdp_settings_set_bool(context->settings, FreeRDP_CertificateCallbackPreferPEM, TRUE)) + return FALSE; +#endif + + if (!wfc->isConsole) + { + instance->VerifyCertificateEx = wf_verify_certificate_ex; + instance->VerifyChangedCertificateEx = wf_verify_changed_certificate_ex; + instance->PresentGatewayMessage = wf_present_gateway_message; + } + +#ifdef WITH_PROGRESS_BAR + CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + CoCreateInstance(&CLSID_TaskbarList, nullptr, CLSCTX_ALL, &IID_ITaskbarList3, + (void**)&wfc->taskBarList); +#endif + + return TRUE; +} + +static void wfreerdp_client_free(freerdp* instance, rdpContext* context) +{ + WINPR_UNUSED(instance); + if (!context) + return; + +#ifdef WITH_PROGRESS_BAR + CoUninitialize(); +#endif +} + +static int wfreerdp_client_start(rdpContext* context) +{ + wfContext* wfc = (wfContext*)context; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->settings); + + freerdp* instance = context->instance; + WINPR_ASSERT(instance); + + rdpSettings* settings = context->settings; + WINPR_ASSERT(settings); + + HINSTANCE hInstance = GetModuleHandle(nullptr); + HWND hWndParent = (HWND)freerdp_settings_get_uint64(settings, FreeRDP_ParentWindowId); + if (!freerdp_settings_set_bool(settings, FreeRDP_EmbeddedWindow, (hWndParent) ? TRUE : FALSE)) + return -1; + + wfc->hWndParent = hWndParent; + + if (freerdp_settings_get_bool(settings, FreeRDP_EmbeddedWindow)) + { + typedef UINT(WINAPI * GetDpiForWindow_t)(HWND hwnd); + typedef BOOL(WINAPI * SetProcessDPIAware_t)(void); + + HMODULE module = GetModuleHandle(_T("User32")); + if (module) + { + GetDpiForWindow_t pGetDpiForWindow = + GetProcAddressAs(module, "GetDpiForWindow", GetDpiForWindow_t); + SetProcessDPIAware_t pSetProcessDPIAware = + GetProcAddressAs(module, "SetProcessDPIAware", SetProcessDPIAware_t); + if (pGetDpiForWindow && pSetProcessDPIAware) + { + const UINT dpiAwareness = pGetDpiForWindow(hWndParent); + if (dpiAwareness != USER_DEFAULT_SCREEN_DPI) + pSetProcessDPIAware(); + } + FreeLibrary(module); + } + } + + /* initial windows system item position where we will insert new menu item + * after default 5 items (restore, move, size, minimize, maximize) + * gets incremented each time wf_append_item_to_system_menu is called + * or maybe could use GetMenuItemCount() to get initial item count ? */ + wfc->systemMenuInsertPosition = 6; + + wfc->hInstance = hInstance; + wfc->cursor = LoadCursor(nullptr, IDC_ARROW); + wfc->icon = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDI_ICON1)); + wfc->wndClassName = _tcsdup(_T("FreeRDP")); + wfc->wndClass.cbSize = sizeof(WNDCLASSEX); + wfc->wndClass.style = CS_HREDRAW | CS_VREDRAW; + wfc->wndClass.lpfnWndProc = wf_event_proc; + wfc->wndClass.cbClsExtra = 0; + wfc->wndClass.cbWndExtra = 0; + wfc->wndClass.hCursor = nullptr; + wfc->wndClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); + wfc->wndClass.lpszMenuName = nullptr; + wfc->wndClass.lpszClassName = wfc->wndClassName; + wfc->wndClass.hInstance = hInstance; + wfc->wndClass.hIcon = wfc->icon; + wfc->wndClass.hIconSm = wfc->icon; + RegisterClassEx(&(wfc->wndClass)); + wfc->keyboardThread = + CreateThread(nullptr, 0, wf_keyboard_thread, (void*)wfc, 0, &wfc->keyboardThreadId); + + if (!wfc->keyboardThread) + return -1; + + wfc->common.thread = + CreateThread(nullptr, 0, wf_client_thread, (void*)instance, 0, &wfc->mainThreadId); + + if (!wfc->common.thread) + return -1; + + return 0; +} + +static int wfreerdp_client_stop(rdpContext* context) +{ + int rc; + wfContext* wfc = (wfContext*)context; + + WINPR_ASSERT(wfc); + PostThreadMessage(wfc->mainThreadId, WM_QUIT, 0, 0); + rc = freerdp_client_common_stop(context); + wfc->mainThreadId = 0; + + if (wfc->keyboardThread) + { + PostThreadMessage(wfc->keyboardThreadId, WM_QUIT, 0, 0); + (void)WaitForSingleObject(wfc->keyboardThread, INFINITE); + (void)CloseHandle(wfc->keyboardThread); + wfc->keyboardThread = nullptr; + wfc->keyboardThreadId = 0; + } + + return 0; +} + +int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints) +{ + pEntryPoints->Version = 1; + pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1); + pEntryPoints->GlobalInit = wfreerdp_client_global_init; + pEntryPoints->GlobalUninit = wfreerdp_client_global_uninit; + pEntryPoints->ContextSize = sizeof(wfContext); + pEntryPoints->ClientNew = wfreerdp_client_new; + pEntryPoints->ClientFree = wfreerdp_client_free; + pEntryPoints->ClientStart = wfreerdp_client_start; + pEntryPoints->ClientStop = wfreerdp_client_stop; + return 0; +} diff --git a/third_party/FreeRDP/client/Windows/wf_client.h b/third_party/FreeRDP/client/Windows/wf_client.h new file mode 100644 index 0000000..8526176 --- /dev/null +++ b/third_party/FreeRDP/client/Windows/wf_client.h @@ -0,0 +1,158 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Windows Client + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_WIN_INTERFACE_H +#define FREERDP_CLIENT_WIN_INTERFACE_H + +#include + +#include + +#ifdef WITH_PROGRESS_BAR +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "wf_channels.h" +#include "wf_floatbar.h" +#include "wf_event.h" +#include "wf_cliprdr.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +// System menu constants +#define SYSCOMMAND_ID_SMARTSIZING 1000 +#define SYSCOMMAND_ID_REQUEST_CONTROL 1001 + + typedef struct + { + rdpBitmap _bitmap; + HDC hdc; + HBITMAP bitmap; + HBITMAP org_bitmap; + BYTE* pdata; + } wfBitmap; + + typedef struct + { + rdpPointer pointer; + HCURSOR cursor; + } wfPointer; + + struct wf_context + { + rdpClientContext common; + + int offset_x; + int offset_y; + int fullscreen_toggle; + int fullscreen; + int percentscreen; + WCHAR* window_title; + int client_x; + int client_y; + int client_width; + int client_height; + + HANDLE keyboardThread; + + HICON icon; + HWND hWndParent; + HINSTANCE hInstance; + WNDCLASSEX wndClass; + LPCTSTR wndClassName; + HCURSOR hDefaultCursor; + + UINT systemMenuInsertPosition; + + HWND hwnd; + BOOL is_shown; + ITaskbarList3* taskBarList; + POINT diff; + + wfBitmap* primary; + wfBitmap* drawing; + HCURSOR cursor; + HBRUSH brush; + HBRUSH org_brush; + RECT update_rect; + RECT scale_update_rect; + + DWORD mainThreadId; + DWORD keyboardThreadId; + + rdpFile* connectionRdpFile; + + BOOL disablewindowtracking; + + BOOL updating_scrollbars; + BOOL xScrollVisible; + int xMinScroll; + int xCurrentScroll; + int xMaxScroll; + + BOOL yScrollVisible; + int yMinScroll; + int yCurrentScroll; + int yMaxScroll; + + void* clipboard; + CliprdrClientContext* cliprdr; + + wfFloatBar* floatbar; + + RailClientContext* rail; + wHashTable* railWindows; + BOOL isConsole; + + DispClientContext* disp; + UINT64 lastSentDate; + BOOL wasMaximized; + }; + + /** + * Client Interface + */ + + FREERDP_API int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints); + FREERDP_API int freerdp_client_set_window_size(wfContext* wfc, int width, int height); + FREERDP_API void wf_size_scrollbars(wfContext* wfc, UINT32 client_width, UINT32 client_height); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_CLIENT_WIN_INTERFACE_H */ diff --git a/third_party/FreeRDP/client/Windows/wf_cliprdr.c b/third_party/FreeRDP/client/Windows/wf_cliprdr.c new file mode 100644 index 0000000..8fc563c --- /dev/null +++ b/third_party/FreeRDP/client/Windows/wf_cliprdr.c @@ -0,0 +1,2567 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Windows Clipboard Redirection + * + * Copyright 2012 Jason Champion + * Copyright 2014 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * 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 + +#define CINTERFACE +#define COBJMACROS + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#include + +#include "wf_cliprdr.h" + +#define TAG CLIENT_TAG("windows") + +#ifdef WITH_DEBUG_CLIPRDR +#define DEBUG_CLIPRDR(...) WLog_DBG(TAG, __VA_ARGS__) +#else +#define DEBUG_CLIPRDR(...) \ + do \ + { \ + } while (0) +#endif + +typedef BOOL(WINAPI* fnAddClipboardFormatListener)(HWND hwnd); +typedef BOOL(WINAPI* fnRemoveClipboardFormatListener)(HWND hwnd); +typedef BOOL(WINAPI* fnGetUpdatedClipboardFormats)(PUINT lpuiFormats, UINT cFormats, + PUINT pcFormatsOut); + +typedef struct +{ + UINT32 remote_format_id; + UINT32 local_format_id; + WCHAR* name; +} formatMapping; + +typedef struct +{ + IEnumFORMATETC iEnumFORMATETC; + + LONG m_lRefCount; + LONG m_nIndex; + LONG m_nNumFormats; + FORMATETC* m_pFormatEtc; +} CliprdrEnumFORMATETC; + +typedef struct +{ + IStream iStream; + + LONG m_lRefCount; + ULONG m_lIndex; + ULARGE_INTEGER m_lSize; + ULARGE_INTEGER m_lOffset; + FILEDESCRIPTORW m_Dsc; + void* m_pData; +} CliprdrStream; + +typedef struct +{ + IDataObject iDataObject; + + LONG m_lRefCount; + FORMATETC* m_pFormatEtc; + STGMEDIUM* m_pStgMedium; + ULONG m_nNumFormats; + ULONG m_nStreams; + IStream** m_pStream; + void* m_pData; +} CliprdrDataObject; + +typedef struct +{ + wfContext* wfc; + rdpChannels* channels; + CliprdrClientContext* context; + + BOOL sync; + UINT32 capabilities; + + size_t map_size; + size_t map_capacity; + formatMapping* format_mappings; + + UINT32 requestedFormatId; + + HWND hwnd; + HANDLE hmem; + HANDLE thread; + HANDLE response_data_event; + + LPDATAOBJECT data_obj; + ULONG req_fsize; + char* req_fdata; + HANDLE req_fevent; + + size_t nFiles; + size_t file_array_size; + WCHAR** file_names; + FILEDESCRIPTORW** fileDescriptor; + + BOOL legacyApi; + HMODULE hUser32; + HWND hWndNextViewer; + fnAddClipboardFormatListener AddClipboardFormatListener; + fnRemoveClipboardFormatListener RemoveClipboardFormatListener; + fnGetUpdatedClipboardFormats GetUpdatedClipboardFormats; +} wfClipboard; + +#define WM_CLIPRDR_MESSAGE (WM_USER + 156) +#define OLE_SETCLIPBOARD 1 + +static BOOL wf_create_file_obj(wfClipboard* cliprdrrdr, IDataObject** ppDataObject); +static void wf_destroy_file_obj(IDataObject* instance); +static UINT32 get_remote_format_id(wfClipboard* clipboard, UINT32 local_format); +static UINT cliprdr_send_data_request(wfClipboard* clipboard, UINT32 format); +static UINT cliprdr_send_lock(wfClipboard* clipboard); +static UINT cliprdr_send_unlock(wfClipboard* clipboard); +static UINT cliprdr_send_request_filecontents(wfClipboard* clipboard, const void* streamid, + ULONG index, UINT32 flag, UINT64 position, + ULONG request); + +static void CliprdrDataObject_Delete(CliprdrDataObject* instance); + +static CliprdrEnumFORMATETC* CliprdrEnumFORMATETC_New(ULONG nFormats, FORMATETC* pFormatEtc); +static void CliprdrEnumFORMATETC_Delete(CliprdrEnumFORMATETC* instance); + +static void CliprdrStream_Delete(CliprdrStream* instance); + +static BOOL try_open_clipboard(HWND hwnd) +{ + for (size_t x = 0; x < 10; x++) + { + if (OpenClipboard(hwnd)) + return TRUE; + Sleep(10); + } + return FALSE; +} + +/** + * IStream + */ + +static HRESULT STDMETHODCALLTYPE CliprdrStream_QueryInterface(IStream* This, REFIID riid, + void** ppvObject) +{ + if (IsEqualIID(riid, &IID_IStream) || IsEqualIID(riid, &IID_IUnknown)) + { + IStream_AddRef(This); + *ppvObject = This; + return S_OK; + } + else + { + *ppvObject = 0; + return E_NOINTERFACE; + } +} + +static ULONG STDMETHODCALLTYPE CliprdrStream_AddRef(IStream* This) +{ + CliprdrStream* instance = (CliprdrStream*)This; + + if (!instance) + return 0; + + return InterlockedIncrement(&instance->m_lRefCount); +} + +static ULONG STDMETHODCALLTYPE CliprdrStream_Release(IStream* This) +{ + LONG count; + CliprdrStream* instance = (CliprdrStream*)This; + + if (!instance) + return 0; + + count = InterlockedDecrement(&instance->m_lRefCount); + + if (count == 0) + { + CliprdrStream_Delete(instance); + return 0; + } + else + { + return count; + } +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_Read(IStream* This, void* pv, ULONG cb, + ULONG* pcbRead) +{ + int ret; + CliprdrStream* instance = (CliprdrStream*)This; + wfClipboard* clipboard; + + if (!pv || !pcbRead || !instance) + return E_INVALIDARG; + + clipboard = (wfClipboard*)instance->m_pData; + *pcbRead = 0; + + if (instance->m_lOffset.QuadPart >= instance->m_lSize.QuadPart) + return S_FALSE; + + ret = cliprdr_send_request_filecontents(clipboard, (void*)This, instance->m_lIndex, + FILECONTENTS_RANGE, instance->m_lOffset.QuadPart, cb); + + if (ret < 0) + return E_FAIL; + + if (clipboard->req_fdata) + { + CopyMemory(pv, clipboard->req_fdata, clipboard->req_fsize); + free(clipboard->req_fdata); + } + + *pcbRead = clipboard->req_fsize; + instance->m_lOffset.QuadPart += clipboard->req_fsize; + + if (clipboard->req_fsize < cb) + return S_FALSE; + + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_Write(IStream* This, const void* pv, ULONG cb, + ULONG* pcbWritten) +{ + (void)This; + (void)pv; + (void)cb; + (void)pcbWritten; + return STG_E_ACCESSDENIED; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_Seek(IStream* This, LARGE_INTEGER dlibMove, + DWORD dwOrigin, ULARGE_INTEGER* plibNewPosition) +{ + ULONGLONG newoffset; + CliprdrStream* instance = (CliprdrStream*)This; + + if (!instance) + return E_INVALIDARG; + + newoffset = instance->m_lOffset.QuadPart; + + switch (dwOrigin) + { + case STREAM_SEEK_SET: + newoffset = dlibMove.QuadPart; + break; + + case STREAM_SEEK_CUR: + newoffset += dlibMove.QuadPart; + break; + + case STREAM_SEEK_END: + newoffset = instance->m_lSize.QuadPart + dlibMove.QuadPart; + break; + + default: + return E_INVALIDARG; + } + + if (newoffset < 0 || newoffset >= instance->m_lSize.QuadPart) + return E_FAIL; + + instance->m_lOffset.QuadPart = newoffset; + + if (plibNewPosition) + plibNewPosition->QuadPart = instance->m_lOffset.QuadPart; + + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_SetSize(IStream* This, ULARGE_INTEGER libNewSize) +{ + (void)This; + (void)libNewSize; + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_CopyTo(IStream* This, IStream* pstm, + ULARGE_INTEGER cb, ULARGE_INTEGER* pcbRead, + ULARGE_INTEGER* pcbWritten) +{ + (void)This; + (void)pstm; + (void)cb; + (void)pcbRead; + (void)pcbWritten; + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_Commit(IStream* This, DWORD grfCommitFlags) +{ + (void)This; + (void)grfCommitFlags; + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_Revert(IStream* This) +{ + (void)This; + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_LockRegion(IStream* This, ULARGE_INTEGER libOffset, + ULARGE_INTEGER cb, DWORD dwLockType) +{ + (void)This; + (void)libOffset; + (void)cb; + (void)dwLockType; + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_UnlockRegion(IStream* This, ULARGE_INTEGER libOffset, + ULARGE_INTEGER cb, DWORD dwLockType) +{ + (void)This; + (void)libOffset; + (void)cb; + (void)dwLockType; + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_Stat(IStream* This, STATSTG* pstatstg, + DWORD grfStatFlag) +{ + CliprdrStream* instance = (CliprdrStream*)This; + + if (!instance) + return E_INVALIDARG; + + if (pstatstg == nullptr) + return STG_E_INVALIDPOINTER; + + ZeroMemory(pstatstg, sizeof(STATSTG)); + + switch (grfStatFlag) + { + case STATFLAG_DEFAULT: + return STG_E_INSUFFICIENTMEMORY; + + case STATFLAG_NONAME: + pstatstg->cbSize.QuadPart = instance->m_lSize.QuadPart; + pstatstg->grfLocksSupported = LOCK_EXCLUSIVE; + pstatstg->grfMode = GENERIC_READ; + pstatstg->grfStateBits = 0; + pstatstg->type = STGTY_STREAM; + break; + + case STATFLAG_NOOPEN: + return STG_E_INVALIDFLAG; + + default: + return STG_E_INVALIDFLAG; + } + + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE CliprdrStream_Clone(IStream* This, IStream** ppstm) +{ + (void)This; + (void)ppstm; + return E_NOTIMPL; +} + +static CliprdrStream* CliprdrStream_New(ULONG index, void* pData, const FILEDESCRIPTORW* dsc) +{ + IStream* iStream; + BOOL success = FALSE; + BOOL isDir = FALSE; + CliprdrStream* instance; + wfClipboard* clipboard = (wfClipboard*)pData; + instance = (CliprdrStream*)calloc(1, sizeof(CliprdrStream)); + + if (instance) + { + instance->m_Dsc = *dsc; + iStream = &instance->iStream; + iStream->lpVtbl = (IStreamVtbl*)calloc(1, sizeof(IStreamVtbl)); + + if (iStream->lpVtbl) + { + iStream->lpVtbl->QueryInterface = CliprdrStream_QueryInterface; + iStream->lpVtbl->AddRef = CliprdrStream_AddRef; + iStream->lpVtbl->Release = CliprdrStream_Release; + iStream->lpVtbl->Read = CliprdrStream_Read; + iStream->lpVtbl->Write = CliprdrStream_Write; + iStream->lpVtbl->Seek = CliprdrStream_Seek; + iStream->lpVtbl->SetSize = CliprdrStream_SetSize; + iStream->lpVtbl->CopyTo = CliprdrStream_CopyTo; + iStream->lpVtbl->Commit = CliprdrStream_Commit; + iStream->lpVtbl->Revert = CliprdrStream_Revert; + iStream->lpVtbl->LockRegion = CliprdrStream_LockRegion; + iStream->lpVtbl->UnlockRegion = CliprdrStream_UnlockRegion; + iStream->lpVtbl->Stat = CliprdrStream_Stat; + iStream->lpVtbl->Clone = CliprdrStream_Clone; + instance->m_lRefCount = 1; + instance->m_lIndex = index; + instance->m_pData = pData; + instance->m_lOffset.QuadPart = 0; + + if (instance->m_Dsc.dwFlags & FD_ATTRIBUTES) + { + if (instance->m_Dsc.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + isDir = TRUE; + } + + if (((instance->m_Dsc.dwFlags & FD_FILESIZE) == 0) && !isDir) + { + /* get content size of this stream */ + if (cliprdr_send_request_filecontents(clipboard, (void*)instance, + instance->m_lIndex, FILECONTENTS_SIZE, 0, + 8) == CHANNEL_RC_OK) + { + success = TRUE; + } + + instance->m_lSize.QuadPart = *((LONGLONG*)clipboard->req_fdata); + free(clipboard->req_fdata); + } + else + { + instance->m_lSize.QuadPart = + ((UINT64)instance->m_Dsc.nFileSizeHigh << 32) | instance->m_Dsc.nFileSizeLow; + success = TRUE; + } + } + } + + if (!success) + { + CliprdrStream_Delete(instance); + instance = nullptr; + } + + return instance; +} + +void CliprdrStream_Delete(CliprdrStream* instance) +{ + if (instance) + { + free(instance->iStream.lpVtbl); + free(instance); + } +} + +/** + * IDataObject + */ + +static LONG cliprdr_lookup_format(CliprdrDataObject* instance, FORMATETC* pFormatEtc) +{ + if (!instance || !pFormatEtc) + return -1; + + for (ULONG i = 0; i < instance->m_nNumFormats; i++) + { + if ((pFormatEtc->tymed & instance->m_pFormatEtc[i].tymed) && + pFormatEtc->cfFormat == instance->m_pFormatEtc[i].cfFormat && + pFormatEtc->dwAspect & instance->m_pFormatEtc[i].dwAspect) + { + return (LONG)i; + } + } + + return -1; +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_QueryInterface(IDataObject* This, REFIID riid, + void** ppvObject) +{ + (void)This; + + if (!ppvObject) + return E_INVALIDARG; + + if (IsEqualIID(riid, &IID_IDataObject) || IsEqualIID(riid, &IID_IUnknown)) + { + IDataObject_AddRef(This); + *ppvObject = This; + return S_OK; + } + else + { + *ppvObject = 0; + return E_NOINTERFACE; + } +} + +static ULONG STDMETHODCALLTYPE CliprdrDataObject_AddRef(IDataObject* This) +{ + CliprdrDataObject* instance = (CliprdrDataObject*)This; + + if (!instance) + return E_INVALIDARG; + + return InterlockedIncrement(&instance->m_lRefCount); +} + +static ULONG STDMETHODCALLTYPE CliprdrDataObject_Release(IDataObject* This) +{ + LONG count; + CliprdrDataObject* instance = (CliprdrDataObject*)This; + + if (!instance) + return E_INVALIDARG; + + count = InterlockedDecrement(&instance->m_lRefCount); + + if (count == 0) + { + CliprdrDataObject_Delete(instance); + return 0; + } + else + return count; +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_GetData(IDataObject* This, FORMATETC* pFormatEtc, + STGMEDIUM* pMedium) +{ + LONG idx; + CliprdrDataObject* instance = (CliprdrDataObject*)This; + wfClipboard* clipboard; + + if (!pFormatEtc || !pMedium || !instance) + return E_INVALIDARG; + + clipboard = (wfClipboard*)instance->m_pData; + + if (!clipboard) + return E_INVALIDARG; + + if ((idx = cliprdr_lookup_format(instance, pFormatEtc)) == -1) + return DV_E_FORMATETC; + + pMedium->tymed = instance->m_pFormatEtc[idx].tymed; + pMedium->pUnkForRelease = 0; + + if (instance->m_pFormatEtc[idx].cfFormat == RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW)) + { + FILEGROUPDESCRIPTOR* dsc; + DWORD remote = get_remote_format_id(clipboard, instance->m_pFormatEtc[idx].cfFormat); + + if (cliprdr_send_data_request(clipboard, remote) != 0) + return E_UNEXPECTED; + + pMedium->u.hGlobal = clipboard->hmem; + /* points to a FILEGROUPDESCRIPTOR structure */ + /* GlobalLock returns a pointer to the first byte of the memory block, + * in which is a FILEGROUPDESCRIPTOR structure, whose first UINT member + * is the number of FILEDESCRIPTOR's */ + dsc = (FILEGROUPDESCRIPTOR*)GlobalLock(clipboard->hmem); + instance->m_nStreams = dsc->cItems; + GlobalUnlock(clipboard->hmem); + + if (instance->m_nStreams > 0) + { + if (!instance->m_pStream) + { + instance->m_pStream = (LPSTREAM*)calloc(instance->m_nStreams, sizeof(LPSTREAM)); + + if (instance->m_pStream) + { + for (ULONG i = 0; i < instance->m_nStreams; i++) + { + instance->m_pStream[i] = + (IStream*)CliprdrStream_New(i, clipboard, &dsc->fgd[i]); + + if (!instance->m_pStream[i]) + return E_OUTOFMEMORY; + } + } + } + } + + if (!instance->m_pStream) + { + if (clipboard->hmem) + { + GlobalFree(clipboard->hmem); + clipboard->hmem = nullptr; + } + + pMedium->u.hGlobal = nullptr; + return E_OUTOFMEMORY; + } + } + else if (instance->m_pFormatEtc[idx].cfFormat == RegisterClipboardFormat(CFSTR_FILECONTENTS)) + { + if ((pFormatEtc->lindex >= 0) && ((ULONG)pFormatEtc->lindex < instance->m_nStreams)) + { + pMedium->u.pstm = instance->m_pStream[pFormatEtc->lindex]; + IDataObject_AddRef(instance->m_pStream[pFormatEtc->lindex]); + } + else + return E_INVALIDARG; + } + else + return E_UNEXPECTED; + + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_GetDataHere(IDataObject* This, + FORMATETC* pformatetc, + STGMEDIUM* pmedium) +{ + (void)This; + (void)pformatetc; + (void)pmedium; + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_QueryGetData(IDataObject* This, + FORMATETC* pformatetc) +{ + CliprdrDataObject* instance = (CliprdrDataObject*)This; + + if (!pformatetc) + return E_INVALIDARG; + + if (cliprdr_lookup_format(instance, pformatetc) == -1) + return DV_E_FORMATETC; + + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_GetCanonicalFormatEtc(IDataObject* This, + FORMATETC* pformatectIn, + FORMATETC* pformatetcOut) +{ + (void)This; + (void)pformatectIn; + + if (!pformatetcOut) + return E_INVALIDARG; + + pformatetcOut->ptd = nullptr; + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_SetData(IDataObject* This, FORMATETC* pformatetc, + STGMEDIUM* pmedium, BOOL fRelease) +{ + (void)This; + (void)pformatetc; + (void)pmedium; + (void)fRelease; + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_EnumFormatEtc(IDataObject* This, + DWORD dwDirection, + IEnumFORMATETC** ppenumFormatEtc) +{ + CliprdrDataObject* instance = (CliprdrDataObject*)This; + + if (!instance || !ppenumFormatEtc) + return E_INVALIDARG; + + if (dwDirection == DATADIR_GET) + { + *ppenumFormatEtc = (IEnumFORMATETC*)CliprdrEnumFORMATETC_New(instance->m_nNumFormats, + instance->m_pFormatEtc); + return (*ppenumFormatEtc) ? S_OK : E_OUTOFMEMORY; + } + else + { + return E_NOTIMPL; + } +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_DAdvise(IDataObject* This, FORMATETC* pformatetc, + DWORD advf, IAdviseSink* pAdvSink, + DWORD* pdwConnection) +{ + (void)This; + (void)pformatetc; + (void)advf; + (void)pAdvSink; + (void)pdwConnection; + return OLE_E_ADVISENOTSUPPORTED; +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_DUnadvise(IDataObject* This, DWORD dwConnection) +{ + (void)This; + (void)dwConnection; + return OLE_E_ADVISENOTSUPPORTED; +} + +static HRESULT STDMETHODCALLTYPE CliprdrDataObject_EnumDAdvise(IDataObject* This, + IEnumSTATDATA** ppenumAdvise) +{ + (void)This; + (void)ppenumAdvise; + return OLE_E_ADVISENOTSUPPORTED; +} + +static CliprdrDataObject* CliprdrDataObject_New(FORMATETC* fmtetc, STGMEDIUM* stgmed, ULONG count, + void* data) +{ + CliprdrDataObject* instance; + IDataObject* iDataObject; + instance = (CliprdrDataObject*)calloc(1, sizeof(CliprdrDataObject)); + + if (!instance) + goto error; + + iDataObject = &instance->iDataObject; + iDataObject->lpVtbl = (IDataObjectVtbl*)calloc(1, sizeof(IDataObjectVtbl)); + + if (!iDataObject->lpVtbl) + goto error; + + iDataObject->lpVtbl->QueryInterface = CliprdrDataObject_QueryInterface; + iDataObject->lpVtbl->AddRef = CliprdrDataObject_AddRef; + iDataObject->lpVtbl->Release = CliprdrDataObject_Release; + iDataObject->lpVtbl->GetData = CliprdrDataObject_GetData; + iDataObject->lpVtbl->GetDataHere = CliprdrDataObject_GetDataHere; + iDataObject->lpVtbl->QueryGetData = CliprdrDataObject_QueryGetData; + iDataObject->lpVtbl->GetCanonicalFormatEtc = CliprdrDataObject_GetCanonicalFormatEtc; + iDataObject->lpVtbl->SetData = CliprdrDataObject_SetData; + iDataObject->lpVtbl->EnumFormatEtc = CliprdrDataObject_EnumFormatEtc; + iDataObject->lpVtbl->DAdvise = CliprdrDataObject_DAdvise; + iDataObject->lpVtbl->DUnadvise = CliprdrDataObject_DUnadvise; + iDataObject->lpVtbl->EnumDAdvise = CliprdrDataObject_EnumDAdvise; + instance->m_lRefCount = 1; + instance->m_nNumFormats = count; + instance->m_pData = data; + instance->m_nStreams = 0; + instance->m_pStream = nullptr; + + if (count > 0) + { + instance->m_pFormatEtc = (FORMATETC*)calloc(count, sizeof(FORMATETC)); + + if (!instance->m_pFormatEtc) + goto error; + + instance->m_pStgMedium = (STGMEDIUM*)calloc(count, sizeof(STGMEDIUM)); + + if (!instance->m_pStgMedium) + goto error; + + for (ULONG i = 0; i < count; i++) + { + instance->m_pFormatEtc[i] = fmtetc[i]; + instance->m_pStgMedium[i] = stgmed[i]; + } + } + + return instance; +error: + CliprdrDataObject_Delete(instance); + return nullptr; +} + +void CliprdrDataObject_Delete(CliprdrDataObject* instance) +{ + if (instance) + { + free(instance->iDataObject.lpVtbl); + free(instance->m_pFormatEtc); + free(instance->m_pStgMedium); + + if (instance->m_pStream) + { + for (ULONG i = 0; i < instance->m_nStreams; i++) + CliprdrStream_Release(instance->m_pStream[i]); + + free(instance->m_pStream); + } + + free(instance); + } +} + +static BOOL wf_create_file_obj(wfClipboard* clipboard, IDataObject** ppDataObject) +{ + FORMATETC fmtetc[2] = WINPR_C_ARRAY_INIT; + STGMEDIUM stgmeds[2] = WINPR_C_ARRAY_INIT; + + if (!ppDataObject) + return FALSE; + + fmtetc[0].cfFormat = RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW); + fmtetc[0].dwAspect = DVASPECT_CONTENT; + + fmtetc[0].tymed = TYMED_HGLOBAL; + stgmeds[0].tymed = TYMED_HGLOBAL; + + fmtetc[1].cfFormat = RegisterClipboardFormat(CFSTR_FILECONTENTS); + fmtetc[1].dwAspect = DVASPECT_CONTENT; + + fmtetc[1].tymed = TYMED_ISTREAM; + stgmeds[1].tymed = TYMED_ISTREAM; + + *ppDataObject = (IDataObject*)CliprdrDataObject_New(fmtetc, stgmeds, 2, clipboard); + return (*ppDataObject) ? TRUE : FALSE; +} + +static void wf_destroy_file_obj(IDataObject* instance) +{ + if (instance) + IDataObject_Release(instance); +} + +/** + * IEnumFORMATETC + */ + +static void cliprdr_format_deep_copy(FORMATETC* dest, FORMATETC* source) +{ + *dest = *source; + + if (source->ptd) + { + dest->ptd = (DVTARGETDEVICE*)CoTaskMemAlloc(sizeof(DVTARGETDEVICE)); + + if (dest->ptd) + *(dest->ptd) = *(source->ptd); + } +} + +static HRESULT STDMETHODCALLTYPE CliprdrEnumFORMATETC_QueryInterface(IEnumFORMATETC* This, + REFIID riid, void** ppvObject) +{ + (void)This; + + if (IsEqualIID(riid, &IID_IEnumFORMATETC) || IsEqualIID(riid, &IID_IUnknown)) + { + IEnumFORMATETC_AddRef(This); + *ppvObject = This; + return S_OK; + } + else + { + *ppvObject = 0; + return E_NOINTERFACE; + } +} + +static ULONG STDMETHODCALLTYPE CliprdrEnumFORMATETC_AddRef(IEnumFORMATETC* This) +{ + CliprdrEnumFORMATETC* instance = (CliprdrEnumFORMATETC*)This; + + if (!instance) + return 0; + + return InterlockedIncrement(&instance->m_lRefCount); +} + +static ULONG STDMETHODCALLTYPE CliprdrEnumFORMATETC_Release(IEnumFORMATETC* This) +{ + LONG count; + CliprdrEnumFORMATETC* instance = (CliprdrEnumFORMATETC*)This; + + if (!instance) + return 0; + + count = InterlockedDecrement(&instance->m_lRefCount); + + if (count == 0) + { + CliprdrEnumFORMATETC_Delete(instance); + return 0; + } + else + { + return count; + } +} + +static HRESULT STDMETHODCALLTYPE CliprdrEnumFORMATETC_Next(IEnumFORMATETC* This, ULONG celt, + FORMATETC* rgelt, ULONG* pceltFetched) +{ + ULONG copied = 0; + CliprdrEnumFORMATETC* instance = (CliprdrEnumFORMATETC*)This; + + if (!instance || !celt || !rgelt) + return E_INVALIDARG; + + while ((instance->m_nIndex < instance->m_nNumFormats) && (copied < celt)) + { + cliprdr_format_deep_copy(&rgelt[copied++], &instance->m_pFormatEtc[instance->m_nIndex++]); + } + + if (pceltFetched != 0) + *pceltFetched = copied; + + return (copied == celt) ? S_OK : E_FAIL; +} + +static HRESULT STDMETHODCALLTYPE CliprdrEnumFORMATETC_Skip(IEnumFORMATETC* This, ULONG celt) +{ + CliprdrEnumFORMATETC* instance = (CliprdrEnumFORMATETC*)This; + + if (!instance) + return E_INVALIDARG; + + if (instance->m_nIndex + (LONG)celt > instance->m_nNumFormats) + return E_FAIL; + + instance->m_nIndex += celt; + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE CliprdrEnumFORMATETC_Reset(IEnumFORMATETC* This) +{ + CliprdrEnumFORMATETC* instance = (CliprdrEnumFORMATETC*)This; + + if (!instance) + return E_INVALIDARG; + + instance->m_nIndex = 0; + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE CliprdrEnumFORMATETC_Clone(IEnumFORMATETC* This, + IEnumFORMATETC** ppEnum) +{ + CliprdrEnumFORMATETC* instance = (CliprdrEnumFORMATETC*)This; + + if (!instance || !ppEnum) + return E_INVALIDARG; + + *ppEnum = + (IEnumFORMATETC*)CliprdrEnumFORMATETC_New(instance->m_nNumFormats, instance->m_pFormatEtc); + + if (!*ppEnum) + return E_OUTOFMEMORY; + + ((CliprdrEnumFORMATETC*)*ppEnum)->m_nIndex = instance->m_nIndex; + return S_OK; +} + +CliprdrEnumFORMATETC* CliprdrEnumFORMATETC_New(ULONG nFormats, FORMATETC* pFormatEtc) +{ + CliprdrEnumFORMATETC* instance; + IEnumFORMATETC* iEnumFORMATETC; + + if ((nFormats != 0) && !pFormatEtc) + return nullptr; + + instance = (CliprdrEnumFORMATETC*)calloc(1, sizeof(CliprdrEnumFORMATETC)); + + if (!instance) + goto error; + + iEnumFORMATETC = &instance->iEnumFORMATETC; + iEnumFORMATETC->lpVtbl = (IEnumFORMATETCVtbl*)calloc(1, sizeof(IEnumFORMATETCVtbl)); + + if (!iEnumFORMATETC->lpVtbl) + goto error; + + iEnumFORMATETC->lpVtbl->QueryInterface = CliprdrEnumFORMATETC_QueryInterface; + iEnumFORMATETC->lpVtbl->AddRef = CliprdrEnumFORMATETC_AddRef; + iEnumFORMATETC->lpVtbl->Release = CliprdrEnumFORMATETC_Release; + iEnumFORMATETC->lpVtbl->Next = CliprdrEnumFORMATETC_Next; + iEnumFORMATETC->lpVtbl->Skip = CliprdrEnumFORMATETC_Skip; + iEnumFORMATETC->lpVtbl->Reset = CliprdrEnumFORMATETC_Reset; + iEnumFORMATETC->lpVtbl->Clone = CliprdrEnumFORMATETC_Clone; + instance->m_lRefCount = 1; + instance->m_nIndex = 0; + instance->m_nNumFormats = nFormats; + + if (nFormats > 0) + { + instance->m_pFormatEtc = (FORMATETC*)calloc(nFormats, sizeof(FORMATETC)); + + if (!instance->m_pFormatEtc) + goto error; + + for (ULONG i = 0; i < nFormats; i++) + cliprdr_format_deep_copy(&instance->m_pFormatEtc[i], &pFormatEtc[i]); + } + + return instance; +error: + CliprdrEnumFORMATETC_Delete(instance); + return nullptr; +} + +void CliprdrEnumFORMATETC_Delete(CliprdrEnumFORMATETC* instance) +{ + if (instance) + { + free(instance->iEnumFORMATETC.lpVtbl); + + if (instance->m_pFormatEtc) + { + for (LONG i = 0; i < instance->m_nNumFormats; i++) + { + if (instance->m_pFormatEtc[i].ptd) + CoTaskMemFree(instance->m_pFormatEtc[i].ptd); + } + + free(instance->m_pFormatEtc); + } + + free(instance); + } +} + +/***********************************************************************************/ + +static UINT32 get_local_format_id_by_name(wfClipboard* clipboard, const TCHAR* format_name) +{ + formatMapping* map; + WCHAR* unicode_name; +#if !defined(UNICODE) + size_t size; +#endif + + if (!clipboard || !format_name) + return 0; + +#if defined(UNICODE) + unicode_name = _wcsdup(format_name); +#else + size = _tcslen(format_name); + unicode_name = calloc(size + 1, sizeof(WCHAR)); + + if (!unicode_name) + return 0; + + MultiByteToWideChar(CP_OEMCP, 0, format_name, strlen(format_name), unicode_name, size); +#endif + + if (!unicode_name) + return 0; + + for (size_t i = 0; i < clipboard->map_size; i++) + { + map = &clipboard->format_mappings[i]; + + if (map->name) + { + if (wcscmp(map->name, unicode_name) == 0) + { + free(unicode_name); + return map->local_format_id; + } + } + } + + free(unicode_name); + return 0; +} + +static inline BOOL file_transferring(wfClipboard* clipboard) +{ + return get_local_format_id_by_name(clipboard, CFSTR_FILEDESCRIPTORW) ? TRUE : FALSE; +} + +static UINT32 get_remote_format_id(wfClipboard* clipboard, UINT32 local_format) +{ + formatMapping* map; + + if (!clipboard) + return 0; + + for (UINT32 i = 0; i < clipboard->map_size; i++) + { + map = &clipboard->format_mappings[i]; + + if (map->local_format_id == local_format) + return map->remote_format_id; + } + + return local_format; +} + +static void map_ensure_capacity(wfClipboard* clipboard) +{ + if (!clipboard) + return; + + if (clipboard->map_size >= clipboard->map_capacity) + { + size_t new_size = clipboard->map_capacity; + do + { + WINPR_ASSERT(new_size <= SIZE_MAX - 128ull); + new_size += 128ull; + } while (new_size <= clipboard->map_size); + formatMapping* new_map = + (formatMapping*)realloc(clipboard->format_mappings, sizeof(formatMapping) * new_size); + + if (!new_map) + return; + + clipboard->format_mappings = new_map; + clipboard->map_capacity = new_size; + } +} + +static BOOL clear_format_map(wfClipboard* clipboard) +{ + formatMapping* map; + + if (!clipboard) + return FALSE; + + if (clipboard->format_mappings) + { + for (size_t i = 0; i < clipboard->map_capacity; i++) + { + map = &clipboard->format_mappings[i]; + map->remote_format_id = 0; + map->local_format_id = 0; + free(map->name); + map->name = nullptr; + } + } + + clipboard->map_size = 0; + return TRUE; +} + +static UINT cliprdr_send_tempdir(wfClipboard* clipboard) +{ + CLIPRDR_TEMP_DIRECTORY tempDirectory; + + if (!clipboard) + return -1; + + if (GetEnvironmentVariableA("TEMP", tempDirectory.szTempDir, sizeof(tempDirectory.szTempDir)) == + 0) + return -1; + + return clipboard->context->TempDirectory(clipboard->context, &tempDirectory); +} + +static BOOL cliprdr_GetUpdatedClipboardFormats(wfClipboard* clipboard, PUINT lpuiFormats, + UINT cFormats, PUINT pcFormatsOut) +{ + UINT index = 0; + UINT format = 0; + BOOL clipboardOpen = FALSE; + + if (!clipboard->legacyApi) + return clipboard->GetUpdatedClipboardFormats(lpuiFormats, cFormats, pcFormatsOut); + + clipboardOpen = try_open_clipboard(clipboard->hwnd); + + if (!clipboardOpen) + { + *pcFormatsOut = 0; + return TRUE; /* Other app holding clipboard */ + } + + while (index < cFormats) + { + format = EnumClipboardFormats(format); + + if (!format) + break; + + lpuiFormats[index] = format; + index++; + } + + *pcFormatsOut = index; + CloseClipboard(); + return TRUE; +} + +static UINT cliprdr_send_format_list(wfClipboard* clipboard) +{ + UINT rc; + int count = 0; + UINT32 numFormats = 0; + UINT32 formatId = 0; + char formatName[1024]; + CLIPRDR_FORMAT* formats = nullptr; + CLIPRDR_FORMAT_LIST formatList = WINPR_C_ARRAY_INIT; + + if (!clipboard) + return ERROR_INTERNAL_ERROR; + + /* Ignore if other app is holding clipboard */ + if (try_open_clipboard(clipboard->hwnd)) + { + count = CountClipboardFormats(); + numFormats = (UINT32)count; + formats = (CLIPRDR_FORMAT*)calloc(numFormats, sizeof(CLIPRDR_FORMAT)); + + if (!formats) + { + CloseClipboard(); + return CHANNEL_RC_NO_MEMORY; + } + + { + UINT32 index = 0; + + if (IsClipboardFormatAvailable(CF_HDROP)) + { + formats[index++].formatId = RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW); + formats[index++].formatId = RegisterClipboardFormat(CFSTR_FILECONTENTS); + } + else + { + while ((formatId = EnumClipboardFormats(formatId)) != 0) + formats[index++].formatId = formatId; + } + + numFormats = index; + } + + if (!CloseClipboard()) + { + free(formats); + return ERROR_INTERNAL_ERROR; + } + + for (UINT index = 0; index < numFormats; index++) + { + if (GetClipboardFormatNameA(formats[index].formatId, formatName, sizeof(formatName))) + { + formats[index].formatName = _strdup(formatName); + } + } + } + + formatList.numFormats = numFormats; + formatList.formats = formats; + formatList.common.msgType = CB_FORMAT_LIST; + rc = clipboard->context->ClientFormatList(clipboard->context, &formatList); + + for (UINT index = 0; index < numFormats; index++) + free(formats[index].formatName); + + free(formats); + return rc; +} + +static UINT cliprdr_send_data_request(wfClipboard* clipboard, UINT32 formatId) +{ + UINT rc; + UINT32 remoteFormatId; + CLIPRDR_FORMAT_DATA_REQUEST formatDataRequest; + + if (!clipboard || !clipboard->context || !clipboard->context->ClientFormatDataRequest) + return ERROR_INTERNAL_ERROR; + + remoteFormatId = get_remote_format_id(clipboard, formatId); + + formatDataRequest.requestedFormatId = remoteFormatId; + clipboard->requestedFormatId = formatId; + rc = clipboard->context->ClientFormatDataRequest(clipboard->context, &formatDataRequest); + + if (WaitForSingleObject(clipboard->response_data_event, INFINITE) != WAIT_OBJECT_0) + rc = ERROR_INTERNAL_ERROR; + else if (!ResetEvent(clipboard->response_data_event)) + rc = ERROR_INTERNAL_ERROR; + + return rc; +} + +UINT cliprdr_send_request_filecontents(wfClipboard* clipboard, const void* streamid, ULONG index, + UINT32 flag, UINT64 position, ULONG nreq) +{ + UINT rc; + CLIPRDR_FILE_CONTENTS_REQUEST fileContentsRequest; + + if (!clipboard || !clipboard->context || !clipboard->context->ClientFileContentsRequest) + return ERROR_INTERNAL_ERROR; + + fileContentsRequest.streamId = (UINT32)(ULONG_PTR)streamid; + fileContentsRequest.listIndex = index; + fileContentsRequest.dwFlags = flag; + fileContentsRequest.nPositionLow = position & 0xFFFFFFFF; + fileContentsRequest.nPositionHigh = (position >> 32) & 0xFFFFFFFF; + fileContentsRequest.cbRequested = nreq; + fileContentsRequest.clipDataId = 0; + fileContentsRequest.common.msgFlags = 0; + rc = clipboard->context->ClientFileContentsRequest(clipboard->context, &fileContentsRequest); + + if (WaitForSingleObject(clipboard->req_fevent, INFINITE) != WAIT_OBJECT_0) + rc = ERROR_INTERNAL_ERROR; + else if (!ResetEvent(clipboard->req_fevent)) + rc = ERROR_INTERNAL_ERROR; + + return rc; +} + +static UINT cliprdr_send_response_filecontents(wfClipboard* clipboard, UINT32 streamId, UINT32 size, + BYTE* data) +{ + CLIPRDR_FILE_CONTENTS_RESPONSE fileContentsResponse; + + if (!clipboard || !clipboard->context || !clipboard->context->ClientFileContentsResponse) + return ERROR_INTERNAL_ERROR; + + fileContentsResponse.streamId = streamId; + fileContentsResponse.cbRequested = size; + fileContentsResponse.requestedData = data; + fileContentsResponse.common.msgFlags = CB_RESPONSE_OK; + return clipboard->context->ClientFileContentsResponse(clipboard->context, + &fileContentsResponse); +} + +static LRESULT CALLBACK cliprdr_proc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) +{ + static wfClipboard* clipboard = nullptr; + + switch (Msg) + { + case WM_CREATE: + DEBUG_CLIPRDR("info: WM_CREATE"); + clipboard = (wfClipboard*)((CREATESTRUCT*)lParam)->lpCreateParams; + clipboard->hwnd = hWnd; + + if (!clipboard->legacyApi) + clipboard->AddClipboardFormatListener(hWnd); + else + clipboard->hWndNextViewer = SetClipboardViewer(hWnd); + + break; + + case WM_CLOSE: + DEBUG_CLIPRDR("info: WM_CLOSE"); + + if (!clipboard->legacyApi) + clipboard->RemoveClipboardFormatListener(hWnd); + + break; + + case WM_DESTROY: + if (clipboard->legacyApi) + ChangeClipboardChain(hWnd, clipboard->hWndNextViewer); + + break; + + case WM_CLIPBOARDUPDATE: + DEBUG_CLIPRDR("info: WM_CLIPBOARDUPDATE"); + + if (clipboard->sync) + { + if ((GetClipboardOwner() != clipboard->hwnd) && + (S_FALSE == OleIsCurrentClipboard(clipboard->data_obj))) + { + if (clipboard->hmem) + { + GlobalFree(clipboard->hmem); + clipboard->hmem = nullptr; + } + + cliprdr_send_format_list(clipboard); + } + } + + break; + + case WM_RENDERALLFORMATS: + DEBUG_CLIPRDR("info: WM_RENDERALLFORMATS"); + + /* discard all contexts in clipboard */ + if (!try_open_clipboard(clipboard->hwnd)) + { + DEBUG_CLIPRDR("OpenClipboard failed with 0x%x", GetLastError()); + break; + } + + EmptyClipboard(); + CloseClipboard(); + break; + + case WM_RENDERFORMAT: + DEBUG_CLIPRDR("info: WM_RENDERFORMAT"); + + if (cliprdr_send_data_request(clipboard, (UINT32)wParam) != 0) + { + DEBUG_CLIPRDR("error: cliprdr_send_data_request failed."); + break; + } + + if (!SetClipboardData((UINT)wParam, clipboard->hmem)) + { + DEBUG_CLIPRDR("SetClipboardData failed with 0x%x", GetLastError()); + + if (clipboard->hmem) + { + GlobalFree(clipboard->hmem); + clipboard->hmem = nullptr; + } + } + + /* Note: GlobalFree() is not needed when success */ + break; + + case WM_DRAWCLIPBOARD: + if (clipboard->legacyApi) + { + if ((GetClipboardOwner() != clipboard->hwnd) && + (S_FALSE == OleIsCurrentClipboard(clipboard->data_obj))) + { + cliprdr_send_format_list(clipboard); + } + + SendMessage(clipboard->hWndNextViewer, Msg, wParam, lParam); + } + + break; + + case WM_CHANGECBCHAIN: + if (clipboard->legacyApi) + { + HWND hWndCurrViewer = (HWND)wParam; + HWND hWndNextViewer = (HWND)lParam; + + if (hWndCurrViewer == clipboard->hWndNextViewer) + clipboard->hWndNextViewer = hWndNextViewer; + else if (clipboard->hWndNextViewer) + SendMessage(clipboard->hWndNextViewer, Msg, wParam, lParam); + } + + break; + + case WM_CLIPRDR_MESSAGE: + DEBUG_CLIPRDR("info: WM_CLIPRDR_MESSAGE"); + + switch (wParam) + { + case OLE_SETCLIPBOARD: + DEBUG_CLIPRDR("info: OLE_SETCLIPBOARD"); + + if (wf_create_file_obj(clipboard, &clipboard->data_obj)) + { + if (OleSetClipboard(clipboard->data_obj) != S_OK) + { + wf_destroy_file_obj(clipboard->data_obj); + clipboard->data_obj = nullptr; + } + } + + break; + + default: + break; + } + + break; + + case WM_DESTROYCLIPBOARD: + case WM_ASKCBFORMATNAME: + case WM_HSCROLLCLIPBOARD: + case WM_PAINTCLIPBOARD: + case WM_SIZECLIPBOARD: + case WM_VSCROLLCLIPBOARD: + default: + return DefWindowProc(hWnd, Msg, wParam, lParam); + } + + return 0; +} + +static int create_cliprdr_window(wfClipboard* clipboard) +{ + WNDCLASSEX wnd_cls = WINPR_C_ARRAY_INIT; + + wnd_cls.cbSize = sizeof(WNDCLASSEX); + wnd_cls.style = CS_OWNDC; + wnd_cls.lpfnWndProc = cliprdr_proc; + wnd_cls.cbClsExtra = 0; + wnd_cls.cbWndExtra = 0; + wnd_cls.hIcon = nullptr; + wnd_cls.hCursor = nullptr; + wnd_cls.hbrBackground = nullptr; + wnd_cls.lpszMenuName = nullptr; + wnd_cls.lpszClassName = _T("ClipboardHiddenMessageProcessor"); + wnd_cls.hInstance = GetModuleHandle(nullptr); + wnd_cls.hIconSm = nullptr; + RegisterClassEx(&wnd_cls); + clipboard->hwnd = + CreateWindowEx(WS_EX_LEFT, _T("ClipboardHiddenMessageProcessor"), _T("rdpclip"), 0, 0, 0, 0, + 0, HWND_MESSAGE, nullptr, GetModuleHandle(nullptr), clipboard); + + if (!clipboard->hwnd) + { + DEBUG_CLIPRDR("error: CreateWindowEx failed with %x.", GetLastError()); + return -1; + } + + return 0; +} + +static DWORD WINAPI cliprdr_thread_func(LPVOID arg) +{ + int ret; + MSG msg; + BOOL mcode; + wfClipboard* clipboard = (wfClipboard*)arg; + OleInitialize(0); + + if ((ret = create_cliprdr_window(clipboard)) != 0) + { + OleUninitialize(); + DEBUG_CLIPRDR("error: create clipboard window failed."); + return 0; + } + + while ((mcode = GetMessage(&msg, 0, 0, 0)) != 0) + { + if (mcode == -1) + { + DEBUG_CLIPRDR("error: clipboard thread GetMessage failed."); + break; + } + else + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + OleUninitialize(); + return 0; +} + +static void clear_file_array(wfClipboard* clipboard) +{ + if (!clipboard) + return; + + /* clear file_names array */ + if (clipboard->file_names) + { + for (size_t i = 0; i < clipboard->nFiles; i++) + { + free(clipboard->file_names[i]); + clipboard->file_names[i] = nullptr; + } + + free(clipboard->file_names); + clipboard->file_names = nullptr; + } + + /* clear fileDescriptor array */ + if (clipboard->fileDescriptor) + { + for (size_t i = 0; i < clipboard->nFiles; i++) + { + free(clipboard->fileDescriptor[i]); + clipboard->fileDescriptor[i] = nullptr; + } + + free(clipboard->fileDescriptor); + clipboard->fileDescriptor = nullptr; + } + + clipboard->file_array_size = 0; + clipboard->nFiles = 0; +} + +static BOOL wf_cliprdr_get_file_contents(WCHAR* file_name, BYTE* buffer, LONG positionLow, + LONG positionHigh, DWORD nRequested, DWORD* puSize) +{ + BOOL res = FALSE; + HANDLE hFile; + DWORD nGet, rc; + + if (!file_name || !buffer || !puSize) + { + WLog_ERR(TAG, "get file contents Invalid Arguments."); + return FALSE; + } + + hFile = CreateFileW(file_name, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, nullptr); + + if (hFile == INVALID_HANDLE_VALUE) + return FALSE; + + rc = SetFilePointer(hFile, positionLow, &positionHigh, FILE_BEGIN); + + if (rc == INVALID_SET_FILE_POINTER) + goto error; + + if (!ReadFile(hFile, buffer, nRequested, &nGet, nullptr)) + { + DEBUG_CLIPRDR("ReadFile failed with 0x%08lX.", GetLastError()); + goto error; + } + + res = TRUE; +error: + + if (!CloseHandle(hFile)) + res = FALSE; + + if (res) + *puSize = nGet; + + return res; +} + +/* path_name has a '\' at the end. e.g. c:\newfolder\, file_name is c:\newfolder\new.txt */ +static FILEDESCRIPTORW* wf_cliprdr_get_file_descriptor(WCHAR* file_name, size_t pathLen) +{ + HANDLE hFile; + FILEDESCRIPTORW* fd; + fd = (FILEDESCRIPTORW*)calloc(1, sizeof(FILEDESCRIPTORW)); + + if (!fd) + return nullptr; + + hFile = CreateFileW(file_name, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, nullptr); + + if (hFile == INVALID_HANDLE_VALUE) + { + free(fd); + return nullptr; + } + + fd->dwFlags = FD_ATTRIBUTES | FD_FILESIZE | FD_WRITESTIME | FD_PROGRESSUI; + fd->dwFileAttributes = GetFileAttributes(file_name); + + if (!GetFileTime(hFile, nullptr, nullptr, &fd->ftLastWriteTime)) + { + fd->dwFlags &= ~FD_WRITESTIME; + } + + fd->nFileSizeLow = GetFileSize(hFile, &fd->nFileSizeHigh); + wcscpy_s(fd->cFileName, sizeof(fd->cFileName) / 2, file_name + pathLen); + (void)CloseHandle(hFile); + return fd; +} + +static BOOL wf_cliprdr_array_ensure_capacity(wfClipboard* clipboard) +{ + if (!clipboard) + return FALSE; + + if (clipboard->nFiles == clipboard->file_array_size) + { + size_t new_size; + FILEDESCRIPTORW** new_fd; + WCHAR** new_name; + new_size = (clipboard->file_array_size + 1) * 2; + new_fd = (FILEDESCRIPTORW**)realloc(clipboard->fileDescriptor, + new_size * sizeof(FILEDESCRIPTORW*)); + + if (new_fd) + clipboard->fileDescriptor = new_fd; + + new_name = (WCHAR**)realloc(clipboard->file_names, new_size * sizeof(WCHAR*)); + + if (new_name) + clipboard->file_names = new_name; + + if (!new_fd || !new_name) + return FALSE; + + clipboard->file_array_size = new_size; + } + + return TRUE; +} + +static BOOL wf_cliprdr_add_to_file_arrays(wfClipboard* clipboard, WCHAR* full_file_name, + size_t pathLen) +{ + if (!wf_cliprdr_array_ensure_capacity(clipboard)) + return FALSE; + + /* add to name array */ + clipboard->file_names[clipboard->nFiles] = (LPWSTR)malloc(MAX_PATH * 2); + + if (!clipboard->file_names[clipboard->nFiles]) + return FALSE; + + wcscpy_s(clipboard->file_names[clipboard->nFiles], MAX_PATH, full_file_name); + /* add to descriptor array */ + clipboard->fileDescriptor[clipboard->nFiles] = + wf_cliprdr_get_file_descriptor(full_file_name, pathLen); + + if (!clipboard->fileDescriptor[clipboard->nFiles]) + { + free(clipboard->file_names[clipboard->nFiles]); + return FALSE; + } + + clipboard->nFiles++; + return TRUE; +} + +static BOOL wf_cliprdr_traverse_directory(wfClipboard* clipboard, WCHAR* Dir, size_t pathLen) +{ + HANDLE hFind; + WCHAR DirSpec[MAX_PATH]; + WIN32_FIND_DATA FindFileData; + + if (!clipboard || !Dir) + return FALSE; + + StringCchCopy(DirSpec, MAX_PATH, Dir); + StringCchCat(DirSpec, MAX_PATH, TEXT("\\*")); + hFind = FindFirstFile(DirSpec, &FindFileData); + + if (hFind == INVALID_HANDLE_VALUE) + { + DEBUG_CLIPRDR("FindFirstFile failed with 0x%x.", GetLastError()); + return FALSE; + } + + while (FindNextFile(hFind, &FindFileData)) + { + if ((((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) && + (wcscmp(FindFileData.cFileName, _T(".")) == 0)) || + (wcscmp(FindFileData.cFileName, _T("..")) == 0)) + { + continue; + } + + if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) + { + WCHAR DirAdd[MAX_PATH]; + StringCchCopy(DirAdd, MAX_PATH, Dir); + StringCchCat(DirAdd, MAX_PATH, _T("\\")); + StringCchCat(DirAdd, MAX_PATH, FindFileData.cFileName); + + if (!wf_cliprdr_add_to_file_arrays(clipboard, DirAdd, pathLen)) + return FALSE; + + if (!wf_cliprdr_traverse_directory(clipboard, DirAdd, pathLen)) + return FALSE; + } + else + { + WCHAR fileName[MAX_PATH]; + StringCchCopy(fileName, MAX_PATH, Dir); + StringCchCat(fileName, MAX_PATH, _T("\\")); + StringCchCat(fileName, MAX_PATH, FindFileData.cFileName); + + if (!wf_cliprdr_add_to_file_arrays(clipboard, fileName, pathLen)) + return FALSE; + } + } + + FindClose(hFind); + return TRUE; +} + +static UINT wf_cliprdr_send_client_capabilities(wfClipboard* clipboard) +{ + CLIPRDR_CAPABILITIES capabilities; + CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet; + + if (!clipboard || !clipboard->context || !clipboard->context->ClientCapabilities) + return ERROR_INTERNAL_ERROR; + + capabilities.cCapabilitiesSets = 1; + capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET*)&(generalCapabilitySet); + generalCapabilitySet.capabilitySetType = CB_CAPSTYPE_GENERAL; + generalCapabilitySet.capabilitySetLength = 12; + generalCapabilitySet.version = CB_CAPS_VERSION_2; + generalCapabilitySet.generalFlags = + CB_USE_LONG_FORMAT_NAMES | CB_STREAM_FILECLIP_ENABLED | CB_FILECLIP_NO_FILE_PATHS; + return clipboard->context->ClientCapabilities(clipboard->context, &capabilities); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_cliprdr_monitor_ready(CliprdrClientContext* context, + const CLIPRDR_MONITOR_READY* monitorReady) +{ + UINT rc; + wfClipboard* clipboard = (wfClipboard*)context->custom; + + if (!context || !monitorReady) + return ERROR_INTERNAL_ERROR; + + clipboard->sync = TRUE; + rc = wf_cliprdr_send_client_capabilities(clipboard); + + if (rc != CHANNEL_RC_OK) + return rc; + + return cliprdr_send_format_list(clipboard); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_cliprdr_server_capabilities(CliprdrClientContext* context, + const CLIPRDR_CAPABILITIES* capabilities) +{ + CLIPRDR_CAPABILITY_SET* capabilitySet; + wfClipboard* clipboard = (wfClipboard*)context->custom; + + if (!context || !capabilities) + return ERROR_INTERNAL_ERROR; + + for (UINT32 index = 0; index < capabilities->cCapabilitiesSets; index++) + { + capabilitySet = &(capabilities->capabilitySets[index]); + + if ((capabilitySet->capabilitySetType == CB_CAPSTYPE_GENERAL) && + (capabilitySet->capabilitySetLength >= CB_CAPSTYPE_GENERAL_LEN)) + { + CLIPRDR_GENERAL_CAPABILITY_SET* generalCapabilitySet = + (CLIPRDR_GENERAL_CAPABILITY_SET*)capabilitySet; + clipboard->capabilities = generalCapabilitySet->generalFlags; + break; + } + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_cliprdr_server_format_list(CliprdrClientContext* context, + const CLIPRDR_FORMAT_LIST* formatList) +{ + UINT rc = ERROR_INTERNAL_ERROR; + formatMapping* mapping; + CLIPRDR_FORMAT* format; + wfClipboard* clipboard = (wfClipboard*)context->custom; + + if (!clear_format_map(clipboard)) + return ERROR_INTERNAL_ERROR; + + for (UINT32 i = 0; i < formatList->numFormats; i++) + { + format = &(formatList->formats[i]); + mapping = &(clipboard->format_mappings[i]); + mapping->remote_format_id = format->formatId; + + if (format->formatName) + { + mapping->name = ConvertUtf8ToWCharAlloc(format->formatName, nullptr); + + if (mapping->name) + mapping->local_format_id = RegisterClipboardFormatW((LPWSTR)mapping->name); + } + else + { + mapping->name = nullptr; + mapping->local_format_id = mapping->remote_format_id; + } + + clipboard->map_size++; + map_ensure_capacity(clipboard); + } + + if (file_transferring(clipboard)) + { + if (PostMessage(clipboard->hwnd, WM_CLIPRDR_MESSAGE, OLE_SETCLIPBOARD, 0)) + rc = CHANNEL_RC_OK; + } + else + { + if (!try_open_clipboard(clipboard->hwnd)) + return CHANNEL_RC_OK; /* Ignore, other app holding clipboard */ + + if (EmptyClipboard()) + { + for (UINT32 i = 0; i < (UINT32)clipboard->map_size; i++) + SetClipboardData(clipboard->format_mappings[i].local_format_id, nullptr); + + rc = CHANNEL_RC_OK; + } + + if (!CloseClipboard() && GetLastError()) + return ERROR_INTERNAL_ERROR; + } + + return rc; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +wf_cliprdr_server_format_list_response(CliprdrClientContext* context, + const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse) +{ + (void)context; + (void)formatListResponse; + + if (formatListResponse->common.msgFlags != CB_RESPONSE_OK) + WLog_WARN(TAG, "format list update failed"); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +wf_cliprdr_server_lock_clipboard_data(CliprdrClientContext* context, + const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData) +{ + (void)context; + (void)lockClipboardData; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +wf_cliprdr_server_unlock_clipboard_data(CliprdrClientContext* context, + const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData) +{ + (void)context; + (void)unlockClipboardData; + return CHANNEL_RC_OK; +} + +static BOOL wf_cliprdr_process_filename(wfClipboard* clipboard, WCHAR* wFileName, size_t str_len) +{ + size_t pathLen; + size_t offset = str_len; + + if (!clipboard || !wFileName) + return FALSE; + + /* find the last '\' in full file name */ + while (offset > 0) + { + if (wFileName[offset] == L'\\') + break; + else + offset--; + } + + pathLen = offset + 1; + + if (!wf_cliprdr_add_to_file_arrays(clipboard, wFileName, pathLen)) + return FALSE; + + if ((clipboard->fileDescriptor[clipboard->nFiles - 1]->dwFileAttributes & + FILE_ATTRIBUTE_DIRECTORY) != 0) + { + /* this is a directory */ + if (!wf_cliprdr_traverse_directory(clipboard, wFileName, pathLen)) + return FALSE; + } + + return TRUE; +} + +static SSIZE_T wf_cliprdr_tryopen(wfClipboard* clipboard, UINT32 requestedFormatId, BYTE** pData) +{ + SSIZE_T rc = -1; + WINPR_ASSERT(clipboard); + WINPR_ASSERT(pData); + + *pData = nullptr; + + /* Ignore if other app is holding the clipboard */ + if (!try_open_clipboard(clipboard->hwnd)) + return 0; + + HANDLE hClipdata = GetClipboardData(requestedFormatId); + + if (!hClipdata) + goto fail; + + char* globlemem = (char*)GlobalLock(hClipdata); + const SSIZE_T size = GlobalSize(hClipdata); + if (size <= 0) + goto unlock; + + BYTE* buff = malloc(size); + if (buff == nullptr) + goto fail; + CopyMemory(buff, globlemem, size); + *pData = buff; + rc = size; + +unlock: + GlobalUnlock(hClipdata); + +fail: + CloseClipboard(); + + return rc; +} + +static SSIZE_T wf_cliprdr_get_filedescriptor(wfClipboard* clipboard, BYTE** pData) +{ + WINPR_ASSERT(clipboard); + WINPR_ASSERT(pData); + + SSIZE_T rc = -1; + LPDATAOBJECT dataObj = nullptr; + FORMATETC format_etc = WINPR_C_ARRAY_INIT; + STGMEDIUM stg_medium = WINPR_C_ARRAY_INIT; + + *pData = nullptr; + + HRESULT result = OleGetClipboard(&dataObj); + if (FAILED(result)) + return -1; + + /* get DROPFILES struct from OLE */ + format_etc.cfFormat = CF_HDROP; + format_etc.tymed = TYMED_HGLOBAL; + format_etc.dwAspect = 1; + format_etc.lindex = -1; + result = IDataObject_GetData(dataObj, &format_etc, &stg_medium); + + if (FAILED(result)) + { + DEBUG_CLIPRDR("dataObj->GetData failed."); + goto exit; + } + + HGLOBAL hdl = stg_medium.u.hGlobal; + DROPFILES* dropFiles = (DROPFILES*)GlobalLock(hdl); + + if (!dropFiles) + { + ReleaseStgMedium(&stg_medium); + clipboard->nFiles = 0; + goto exit; + } + + clear_file_array(clipboard); + + if (dropFiles->fWide) + { + /* dropFiles contains file names */ + size_t len = 0; + for (WCHAR* wFileName = (WCHAR*)((char*)dropFiles + dropFiles->pFiles); + (len = wcslen(wFileName)) > 0; wFileName += len + 1) + { + wf_cliprdr_process_filename(clipboard, wFileName, wcslen(wFileName)); + } + } + else + { + size_t len = 0; + for (char* p = (char*)((char*)dropFiles + dropFiles->pFiles); (len = strlen(p)) > 0; + p += len + 1, clipboard->nFiles++) + { + const int ilen = WINPR_ASSERTING_INT_CAST(int, len); + const int cchWideChar = MultiByteToWideChar(CP_ACP, MB_COMPOSITE, p, ilen, nullptr, 0); + WCHAR* wFileName = (LPWSTR)calloc(cchWideChar, sizeof(WCHAR)); + MultiByteToWideChar(CP_ACP, MB_COMPOSITE, p, ilen, wFileName, cchWideChar); + wf_cliprdr_process_filename(clipboard, wFileName, cchWideChar); + free(wFileName); + } + } + + GlobalUnlock(hdl); + ReleaseStgMedium(&stg_medium); +exit: +{ + const size_t size = 4ull + clipboard->nFiles * sizeof(FILEDESCRIPTORW); + FILEGROUPDESCRIPTORW* groupDsc = (FILEGROUPDESCRIPTORW*)calloc(size, 1); + + if (groupDsc) + { + groupDsc->cItems = WINPR_ASSERTING_INT_CAST(UINT, clipboard->nFiles); + + for (size_t i = 0; i < clipboard->nFiles; i++) + { + if (clipboard->fileDescriptor[i]) + groupDsc->fgd[i] = *clipboard->fileDescriptor[i]; + } + + *pData = (BYTE*)groupDsc; + rc = size; + } +} + + IDataObject_Release(dataObj); + return rc; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +wf_cliprdr_server_format_data_request(CliprdrClientContext* context, + const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest) +{ + if (!context || !formatDataRequest) + return ERROR_INTERNAL_ERROR; + + wfClipboard* clipboard = (wfClipboard*)context->custom; + + if (!clipboard) + return ERROR_INTERNAL_ERROR; + + const UINT32 requestedFormatId = formatDataRequest->requestedFormatId; + BYTE* requestedFormatData = nullptr; + SSIZE_T res = 0; + if (requestedFormatId == RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW)) + { + res = wf_cliprdr_get_filedescriptor(clipboard, &requestedFormatData); + } + else + { + res = wf_cliprdr_tryopen(clipboard, requestedFormatId, &requestedFormatData); + } + + UINT rc = ERROR_INTERNAL_ERROR; + if (res >= 0) + { + const CLIPRDR_FORMAT_DATA_RESPONSE response = { + .common = { .msgType = CB_FORMAT_DATA_RESPONSE, + .msgFlags = CB_RESPONSE_OK, + .dataLen = (uint32_t)res }, + .requestedFormatData = requestedFormatData + }; + + rc = clipboard->context->ClientFormatDataResponse(clipboard->context, &response); + } + else + { + const CLIPRDR_FORMAT_DATA_RESPONSE response = { .common = { .msgType = + CB_FORMAT_DATA_RESPONSE, + .msgFlags = CB_RESPONSE_FAIL, + .dataLen = 0 }, + .requestedFormatData = nullptr }; + + rc = clipboard->context->ClientFormatDataResponse(clipboard->context, &response); + } + + free(requestedFormatData); + return rc; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +wf_cliprdr_server_format_data_response(CliprdrClientContext* context, + const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse) +{ + BYTE* data; + HANDLE hMem; + wfClipboard* clipboard; + + if (!context || !formatDataResponse) + return ERROR_INTERNAL_ERROR; + + clipboard = (wfClipboard*)context->custom; + + if (!clipboard) + return ERROR_INTERNAL_ERROR; + + if (formatDataResponse->common.msgFlags != CB_RESPONSE_OK) + { + clipboard->hmem = nullptr; + + if (!SetEvent(clipboard->response_data_event)) + return ERROR_INTERNAL_ERROR; + + return CHANNEL_RC_OK; + } + + hMem = GlobalAlloc(GMEM_MOVEABLE, formatDataResponse->common.dataLen); + + if (!hMem) + return ERROR_INTERNAL_ERROR; + + data = (BYTE*)GlobalLock(hMem); + + if (!data) + { + GlobalFree(hMem); + return ERROR_INTERNAL_ERROR; + } + + CopyMemory(data, formatDataResponse->requestedFormatData, formatDataResponse->common.dataLen); + + if (!GlobalUnlock(hMem) && GetLastError()) + { + GlobalFree(hMem); + return ERROR_INTERNAL_ERROR; + } + + clipboard->hmem = hMem; + + if (!SetEvent(clipboard->response_data_event)) + return ERROR_INTERNAL_ERROR; + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +wf_cliprdr_server_file_contents_request(CliprdrClientContext* context, + const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + DWORD uSize = 0; + BYTE* pData = nullptr; + HRESULT hRet = S_OK; + FORMATETC vFormatEtc = WINPR_C_ARRAY_INIT; + LPDATAOBJECT pDataObj = nullptr; + STGMEDIUM vStgMedium = WINPR_C_ARRAY_INIT; + BOOL bIsStreamFile = TRUE; + static LPSTREAM pStreamStc = nullptr; + static UINT32 uStreamIdStc = 0; + wfClipboard* clipboard; + UINT rc = ERROR_INTERNAL_ERROR; + UINT sRc; + UINT32 cbRequested; + + if (!context || !fileContentsRequest) + return ERROR_INTERNAL_ERROR; + + clipboard = (wfClipboard*)context->custom; + + if (!clipboard) + return ERROR_INTERNAL_ERROR; + + cbRequested = fileContentsRequest->cbRequested; + if (fileContentsRequest->dwFlags == FILECONTENTS_SIZE) + cbRequested = sizeof(UINT64); + + pData = (BYTE*)calloc(1, cbRequested); + + if (!pData) + goto error; + + hRet = OleGetClipboard(&pDataObj); + + if (FAILED(hRet)) + { + WLog_ERR(TAG, "filecontents: get ole clipboard failed."); + goto error; + } + + vFormatEtc.cfFormat = RegisterClipboardFormat(CFSTR_FILECONTENTS); + vFormatEtc.tymed = TYMED_ISTREAM; + vFormatEtc.dwAspect = 1; + vFormatEtc.lindex = fileContentsRequest->listIndex; + vFormatEtc.ptd = nullptr; + + if ((uStreamIdStc != fileContentsRequest->streamId) || !pStreamStc) + { + LPENUMFORMATETC pEnumFormatEtc; + ULONG CeltFetched; + FORMATETC vFormatEtc2; + + if (pStreamStc) + { + IStream_Release(pStreamStc); + pStreamStc = nullptr; + } + + bIsStreamFile = FALSE; + hRet = IDataObject_EnumFormatEtc(pDataObj, DATADIR_GET, &pEnumFormatEtc); + + if (hRet == S_OK) + { + do + { + hRet = IEnumFORMATETC_Next(pEnumFormatEtc, 1, &vFormatEtc2, &CeltFetched); + + if (hRet == S_OK) + { + if (vFormatEtc2.cfFormat == RegisterClipboardFormat(CFSTR_FILECONTENTS)) + { + hRet = IDataObject_GetData(pDataObj, &vFormatEtc, &vStgMedium); + + if (hRet == S_OK) + { + pStreamStc = vStgMedium.u.pstm; + uStreamIdStc = fileContentsRequest->streamId; + bIsStreamFile = TRUE; + } + + break; + } + } + } while (hRet == S_OK); + } + } + + if (bIsStreamFile == TRUE) + { + if (fileContentsRequest->dwFlags == FILECONTENTS_SIZE) + { + STATSTG vStatStg = WINPR_C_ARRAY_INIT; + hRet = IStream_Stat(pStreamStc, &vStatStg, STATFLAG_NONAME); + + if (hRet == S_OK) + { + *((UINT32*)&pData[0]) = vStatStg.cbSize.QuadPart & 0xFFFFFFFF; + *((UINT32*)&pData[4]) = (vStatStg.cbSize.QuadPart >> 32) & 0xFFFFFFFF; + uSize = cbRequested; + } + } + else if (fileContentsRequest->dwFlags == FILECONTENTS_RANGE) + { + LARGE_INTEGER dlibMove; + ULARGE_INTEGER dlibNewPosition; + dlibMove.QuadPart = (INT64)(((UINT64)fileContentsRequest->nPositionHigh << 32) | + fileContentsRequest->nPositionLow); + hRet = IStream_Seek(pStreamStc, dlibMove, STREAM_SEEK_SET, &dlibNewPosition); + + if (SUCCEEDED(hRet)) + hRet = IStream_Read(pStreamStc, pData, cbRequested, (PULONG)&uSize); + } + } + else + { + if (fileContentsRequest->dwFlags == FILECONTENTS_SIZE) + { + if (clipboard->nFiles <= fileContentsRequest->listIndex) + goto error; + *((UINT32*)&pData[0]) = + clipboard->fileDescriptor[fileContentsRequest->listIndex]->nFileSizeLow; + *((UINT32*)&pData[4]) = + clipboard->fileDescriptor[fileContentsRequest->listIndex]->nFileSizeHigh; + uSize = cbRequested; + } + else if (fileContentsRequest->dwFlags == FILECONTENTS_RANGE) + { + BOOL bRet; + if (clipboard->nFiles <= fileContentsRequest->listIndex) + goto error; + bRet = wf_cliprdr_get_file_contents( + clipboard->file_names[fileContentsRequest->listIndex], pData, + fileContentsRequest->nPositionLow, fileContentsRequest->nPositionHigh, cbRequested, + &uSize); + + if (bRet == FALSE) + { + WLog_ERR(TAG, "get file contents failed."); + uSize = 0; + goto error; + } + } + } + + rc = CHANNEL_RC_OK; +error: + + if (pDataObj) + IDataObject_Release(pDataObj); + + if (uSize == 0) + { + free(pData); + pData = nullptr; + } + + sRc = + cliprdr_send_response_filecontents(clipboard, fileContentsRequest->streamId, uSize, pData); + free(pData); + + if (sRc != CHANNEL_RC_OK) + return sRc; + + return rc; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +wf_cliprdr_server_file_contents_response(CliprdrClientContext* context, + const CLIPRDR_FILE_CONTENTS_RESPONSE* fileContentsResponse) +{ + wfClipboard* clipboard; + + if (!context || !fileContentsResponse) + return ERROR_INTERNAL_ERROR; + + if (fileContentsResponse->common.msgFlags != CB_RESPONSE_OK) + return E_FAIL; + + clipboard = (wfClipboard*)context->custom; + + if (!clipboard) + return ERROR_INTERNAL_ERROR; + + clipboard->req_fsize = fileContentsResponse->cbRequested; + clipboard->req_fdata = (char*)malloc(fileContentsResponse->cbRequested); + + if (!clipboard->req_fdata) + return ERROR_INTERNAL_ERROR; + + CopyMemory(clipboard->req_fdata, fileContentsResponse->requestedData, + fileContentsResponse->cbRequested); + + if (!SetEvent(clipboard->req_fevent)) + { + free(clipboard->req_fdata); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +BOOL wf_cliprdr_init(wfContext* wfc, CliprdrClientContext* cliprdr) +{ + wfClipboard* clipboard; + rdpContext* context = (rdpContext*)wfc; + + if (!context || !cliprdr) + return FALSE; + + wfc->clipboard = (wfClipboard*)calloc(1, sizeof(wfClipboard)); + + if (!wfc->clipboard) + return FALSE; + + clipboard = wfc->clipboard; + clipboard->wfc = wfc; + clipboard->context = cliprdr; + clipboard->channels = context->channels; + clipboard->sync = FALSE; + clipboard->map_capacity = 32; + clipboard->map_size = 0; + clipboard->hUser32 = LoadLibraryA("user32.dll"); + + if (clipboard->hUser32) + { + clipboard->AddClipboardFormatListener = GetProcAddressAs( + clipboard->hUser32, "AddClipboardFormatListener", fnAddClipboardFormatListener); + clipboard->RemoveClipboardFormatListener = GetProcAddressAs( + clipboard->hUser32, "RemoveClipboardFormatListener", fnRemoveClipboardFormatListener); + clipboard->GetUpdatedClipboardFormats = GetProcAddressAs( + clipboard->hUser32, "GetUpdatedClipboardFormats", fnGetUpdatedClipboardFormats); + } + + if (!(clipboard->hUser32 && clipboard->AddClipboardFormatListener && + clipboard->RemoveClipboardFormatListener && clipboard->GetUpdatedClipboardFormats)) + clipboard->legacyApi = TRUE; + + if (!(clipboard->format_mappings = + (formatMapping*)calloc(clipboard->map_capacity, sizeof(formatMapping)))) + goto error; + + if (!(clipboard->response_data_event = CreateEvent(nullptr, TRUE, FALSE, nullptr))) + goto error; + + if (!(clipboard->req_fevent = CreateEvent(nullptr, TRUE, FALSE, nullptr))) + goto error; + + if (!(clipboard->thread = CreateThread(nullptr, 0, cliprdr_thread_func, clipboard, 0, nullptr))) + goto error; + + cliprdr->MonitorReady = wf_cliprdr_monitor_ready; + cliprdr->ServerCapabilities = wf_cliprdr_server_capabilities; + cliprdr->ServerFormatList = wf_cliprdr_server_format_list; + cliprdr->ServerFormatListResponse = wf_cliprdr_server_format_list_response; + cliprdr->ServerLockClipboardData = wf_cliprdr_server_lock_clipboard_data; + cliprdr->ServerUnlockClipboardData = wf_cliprdr_server_unlock_clipboard_data; + cliprdr->ServerFormatDataRequest = wf_cliprdr_server_format_data_request; + cliprdr->ServerFormatDataResponse = wf_cliprdr_server_format_data_response; + cliprdr->ServerFileContentsRequest = wf_cliprdr_server_file_contents_request; + cliprdr->ServerFileContentsResponse = wf_cliprdr_server_file_contents_response; + cliprdr->custom = (void*)wfc->clipboard; + return TRUE; +error: + wf_cliprdr_uninit(wfc, cliprdr); + return FALSE; +} + +BOOL wf_cliprdr_uninit(wfContext* wfc, CliprdrClientContext* cliprdr) +{ + wfClipboard* clipboard; + + if (!wfc || !cliprdr) + return FALSE; + + clipboard = wfc->clipboard; + + if (!clipboard) + return FALSE; + + cliprdr->custom = nullptr; + + if (clipboard->hwnd) + PostMessage(clipboard->hwnd, WM_QUIT, 0, 0); + + if (clipboard->thread) + { + (void)WaitForSingleObject(clipboard->thread, INFINITE); + (void)CloseHandle(clipboard->thread); + } + + if (clipboard->response_data_event) + (void)CloseHandle(clipboard->response_data_event); + + if (clipboard->req_fevent) + (void)CloseHandle(clipboard->req_fevent); + + clear_file_array(clipboard); + clear_format_map(clipboard); + free(clipboard->format_mappings); + free(clipboard); + return TRUE; +} diff --git a/third_party/FreeRDP/client/Windows/wf_cliprdr.h b/third_party/FreeRDP/client/Windows/wf_cliprdr.h new file mode 100644 index 0000000..3a6b4a1 --- /dev/null +++ b/third_party/FreeRDP/client/Windows/wf_cliprdr.h @@ -0,0 +1,27 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Windows Clipboard Redirection + * + * Copyright 2012 Jason Champion + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef FREERDP_CLIENT_WIN_CLIPRDR_H +#define FREERDP_CLIENT_WIN_CLIPRDR_H + +#include "wf_client.h" + +BOOL wf_cliprdr_init(wfContext* wfc, CliprdrClientContext* cliprdr); +BOOL wf_cliprdr_uninit(wfContext* wfc, CliprdrClientContext* cliprdr); + +#endif /* FREERDP_CLIENT_WIN_CLIPRDR_H */ diff --git a/third_party/FreeRDP/client/Windows/wf_defaults.c b/third_party/FreeRDP/client/Windows/wf_defaults.c new file mode 100644 index 0000000..5d9af4f --- /dev/null +++ b/third_party/FreeRDP/client/Windows/wf_defaults.c @@ -0,0 +1,164 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2022 Stefan Koell + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include "wf_defaults.h" + +static PCWSTR ValidateString(const BYTE* pb, ULONG cb) +{ + if (!pb || !cb) + return 0; + + if (cb % sizeof(WCHAR) != 0) + return 0; + + return (PCWSTR)pb; +} + +static void AddDefaultSettings_I(rdpSettings* settings, size_t idHostname, size_t idUsername, + size_t idDomain, size_t idPassword) +{ + static const PSTR TERMSRV = "TERMSRV/%s"; + + PSTR TargetName = nullptr; + PSTR UserName = nullptr; + PWSTR TargetNameW = nullptr; + PWSTR ServerHostNameW = nullptr; + PWSTR ParsedUserNameW = nullptr; + PWSTR ParsedDomainW = nullptr; + PWSTR PasswordNullTerminatedW = nullptr; + PCREDENTIALW Credential = WINPR_C_ARRAY_INIT; + + PCSTR ServerHostname = freerdp_settings_get_string(settings, idHostname); + + if (!ServerHostname) + return; + + BOOL bExistUserName = freerdp_settings_get_string(settings, idUsername) != 0; + BOOL bExistPassword = freerdp_settings_get_string(settings, idPassword) != 0; + + if (bExistUserName && bExistPassword) + return; + + int len = _snprintf(TargetName, 0, TERMSRV, ServerHostname); + if (len < 0) + goto fail; + + len++; + TargetName = (PSTR)malloc(len); + + if (!TargetName) + goto fail; + + _snprintf(TargetName, len, TERMSRV, ServerHostname); + + TargetName[len - 1] = 0; + + TargetNameW = ConvertUtf8ToWCharAlloc(TargetName, nullptr); + if (!TargetNameW) + goto fail; + + if (!CredReadW(TargetNameW, CRED_TYPE_GENERIC, 0, &Credential)) + goto fail; + + if (!bExistPassword) + { + const WCHAR* PasswordW = + ValidateString(Credential->CredentialBlob, Credential->CredentialBlobSize); + + PasswordNullTerminatedW = (PWSTR)calloc(Credential->CredentialBlobSize + 1, sizeof(WCHAR)); + + if (!PasswordNullTerminatedW) + goto fail; + + memcpy(PasswordNullTerminatedW, PasswordW, Credential->CredentialBlobSize * sizeof(WCHAR)); + + if (PasswordNullTerminatedW) + { + if (!freerdp_settings_set_string_from_utf16(settings, idPassword, + PasswordNullTerminatedW)) + { + goto fail; + } + } + } + + if (!bExistUserName) + { + const WCHAR* UserNameW = Credential->UserName; + + if (UserNameW) + { + ParsedUserNameW = calloc(CREDUI_MAX_USERNAME_LENGTH + 1, sizeof(WCHAR)); + if (!ParsedUserNameW) + goto fail; + + ParsedDomainW = calloc(CREDUI_MAX_DOMAIN_TARGET_LENGTH + 1, sizeof(WCHAR)); + if (!ParsedDomainW) + goto fail; + + DWORD ParseResult = + CredUIParseUserNameW(UserNameW, ParsedUserNameW, CREDUI_MAX_USERNAME_LENGTH + 1, + ParsedDomainW, CREDUI_MAX_DOMAIN_TARGET_LENGTH + 1); + + if (ParseResult == NO_ERROR) + { + if (!freerdp_settings_set_string_from_utf16(settings, idUsername, ParsedUserNameW)) + goto fail; + + if (*ParsedDomainW != 0) + { + if (!freerdp_settings_set_string_from_utf16(settings, idDomain, ParsedDomainW)) + goto fail; + } + } + else if (ParseResult == ERROR_INVALID_ACCOUNT_NAME) + { + if (!freerdp_settings_set_string_from_utf16(settings, idUsername, UserNameW)) + goto fail; + } + } + } + +fail: + if (Credential) + { + CredFree(Credential); + } + free(TargetName); + free(UserName); + free(TargetNameW); + free(ServerHostNameW); + free(ParsedUserNameW); + free(ParsedDomainW); + free(PasswordNullTerminatedW); + return; +} + +void WINAPI AddDefaultSettings(rdpSettings* settings) +{ + AddDefaultSettings_I(settings, FreeRDP_ServerHostname, FreeRDP_Username, FreeRDP_Domain, + FreeRDP_Password); + AddDefaultSettings_I(settings, FreeRDP_GatewayHostname, FreeRDP_GatewayUsername, + FreeRDP_GatewayDomain, FreeRDP_GatewayPassword); +} diff --git a/third_party/FreeRDP/client/Windows/wf_defaults.h b/third_party/FreeRDP/client/Windows/wf_defaults.h new file mode 100644 index 0000000..79ebfdd --- /dev/null +++ b/third_party/FreeRDP/client/Windows/wf_defaults.h @@ -0,0 +1,28 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2022 Stefan Koell + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_WIN_DEFAULTS_H +#define FREERDP_CLIENT_WIN_DEFAULTS_H + +#include +#include +#include + +FREERDP_API void WINAPI AddDefaultSettings(_Inout_ rdpSettings* settings); + +#endif /* FREERDP_CLIENT_WIN_DEFAULTS_H */ diff --git a/third_party/FreeRDP/client/Windows/wf_event.c b/third_party/FreeRDP/client/Windows/wf_event.c new file mode 100644 index 0000000..ed0fc38 --- /dev/null +++ b/third_party/FreeRDP/client/Windows/wf_event.c @@ -0,0 +1,1004 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Event Handling + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include +#include +#include + +#include "wf_client.h" + +#include "wf_gdi.h" +#include "wf_event.h" + +#include + +#include + +static HWND g_focus_hWnd = nullptr; +static HWND g_main_hWnd = nullptr; +static HWND g_parent_hWnd = nullptr; + +#define RESIZE_MIN_DELAY 200 /* minimum delay in ms between two resizes */ + +static BOOL wf_scale_blt(wfContext* wfc, HDC hdc, int x, int y, int w, int h, HDC hdcSrc, int x1, + int y1, DWORD rop); +static BOOL wf_scale_mouse_event(wfContext* wfc, UINT16 flags, INT32 x, INT32 y); +#if (_WIN32_WINNT >= 0x0500) +static BOOL wf_scale_mouse_event_ex(wfContext* wfc, UINT16 flags, UINT16 buttonMask, INT32 x, + INT32 y); +#endif + +static BOOL g_flipping_in = FALSE; +static BOOL g_flipping_out = FALSE; + +static BOOL g_keystates[256] = WINPR_C_ARRAY_INIT; + +static BOOL ctrl_down(void) +{ + return g_keystates[VK_CONTROL] || g_keystates[VK_LCONTROL] || g_keystates[VK_RCONTROL]; +} + +static BOOL alt_ctrl_down(void) +{ + const BOOL altDown = g_keystates[VK_MENU] || g_keystates[VK_LMENU] || g_keystates[VK_RMENU]; + return altDown && ctrl_down(); +} + +LRESULT CALLBACK wf_ll_kbd_proc(int nCode, WPARAM wParam, LPARAM lParam) +{ + DWORD ext_proc_id = 0; + + wfContext* wfc = nullptr; + DWORD rdp_scancode; + BOOL keystate; + rdpInput* input; + PKBDLLHOOKSTRUCT p; + + DEBUG_KBD("Low-level keyboard hook, hWnd %X nCode %X wParam %X", g_focus_hWnd, nCode, wParam); + + if (g_flipping_in) + { + if (!alt_ctrl_down()) + g_flipping_in = FALSE; + + return CallNextHookEx(nullptr, nCode, wParam, lParam); + } + + if (g_parent_hWnd && g_main_hWnd) + { + wfc = (wfContext*)GetWindowLongPtr(g_main_hWnd, GWLP_USERDATA); + GUITHREADINFO gui_thread_info; + gui_thread_info.cbSize = sizeof(GUITHREADINFO); + HWND fg_win_hwnd = GetForegroundWindow(); + DWORD fg_win_thread_id = GetWindowThreadProcessId(fg_win_hwnd, &ext_proc_id); + BOOL result = GetGUIThreadInfo(fg_win_thread_id, &gui_thread_info); + if (gui_thread_info.hwndFocus != wfc->hWndParent) + { + g_focus_hWnd = nullptr; + return CallNextHookEx(nullptr, nCode, wParam, lParam); + } + + g_focus_hWnd = g_main_hWnd; + } + + if (g_focus_hWnd && (nCode == HC_ACTION)) + { + switch (wParam) + { + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + case WM_KEYUP: + case WM_SYSKEYUP: + if (!wfc) + wfc = (wfContext*)GetWindowLongPtr(g_focus_hWnd, GWLP_USERDATA); + p = (PKBDLLHOOKSTRUCT)lParam; + + if (!wfc || !p) + return 1; + + input = wfc->common.context.input; + rdp_scancode = MAKE_RDP_SCANCODE((BYTE)p->scanCode, p->flags & LLKHF_EXTENDED); + keystate = g_keystates[p->scanCode & 0xFF]; + + switch (wParam) + { + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + g_keystates[p->scanCode & 0xFF] = TRUE; + break; + case WM_KEYUP: + case WM_SYSKEYUP: + default: + g_keystates[p->scanCode & 0xFF] = FALSE; + break; + } + DEBUG_KBD("keydown %d scanCode 0x%08lX flags 0x%08lX vkCode 0x%08lX", + (wParam == WM_KEYDOWN), p->scanCode, p->flags, p->vkCode); + + if (wfc->fullscreen_toggle && (p->vkCode == VK_RETURN || p->vkCode == VK_CANCEL)) + { + if (alt_ctrl_down()) + { + if (wParam == WM_KEYDOWN) + { + wf_toggle_fullscreen(wfc); + return 1; + } + } + } + + if (rdp_scancode == RDP_SCANCODE_NUMLOCK_EXTENDED) + { + /* Windows sends NumLock as extended - rdp doesn't */ + DEBUG_KBD("hack: NumLock (x45) should not be extended"); + rdp_scancode = RDP_SCANCODE_NUMLOCK; + } + else if (rdp_scancode == RDP_SCANCODE_NUMLOCK) + { + /* Windows sends Pause as if it was a RDP NumLock (handled above). + * It must however be sent as a one-shot Ctrl+NumLock */ + if (wParam == WM_KEYDOWN) + { + DEBUG_KBD("Pause, sent as Ctrl+NumLock"); + freerdp_input_send_keyboard_event_ex(input, TRUE, FALSE, + RDP_SCANCODE_LCONTROL); + freerdp_input_send_keyboard_event_ex(input, TRUE, FALSE, + RDP_SCANCODE_NUMLOCK); + freerdp_input_send_keyboard_event_ex(input, FALSE, FALSE, + RDP_SCANCODE_LCONTROL); + freerdp_input_send_keyboard_event_ex(input, FALSE, FALSE, + RDP_SCANCODE_NUMLOCK); + } + else + { + DEBUG_KBD("Pause up"); + } + + return 1; + } + else if (rdp_scancode == RDP_SCANCODE_RSHIFT_EXTENDED) + { + DEBUG_KBD("right shift (x36) should not be extended"); + rdp_scancode = RDP_SCANCODE_RSHIFT; + } + + freerdp_input_send_keyboard_event_ex(input, !(p->flags & LLKHF_UP), keystate, + rdp_scancode); + + if (p->vkCode == VK_NUMLOCK || p->vkCode == VK_CAPITAL || p->vkCode == VK_SCROLL || + p->vkCode == VK_KANA) + DEBUG_KBD( + "lock keys are processed on client side too to toggle their indicators"); + else + return 1; + + break; + default: + break; + } + } + + if (g_flipping_out) + { + if (!alt_ctrl_down()) + { + g_flipping_out = FALSE; + g_focus_hWnd = nullptr; + } + } + + return CallNextHookEx(nullptr, nCode, wParam, lParam); +} + +void wf_event_focus_in(wfContext* wfc) +{ + UINT16 syncFlags; + rdpInput* input; + POINT pt; + RECT rc; + input = wfc->common.context.input; + syncFlags = 0; + + if (GetKeyState(VK_NUMLOCK)) + syncFlags |= KBD_SYNC_NUM_LOCK; + + if (GetKeyState(VK_CAPITAL)) + syncFlags |= KBD_SYNC_CAPS_LOCK; + + if (GetKeyState(VK_SCROLL)) + syncFlags |= KBD_SYNC_SCROLL_LOCK; + + if (GetKeyState(VK_KANA)) + syncFlags |= KBD_SYNC_KANA_LOCK; + + input->FocusInEvent(input, syncFlags); + /* send pointer position if the cursor is currently inside our client area */ + GetCursorPos(&pt); + ScreenToClient(wfc->hwnd, &pt); + GetClientRect(wfc->hwnd, &rc); + + if (pt.x >= rc.left && pt.x < rc.right && pt.y >= rc.top && pt.y < rc.bottom) + input->MouseEvent(input, PTR_FLAGS_MOVE, (UINT16)pt.x, (UINT16)pt.y); +} + +static BOOL wf_event_process_WM_MOUSEWHEEL(wfContext* wfc, HWND hWnd, UINT Msg, WPARAM wParam, + LPARAM lParam, BOOL horizontal, INT32 x, INT32 y) +{ + int delta; + UINT16 flags = 0; + rdpInput* input; + + WINPR_ASSERT(wfc); + + input = wfc->common.context.input; + WINPR_ASSERT(input); + + DefWindowProc(hWnd, Msg, wParam, lParam); + delta = ((signed short)HIWORD(wParam)); /* GET_WHEEL_DELTA_WPARAM(wParam); */ + + if (horizontal) + flags |= PTR_FLAGS_HWHEEL; + else + flags |= PTR_FLAGS_WHEEL; + + if (delta < 0) + { + flags |= PTR_FLAGS_WHEEL_NEGATIVE; + /* 9bit twos complement, delta already negative */ + delta = 0x100 + delta; + } + + flags |= delta; + return wf_scale_mouse_event(wfc, flags, x, y); +} + +static void wf_sizing(wfContext* wfc, WPARAM wParam, LPARAM lParam) +{ + rdpSettings* settings = wfc->common.context.settings; + // Holding the CTRL key down while resizing the window will force the desktop aspect ratio. + LPRECT rect; + + if ((freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) || + freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate)) && + ctrl_down()) + { + rect = (LPRECT)wParam; + + switch (lParam) + { + case WMSZ_LEFT: + case WMSZ_RIGHT: + case WMSZ_BOTTOMRIGHT: + // Adjust height + rect->bottom = + rect->top + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) * + (rect->right - rect->left) / + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + break; + + case WMSZ_TOP: + case WMSZ_BOTTOM: + case WMSZ_TOPRIGHT: + // Adjust width + rect->right = + rect->left + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) * + (rect->bottom - rect->top) / + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + break; + + case WMSZ_BOTTOMLEFT: + case WMSZ_TOPLEFT: + // adjust width + rect->left = + rect->right - (freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) * + (rect->bottom - rect->top) / + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)); + break; + } + } +} + +static void wf_send_resize(wfContext* wfc) +{ + RECT windowRect; + int targetWidth = wfc->client_width; + int targetHeight = wfc->client_height; + rdpSettings* settings = wfc->common.context.settings; + + if (freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate) && + wfc->disp != nullptr) + { + if (GetTickCount64() - wfc->lastSentDate > RESIZE_MIN_DELAY) + { + if (wfc->fullscreen) + { + GetWindowRect(wfc->hwnd, &windowRect); + targetWidth = windowRect.right - windowRect.left; + targetHeight = windowRect.bottom - windowRect.top; + } + if (freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingWidth) != targetWidth || + freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingHeight) != targetHeight) + { + DISPLAY_CONTROL_MONITOR_LAYOUT layout = WINPR_C_ARRAY_INIT; + + layout.Flags = DISPLAY_CONTROL_MONITOR_PRIMARY; + layout.Top = layout.Left = 0; + layout.Width = targetWidth; + layout.Height = targetHeight; + layout.Orientation = + freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation); + layout.DesktopScaleFactor = + freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor); + layout.DeviceScaleFactor = + freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor); + layout.PhysicalWidth = targetWidth; + layout.PhysicalHeight = targetHeight; + + if (IFCALLRESULT(CHANNEL_RC_OK, wfc->disp->SendMonitorLayout, wfc->disp, 1, + &layout) != CHANNEL_RC_OK) + { + WLog_ERR("", "SendMonitorLayout failed."); + } + (void)freerdp_settings_set_uint32(settings, FreeRDP_SmartSizingWidth, targetWidth); + (void)freerdp_settings_set_uint32(settings, FreeRDP_SmartSizingHeight, + targetHeight); + } + wfc->lastSentDate = GetTickCount64(); + } + } +} + +LRESULT CALLBACK wf_event_proc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) +{ + HDC hdc = WINPR_C_ARRAY_INIT; + PAINTSTRUCT ps = WINPR_C_ARRAY_INIT; + BOOL processed = FALSE; + RECT windowRect = WINPR_C_ARRAY_INIT; + MINMAXINFO* minmax = nullptr; + SCROLLINFO si = WINPR_C_ARRAY_INIT; + processed = TRUE; + LONG_PTR ptr = GetWindowLongPtr(hWnd, GWLP_USERDATA); + wfContext* wfc = (wfContext*)ptr; + + if (wfc != nullptr) + { + rdpInput* input = wfc->common.context.input; + rdpSettings* settings = wfc->common.context.settings; + + if (!g_parent_hWnd && wfc->hWndParent) + g_parent_hWnd = wfc->hWndParent; + + if (!g_main_hWnd) + g_main_hWnd = wfc->hwnd; + + switch (Msg) + { + case WM_MOVE: + if (!wfc->disablewindowtracking) + { + int x = (int)(short)LOWORD(lParam); + int y = (int)(short)HIWORD(lParam); + wfc->client_x = x; + wfc->client_y = y; + } + + break; + + case WM_GETMINMAXINFO: + if (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) || + (freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate))) + { + processed = FALSE; + } + else + { + // Set maximum window size for resizing + minmax = (MINMAXINFO*)lParam; + + // always use the last determined canvas diff, because it could be + // that the window is minimized when this gets called + // wf_update_canvas_diff(wfc); + + if (!wfc->fullscreen) + { + // add window decoration + minmax->ptMaxTrackSize.x = + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) + + wfc->diff.x; + minmax->ptMaxTrackSize.y = + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) + + wfc->diff.y; + } + } + + break; + + case WM_SIZING: + wf_sizing(wfc, lParam, wParam); + break; + + case WM_SIZE: + GetWindowRect(wfc->hwnd, &windowRect); + + if (!wfc->fullscreen) + { + wfc->client_width = LOWORD(lParam); + wfc->client_height = HIWORD(lParam); + wfc->client_x = windowRect.left; + wfc->client_y = windowRect.top; + } + else + { + wfc->wasMaximized = TRUE; + wf_send_resize(wfc); + } + + if (wfc->client_width && wfc->client_height) + { + wf_size_scrollbars(wfc, LOWORD(lParam), HIWORD(lParam)); + + // Workaround: when the window is maximized, the call to "ShowScrollBars" + // returns TRUE but has no effect. + if (wParam == SIZE_MAXIMIZED && !wfc->fullscreen) + { + SetWindowPos(wfc->hwnd, HWND_TOP, 0, 0, windowRect.right - windowRect.left, + windowRect.bottom - windowRect.top, + SWP_NOMOVE | SWP_FRAMECHANGED); + wfc->wasMaximized = TRUE; + wf_send_resize(wfc); + } + else if (wParam == SIZE_RESTORED && !wfc->fullscreen && wfc->wasMaximized) + { + wfc->wasMaximized = FALSE; + wf_send_resize(wfc); + } + else if (wParam == SIZE_MINIMIZED) + { + g_focus_hWnd = nullptr; + } + } + + break; + + case WM_EXITSIZEMOVE: + wf_size_scrollbars(wfc, wfc->client_width, wfc->client_height); + wf_send_resize(wfc); + break; + + case WM_ERASEBKGND: + /* Say we handled it - prevents flickering */ + return (LRESULT)1; + + case WM_PAINT: + { + hdc = BeginPaint(hWnd, &ps); + const int x = ps.rcPaint.left; + const int y = ps.rcPaint.top; + const int w = ps.rcPaint.right - ps.rcPaint.left + 1; + const int h = ps.rcPaint.bottom - ps.rcPaint.top + 1; + wf_scale_blt(wfc, hdc, x, y, w, h, wfc->primary->hdc, + x - wfc->offset_x + wfc->xCurrentScroll, + y - wfc->offset_y + wfc->yCurrentScroll, SRCCOPY); + EndPaint(hWnd, &ps); + } + break; +#if (_WIN32_WINNT >= 0x0500) + + case WM_XBUTTONDOWN: + wf_scale_mouse_event_ex(wfc, PTR_XFLAGS_DOWN, GET_XBUTTON_WPARAM(wParam), + GET_X_LPARAM(lParam) - wfc->offset_x, + GET_Y_LPARAM(lParam) - wfc->offset_y); + break; + + case WM_XBUTTONUP: + wf_scale_mouse_event_ex(wfc, 0, GET_XBUTTON_WPARAM(wParam), + GET_X_LPARAM(lParam) - wfc->offset_x, + GET_Y_LPARAM(lParam) - wfc->offset_y); + break; +#endif + + case WM_MBUTTONDOWN: + wf_scale_mouse_event(wfc, PTR_FLAGS_DOWN | PTR_FLAGS_BUTTON3, + GET_X_LPARAM(lParam) - wfc->offset_x, + GET_Y_LPARAM(lParam) - wfc->offset_y); + break; + + case WM_MBUTTONUP: + wf_scale_mouse_event(wfc, PTR_FLAGS_BUTTON3, GET_X_LPARAM(lParam) - wfc->offset_x, + GET_Y_LPARAM(lParam) - wfc->offset_y); + break; + + case WM_LBUTTONDOWN: + wf_scale_mouse_event(wfc, PTR_FLAGS_DOWN | PTR_FLAGS_BUTTON1, + GET_X_LPARAM(lParam) - wfc->offset_x, + GET_Y_LPARAM(lParam) - wfc->offset_y); + SetCapture(wfc->hwnd); + break; + + case WM_LBUTTONUP: + wf_scale_mouse_event(wfc, PTR_FLAGS_BUTTON1, GET_X_LPARAM(lParam) - wfc->offset_x, + GET_Y_LPARAM(lParam) - wfc->offset_y); + ReleaseCapture(); + break; + + case WM_RBUTTONDOWN: + wf_scale_mouse_event(wfc, PTR_FLAGS_DOWN | PTR_FLAGS_BUTTON2, + GET_X_LPARAM(lParam) - wfc->offset_x, + GET_Y_LPARAM(lParam) - wfc->offset_y); + break; + + case WM_RBUTTONUP: + wf_scale_mouse_event(wfc, PTR_FLAGS_BUTTON2, GET_X_LPARAM(lParam) - wfc->offset_x, + GET_Y_LPARAM(lParam) - wfc->offset_y); + break; + + case WM_MOUSEMOVE: + wf_scale_mouse_event(wfc, PTR_FLAGS_MOVE, GET_X_LPARAM(lParam) - wfc->offset_x, + GET_Y_LPARAM(lParam) - wfc->offset_y); + break; +#if (_WIN32_WINNT >= 0x0400) || (_WIN32_WINDOWS > 0x0400) + + case WM_MOUSEWHEEL: + wf_event_process_WM_MOUSEWHEEL(wfc, hWnd, Msg, wParam, lParam, FALSE, + GET_X_LPARAM(lParam) - wfc->offset_x, + GET_Y_LPARAM(lParam) - wfc->offset_y); + break; +#endif +#if (_WIN32_WINNT >= 0x0600) + + case WM_MOUSEHWHEEL: + wf_event_process_WM_MOUSEWHEEL(wfc, hWnd, Msg, wParam, lParam, TRUE, + GET_X_LPARAM(lParam) - wfc->offset_x, + GET_Y_LPARAM(lParam) - wfc->offset_y); + break; +#endif + + case WM_SETCURSOR: + if (LOWORD(lParam) == HTCLIENT) + SetCursor(wfc->cursor); + else + DefWindowProc(hWnd, Msg, wParam, lParam); + + break; + + case WM_HSCROLL: + { + int xDelta; // xDelta = new_pos - current_pos + int xNewPos; // new position + int yDelta = 0; + + switch (LOWORD(wParam)) + { + // User clicked the scroll bar shaft left of the scroll box. + case SB_PAGEUP: + xNewPos = wfc->xCurrentScroll - 50; + break; + + // User clicked the scroll bar shaft right of the scroll box. + case SB_PAGEDOWN: + xNewPos = wfc->xCurrentScroll + 50; + break; + + // User clicked the left arrow. + case SB_LINEUP: + xNewPos = wfc->xCurrentScroll - 5; + break; + + // User clicked the right arrow. + case SB_LINEDOWN: + xNewPos = wfc->xCurrentScroll + 5; + break; + + // User dragged the scroll box. + case SB_THUMBPOSITION: + xNewPos = HIWORD(wParam); + break; + + // user is dragging the scrollbar + case SB_THUMBTRACK: + xNewPos = HIWORD(wParam); + break; + + default: + xNewPos = wfc->xCurrentScroll; + } + + // New position must be between 0 and the screen width. + xNewPos = MAX(0, xNewPos); + xNewPos = MIN(wfc->xMaxScroll, xNewPos); + + // If the current position does not change, do not scroll. + if (xNewPos == wfc->xCurrentScroll) + break; + + // Determine the amount scrolled (in pixels). + xDelta = xNewPos - wfc->xCurrentScroll; + // Reset the current scroll position. + wfc->xCurrentScroll = xNewPos; + // Scroll the window. (The system repaints most of the + // client area when ScrollWindowEx is called; however, it is + // necessary to call UpdateWindow in order to repaint the + // rectangle of pixels that were invalidated.) + ScrollWindowEx(wfc->hwnd, -xDelta, -yDelta, (CONST RECT*)nullptr, + (CONST RECT*)nullptr, (HRGN) nullptr, (PRECT) nullptr, + SW_INVALIDATE); + UpdateWindow(wfc->hwnd); + // Reset the scroll bar. + si.cbSize = sizeof(si); + si.fMask = SIF_POS; + si.nPos = wfc->xCurrentScroll; + SetScrollInfo(wfc->hwnd, SB_HORZ, &si, TRUE); + } + break; + + case WM_VSCROLL: + { + int xDelta = 0; + int yDelta; // yDelta = new_pos - current_pos + int yNewPos; // new position + + switch (LOWORD(wParam)) + { + // User clicked the scroll bar shaft above the scroll box. + case SB_PAGEUP: + yNewPos = wfc->yCurrentScroll - 50; + break; + + // User clicked the scroll bar shaft below the scroll box. + case SB_PAGEDOWN: + yNewPos = wfc->yCurrentScroll + 50; + break; + + // User clicked the top arrow. + case SB_LINEUP: + yNewPos = wfc->yCurrentScroll - 5; + break; + + // User clicked the bottom arrow. + case SB_LINEDOWN: + yNewPos = wfc->yCurrentScroll + 5; + break; + + // User dragged the scroll box. + case SB_THUMBPOSITION: + yNewPos = HIWORD(wParam); + break; + + // user is dragging the scrollbar + case SB_THUMBTRACK: + yNewPos = HIWORD(wParam); + break; + + default: + yNewPos = wfc->yCurrentScroll; + } + + // New position must be between 0 and the screen height. + yNewPos = MAX(0, yNewPos); + yNewPos = MIN(wfc->yMaxScroll, yNewPos); + + // If the current position does not change, do not scroll. + if (yNewPos == wfc->yCurrentScroll) + break; + + // Determine the amount scrolled (in pixels). + yDelta = yNewPos - wfc->yCurrentScroll; + // Reset the current scroll position. + wfc->yCurrentScroll = yNewPos; + // Scroll the window. (The system repaints most of the + // client area when ScrollWindowEx is called; however, it is + // necessary to call UpdateWindow in order to repaint the + // rectangle of pixels that were invalidated.) + ScrollWindowEx(wfc->hwnd, -xDelta, -yDelta, (CONST RECT*)nullptr, + (CONST RECT*)nullptr, (HRGN) nullptr, (PRECT) nullptr, + SW_INVALIDATE); + UpdateWindow(wfc->hwnd); + // Reset the scroll bar. + si.cbSize = sizeof(si); + si.fMask = SIF_POS; + si.nPos = wfc->yCurrentScroll; + SetScrollInfo(wfc->hwnd, SB_VERT, &si, TRUE); + } + break; + + case WM_SYSCOMMAND: + { + if (wParam == SYSCOMMAND_ID_SMARTSIZING) + { + HMENU hMenu = GetSystemMenu(wfc->hwnd, FALSE); + const BOOL rc = freerdp_settings_get_bool(settings, FreeRDP_SmartSizing); + (void)freerdp_settings_set_bool(settings, FreeRDP_SmartSizing, !rc); + CheckMenuItem(hMenu, SYSCOMMAND_ID_SMARTSIZING, + freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) + ? MF_CHECKED + : MF_UNCHECKED); + if (!freerdp_settings_get_bool(settings, FreeRDP_SmartSizing)) + { + SetWindowPos(wfc->hwnd, HWND_TOP, -1, -1, + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) + + wfc->diff.x, + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) + + wfc->diff.y, + SWP_NOMOVE); + } + else + { + wf_size_scrollbars(wfc, wfc->client_width, wfc->client_height); + wf_send_resize(wfc); + } + } + else if (wParam == SYSCOMMAND_ID_REQUEST_CONTROL) + { + freerdp_client_encomsp_set_control(wfc->common.encomsp, TRUE); + } + else + { + processed = FALSE; + } + } + break; + + default: + processed = FALSE; + break; + } + } + else + { + processed = FALSE; + } + + if (processed) + return 0; + + switch (Msg) + { + case WM_DESTROY: + PostQuitMessage(WM_QUIT); + break; + + case WM_SETFOCUS: + DEBUG_KBD("getting focus %X", hWnd); + + (void)freerdp_settings_set_bool(wfc->common.context.settings, FreeRDP_SuspendInput, + FALSE); + + if (alt_ctrl_down()) + g_flipping_in = TRUE; + + g_focus_hWnd = hWnd; + freerdp_set_focus(wfc->common.context.instance); + wf_event_focus_in(wfc); + break; + + case WM_KILLFOCUS: + (void)freerdp_settings_set_bool(wfc->common.context.settings, FreeRDP_SuspendInput, + TRUE); + + if (g_focus_hWnd == hWnd && wfc && !wfc->fullscreen) + { + DEBUG_KBD("losing focus %X", hWnd); + + if (alt_ctrl_down()) + g_flipping_out = TRUE; + else + g_focus_hWnd = nullptr; + } + + break; + + case WM_ACTIVATE: + { + int activate = (int)(short)LOWORD(wParam); + BOOL minimized_flag = (BOOL)HIWORD(wParam); + + if (activate != WA_INACTIVE && !minimized_flag) + { + if (alt_ctrl_down()) + g_flipping_in = TRUE; + + g_focus_hWnd = hWnd; + } + else + { + if (alt_ctrl_down()) + g_flipping_out = TRUE; + else + g_focus_hWnd = nullptr; + } + } + + default: + return DefWindowProc(hWnd, Msg, wParam, lParam); + break; + } + + return 0; +} + +BOOL wf_scale_blt(wfContext* wfc, HDC hdc, int x, int y, int w, int h, HDC hdcSrc, int x1, int y1, + DWORD rop) +{ + UINT32 ww, wh, dw, dh; + WINPR_ASSERT(wfc); + + rdpSettings* settings = wfc->common.context.settings; + WINPR_ASSERT(settings); + + if (!wfc->client_width) + wfc->client_width = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + + if (!wfc->client_height) + wfc->client_height = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + + ww = wfc->client_width; + wh = wfc->client_height; + dw = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + dh = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + + if (!ww) + ww = dw; + + if (!wh) + wh = dh; + + if (wfc->fullscreen || !freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) || + (ww == dw && wh == dh)) + { + return BitBlt(hdc, x, y, w, h, wfc->primary->hdc, x1, y1, SRCCOPY); + } + else + { + SetStretchBltMode(hdc, HALFTONE); + SetBrushOrgEx(hdc, 0, 0, nullptr); + return StretchBlt(hdc, 0, 0, ww, wh, wfc->primary->hdc, 0, 0, dw, dh, SRCCOPY); + } + + return TRUE; +} + +static BOOL wf_scale_mouse_pos(wfContext* wfc, INT32 x, INT32 y, UINT16* px, UINT16* py) +{ + int ww, wh, dw, dh; + rdpSettings* settings; + + if (!wfc || !px || !py) + return FALSE; + + settings = wfc->common.context.settings; + + if (!settings) + return FALSE; + + if (!wfc->client_width) + wfc->client_width = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + + if (!wfc->client_height) + wfc->client_height = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + + ww = wfc->client_width; + wh = wfc->client_height; + dw = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + dh = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + + if (!freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) || ((ww == dw) && (wh == dh))) + { + x += wfc->xCurrentScroll; + y += wfc->yCurrentScroll; + } + else + { + x = x * dw / ww + wfc->xCurrentScroll; + y = y * dh / wh + wfc->yCurrentScroll; + } + + *px = MIN(UINT16_MAX, MAX(0, x)); + *py = MIN(UINT16_MAX, MAX(0, y)); + + return TRUE; +} + +static BOOL wf_pub_mouse_event(wfContext* wfc, UINT16 flags, UINT16 x, UINT16 y) +{ + MouseEventEventArgs eventArgs = WINPR_C_ARRAY_INIT; + + eventArgs.flags = flags; + eventArgs.x = x; + eventArgs.y = y; + PubSub_OnMouseEvent(wfc->common.context.pubSub, &wfc->common.context, &eventArgs); + return TRUE; +} + +static BOOL wf_scale_mouse_event(wfContext* wfc, UINT16 flags, INT32 x, INT32 y) +{ + UINT16 px, py; + + WINPR_ASSERT(wfc); + + if (!wf_scale_mouse_pos(wfc, x, y, &px, &py)) + return FALSE; + + /* Fix: correct return value check (TRUE means success). */ + if (!freerdp_client_send_button_event(&wfc->common, FALSE, flags, px, py)) + return FALSE; + + return wf_pub_mouse_event(wfc, flags, px, py); +} + +#if (_WIN32_WINNT >= 0x0500) +static BOOL wf_scale_mouse_event_ex(wfContext* wfc, UINT16 flags, UINT16 buttonMask, INT32 x, + INT32 y) +{ + UINT16 px, py; + + WINPR_ASSERT(wfc); + + if (buttonMask & XBUTTON1) + flags |= PTR_XFLAGS_BUTTON1; + + if (buttonMask & XBUTTON2) + flags |= PTR_XFLAGS_BUTTON2; + + if (!wf_scale_mouse_pos(wfc, x, y, &px, &py)) + return FALSE; + + /* Fix: correct return value check for extended mouse events. */ + if (!freerdp_client_send_extended_button_event(&wfc->common, FALSE, flags, px, py)) + return FALSE; + + return wf_pub_mouse_event(wfc, flags, px, py); +} +#endif + +BOOL wf_keyboard_set_indicators(rdpContext* context, UINT16 led_flags) +{ + wfContext* wfc = (wfContext*)context; + BYTE keyState[256] = WINPR_C_ARRAY_INIT; + + if (!wfc || !GetKeyboardState(keyState)) + return FALSE; + + if (led_flags & KBD_SYNC_NUM_LOCK) + keyState[VK_NUMLOCK] |= 1; + else + keyState[VK_NUMLOCK] &= ~1; + + if (led_flags & KBD_SYNC_CAPS_LOCK) + keyState[VK_CAPITAL] |= 1; + else + keyState[VK_CAPITAL] &= ~1; + + if (led_flags & KBD_SYNC_SCROLL_LOCK) + keyState[VK_SCROLL] |= 1; + else + keyState[VK_SCROLL] &= ~1; + + if (led_flags & KBD_SYNC_KANA_LOCK) + keyState[VK_KANA] |= 1; + else + keyState[VK_KANA] &= ~1; + + return SetKeyboardState(keyState); +} diff --git a/third_party/FreeRDP/client/Windows/wf_event.h b/third_party/FreeRDP/client/Windows/wf_event.h new file mode 100644 index 0000000..1ae75b9 --- /dev/null +++ b/third_party/FreeRDP/client/Windows/wf_event.h @@ -0,0 +1,45 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Event Handling + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_WIN_EVENT_H +#define FREERDP_CLIENT_WIN_EVENT_H + +#include "wf_client.h" +#include + +LRESULT CALLBACK wf_ll_kbd_proc(int nCode, WPARAM wParam, LPARAM lParam); +LRESULT CALLBACK wf_event_proc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); + +void wf_event_focus_in(wfContext* wfc); + +BOOL wf_keyboard_set_indicators(rdpContext* context, UINT16 led_flags); + +#define KBD_TAG CLIENT_TAG("windows") +#ifdef WITH_DEBUG_KBD +#define DEBUG_KBD(...) WLog_DBG(KBD_TAG, __VA_ARGS__) +#else +#define DEBUG_KBD(...) \ + do \ + { \ + } while (0) +#endif + +#endif /* FREERDP_CLIENT_WIN_EVENT_H */ diff --git a/third_party/FreeRDP/client/Windows/wf_floatbar.c b/third_party/FreeRDP/client/Windows/wf_floatbar.c new file mode 100644 index 0000000..b97bf01 --- /dev/null +++ b/third_party/FreeRDP/client/Windows/wf_floatbar.c @@ -0,0 +1,733 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Windows Float Bar + * + * Copyright 2013 Zhang Zhaolong + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "wf_client.h" +#include "wf_floatbar.h" + +#include "resource/resource.h" +#include "wf_gdi.h" +#ifdef _MSC_VER +#pragma comment(lib, "Msimg32.lib") +#endif + +#define TAG CLIENT_TAG("windows.floatbar") + +/* TIMERs */ +#define TIMER_HIDE 1 +#define TIMER_ANIMAT_SHOW 2 +#define TIMER_ANIMAT_HIDE 3 + +/* Button Type */ +#define BUTTON_LOCKPIN 0 +#define BUTTON_MINIMIZE 1 +#define BUTTON_RESTORE 2 +#define BUTTON_CLOSE 3 +#define BTN_MAX 4 + +/* bmp size */ +#define BACKGROUND_W 576 +#define BACKGROUND_H 27 +#define BUTTON_OFFSET 5 +#define BUTTON_Y 2 +#define BUTTON_WIDTH 23 +#define BUTTON_HEIGHT 21 +#define BUTTON_SPACING 1 + +#define LOCK_X (BACKGROUND_H + BUTTON_OFFSET) +#define CLOSE_X ((BACKGROUND_W - (BACKGROUND_H + BUTTON_OFFSET)) - BUTTON_WIDTH) +#define RESTORE_X (CLOSE_X - (BUTTON_WIDTH + BUTTON_SPACING)) +#define MINIMIZE_X (RESTORE_X - (BUTTON_WIDTH + BUTTON_SPACING)) +#define TEXT_X (BACKGROUND_H + ((BUTTON_WIDTH + BUTTON_SPACING) * 3) + 5) + +typedef struct +{ + wfFloatBar* floatbar; + int type; + int x, y, h, w; + int active; + HBITMAP bmp; + HBITMAP bmp_act; + + /* Lock Specified */ + HBITMAP locked_bmp; + HBITMAP locked_bmp_act; + HBITMAP unlocked_bmp; + HBITMAP unlocked_bmp_act; +} Button; + +struct s_FloatBar +{ + HINSTANCE root_window; + DWORD flags; + HWND parent; + HWND hwnd; + RECT rect; + LONG width; + LONG height; + LONG offset; + wfContext* wfc; + Button* buttons[BTN_MAX]; + BOOL shown; + BOOL locked; + HDC hdcmem; + RECT textRect; + UINT_PTR animating; +}; + +static BOOL floatbar_kill_timers(wfFloatBar* floatbar) +{ + UINT_PTR timers[] = { TIMER_HIDE, TIMER_ANIMAT_HIDE, TIMER_ANIMAT_SHOW }; + + if (!floatbar) + return FALSE; + + for (size_t x = 0; x < ARRAYSIZE(timers); x++) + KillTimer(floatbar->hwnd, timers[x]); + + floatbar->animating = 0; + return TRUE; +} + +static BOOL floatbar_animation(wfFloatBar* const floatbar, const BOOL show) +{ + UINT_PTR timer = show ? TIMER_ANIMAT_SHOW : TIMER_ANIMAT_HIDE; + + if (!floatbar) + return FALSE; + + if (floatbar->shown == show) + return TRUE; + + if (floatbar->animating == timer) + return TRUE; + + floatbar->animating = timer; + + if (SetTimer(floatbar->hwnd, timer, USER_TIMER_MINIMUM, nullptr) == 0) + { + DWORD err = GetLastError(); + WLog_ERR(TAG, "SetTimer failed with %08" PRIx32, err); + return FALSE; + } + + return TRUE; +} + +static BOOL floatbar_trigger_hide(wfFloatBar* floatbar) +{ + if (!floatbar_kill_timers(floatbar)) + return FALSE; + + if (!floatbar->locked && floatbar->shown) + { + if (SetTimer(floatbar->hwnd, TIMER_HIDE, 3000, nullptr) == 0) + { + DWORD err = GetLastError(); + WLog_ERR(TAG, "SetTimer failed with %08" PRIx32, err); + return FALSE; + } + } + + return TRUE; +} + +static BOOL floatbar_hide(wfFloatBar* floatbar) +{ + if (!floatbar_kill_timers(floatbar)) + return FALSE; + + floatbar->offset = floatbar->height - 2; + + if (!MoveWindow(floatbar->hwnd, floatbar->rect.left, -floatbar->offset, floatbar->width, + floatbar->height, TRUE)) + { + DWORD err = GetLastError(); + WLog_ERR(TAG, "MoveWindow failed with %08" PRIx32, err); + return FALSE; + } + + floatbar->shown = FALSE; + + if (!floatbar_trigger_hide(floatbar)) + return FALSE; + + return TRUE; +} + +static BOOL floatbar_show(wfFloatBar* floatbar) +{ + if (!floatbar_kill_timers(floatbar)) + return FALSE; + + floatbar->offset = 0; + + if (!MoveWindow(floatbar->hwnd, floatbar->rect.left, -floatbar->offset, floatbar->width, + floatbar->height, TRUE)) + { + DWORD err = GetLastError(); + WLog_ERR(TAG, "MoveWindow failed with %08" PRIx32, err); + return FALSE; + } + + floatbar->shown = TRUE; + + if (!floatbar_trigger_hide(floatbar)) + return FALSE; + + return TRUE; +} + +static BOOL button_set_locked(Button* button, BOOL locked) +{ + if (locked) + { + button->bmp = button->locked_bmp; + button->bmp_act = button->locked_bmp_act; + } + else + { + button->bmp = button->unlocked_bmp; + button->bmp_act = button->unlocked_bmp_act; + } + + InvalidateRect(button->floatbar->hwnd, nullptr, FALSE); + UpdateWindow(button->floatbar->hwnd); + return TRUE; +} + +static BOOL update_locked_state(wfFloatBar* floatbar) +{ + Button* button; + + if (!floatbar) + return FALSE; + + button = floatbar->buttons[3]; + + if (!button_set_locked(button, floatbar->locked)) + return FALSE; + + return TRUE; +} + +static int button_hit(Button* const button) +{ + wfFloatBar* const floatbar = button->floatbar; + + switch (button->type) + { + case BUTTON_LOCKPIN: + floatbar->locked = !floatbar->locked; + update_locked_state(floatbar); + break; + + case BUTTON_MINIMIZE: + ShowWindow(floatbar->parent, SW_MINIMIZE); + break; + + case BUTTON_RESTORE: + wf_toggle_fullscreen(floatbar->wfc); + break; + + case BUTTON_CLOSE: + SendMessage(floatbar->parent, WM_DESTROY, 0, 0); + break; + + default: + return 0; + } + + return 0; +} + +static int button_paint(const Button* const button, const HDC hdc) +{ + if (button != nullptr) + { + wfFloatBar* floatbar = button->floatbar; + BLENDFUNCTION bf; + SelectObject(floatbar->hdcmem, button->active ? button->bmp_act : button->bmp); + bf.BlendOp = AC_SRC_OVER; + bf.BlendFlags = 0; + bf.SourceConstantAlpha = 255; + bf.AlphaFormat = AC_SRC_ALPHA; + AlphaBlend(hdc, button->x, button->y, button->w, button->h, floatbar->hdcmem, 0, 0, + button->w, button->h, bf); + } + + return 0; +} + +static Button* floatbar_create_button(wfFloatBar* const floatbar, const int type, const int resid, + const int resid_act, const int x, const int y, const int h, + const int w) +{ + Button* button = (Button*)calloc(1, sizeof(Button)); + + if (!button) + return nullptr; + + button->floatbar = floatbar; + button->type = type; + button->x = x; + button->y = y; + button->w = w; + button->h = h; + button->active = FALSE; + button->bmp = (HBITMAP)LoadImage(floatbar->root_window, MAKEINTRESOURCE(resid), IMAGE_BITMAP, 0, + 0, LR_DEFAULTCOLOR); + button->bmp_act = (HBITMAP)LoadImage(floatbar->root_window, MAKEINTRESOURCE(resid_act), + IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR); + return button; +} + +static Button* floatbar_create_lock_button(wfFloatBar* const floatbar, const int unlock_resid, + const int unlock_resid_act, const int lock_resid, + const int lock_resid_act, const int x, const int y, + const int h, const int w) +{ + Button* button = floatbar_create_button(floatbar, BUTTON_LOCKPIN, unlock_resid, + unlock_resid_act, x, y, h, w); + + if (!button) + return nullptr; + + button->unlocked_bmp = button->bmp; + button->unlocked_bmp_act = button->bmp_act; + button->locked_bmp = (HBITMAP)LoadImage(floatbar->wfc->hInstance, MAKEINTRESOURCE(lock_resid), + IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR); + button->locked_bmp_act = + (HBITMAP)LoadImage(floatbar->wfc->hInstance, MAKEINTRESOURCE(lock_resid_act), IMAGE_BITMAP, + 0, 0, LR_DEFAULTCOLOR); + return button; +} + +static Button* floatbar_get_button(const wfFloatBar* const floatbar, const int x, const int y) +{ + if ((y > BUTTON_Y) && (y < BUTTON_Y + BUTTON_HEIGHT)) + { + for (int i = 0; i < BTN_MAX; i++) + { + if ((floatbar->buttons[i] != nullptr) && (x > floatbar->buttons[i]->x) && + (x < floatbar->buttons[i]->x + floatbar->buttons[i]->w)) + { + return floatbar->buttons[i]; + } + } + } + + return nullptr; +} + +static BOOL floatbar_paint(wfFloatBar* const floatbar, const HDC hdc) +{ + HPEN hpen; + HGDIOBJECT orig; + /* paint background */ + GRADIENT_RECT gradientRect = { 0, 1 }; + COLORREF rgbTop = RGB(117, 154, 198); + COLORREF rgbBottom = RGB(6, 55, 120); + const int top = 0; + int left = 0; + int bottom = BACKGROUND_H - 1; + int right = BACKGROUND_W - 1; + const int angleOffset = BACKGROUND_H - 1; + TRIVERTEX triVertext[2] = { { left, top, GetRValue(rgbTop) << 8, GetGValue(rgbTop) << 8, + GetBValue(rgbTop) << 8, 0x0000 }, + { right, bottom, GetRValue(rgbBottom) << 8, + GetGValue(rgbBottom) << 8, GetBValue(rgbBottom) << 8, 0x0000 } }; + + if (!floatbar) + return FALSE; + + GradientFill(hdc, triVertext, 2, &gradientRect, 1, GRADIENT_FILL_RECT_V); + /* paint shadow */ + hpen = CreatePen(PS_SOLID, 1, RGB(71, 71, 71)); + orig = SelectObject(hdc, hpen); + MoveToEx(hdc, left, top, nullptr); + LineTo(hdc, left + angleOffset, bottom); + LineTo(hdc, right - angleOffset, bottom); + LineTo(hdc, right + 1, top - 1); + DeleteObject(hpen); + hpen = CreatePen(PS_SOLID, 1, RGB(107, 141, 184)); + SelectObject(hdc, hpen); + left += 1; + bottom -= 1; + right -= 1; + MoveToEx(hdc, left, top, nullptr); + LineTo(hdc, left + (angleOffset - 1), bottom); + LineTo(hdc, right - (angleOffset - 1), bottom); + LineTo(hdc, right + 1, top - 1); + DeleteObject(hpen); + SelectObject(hdc, orig); + + const size_t wlen = wcslen(floatbar->wfc->window_title); + DrawText(hdc, floatbar->wfc->window_title, WINPR_ASSERTING_INT_CAST(int, wlen), + &floatbar->textRect, + DT_CENTER | DT_VCENTER | DT_END_ELLIPSIS | DT_NOPREFIX | DT_SINGLELINE); + + /* paint buttons */ + + for (int i = 0; i < BTN_MAX; i++) + button_paint(floatbar->buttons[i], hdc); + + return TRUE; +} + +static LRESULT CALLBACK floatbar_proc(const HWND hWnd, const UINT Msg, const WPARAM wParam, + const LPARAM lParam) +{ + static int dragging = FALSE; + static int lbtn_dwn = FALSE; + static int btn_dwn_x = 0; + static wfFloatBar* floatbar; + static TRACKMOUSEEVENT tme; + PAINTSTRUCT ps; + Button* button; + HDC hdc; + int pos_x; + int pos_y; + NONCLIENTMETRICS ncm; + int xScreen = GetSystemMetrics(SM_CXSCREEN); + + switch (Msg) + { + case WM_CREATE: + floatbar = ((wfFloatBar*)((CREATESTRUCT*)lParam)->lpCreateParams); + floatbar->hwnd = hWnd; + GetWindowRect(floatbar->hwnd, &floatbar->rect); + floatbar->width = floatbar->rect.right - floatbar->rect.left; + floatbar->height = floatbar->rect.bottom - floatbar->rect.top; + hdc = GetDC(hWnd); + floatbar->hdcmem = CreateCompatibleDC(hdc); + ReleaseDC(hWnd, hdc); + tme.cbSize = sizeof(TRACKMOUSEEVENT); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = hWnd; + tme.dwHoverTime = HOVER_DEFAULT; + // Use caption font, white, draw transparent + GetClientRect(hWnd, &floatbar->textRect); + InflateRect(&floatbar->textRect, -TEXT_X, 0); + SetBkMode(hdc, TRANSPARENT); + SetTextColor(hdc, RGB(255, 255, 255)); + ncm.cbSize = sizeof(NONCLIENTMETRICS); + SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0); + SelectObject(hdc, CreateFontIndirect(&ncm.lfCaptionFont)); + floatbar_trigger_hide(floatbar); + break; + + case WM_PAINT: + hdc = BeginPaint(hWnd, &ps); + floatbar_paint(floatbar, hdc); + EndPaint(hWnd, &ps); + break; + + case WM_LBUTTONDOWN: + pos_x = lParam & 0xffff; + pos_y = (lParam >> 16) & 0xffff; + button = floatbar_get_button(floatbar, pos_x, pos_y); + + if (!button) + { + SetCapture(hWnd); + dragging = TRUE; + btn_dwn_x = lParam & 0xffff; + } + else + lbtn_dwn = TRUE; + + break; + + case WM_LBUTTONUP: + pos_x = lParam & 0xffff; + pos_y = (lParam >> 16) & 0xffff; + ReleaseCapture(); + dragging = FALSE; + + if (lbtn_dwn) + { + button = floatbar_get_button(floatbar, pos_x, pos_y); + + if (button) + button_hit(button); + + lbtn_dwn = FALSE; + } + + break; + + case WM_MOUSEMOVE: + pos_x = lParam & 0xffff; + pos_y = (lParam >> 16) & 0xffff; + + if (!floatbar->locked) + floatbar_animation(floatbar, TRUE); + + if (dragging) + { + floatbar->rect.left = floatbar->rect.left + (lParam & 0xffff) - btn_dwn_x; + + if (floatbar->rect.left < 0) + floatbar->rect.left = 0; + else if (floatbar->rect.left > xScreen - floatbar->width) + floatbar->rect.left = xScreen - floatbar->width; + + MoveWindow(hWnd, floatbar->rect.left, 0, floatbar->width, floatbar->height, TRUE); + } + else + { + for (int i = 0; i < BTN_MAX; i++) + { + if (floatbar->buttons[i] != nullptr) + { + floatbar->buttons[i]->active = FALSE; + } + } + + button = floatbar_get_button(floatbar, pos_x, pos_y); + + if (button) + button->active = TRUE; + + InvalidateRect(hWnd, nullptr, FALSE); + UpdateWindow(hWnd); + } + + TrackMouseEvent(&tme); + break; + + case WM_CAPTURECHANGED: + dragging = FALSE; + break; + + case WM_MOUSELEAVE: + { + for (int i = 0; i < BTN_MAX; i++) + { + if (floatbar->buttons[i] != nullptr) + { + floatbar->buttons[i]->active = FALSE; + } + } + + InvalidateRect(hWnd, nullptr, FALSE); + UpdateWindow(hWnd); + floatbar_trigger_hide(floatbar); + break; + } + + case WM_TIMER: + switch (wParam) + { + case TIMER_HIDE: + floatbar_animation(floatbar, FALSE); + break; + + case TIMER_ANIMAT_SHOW: + { + floatbar->offset--; + MoveWindow(floatbar->hwnd, floatbar->rect.left, -floatbar->offset, + floatbar->width, floatbar->height, TRUE); + + if (floatbar->offset <= 0) + floatbar_show(floatbar); + + break; + } + + case TIMER_ANIMAT_HIDE: + { + floatbar->offset++; + MoveWindow(floatbar->hwnd, floatbar->rect.left, -floatbar->offset, + floatbar->width, floatbar->height, TRUE); + + if (floatbar->offset >= floatbar->height - 2) + floatbar_hide(floatbar); + + break; + } + + default: + break; + } + + break; + + case WM_DESTROY: + DeleteDC(floatbar->hdcmem); + PostQuitMessage(0); + break; + + default: + return DefWindowProc(hWnd, Msg, wParam, lParam); + } + + return 0; +} + +static BOOL floatbar_window_create(wfFloatBar* floatbar) +{ + WNDCLASSEX wnd_cls; + HWND barWnd; + HRGN hRgn; + POINT pt[4]; + RECT rect; + LONG x; + + if (!floatbar) + return FALSE; + + if (!GetWindowRect(floatbar->parent, &rect)) + return FALSE; + + x = (rect.right - rect.left - BACKGROUND_W) / 2; + wnd_cls.cbSize = sizeof(WNDCLASSEX); + wnd_cls.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wnd_cls.lpfnWndProc = floatbar_proc; + wnd_cls.cbClsExtra = 0; + wnd_cls.cbWndExtra = 0; + wnd_cls.hIcon = LoadIcon(nullptr, IDI_APPLICATION); + wnd_cls.hCursor = LoadCursor(floatbar->root_window, IDC_ARROW); + wnd_cls.hbrBackground = nullptr; + wnd_cls.lpszMenuName = nullptr; + wnd_cls.lpszClassName = L"floatbar"; + wnd_cls.hInstance = floatbar->root_window; + wnd_cls.hIconSm = LoadIcon(nullptr, IDI_APPLICATION); + RegisterClassEx(&wnd_cls); + barWnd = + CreateWindowEx(WS_EX_TOPMOST, L"floatbar", L"floatbar", WS_CHILD, x, 0, BACKGROUND_W, + BACKGROUND_H, floatbar->parent, nullptr, floatbar->root_window, floatbar); + + if (barWnd == nullptr) + return FALSE; + + pt[0].x = 0; + pt[0].y = 0; + pt[1].x = BACKGROUND_W; + pt[1].y = 0; + pt[2].x = BACKGROUND_W - BACKGROUND_H; + pt[2].y = BACKGROUND_H; + pt[3].x = BACKGROUND_H; + pt[3].y = BACKGROUND_H; + hRgn = CreatePolygonRgn(pt, 4, ALTERNATE); + SetWindowRgn(barWnd, hRgn, TRUE); + return TRUE; +} + +void wf_floatbar_free(wfFloatBar* floatbar) +{ + if (!floatbar) + return; + + free(floatbar); +} + +wfFloatBar* wf_floatbar_new(wfContext* wfc, HINSTANCE window, DWORD flags) +{ + wfFloatBar* floatbar; + + /* Floatbar not enabled */ + if ((flags & 0x0001) == 0) + return nullptr; + + if (!wfc) + return nullptr; + + // TODO: Disable for remote app + floatbar = (wfFloatBar*)calloc(1, sizeof(wfFloatBar)); + + if (!floatbar) + return nullptr; + + floatbar->root_window = window; + floatbar->flags = flags; + floatbar->wfc = wfc; + floatbar->locked = (flags & 0x0002) != 0; + floatbar->shown = (flags & 0x0006) != 0; /* If it is loked or shown show it */ + floatbar->hwnd = nullptr; + floatbar->parent = wfc->hwnd; + floatbar->hdcmem = nullptr; + + if (wfc->fullscreen_toggle) + { + floatbar->buttons[0] = + floatbar_create_button(floatbar, BUTTON_MINIMIZE, IDB_MINIMIZE, IDB_MINIMIZE_ACT, + MINIMIZE_X, BUTTON_Y, BUTTON_HEIGHT, BUTTON_WIDTH); + floatbar->buttons[1] = + floatbar_create_button(floatbar, BUTTON_RESTORE, IDB_RESTORE, IDB_RESTORE_ACT, + RESTORE_X, BUTTON_Y, BUTTON_HEIGHT, BUTTON_WIDTH); + } + else + { + floatbar->buttons[0] = nullptr; + floatbar->buttons[1] = nullptr; + } + + floatbar->buttons[2] = floatbar_create_button(floatbar, BUTTON_CLOSE, IDB_CLOSE, IDB_CLOSE_ACT, + CLOSE_X, BUTTON_Y, BUTTON_HEIGHT, BUTTON_WIDTH); + floatbar->buttons[3] = + floatbar_create_lock_button(floatbar, IDB_UNLOCK, IDB_UNLOCK_ACT, IDB_LOCK, IDB_LOCK_ACT, + LOCK_X, BUTTON_Y, BUTTON_HEIGHT, BUTTON_WIDTH); + + if (!floatbar_window_create(floatbar)) + goto fail; + + if (!update_locked_state(floatbar)) + goto fail; + + if (!wf_floatbar_toggle_fullscreen( + floatbar, freerdp_settings_get_bool(wfc->common.context.settings, FreeRDP_Fullscreen))) + goto fail; + + return floatbar; +fail: + wf_floatbar_free(floatbar); + return nullptr; +} + +BOOL wf_floatbar_toggle_fullscreen(wfFloatBar* floatbar, BOOL fullscreen) +{ + BOOL show_fs, show_wn; + + if (!floatbar) + return FALSE; + + show_fs = (floatbar->flags & 0x0010) != 0; + show_wn = (floatbar->flags & 0x0020) != 0; + + if ((show_fs && fullscreen) || (show_wn && !fullscreen)) + { + ShowWindow(floatbar->hwnd, SW_SHOWNORMAL); + Sleep(10); + + if (floatbar->shown) + floatbar_show(floatbar); + else + floatbar_hide(floatbar); + } + else + { + ShowWindow(floatbar->hwnd, SW_HIDE); + } + + return TRUE; +} diff --git a/third_party/FreeRDP/client/Windows/wf_floatbar.h b/third_party/FreeRDP/client/Windows/wf_floatbar.h new file mode 100644 index 0000000..761eeb2 --- /dev/null +++ b/third_party/FreeRDP/client/Windows/wf_floatbar.h @@ -0,0 +1,33 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Windows Float Bar + * + * Copyright 2013 Zhang Zhaolong + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_WIN_FLOATBAR_H +#define FREERDP_CLIENT_WIN_FLOATBAR_H + +#include + +typedef struct s_FloatBar wfFloatBar; +typedef struct wf_context wfContext; + +wfFloatBar* wf_floatbar_new(wfContext* wfc, HINSTANCE window, DWORD flags); +void wf_floatbar_free(wfFloatBar* floatbar); + +BOOL wf_floatbar_toggle_fullscreen(wfFloatBar* floatbar, BOOL fullscreen); + +#endif /* FREERDP_CLIENT_WIN_FLOATBAR_H */ diff --git a/third_party/FreeRDP/client/Windows/wf_gdi.c b/third_party/FreeRDP/client/Windows/wf_gdi.c new file mode 100644 index 0000000..5aa756f --- /dev/null +++ b/third_party/FreeRDP/client/Windows/wf_gdi.c @@ -0,0 +1,864 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Windows GDI + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wf_client.h" +#include "wf_graphics.h" +#include "wf_gdi.h" + +#define TAG CLIENT_TAG("windows.gdi") + +static const BYTE wf_rop2_table[] = { + R2_BLACK, /* 0 */ + R2_NOTMERGEPEN, /* DPon */ + R2_MASKNOTPEN, /* DPna */ + R2_NOTCOPYPEN, /* Pn */ + R2_MASKPENNOT, /* PDna */ + R2_NOT, /* Dn */ + R2_XORPEN, /* DPx */ + R2_NOTMASKPEN, /* DPan */ + R2_MASKPEN, /* DPa */ + R2_NOTXORPEN, /* DPxn */ + R2_NOP, /* D */ + R2_MERGENOTPEN, /* DPno */ + R2_COPYPEN, /* P */ + R2_MERGEPENNOT, /* PDno */ + R2_MERGEPEN, /* PDo */ + R2_WHITE, /* 1 */ +}; + +static BOOL wf_decode_color(wfContext* wfc, const UINT32 srcColor, COLORREF* color, UINT32* format) +{ + rdpGdi* gdi; + rdpSettings* settings; + UINT32 SrcFormat, DstFormat; + + if (!wfc) + return FALSE; + + gdi = wfc->common.context.gdi; + settings = wfc->common.context.settings; + + if (!gdi || !settings) + return FALSE; + + SrcFormat = gdi_get_pixel_format(freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth)); + + if (format) + *format = SrcFormat; + + switch (FreeRDPGetBitsPerPixel(gdi->dstFormat)) + { + case 32: + DstFormat = PIXEL_FORMAT_ABGR32; + break; + + case 24: + DstFormat = PIXEL_FORMAT_BGR24; + break; + + case 16: + DstFormat = PIXEL_FORMAT_RGB16; + break; + + default: + return FALSE; + } + + *color = FreeRDPConvertColor(srcColor, SrcFormat, DstFormat, &gdi->palette); + return TRUE; +} + +static BOOL wf_set_rop2(HDC hdc, int rop2) +{ + if ((rop2 < 0x01) || (rop2 > 0x10)) + { + WLog_ERR(TAG, "Unsupported ROP2: %d", rop2); + return FALSE; + } + + SetROP2(hdc, wf_rop2_table[rop2 - 1]); + return TRUE; +} + +static wfBitmap* wf_glyph_new(wfContext* wfc, GLYPH_DATA* glyph) +{ + wfBitmap* glyph_bmp; + glyph_bmp = wf_image_new(wfc, glyph->cx, glyph->cy, PIXEL_FORMAT_MONO, glyph->aj); + if (!glyph_bmp) + WLog_ERR(TAG, "wf_image_new failed for glyph"); + return glyph_bmp; +} + +static void wf_glyph_free(wfBitmap* glyph) +{ + wf_image_free(glyph); +} + +static BYTE* wf_glyph_convert(wfContext* wfc, int width, int height, const BYTE* data) +{ + const int src_bytes_per_row = (width + 7) / 8; + const int dst_bytes_per_row = src_bytes_per_row + (src_bytes_per_row % 2); + BYTE* cdata = (BYTE*)malloc(dst_bytes_per_row * height); + if (!cdata) + { + WLog_ERR(TAG, "malloc failed for cdata buffer"); + return nullptr; + } + const BYTE* src = data; + + for (int indexy = 0; indexy < height; indexy++) + { + BYTE* dst = &cdata[1ull * indexy * dst_bytes_per_row]; + + for (int indexx = 0; indexx < dst_bytes_per_row; indexx++) + { + if (indexx < src_bytes_per_row) + *dst++ = *src++; + else + *dst++ = 0; + } + } + + return cdata; +} + +static HBRUSH wf_create_brush(wfContext* wfc, rdpBrush* brush, UINT32 color, UINT32 bpp) +{ + HBRUSH br; + LOGBRUSH lbr; + BYTE* cdata; + BYTE ipattern[8]; + HBITMAP pattern = nullptr; + lbr.lbStyle = brush->style; + + if (lbr.lbStyle == BS_DIBPATTERN || lbr.lbStyle == BS_DIBPATTERN8X8 || + lbr.lbStyle == BS_DIBPATTERNPT) + lbr.lbColor = DIB_RGB_COLORS; + else + lbr.lbColor = color; + + if (lbr.lbStyle == BS_PATTERN || lbr.lbStyle == BS_PATTERN8X8) + { + if (brush->bpp > 1) + { + UINT32 format = gdi_get_pixel_format(bpp); + pattern = wf_create_dib(wfc, 8, 8, format, brush->data, nullptr); + lbr.lbHatch = (ULONG_PTR)pattern; + } + else + { + for (UINT32 i = 0; i != 8; i++) + ipattern[7 - i] = brush->data[i]; + + cdata = wf_glyph_convert(wfc, 8, 8, ipattern); + pattern = CreateBitmap(8, 8, 1, 1, cdata); + lbr.lbHatch = (ULONG_PTR)pattern; + free(cdata); + } + } + else if (lbr.lbStyle == BS_HATCHED) + { + lbr.lbHatch = brush->hatch; + } + else + { + lbr.lbHatch = 0; + } + + br = CreateBrushIndirect(&lbr); + SetBrushOrgEx(wfc->drawing->hdc, brush->x, brush->y, nullptr); + + if (pattern != nullptr) + DeleteObject(pattern); + + return br; +} + +BOOL wf_scale_rect(wfContext* wfc, RECT* source) +{ + UINT32 ww, wh, dw, dh; + rdpSettings* settings; + + if (!wfc || !source || !wfc->common.context.settings) + return FALSE; + + settings = wfc->common.context.settings; + + if (!settings) + return FALSE; + + dw = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + dh = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + + if (!wfc->client_width) + wfc->client_width = dw; + + if (!wfc->client_height) + wfc->client_height = dh; + + ww = wfc->client_width; + wh = wfc->client_height; + + if (!ww) + ww = dw; + + if (!wh) + wh = dh; + + if (freerdp_settings_get_bool(wfc->common.context.settings, FreeRDP_SmartSizing) && + (ww != dw || wh != dh)) + { + source->bottom = source->bottom * wh / dh + 20; + source->top = source->top * wh / dh - 20; + source->left = source->left * ww / dw - 20; + source->right = source->right * ww / dw + 20; + } + + source->bottom -= wfc->yCurrentScroll; + source->top -= wfc->yCurrentScroll; + source->left -= wfc->xCurrentScroll; + source->right -= wfc->xCurrentScroll; + return TRUE; +} + +void wf_invalidate_region(wfContext* wfc, UINT32 x, UINT32 y, UINT32 width, UINT32 height) +{ + RECT rect; + rdpGdi* gdi = wfc->common.context.gdi; + wfc->update_rect.left = x + wfc->offset_x; + wfc->update_rect.top = y + wfc->offset_y; + wfc->update_rect.right = wfc->update_rect.left + width; + wfc->update_rect.bottom = wfc->update_rect.top + height; + wf_scale_rect(wfc, &(wfc->update_rect)); + InvalidateRect(wfc->hwnd, &(wfc->update_rect), FALSE); + rect.left = x; + rect.right = width; + rect.top = y; + rect.bottom = height; + wf_scale_rect(wfc, &rect); + gdi_InvalidateRegion(gdi->primary->hdc, rect.left, rect.top, rect.right, rect.bottom); +} + +void wf_update_offset(wfContext* wfc) +{ + rdpSettings* settings; + settings = wfc->common.context.settings; + + if (wfc->fullscreen) + { + if (freerdp_settings_get_bool(wfc->common.context.settings, FreeRDP_UseMultimon)) + { + int x = GetSystemMetrics(SM_XVIRTUALSCREEN); + int y = GetSystemMetrics(SM_YVIRTUALSCREEN); + int w = GetSystemMetrics(SM_CXVIRTUALSCREEN); + int h = GetSystemMetrics(SM_CYVIRTUALSCREEN); + wfc->offset_x = (w - freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth)) / 2; + + if (wfc->offset_x < x) + wfc->offset_x = x; + + wfc->offset_y = (h - freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)) / 2; + + if (wfc->offset_y < y) + wfc->offset_y = y; + } + else + { + wfc->offset_x = (GetSystemMetrics(SM_CXSCREEN) - + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth)) / + 2; + + if (wfc->offset_x < 0) + wfc->offset_x = 0; + + wfc->offset_y = (GetSystemMetrics(SM_CYSCREEN) - + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)) / + 2; + + if (wfc->offset_y < 0) + wfc->offset_y = 0; + } + } + else + { + wfc->offset_x = 0; + wfc->offset_y = 0; + } +} + +void wf_resize_window(wfContext* wfc) +{ + rdpSettings* settings; + settings = wfc->common.context.settings; + + if (wfc->fullscreen) + { + if (freerdp_settings_get_bool(wfc->common.context.settings, FreeRDP_UseMultimon)) + { + int x = GetSystemMetrics(SM_XVIRTUALSCREEN); + int y = GetSystemMetrics(SM_YVIRTUALSCREEN); + int w = GetSystemMetrics(SM_CXVIRTUALSCREEN); + int h = GetSystemMetrics(SM_CYVIRTUALSCREEN); + SetWindowLongPtr(wfc->hwnd, GWL_STYLE, WS_POPUP); + SetWindowPos(wfc->hwnd, HWND_TOP, x, y, w, h, SWP_FRAMECHANGED); + } + else + { + SetWindowLongPtr(wfc->hwnd, GWL_STYLE, WS_POPUP); + SetWindowPos(wfc->hwnd, HWND_TOP, 0, 0, GetSystemMetrics(SM_CXSCREEN), + GetSystemMetrics(SM_CYSCREEN), SWP_FRAMECHANGED); + } + } + else if (!freerdp_settings_get_bool(wfc->common.context.settings, FreeRDP_Decorations)) + { + SetWindowLongPtr(wfc->hwnd, GWL_STYLE, WS_CHILD); + + if (freerdp_settings_get_bool(settings, FreeRDP_EmbeddedWindow)) + { + if (!wfc->client_height) + wfc->client_height = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + + if (!wfc->client_width) + wfc->client_width = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + + wf_update_canvas_diff(wfc); + /* Now resize to get full canvas size and room for caption and borders */ + SetWindowPos(wfc->hwnd, HWND_TOP, wfc->client_x, wfc->client_y, + wfc->client_width + wfc->diff.x, wfc->client_height + wfc->diff.y, + 0 /*SWP_FRAMECHANGED*/); + } + else + { + /* Now resize to get full canvas size and room for caption and borders */ + SetWindowPos(wfc->hwnd, HWND_TOP, 0, 0, + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight), + SWP_FRAMECHANGED); + wf_update_canvas_diff(wfc); + SetWindowPos(wfc->hwnd, HWND_TOP, -1, -1, + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) + wfc->diff.x, + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) + wfc->diff.y, + SWP_NOMOVE | SWP_FRAMECHANGED); + } + } + else + { + SetWindowLongPtr(wfc->hwnd, GWL_STYLE, + WS_CAPTION | WS_OVERLAPPED | WS_SYSMENU | WS_MINIMIZEBOX | WS_SIZEBOX | + WS_MAXIMIZEBOX); + + if (!wfc->client_height) + wfc->client_height = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + + if (!wfc->client_width) + wfc->client_width = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + + if (!wfc->client_x) + wfc->client_x = 10; + + if (!wfc->client_y) + wfc->client_y = 10; + + wf_update_canvas_diff(wfc); + /* Now resize to get full canvas size and room for caption and borders */ + int width, height; + if (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) && + freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingWidth) && + freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingHeight)) + { + width = freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingWidth); + height = freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingHeight); + } + else + { + width = wfc->client_width + wfc->diff.x; + height = wfc->client_height + wfc->diff.y; + } + + int xpos, ypos; + if ((freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosX) != UINT32_MAX) && + (freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosY) != UINT32_MAX)) + { + xpos = freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosX); + ypos = freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosY); + } + else + { + xpos = wfc->client_x; + ypos = wfc->client_y; + } + SetWindowPos(wfc->hwnd, HWND_TOP, xpos, ypos, width, height, 0 /*SWP_FRAMECHANGED*/); + // wf_size_scrollbars(wfc, wfc->client_width, wfc->client_height); + } + + wf_update_offset(wfc); +} + +void wf_toggle_fullscreen(wfContext* wfc) +{ + ShowWindow(wfc->hwnd, SW_HIDE); + wfc->fullscreen = !wfc->fullscreen; + + if (wfc->fullscreen) + { + wfc->disablewindowtracking = TRUE; + } + + wf_floatbar_toggle_fullscreen(wfc->floatbar, wfc->fullscreen); + SetParent(wfc->hwnd, wfc->fullscreen ? nullptr : wfc->hWndParent); + wf_resize_window(wfc); + ShowWindow(wfc->hwnd, SW_SHOW); + SetForegroundWindow(wfc->hwnd); + + if (!wfc->fullscreen) + { + // Re-enable window tracking AFTER resizing it back, otherwise it can lean to repositioning + // errors. + wfc->disablewindowtracking = FALSE; + } +} + +static BOOL wf_gdi_palette_update(rdpContext* context, const PALETTE_UPDATE* palette) +{ + return TRUE; +} + +void wf_set_null_clip_rgn(wfContext* wfc) +{ + SelectClipRgn(wfc->drawing->hdc, nullptr); +} + +void wf_set_clip_rgn(wfContext* wfc, int x, int y, int width, int height) +{ + HRGN clip; + clip = CreateRectRgn(x, y, x + width, y + height); + SelectClipRgn(wfc->drawing->hdc, clip); + DeleteObject(clip); +} + +static BOOL wf_gdi_set_bounds(rdpContext* context, const rdpBounds* bounds) +{ + HRGN hrgn; + wfContext* wfc = (wfContext*)context; + + if (!context || !bounds) + return FALSE; + + if (bounds != nullptr) + { + hrgn = CreateRectRgn(bounds->left, bounds->top, bounds->right + 1, bounds->bottom + 1); + SelectClipRgn(wfc->drawing->hdc, hrgn); + DeleteObject(hrgn); + } + else + SelectClipRgn(wfc->drawing->hdc, nullptr); + + return TRUE; +} + +static BOOL wf_gdi_dstblt(rdpContext* context, const DSTBLT_ORDER* dstblt) +{ + wfContext* wfc = (wfContext*)context; + + if (!context || !dstblt) + return FALSE; + + if (!BitBlt(wfc->drawing->hdc, dstblt->nLeftRect, dstblt->nTopRect, dstblt->nWidth, + dstblt->nHeight, nullptr, 0, 0, gdi_rop3_code(dstblt->bRop))) + return FALSE; + + wf_invalidate_region(wfc, dstblt->nLeftRect, dstblt->nTopRect, dstblt->nWidth, dstblt->nHeight); + return TRUE; +} + +static BOOL wf_gdi_patblt(rdpContext* context, PATBLT_ORDER* patblt) +{ + HBRUSH brush; + HBRUSH org_brush; + int org_bkmode; + COLORREF fgcolor; + COLORREF bgcolor; + COLORREF org_bkcolor; + COLORREF org_textcolor; + BOOL rc; + wfContext* wfc = (wfContext*)context; + + if (!context || !patblt) + return FALSE; + + if (!wf_decode_color(wfc, patblt->foreColor, &fgcolor, nullptr)) + return FALSE; + + if (!wf_decode_color(wfc, patblt->backColor, &bgcolor, nullptr)) + return FALSE; + + brush = wf_create_brush(wfc, &patblt->brush, fgcolor, + freerdp_settings_get_uint32(context->settings, FreeRDP_ColorDepth)); + org_bkmode = SetBkMode(wfc->drawing->hdc, OPAQUE); + org_bkcolor = SetBkColor(wfc->drawing->hdc, bgcolor); + org_textcolor = SetTextColor(wfc->drawing->hdc, fgcolor); + org_brush = (HBRUSH)SelectObject(wfc->drawing->hdc, brush); + rc = PatBlt(wfc->drawing->hdc, patblt->nLeftRect, patblt->nTopRect, patblt->nWidth, + patblt->nHeight, gdi_rop3_code(patblt->bRop)); + SelectObject(wfc->drawing->hdc, org_brush); + DeleteObject(brush); + SetBkMode(wfc->drawing->hdc, org_bkmode); + SetBkColor(wfc->drawing->hdc, org_bkcolor); + SetTextColor(wfc->drawing->hdc, org_textcolor); + + if (wfc->drawing == wfc->primary) + wf_invalidate_region(wfc, patblt->nLeftRect, patblt->nTopRect, patblt->nWidth, + patblt->nHeight); + + return rc; +} + +static BOOL wf_gdi_scrblt(rdpContext* context, const SCRBLT_ORDER* scrblt) +{ + wfContext* wfc = (wfContext*)context; + + if (!context || !scrblt || !wfc->drawing) + return FALSE; + + if (!BitBlt(wfc->drawing->hdc, scrblt->nLeftRect, scrblt->nTopRect, scrblt->nWidth, + scrblt->nHeight, wfc->primary->hdc, scrblt->nXSrc, scrblt->nYSrc, + gdi_rop3_code(scrblt->bRop))) + return FALSE; + + wf_invalidate_region(wfc, scrblt->nLeftRect, scrblt->nTopRect, scrblt->nWidth, scrblt->nHeight); + return TRUE; +} + +static BOOL wf_gdi_opaque_rect(rdpContext* context, const OPAQUE_RECT_ORDER* opaque_rect) +{ + RECT rect; + HBRUSH brush; + COLORREF brush_color; + wfContext* wfc = (wfContext*)context; + + if (!context || !opaque_rect) + return FALSE; + + if (!wf_decode_color(wfc, opaque_rect->color, &brush_color, nullptr)) + return FALSE; + + rect.left = opaque_rect->nLeftRect; + rect.top = opaque_rect->nTopRect; + rect.right = opaque_rect->nLeftRect + opaque_rect->nWidth; + rect.bottom = opaque_rect->nTopRect + opaque_rect->nHeight; + brush = CreateSolidBrush(brush_color); + FillRect(wfc->drawing->hdc, &rect, brush); + DeleteObject(brush); + + if (wfc->drawing == wfc->primary) + wf_invalidate_region(wfc, rect.left, rect.top, rect.right - rect.left + 1, + rect.bottom - rect.top + 1); + + return TRUE; +} + +static BOOL wf_gdi_multi_opaque_rect(rdpContext* context, + const MULTI_OPAQUE_RECT_ORDER* multi_opaque_rect) +{ + RECT rect; + HBRUSH brush; + COLORREF brush_color; + wfContext* wfc = (wfContext*)context; + + if (!context || !multi_opaque_rect) + return FALSE; + + if (!wf_decode_color(wfc, multi_opaque_rect->color, &brush_color, nullptr)) + return FALSE; + + for (UINT32 i = 0; i < multi_opaque_rect->numRectangles; i++) + { + const DELTA_RECT* rectangle = &multi_opaque_rect->rectangles[i]; + rect.left = rectangle->left; + rect.top = rectangle->top; + rect.right = rectangle->left + rectangle->width; + rect.bottom = rectangle->top + rectangle->height; + brush = CreateSolidBrush(brush_color); + FillRect(wfc->drawing->hdc, &rect, brush); + + if (wfc->drawing == wfc->primary) + wf_invalidate_region(wfc, rect.left, rect.top, rect.right - rect.left + 1, + rect.bottom - rect.top + 1); + + DeleteObject(brush); + } + + return TRUE; +} + +static BOOL wf_gdi_line_to(rdpContext* context, const LINE_TO_ORDER* line_to) +{ + HPEN pen; + HPEN org_pen; + int x, y, w, h; + COLORREF pen_color; + wfContext* wfc = (wfContext*)context; + + if (!context || !line_to) + return FALSE; + + if (!wf_decode_color(wfc, line_to->penColor, &pen_color, nullptr)) + return FALSE; + + pen = CreatePen(line_to->penStyle, line_to->penWidth, pen_color); + wf_set_rop2(wfc->drawing->hdc, line_to->bRop2); + org_pen = (HPEN)SelectObject(wfc->drawing->hdc, pen); + MoveToEx(wfc->drawing->hdc, line_to->nXStart, line_to->nYStart, nullptr); + LineTo(wfc->drawing->hdc, line_to->nXEnd, line_to->nYEnd); + x = (line_to->nXStart < line_to->nXEnd) ? line_to->nXStart : line_to->nXEnd; + y = (line_to->nYStart < line_to->nYEnd) ? line_to->nYStart : line_to->nYEnd; + w = (line_to->nXStart < line_to->nXEnd) ? (line_to->nXEnd - line_to->nXStart) + : (line_to->nXStart - line_to->nXEnd); + h = (line_to->nYStart < line_to->nYEnd) ? (line_to->nYEnd - line_to->nYStart) + : (line_to->nYStart - line_to->nYEnd); + + if (wfc->drawing == wfc->primary) + wf_invalidate_region(wfc, x, y, w, h); + + SelectObject(wfc->drawing->hdc, org_pen); + DeleteObject(pen); + return TRUE; +} + +static BOOL wf_gdi_polyline(rdpContext* context, const POLYLINE_ORDER* polyline) +{ + BOOL rc = FALSE; + int org_rop2; + HPEN hpen; + HPEN org_hpen; + COLORREF pen_color; + wfContext* wfc = (wfContext*)context; + + if (!context || !polyline) + return FALSE; + + if (!wf_decode_color(wfc, polyline->penColor, &pen_color, nullptr)) + return FALSE; + + hpen = CreatePen(0, 1, pen_color); + org_rop2 = wf_set_rop2(wfc->drawing->hdc, polyline->bRop2); + org_hpen = (HPEN)SelectObject(wfc->drawing->hdc, hpen); + + if (polyline->numDeltaEntries > 0) + { + POINT* pts; + POINT temp; + int numPoints; + numPoints = polyline->numDeltaEntries + 1; + pts = (POINT*)malloc(sizeof(POINT) * numPoints); + if (!pts) + { + WLog_ERR(TAG, "malloc failed for polyline points"); + goto fail; + } + pts[0].x = temp.x = polyline->xStart; + pts[0].y = temp.y = polyline->yStart; + + for (UINT32 i = 0; i < polyline->numDeltaEntries; i++) + { + temp.x += polyline->points[i].x; + temp.y += polyline->points[i].y; + pts[i + 1].x = temp.x; + pts[i + 1].y = temp.y; + } + + if (wfc->drawing == wfc->primary) + wf_invalidate_region(wfc, wfc->client_x, wfc->client_y, wfc->client_width, + wfc->client_height); + + Polyline(wfc->drawing->hdc, pts, numPoints); + free(pts); + } + + rc = TRUE; + +fail: + SelectObject(wfc->drawing->hdc, org_hpen); + wf_set_rop2(wfc->drawing->hdc, org_rop2); + DeleteObject(hpen); + return rc; +} + +static BOOL wf_gdi_memblt(rdpContext* context, MEMBLT_ORDER* memblt) +{ + wfBitmap* bitmap; + wfContext* wfc = (wfContext*)context; + + if (!context || !memblt) + return FALSE; + + bitmap = (wfBitmap*)memblt->bitmap; + + if (!bitmap || !wfc->drawing || !wfc->drawing->hdc) + return FALSE; + + if (!BitBlt(wfc->drawing->hdc, memblt->nLeftRect, memblt->nTopRect, memblt->nWidth, + memblt->nHeight, bitmap->hdc, memblt->nXSrc, memblt->nYSrc, + gdi_rop3_code(memblt->bRop))) + return FALSE; + + if (wfc->drawing == wfc->primary) + wf_invalidate_region(wfc, memblt->nLeftRect, memblt->nTopRect, memblt->nWidth, + memblt->nHeight); + + return TRUE; +} + +static BOOL wf_gdi_mem3blt(rdpContext* context, MEM3BLT_ORDER* mem3blt) +{ + BOOL rc = FALSE; + HDC hdc; + wfBitmap* bitmap; + wfContext* wfc = (wfContext*)context; + COLORREF fgcolor, bgcolor, orgColor; + HBRUSH orgBrush = nullptr, brush = nullptr; + + if (!context || !mem3blt) + return FALSE; + + bitmap = (wfBitmap*)mem3blt->bitmap; + + if (!bitmap || !wfc->drawing || !wfc->drawing->hdc) + return FALSE; + + hdc = wfc->drawing->hdc; + + if (!wf_decode_color(wfc, mem3blt->foreColor, &fgcolor, nullptr)) + return FALSE; + + if (!wf_decode_color(wfc, mem3blt->backColor, &bgcolor, nullptr)) + return FALSE; + + orgColor = SetTextColor(hdc, fgcolor); + + switch (mem3blt->brush.style) + { + case GDI_BS_SOLID: + brush = CreateSolidBrush(fgcolor); + break; + + case GDI_BS_HATCHED: + case GDI_BS_PATTERN: + { + HBITMAP bmp = CreateBitmap(8, 8, 1, mem3blt->brush.bpp, mem3blt->brush.data); + brush = CreatePatternBrush(bmp); + } + break; + + default: + goto fail; + } + + orgBrush = SelectObject(hdc, brush); + + if (!BitBlt(hdc, mem3blt->nLeftRect, mem3blt->nTopRect, mem3blt->nWidth, mem3blt->nHeight, + bitmap->hdc, mem3blt->nXSrc, mem3blt->nYSrc, gdi_rop3_code(mem3blt->bRop))) + goto fail; + + if (wfc->drawing == wfc->primary) + wf_invalidate_region(wfc, mem3blt->nLeftRect, mem3blt->nTopRect, mem3blt->nWidth, + mem3blt->nHeight); + + rc = TRUE; +fail: + + if (brush) + SelectObject(hdc, orgBrush); + + SetTextColor(hdc, orgColor); + return rc; +} + +static BOOL wf_gdi_surface_frame_marker(rdpContext* context, + const SURFACE_FRAME_MARKER* surface_frame_marker) +{ + rdpSettings* settings; + + if (!context || !surface_frame_marker || !context->instance) + return FALSE; + + settings = context->settings; + + if (!settings) + return FALSE; + + if (surface_frame_marker->frameAction == SURFACECMD_FRAMEACTION_END && + freerdp_settings_get_uint32(settings, FreeRDP_FrameAcknowledge) > 0) + { + IFCALL(context->update->SurfaceFrameAcknowledge, context, surface_frame_marker->frameId); + } + + return TRUE; +} + +void wf_gdi_register_update_callbacks(rdpUpdate* update) +{ + rdpPrimaryUpdate* primary = update->primary; + update->Palette = wf_gdi_palette_update; + update->SetBounds = wf_gdi_set_bounds; + primary->DstBlt = wf_gdi_dstblt; + primary->PatBlt = wf_gdi_patblt; + primary->ScrBlt = wf_gdi_scrblt; + primary->OpaqueRect = wf_gdi_opaque_rect; + primary->MultiOpaqueRect = wf_gdi_multi_opaque_rect; + primary->LineTo = wf_gdi_line_to; + primary->Polyline = wf_gdi_polyline; + primary->MemBlt = wf_gdi_memblt; + primary->Mem3Blt = wf_gdi_mem3blt; + update->SurfaceFrameMarker = wf_gdi_surface_frame_marker; +} + +void wf_update_canvas_diff(wfContext* wfc) +{ + RECT rc_client, rc_wnd; + int dx, dy; + GetClientRect(wfc->hwnd, &rc_client); + GetWindowRect(wfc->hwnd, &rc_wnd); + dx = (rc_wnd.right - rc_wnd.left) - rc_client.right; + dy = (rc_wnd.bottom - rc_wnd.top) - rc_client.bottom; + + if (!wfc->disablewindowtracking) + { + wfc->diff.x = dx; + wfc->diff.y = dy; + } +} diff --git a/third_party/FreeRDP/client/Windows/wf_gdi.h b/third_party/FreeRDP/client/Windows/wf_gdi.h new file mode 100644 index 0000000..15b39ea --- /dev/null +++ b/third_party/FreeRDP/client/Windows/wf_gdi.h @@ -0,0 +1,37 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Windows GDI + * + * Copyright 2009-2011 Jay Sorg + * Copyright 2010-2011 Vic Lee + * Copyright 2010-2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_WIN_GDI_H +#define FREERDP_CLIENT_WIN_GDI_H + +#include "wf_client.h" + +void wf_invalidate_region(wfContext* wfc, UINT32 x, UINT32 y, UINT32 width, UINT32 height); +void wf_update_offset(wfContext* wfc); +void wf_resize_window(wfContext* wfc); +void wf_toggle_fullscreen(wfContext* wfc); +BOOL wf_scale_rect(wfContext* wfc, RECT* source); + +void wf_gdi_register_update_callbacks(rdpUpdate* update); + +void wf_update_canvas_diff(wfContext* wfc); + +#endif /* FREERDP_CLIENT_WIN_GDI_H */ diff --git a/third_party/FreeRDP/client/Windows/wf_graphics.c b/third_party/FreeRDP/client/Windows/wf_graphics.c new file mode 100644 index 0000000..63c84b1 --- /dev/null +++ b/third_party/FreeRDP/client/Windows/wf_graphics.c @@ -0,0 +1,379 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Windows Graphical Objects + * + * Copyright 2010-2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include +#include + +#include "wf_gdi.h" +#include "wf_graphics.h" + +#define TAG CLIENT_TAG("windows") + +HBITMAP wf_create_dib(wfContext* wfc, UINT32 width, UINT32 height, UINT32 srcFormat, + const BYTE* data, BYTE** pdata) +{ + HDC hdc; + int negHeight; + HBITMAP bitmap; + BITMAPINFO bmi; + BYTE* cdata = nullptr; + UINT32 dstFormat = srcFormat; + /** + * See: http://msdn.microsoft.com/en-us/library/dd183376 + * if biHeight is positive, the bitmap is bottom-up + * if biHeight is negative, the bitmap is top-down + * Since we get top-down bitmaps, let's keep it that way + */ + negHeight = (height < 0) ? height : height * (-1); + hdc = GetDC(nullptr); + bmi.bmiHeader.biSize = sizeof(BITMAPINFO); + bmi.bmiHeader.biWidth = width; + bmi.bmiHeader.biHeight = negHeight; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = FreeRDPGetBitsPerPixel(dstFormat); + bmi.bmiHeader.biCompression = BI_RGB; + bitmap = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, (void**)&cdata, nullptr, 0); + + if (data) + freerdp_image_copy(cdata, dstFormat, 0, 0, 0, width, height, data, srcFormat, 0, 0, 0, + &wfc->common.context.gdi->palette, FREERDP_FLIP_NONE); + + if (pdata) + *pdata = cdata; + + ReleaseDC(nullptr, hdc); + GdiFlush(); + return bitmap; +} + +wfBitmap* wf_image_new(wfContext* wfc, UINT32 width, UINT32 height, UINT32 format, const BYTE* data) +{ + wfBitmap* image = (wfBitmap*)malloc(sizeof(wfBitmap)); + if (!image) + { + WLog_ERR(TAG, "malloc failed for wfBitmap"); + return nullptr; + } + + HDC hdc = GetDC(nullptr); + image->hdc = CreateCompatibleDC(hdc); + image->bitmap = wf_create_dib(wfc, width, height, format, data, &(image->pdata)); + image->org_bitmap = (HBITMAP)SelectObject(image->hdc, image->bitmap); + ReleaseDC(nullptr, hdc); + return image; +} + +void wf_image_free(wfBitmap* image) +{ + if (image != 0) + { + SelectObject(image->hdc, image->org_bitmap); + DeleteObject(image->bitmap); + DeleteDC(image->hdc); + free(image); + } +} + +/* Bitmap Class */ + +static BOOL wf_Bitmap_New(rdpContext* context, rdpBitmap* bitmap) +{ + HDC hdc; + wfContext* wfc = (wfContext*)context; + wfBitmap* wf_bitmap = (wfBitmap*)bitmap; + + if (!context || !bitmap) + return FALSE; + + wf_bitmap = (wfBitmap*)bitmap; + hdc = GetDC(nullptr); + wf_bitmap->hdc = CreateCompatibleDC(hdc); + + if (!bitmap->data) + wf_bitmap->bitmap = CreateCompatibleBitmap(hdc, bitmap->width, bitmap->height); + else + wf_bitmap->bitmap = wf_create_dib(wfc, bitmap->width, bitmap->height, bitmap->format, + bitmap->data, nullptr); + + wf_bitmap->org_bitmap = (HBITMAP)SelectObject(wf_bitmap->hdc, wf_bitmap->bitmap); + ReleaseDC(nullptr, hdc); + return TRUE; +} + +static void wf_Bitmap_Free(rdpContext* context, rdpBitmap* bitmap) +{ + wfBitmap* wf_bitmap = (wfBitmap*)bitmap; + + if (wf_bitmap != 0) + { + SelectObject(wf_bitmap->hdc, wf_bitmap->org_bitmap); + DeleteObject(wf_bitmap->bitmap); + DeleteDC(wf_bitmap->hdc); + + winpr_aligned_free(wf_bitmap->_bitmap.data); + wf_bitmap->_bitmap.data = nullptr; + } +} + +static BOOL wf_Bitmap_Paint(rdpContext* context, rdpBitmap* bitmap) +{ + BOOL rc; + UINT32 width, height; + wfContext* wfc = (wfContext*)context; + wfBitmap* wf_bitmap = (wfBitmap*)bitmap; + + if (!context || !bitmap) + return FALSE; + + width = bitmap->right - bitmap->left + 1; + height = bitmap->bottom - bitmap->top + 1; + rc = BitBlt(wfc->primary->hdc, bitmap->left, bitmap->top, width, height, wf_bitmap->hdc, 0, 0, + SRCCOPY); + wf_invalidate_region(wfc, bitmap->left, bitmap->top, width, height); + return rc; +} + +static BOOL wf_Bitmap_SetSurface(rdpContext* context, rdpBitmap* bitmap, BOOL primary) +{ + wfContext* wfc = (wfContext*)context; + wfBitmap* bmp = (wfBitmap*)bitmap; + rdpGdi* gdi = context->gdi; + + if (!gdi || !wfc) + return FALSE; + + if (primary) + wfc->drawing = wfc->primary; + else if (!bmp) + return FALSE; + else + wfc->drawing = bmp; + + return TRUE; +} + +/* Pointer Class */ + +static BOOL flip_bitmap(const BYTE* src, BYTE* dst, UINT32 scanline, UINT32 nHeight) +{ + BYTE* bottomLine = dst + scanline * (nHeight - 1); + + for (UINT32 x = 0; x < nHeight; x++) + { + memcpy(bottomLine, src, scanline); + src += scanline; + bottomLine -= scanline; + } + + return TRUE; +} + +static BOOL wf_Pointer_New(rdpContext* context, rdpPointer* pointer) +{ + HCURSOR hCur; + ICONINFO info; + rdpGdi* gdi; + BOOL rc = FALSE; + + if (!context || !pointer) + return FALSE; + + gdi = context->gdi; + + if (!gdi) + return FALSE; + + info.fIcon = FALSE; + info.xHotspot = pointer->xPos; + info.yHotspot = pointer->yPos; + + if (pointer->xorBpp == 1) + { + BYTE* pdata = nullptr; + + if ((pointer->lengthAndMask > 0) || (pointer->lengthXorMask > 0)) + { + pdata = + (BYTE*)winpr_aligned_malloc(pointer->lengthAndMask + pointer->lengthXorMask, 16); + + if (!pdata) + goto fail; + } + + CopyMemory(pdata, pointer->andMaskData, pointer->lengthAndMask); + CopyMemory(pdata + pointer->lengthAndMask, pointer->xorMaskData, pointer->lengthXorMask); + info.hbmMask = CreateBitmap(pointer->width, pointer->height * 2, 1, 1, pdata); + winpr_aligned_free(pdata); + info.hbmColor = nullptr; + } + else + { + UINT32 srcFormat; + BYTE* pdata = nullptr; + + if (pointer->lengthAndMask > 0) + { + pdata = (BYTE*)winpr_aligned_malloc(pointer->lengthAndMask, 16); + + if (!pdata) + goto fail; + flip_bitmap(pointer->andMaskData, pdata, (pointer->width + 7) / 8, pointer->height); + } + + info.hbmMask = CreateBitmap(pointer->width, pointer->height, 1, 1, pdata); + winpr_aligned_free(pdata); + + /* currently color xorBpp is only 24 per [T128] section 8.14.3 */ + srcFormat = gdi_get_pixel_format(pointer->xorBpp); + + if (!srcFormat) + goto fail; + + info.hbmColor = wf_create_dib((wfContext*)context, pointer->width, pointer->height, + gdi->dstFormat, nullptr, &pdata); + + if (!info.hbmColor) + goto fail; + + if (!freerdp_image_copy_from_pointer_data( + pdata, gdi->dstFormat, 0, 0, 0, pointer->width, pointer->height, + pointer->xorMaskData, pointer->lengthXorMask, pointer->andMaskData, + pointer->lengthAndMask, pointer->xorBpp, &gdi->palette)) + { + goto fail; + } + } + + hCur = CreateIconIndirect(&info); + ((wfPointer*)pointer)->cursor = hCur; + rc = TRUE; +fail: + + if (info.hbmMask) + DeleteObject(info.hbmMask); + + if (info.hbmColor) + DeleteObject(info.hbmColor); + + return rc; +} + +static void wf_Pointer_Free(rdpContext* context, rdpPointer* pointer) +{ + HCURSOR hCur; + + if (!context || !pointer) + return; + + hCur = ((wfPointer*)pointer)->cursor; + + if (hCur != 0) + DestroyIcon(hCur); +} + +static BOOL wf_Pointer_Set(rdpContext* context, rdpPointer* pointer) +{ + HCURSOR hCur; + wfContext* wfc = (wfContext*)context; + + if (!context || !pointer) + return FALSE; + + hCur = ((wfPointer*)pointer)->cursor; + + if (hCur != nullptr) + { + SetCursor(hCur); + wfc->cursor = hCur; + } + + return TRUE; +} + +static BOOL wf_Pointer_SetNull(rdpContext* context) +{ + if (!context) + return FALSE; + + return TRUE; +} + +static BOOL wf_Pointer_SetDefault(rdpContext* context) +{ + if (!context) + return FALSE; + + return TRUE; +} + +static BOOL wf_Pointer_SetPosition(rdpContext* context, UINT32 x, UINT32 y) +{ + if (!context) + return FALSE; + + return TRUE; +} + +BOOL wf_register_pointer(rdpGraphics* graphics) +{ + wfContext* wfc; + rdpPointer pointer = WINPR_C_ARRAY_INIT; + + if (!graphics) + return FALSE; + + wfc = (wfContext*)graphics->context; + pointer.size = sizeof(wfPointer); + pointer.New = wf_Pointer_New; + pointer.Free = wf_Pointer_Free; + pointer.Set = wf_Pointer_Set; + pointer.SetNull = wf_Pointer_SetNull; + pointer.SetDefault = wf_Pointer_SetDefault; + pointer.SetPosition = wf_Pointer_SetPosition; + graphics_register_pointer(graphics, &pointer); + return TRUE; +} + +/* Graphics Module */ + +BOOL wf_register_graphics(rdpGraphics* graphics) +{ + wfContext* wfc; + rdpGlyph glyph; + rdpBitmap bitmap; + + if (!graphics) + return FALSE; + + wfc = (wfContext*)graphics->context; + bitmap = *graphics->Bitmap_Prototype; + bitmap.size = sizeof(wfBitmap); + bitmap.New = wf_Bitmap_New; + bitmap.Free = wf_Bitmap_Free; + bitmap.Paint = wf_Bitmap_Paint; + bitmap.SetSurface = wf_Bitmap_SetSurface; + graphics_register_bitmap(graphics, &bitmap); + glyph = *graphics->Glyph_Prototype; + graphics_register_glyph(graphics, &glyph); + return TRUE; +} diff --git a/third_party/FreeRDP/client/Windows/wf_graphics.h b/third_party/FreeRDP/client/Windows/wf_graphics.h new file mode 100644 index 0000000..241575f --- /dev/null +++ b/third_party/FreeRDP/client/Windows/wf_graphics.h @@ -0,0 +1,34 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Windows Graphical Objects + * + * Copyright 2010-2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_WIN_GRAPHICS_H +#define FREERDP_CLIENT_WIN_GRAPHICS_H + +#include "wf_client.h" + +HBITMAP wf_create_dib(wfContext* wfc, UINT32 width, UINT32 height, UINT32 format, const BYTE* data, + BYTE** pdata); +wfBitmap* wf_image_new(wfContext* wfc, UINT32 width, UINT32 height, UINT32 format, + const BYTE* data); +void wf_image_free(wfBitmap* image); + +BOOL wf_register_pointer(rdpGraphics* graphics); +BOOL wf_register_graphics(rdpGraphics* graphics); + +#endif /* FREERDP_CLIENT_WIN_GRAPHICS_H */ diff --git a/third_party/FreeRDP/client/Windows/wf_rail.c b/third_party/FreeRDP/client/Windows/wf_rail.c new file mode 100644 index 0000000..a775a58 --- /dev/null +++ b/third_party/FreeRDP/client/Windows/wf_rail.c @@ -0,0 +1,994 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2013-2014 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include +#include + +#include "wf_rail.h" + +#define TAG CLIENT_TAG("windows") + +#define GET_X_LPARAM(lParam) ((UINT16)(lParam & 0xFFFF)) +#define GET_Y_LPARAM(lParam) ((UINT16)((lParam >> 16) & 0xFFFF)) + +struct wf_rail_window +{ + wfContext* wfc; + + HWND hWnd; + + DWORD dwStyle; + DWORD dwExStyle; + + int x; + int y; + int width; + int height; + char* title; +}; + +/* RemoteApp Core Protocol Extension */ + +typedef struct +{ + UINT32 style; + const char* name; + BOOL multi; +} WINDOW_STYLE; + +static const WINDOW_STYLE WINDOW_STYLES[] = { { WS_BORDER, "WS_BORDER", FALSE }, + { WS_CAPTION, "WS_CAPTION", FALSE }, + { WS_CHILD, "WS_CHILD", FALSE }, + { WS_CLIPCHILDREN, "WS_CLIPCHILDREN", FALSE }, + { WS_CLIPSIBLINGS, "WS_CLIPSIBLINGS", FALSE }, + { WS_DISABLED, "WS_DISABLED", FALSE }, + { WS_DLGFRAME, "WS_DLGFRAME", FALSE }, + { WS_GROUP, "WS_GROUP", FALSE }, + { WS_HSCROLL, "WS_HSCROLL", FALSE }, + { WS_ICONIC, "WS_ICONIC", FALSE }, + { WS_MAXIMIZE, "WS_MAXIMIZE", FALSE }, + { WS_MAXIMIZEBOX, "WS_MAXIMIZEBOX", FALSE }, + { WS_MINIMIZE, "WS_MINIMIZE", FALSE }, + { WS_MINIMIZEBOX, "WS_MINIMIZEBOX", FALSE }, + { WS_OVERLAPPED, "WS_OVERLAPPED", FALSE }, + { WS_OVERLAPPEDWINDOW, "WS_OVERLAPPEDWINDOW", TRUE }, + { WS_POPUP, "WS_POPUP", FALSE }, + { WS_POPUPWINDOW, "WS_POPUPWINDOW", TRUE }, + { WS_SIZEBOX, "WS_SIZEBOX", FALSE }, + { WS_SYSMENU, "WS_SYSMENU", FALSE }, + { WS_TABSTOP, "WS_TABSTOP", FALSE }, + { WS_THICKFRAME, "WS_THICKFRAME", FALSE }, + { WS_VISIBLE, "WS_VISIBLE", FALSE } }; + +static const WINDOW_STYLE EXTENDED_WINDOW_STYLES[] = { + { WS_EX_ACCEPTFILES, "WS_EX_ACCEPTFILES", FALSE }, + { WS_EX_APPWINDOW, "WS_EX_APPWINDOW", FALSE }, + { WS_EX_CLIENTEDGE, "WS_EX_CLIENTEDGE", FALSE }, + { WS_EX_COMPOSITED, "WS_EX_COMPOSITED", FALSE }, + { WS_EX_CONTEXTHELP, "WS_EX_CONTEXTHELP", FALSE }, + { WS_EX_CONTROLPARENT, "WS_EX_CONTROLPARENT", FALSE }, + { WS_EX_DLGMODALFRAME, "WS_EX_DLGMODALFRAME", FALSE }, + { WS_EX_LAYERED, "WS_EX_LAYERED", FALSE }, + { WS_EX_LAYOUTRTL, "WS_EX_LAYOUTRTL", FALSE }, + { WS_EX_LEFT, "WS_EX_LEFT", FALSE }, + { WS_EX_LEFTSCROLLBAR, "WS_EX_LEFTSCROLLBAR", FALSE }, + { WS_EX_LTRREADING, "WS_EX_LTRREADING", FALSE }, + { WS_EX_MDICHILD, "WS_EX_MDICHILD", FALSE }, + { WS_EX_NOACTIVATE, "WS_EX_NOACTIVATE", FALSE }, + { WS_EX_NOINHERITLAYOUT, "WS_EX_NOINHERITLAYOUT", FALSE }, + { WS_EX_NOPARENTNOTIFY, "WS_EX_NOPARENTNOTIFY", FALSE }, + { WS_EX_OVERLAPPEDWINDOW, "WS_EX_OVERLAPPEDWINDOW", TRUE }, + { WS_EX_PALETTEWINDOW, "WS_EX_PALETTEWINDOW", TRUE }, + { WS_EX_RIGHT, "WS_EX_RIGHT", FALSE }, + { WS_EX_RIGHTSCROLLBAR, "WS_EX_RIGHTSCROLLBAR", FALSE }, + { WS_EX_RTLREADING, "WS_EX_RTLREADING", FALSE }, + { WS_EX_STATICEDGE, "WS_EX_STATICEDGE", FALSE }, + { WS_EX_TOOLWINDOW, "WS_EX_TOOLWINDOW", FALSE }, + { WS_EX_TOPMOST, "WS_EX_TOPMOST", FALSE }, + { WS_EX_TRANSPARENT, "WS_EX_TRANSPARENT", FALSE }, + { WS_EX_WINDOWEDGE, "WS_EX_WINDOWEDGE", FALSE } +}; + +static void PrintWindowStyles(UINT32 style) +{ + WLog_INFO(TAG, "\tWindow Styles:\t{"); + + for (size_t i = 0; i < ARRAYSIZE(WINDOW_STYLES); i++) + { + if (style & WINDOW_STYLES[i].style) + { + if (WINDOW_STYLES[i].multi) + { + if ((style & WINDOW_STYLES[i].style) != WINDOW_STYLES[i].style) + continue; + } + + WLog_INFO(TAG, "\t\t%s", WINDOW_STYLES[i].name); + } + } +} + +static void PrintExtendedWindowStyles(UINT32 style) +{ + WLog_INFO(TAG, "\tExtended Window Styles:\t{"); + + for (size_t i = 0; i < ARRAYSIZE(EXTENDED_WINDOW_STYLES); i++) + { + if (style & EXTENDED_WINDOW_STYLES[i].style) + { + if (EXTENDED_WINDOW_STYLES[i].multi) + { + if ((style & EXTENDED_WINDOW_STYLES[i].style) != EXTENDED_WINDOW_STYLES[i].style) + continue; + } + + WLog_INFO(TAG, "\t\t%s", EXTENDED_WINDOW_STYLES[i].name); + } + } +} + +static void PrintRailWindowState(const WINDOW_ORDER_INFO* orderInfo, + const WINDOW_STATE_ORDER* windowState) +{ + if (orderInfo->fieldFlags & WINDOW_ORDER_STATE_NEW) + WLog_INFO(TAG, "WindowCreate: WindowId: 0x%08X", orderInfo->windowId); + else + WLog_INFO(TAG, "WindowUpdate: WindowId: 0x%08X", orderInfo->windowId); + + WLog_INFO(TAG, "{"); + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_OWNER) + { + WLog_INFO(TAG, "\tOwnerWindowId: 0x%08X", windowState->ownerWindowId); + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_STYLE) + { + WLog_INFO(TAG, "\tStyle: 0x%08X ExtendedStyle: 0x%08X", windowState->style, + windowState->extendedStyle); + PrintWindowStyles(windowState->style); + PrintExtendedWindowStyles(windowState->extendedStyle); + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_SHOW) + { + WLog_INFO(TAG, "\tShowState: %u", windowState->showState); + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_TITLE) + { + const WCHAR* str = (const WCHAR*)windowState->titleInfo.string; + char* title = + ConvertWCharNToUtf8Alloc(str, windowState->titleInfo.length / sizeof(WCHAR), nullptr); + WLog_INFO(TAG, "\tTitleInfo: %s (length = %hu)", title, windowState->titleInfo.length); + free(title); + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_OFFSET) + { + WLog_INFO(TAG, "\tClientOffsetX: %d ClientOffsetY: %d", windowState->clientOffsetX, + windowState->clientOffsetY); + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_SIZE) + { + WLog_INFO(TAG, "\tClientAreaWidth: %u ClientAreaHeight: %u", windowState->clientAreaWidth, + windowState->clientAreaHeight); + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_RP_CONTENT) + { + WLog_INFO(TAG, "\tRPContent: %u", windowState->RPContent); + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_ROOT_PARENT) + { + WLog_INFO(TAG, "\tRootParentHandle: 0x%08X", windowState->rootParentHandle); + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_WND_OFFSET) + { + WLog_INFO(TAG, "\tWindowOffsetX: %d WindowOffsetY: %d", windowState->windowOffsetX, + windowState->windowOffsetY); + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_WND_CLIENT_DELTA) + { + WLog_INFO(TAG, "\tWindowClientDeltaX: %d WindowClientDeltaY: %d", + windowState->windowClientDeltaX, windowState->windowClientDeltaY); + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_WND_SIZE) + { + WLog_INFO(TAG, "\tWindowWidth: %u WindowHeight: %u", windowState->windowWidth, + windowState->windowHeight); + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_WND_RECTS) + { + RECTANGLE_16* rect; + WLog_INFO(TAG, "\tnumWindowRects: %u", windowState->numWindowRects); + + for (UINT32 index = 0; index < windowState->numWindowRects; index++) + { + rect = &windowState->windowRects[index]; + WLog_INFO(TAG, "\twindowRect[%u]: left: %hu top: %hu right: %hu bottom: %hu", index, + rect->left, rect->top, rect->right, rect->bottom); + } + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_VIS_OFFSET) + { + WLog_INFO(TAG, "\tvisibileOffsetX: %d visibleOffsetY: %d", windowState->visibleOffsetX, + windowState->visibleOffsetY); + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_VISIBILITY) + { + RECTANGLE_16* rect; + WLog_INFO(TAG, "\tnumVisibilityRects: %u", windowState->numVisibilityRects); + + for (UINT32 index = 0; index < windowState->numVisibilityRects; index++) + { + rect = &windowState->visibilityRects[index]; + WLog_INFO(TAG, "\tvisibilityRect[%u]: left: %hu top: %hu right: %hu bottom: %hu", index, + rect->left, rect->top, rect->right, rect->bottom); + } + } + + WLog_INFO(TAG, "}"); +} + +static void PrintRailIconInfo(const WINDOW_ORDER_INFO* orderInfo, const ICON_INFO* iconInfo) +{ + WLog_INFO(TAG, "ICON_INFO"); + WLog_INFO(TAG, "{"); + WLog_INFO(TAG, "\tbigIcon: %s", + (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_ICON_BIG) ? "true" : "false"); + WLog_INFO(TAG, "\tcacheEntry; 0x%08X", iconInfo->cacheEntry); + WLog_INFO(TAG, "\tcacheId: 0x%08X", iconInfo->cacheId); + WLog_INFO(TAG, "\tbpp: %u", iconInfo->bpp); + WLog_INFO(TAG, "\twidth: %u", iconInfo->width); + WLog_INFO(TAG, "\theight: %u", iconInfo->height); + WLog_INFO(TAG, "\tcbColorTable: %u", iconInfo->cbColorTable); + WLog_INFO(TAG, "\tcbBitsMask: %u", iconInfo->cbBitsMask); + WLog_INFO(TAG, "\tcbBitsColor: %u", iconInfo->cbBitsColor); + WLog_INFO(TAG, "\tcolorTable: %p", (void*)iconInfo->colorTable); + WLog_INFO(TAG, "\tbitsMask: %p", (void*)iconInfo->bitsMask); + WLog_INFO(TAG, "\tbitsColor: %p", (void*)iconInfo->bitsColor); + WLog_INFO(TAG, "}"); +} + +LRESULT CALLBACK wf_RailWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + HDC hDC; + int x, y; + int width; + int height; + UINT32 xPos; + UINT32 yPos; + PAINTSTRUCT ps; + UINT32 inputFlags; + wfContext* wfc = nullptr; + rdpInput* input = nullptr; + rdpContext* context = nullptr; + wfRailWindow* railWindow; + railWindow = (wfRailWindow*)GetWindowLongPtr(hWnd, GWLP_USERDATA); + + if (railWindow) + wfc = railWindow->wfc; + + if (wfc) + context = (rdpContext*)wfc; + + if (context) + input = context->input; + + switch (msg) + { + case WM_PAINT: + { + if (!wfc) + return 0; + + hDC = BeginPaint(hWnd, &ps); + x = ps.rcPaint.left; + y = ps.rcPaint.top; + width = ps.rcPaint.right - ps.rcPaint.left + 1; + height = ps.rcPaint.bottom - ps.rcPaint.top + 1; + BitBlt(hDC, x, y, width, height, wfc->primary->hdc, railWindow->x + x, + railWindow->y + y, SRCCOPY); + EndPaint(hWnd, &ps); + } + break; + + case WM_LBUTTONDOWN: + { + if (!railWindow || !input) + return 0; + + xPos = GET_X_LPARAM(lParam) + railWindow->x; + yPos = GET_Y_LPARAM(lParam) + railWindow->y; + inputFlags = PTR_FLAGS_DOWN | PTR_FLAGS_BUTTON1; + + if (input) + input->MouseEvent(input, inputFlags, xPos, yPos); + } + break; + + case WM_LBUTTONUP: + { + if (!railWindow || !input) + return 0; + + xPos = GET_X_LPARAM(lParam) + railWindow->x; + yPos = GET_Y_LPARAM(lParam) + railWindow->y; + inputFlags = PTR_FLAGS_BUTTON1; + + if (input) + input->MouseEvent(input, inputFlags, xPos, yPos); + } + break; + + case WM_RBUTTONDOWN: + { + if (!railWindow || !input) + return 0; + + xPos = GET_X_LPARAM(lParam) + railWindow->x; + yPos = GET_Y_LPARAM(lParam) + railWindow->y; + inputFlags = PTR_FLAGS_DOWN | PTR_FLAGS_BUTTON2; + + if (input) + input->MouseEvent(input, inputFlags, xPos, yPos); + } + break; + + case WM_RBUTTONUP: + { + if (!railWindow || !input) + return 0; + + xPos = GET_X_LPARAM(lParam) + railWindow->x; + yPos = GET_Y_LPARAM(lParam) + railWindow->y; + inputFlags = PTR_FLAGS_BUTTON2; + + if (input) + input->MouseEvent(input, inputFlags, xPos, yPos); + } + break; + + case WM_MOUSEMOVE: + { + if (!railWindow || !input) + return 0; + + xPos = GET_X_LPARAM(lParam) + railWindow->x; + yPos = GET_Y_LPARAM(lParam) + railWindow->y; + inputFlags = PTR_FLAGS_MOVE; + + if (input) + input->MouseEvent(input, inputFlags, xPos, yPos); + } + break; + + case WM_MOUSEWHEEL: + break; + + case WM_CLOSE: + DestroyWindow(hWnd); + break; + + case WM_DESTROY: + PostQuitMessage(0); + break; + + default: + return DefWindowProc(hWnd, msg, wParam, lParam); + } + + return 0; +} + +#define RAIL_DISABLED_WINDOW_STYLES \ + (WS_BORDER | WS_THICKFRAME | WS_DLGFRAME | WS_CAPTION | WS_OVERLAPPED | WS_VSCROLL | \ + WS_HSCROLL | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX) +#define RAIL_DISABLED_EXTENDED_WINDOW_STYLES \ + (WS_EX_DLGMODALFRAME | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE | WS_EX_WINDOWEDGE) + +static BOOL wf_rail_window_common(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo, + const WINDOW_STATE_ORDER* windowState) +{ + wfRailWindow* railWindow = nullptr; + wfContext* wfc = (wfContext*)context; + RailClientContext* rail = wfc->rail; + UINT32 fieldFlags = orderInfo->fieldFlags; + PrintRailWindowState(orderInfo, windowState); + + if (fieldFlags & WINDOW_ORDER_STATE_NEW) + { + BOOL rc; + HANDLE hInstance; + WCHAR* titleW = nullptr; + WNDCLASSEX wndClassEx = WINPR_C_ARRAY_INIT; + railWindow = (wfRailWindow*)calloc(1, sizeof(wfRailWindow)); + + if (!railWindow) + return FALSE; + + railWindow->wfc = wfc; + railWindow->dwStyle = windowState->style; + railWindow->dwStyle &= ~RAIL_DISABLED_WINDOW_STYLES; + railWindow->dwExStyle = windowState->extendedStyle; + railWindow->dwExStyle &= ~RAIL_DISABLED_EXTENDED_WINDOW_STYLES; + railWindow->x = windowState->windowOffsetX; + railWindow->y = windowState->windowOffsetY; + railWindow->width = windowState->windowWidth; + railWindow->height = windowState->windowHeight; + + if (fieldFlags & WINDOW_ORDER_FIELD_TITLE) + { + const WCHAR* str = (const WCHAR*)windowState->titleInfo.string; + char* title = nullptr; + + if (windowState->titleInfo.length == 0) + { + if (!(title = _strdup(""))) + { + WLog_ERR(TAG, "failed to duplicate empty window title string"); + /* error handled below */ + } + } + else if (!(title = ConvertWCharNToUtf8Alloc( + str, windowState->titleInfo.length / sizeof(WCHAR), nullptr))) + { + WLog_ERR(TAG, "failed to convert window title"); + /* error handled below */ + } + + railWindow->title = title; + } + else + { + if (!(railWindow->title = _strdup("RdpRailWindow"))) + WLog_ERR(TAG, "failed to duplicate default window title string"); + } + + if (!railWindow->title) + { + free(railWindow); + return FALSE; + } + + titleW = ConvertUtf8ToWCharAlloc(railWindow->title, nullptr); + hInstance = GetModuleHandle(nullptr); + + wndClassEx.cbSize = sizeof(WNDCLASSEX); + wndClassEx.style = 0; + wndClassEx.lpfnWndProc = wf_RailWndProc; + wndClassEx.cbClsExtra = 0; + wndClassEx.cbWndExtra = 0; + wndClassEx.hIcon = nullptr; + wndClassEx.hCursor = nullptr; + wndClassEx.hbrBackground = nullptr; + wndClassEx.lpszMenuName = nullptr; + wndClassEx.lpszClassName = _T("RdpRailWindow"); + wndClassEx.hInstance = hInstance; + wndClassEx.hIconSm = nullptr; + RegisterClassEx(&wndClassEx); + railWindow->hWnd = CreateWindowExW(railWindow->dwExStyle, /* dwExStyle */ + _T("RdpRailWindow"), /* lpClassName */ + titleW, /* lpWindowName */ + railWindow->dwStyle, /* dwStyle */ + railWindow->x, /* x */ + railWindow->y, /* y */ + railWindow->width, /* nWidth */ + railWindow->height, /* nHeight */ + nullptr, /* hWndParent */ + nullptr, /* hMenu */ + hInstance, /* hInstance */ + nullptr /* lpParam */ + ); + + if (!railWindow->hWnd) + { + free(titleW); + free(railWindow->title); + free(railWindow); + WLog_ERR(TAG, "CreateWindowExW failed with error %" PRIu32 "", GetLastError()); + return FALSE; + } + + SetWindowLongPtr(railWindow->hWnd, GWLP_USERDATA, (LONG_PTR)railWindow); + rc = HashTable_Insert(wfc->railWindows, (void*)(UINT_PTR)orderInfo->windowId, + (void*)railWindow); + free(titleW); + UpdateWindow(railWindow->hWnd); + return rc; + } + else + { + railWindow = (wfRailWindow*)HashTable_GetItemValue(wfc->railWindows, + (void*)(UINT_PTR)orderInfo->windowId); + } + + if (!railWindow) + return TRUE; + + if ((fieldFlags & WINDOW_ORDER_FIELD_WND_OFFSET) || (fieldFlags & WINDOW_ORDER_FIELD_WND_SIZE)) + { + if (fieldFlags & WINDOW_ORDER_FIELD_WND_OFFSET) + { + railWindow->x = windowState->windowOffsetX; + railWindow->y = windowState->windowOffsetY; + } + + if (fieldFlags & WINDOW_ORDER_FIELD_WND_SIZE) + { + railWindow->width = windowState->windowWidth; + railWindow->height = windowState->windowHeight; + } + + SetWindowPos(railWindow->hWnd, nullptr, railWindow->x, railWindow->y, railWindow->width, + railWindow->height, 0); + } + + if (fieldFlags & WINDOW_ORDER_FIELD_OWNER) + { + } + + if (fieldFlags & WINDOW_ORDER_FIELD_STYLE) + { + railWindow->dwStyle = windowState->style; + railWindow->dwStyle &= ~RAIL_DISABLED_WINDOW_STYLES; + railWindow->dwExStyle = windowState->extendedStyle; + railWindow->dwExStyle &= ~RAIL_DISABLED_EXTENDED_WINDOW_STYLES; + SetWindowLongPtr(railWindow->hWnd, GWL_STYLE, (LONG)railWindow->dwStyle); + SetWindowLongPtr(railWindow->hWnd, GWL_EXSTYLE, (LONG)railWindow->dwExStyle); + } + + if (fieldFlags & WINDOW_ORDER_FIELD_SHOW) + { + ShowWindow(railWindow->hWnd, windowState->showState); + } + + if (fieldFlags & WINDOW_ORDER_FIELD_TITLE) + { + const WCHAR* str = (const WCHAR*)windowState->titleInfo.string; + char* title = nullptr; + + if (windowState->titleInfo.length == 0) + { + if (!(title = _strdup(""))) + { + WLog_ERR(TAG, "failed to duplicate empty window title string"); + return FALSE; + } + } + else if (!(title = ConvertWCharNToUtf8Alloc( + str, windowState->titleInfo.length / sizeof(WCHAR), nullptr))) + { + WLog_ERR(TAG, "failed to convert window title"); + return FALSE; + } + + free(railWindow->title); + railWindow->title = title; + SetWindowTextW(railWindow->hWnd, str); + } + + if (fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_OFFSET) + { + } + + if (fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_SIZE) + { + } + + if (fieldFlags & WINDOW_ORDER_FIELD_WND_CLIENT_DELTA) + { + } + + if (fieldFlags & WINDOW_ORDER_FIELD_RP_CONTENT) + { + } + + if (fieldFlags & WINDOW_ORDER_FIELD_ROOT_PARENT) + { + } + + if (fieldFlags & WINDOW_ORDER_FIELD_WND_RECTS) + { + HRGN hWndRect; + HRGN hWndRects; + RECTANGLE_16* rect; + + if (windowState->numWindowRects > 0) + { + rect = &(windowState->windowRects[0]); + hWndRects = CreateRectRgn(rect->left, rect->top, rect->right, rect->bottom); + + for (UINT32 index = 1; index < windowState->numWindowRects; index++) + { + rect = &(windowState->windowRects[index]); + hWndRect = CreateRectRgn(rect->left, rect->top, rect->right, rect->bottom); + CombineRgn(hWndRects, hWndRects, hWndRect, RGN_OR); + DeleteObject(hWndRect); + } + + SetWindowRgn(railWindow->hWnd, hWndRects, TRUE); + DeleteObject(hWndRects); + } + } + + if (fieldFlags & WINDOW_ORDER_FIELD_VIS_OFFSET) + { + } + + if (fieldFlags & WINDOW_ORDER_FIELD_VISIBILITY) + { + } + + UpdateWindow(railWindow->hWnd); + return TRUE; +} + +static BOOL wf_rail_window_delete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo) +{ + wfRailWindow* railWindow = nullptr; + wfContext* wfc = (wfContext*)context; + RailClientContext* rail = wfc->rail; + WLog_DBG(TAG, "RailWindowDelete"); + railWindow = (wfRailWindow*)HashTable_GetItemValue(wfc->railWindows, + (void*)(UINT_PTR)orderInfo->windowId); + + if (!railWindow) + return TRUE; + + HashTable_Remove(wfc->railWindows, (void*)(UINT_PTR)orderInfo->windowId); + DestroyWindow(railWindow->hWnd); + free(railWindow); + return TRUE; +} + +static BOOL wf_rail_window_icon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo, + const WINDOW_ICON_ORDER* windowIcon) +{ + HDC hDC; + int bpp; + int width; + int height; + HICON hIcon; + BOOL bigIcon; + ICONINFO iconInfo = WINPR_C_ARRAY_INIT; + BITMAPINFO bitmapInfo = WINPR_C_ARRAY_INIT; + wfRailWindow* railWindow; + BITMAPINFOHEADER* bitmapInfoHeader; + wfContext* wfc = (wfContext*)context; + RailClientContext* rail = wfc->rail; + WLog_DBG(TAG, "RailWindowIcon"); + PrintRailIconInfo(orderInfo, windowIcon->iconInfo); + railWindow = (wfRailWindow*)HashTable_GetItemValue(wfc->railWindows, + (void*)(UINT_PTR)orderInfo->windowId); + + if (!railWindow) + return TRUE; + + bigIcon = (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_ICON_BIG) ? TRUE : FALSE; + hDC = GetDC(railWindow->hWnd); + iconInfo.fIcon = TRUE; + iconInfo.xHotspot = 0; + iconInfo.yHotspot = 0; + + bitmapInfoHeader = &(bitmapInfo.bmiHeader); + bpp = windowIcon->iconInfo->bpp; + width = windowIcon->iconInfo->width; + height = windowIcon->iconInfo->height; + bitmapInfoHeader->biSize = sizeof(BITMAPINFOHEADER); + bitmapInfoHeader->biWidth = width; + bitmapInfoHeader->biHeight = height; + bitmapInfoHeader->biPlanes = 1; + bitmapInfoHeader->biBitCount = bpp; + bitmapInfoHeader->biCompression = 0; + bitmapInfoHeader->biSizeImage = height * width * ((bpp + 7) / 8); + bitmapInfoHeader->biXPelsPerMeter = width; + bitmapInfoHeader->biYPelsPerMeter = height; + bitmapInfoHeader->biClrUsed = 0; + bitmapInfoHeader->biClrImportant = 0; + iconInfo.hbmMask = CreateDIBitmap(hDC, bitmapInfoHeader, CBM_INIT, + windowIcon->iconInfo->bitsMask, &bitmapInfo, DIB_RGB_COLORS); + iconInfo.hbmColor = + CreateDIBitmap(hDC, bitmapInfoHeader, CBM_INIT, windowIcon->iconInfo->bitsColor, + &bitmapInfo, DIB_RGB_COLORS); + hIcon = CreateIconIndirect(&iconInfo); + + if (hIcon) + { + WPARAM wParam; + LPARAM lParam; + wParam = (WPARAM)bigIcon ? ICON_BIG : ICON_SMALL; + lParam = (LPARAM)hIcon; + SendMessage(railWindow->hWnd, WM_SETICON, wParam, lParam); + } + + ReleaseDC(nullptr, hDC); + + if (windowIcon->iconInfo->cacheEntry != 0xFFFF) + { + /* icon should be cached */ + } + + return TRUE; +} + +static BOOL wf_rail_window_cached_icon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo, + const WINDOW_CACHED_ICON_ORDER* windowCachedIcon) +{ + WLog_DBG(TAG, "RailWindowCachedIcon"); + return TRUE; +} + +static void wf_rail_notify_icon_common(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo, + const NOTIFY_ICON_STATE_ORDER* notifyIconState) +{ + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_VERSION) + { + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_TIP) + { + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_INFO_TIP) + { + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_STATE) + { + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_ICON) + { + const ICON_INFO* iconInfo = &(notifyIconState->icon); + PrintRailIconInfo(orderInfo, iconInfo); + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_CACHED_ICON) + { + } +} + +static BOOL wf_rail_notify_icon_create(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo, + const NOTIFY_ICON_STATE_ORDER* notifyIconState) +{ + wfContext* wfc = (wfContext*)context; + RailClientContext* rail = wfc->rail; + WLog_DBG(TAG, "RailNotifyIconCreate"); + wf_rail_notify_icon_common(context, orderInfo, notifyIconState); + return TRUE; +} + +static BOOL wf_rail_notify_icon_update(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo, + const NOTIFY_ICON_STATE_ORDER* notifyIconState) +{ + wfContext* wfc = (wfContext*)context; + RailClientContext* rail = wfc->rail; + WLog_DBG(TAG, "RailNotifyIconUpdate"); + wf_rail_notify_icon_common(context, orderInfo, notifyIconState); + return TRUE; +} + +static BOOL wf_rail_notify_icon_delete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo) +{ + wfContext* wfc = (wfContext*)context; + RailClientContext* rail = wfc->rail; + WLog_DBG(TAG, "RailNotifyIconDelete"); + return TRUE; +} + +static BOOL wf_rail_monitored_desktop(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo, + const MONITORED_DESKTOP_ORDER* monitoredDesktop) +{ + wfContext* wfc = (wfContext*)context; + RailClientContext* rail = wfc->rail; + WLog_DBG(TAG, "RailMonitorDesktop"); + return TRUE; +} + +static BOOL wf_rail_non_monitored_desktop(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo) +{ + wfContext* wfc = (wfContext*)context; + RailClientContext* rail = wfc->rail; + WLog_DBG(TAG, "RailNonMonitorDesktop"); + return TRUE; +} + +void wf_rail_register_update_callbacks(rdpUpdate* update) +{ + rdpWindowUpdate* window = update->window; + window->WindowCreate = wf_rail_window_common; + window->WindowUpdate = wf_rail_window_common; + window->WindowDelete = wf_rail_window_delete; + window->WindowIcon = wf_rail_window_icon; + window->WindowCachedIcon = wf_rail_window_cached_icon; + window->NotifyIconCreate = wf_rail_notify_icon_create; + window->NotifyIconUpdate = wf_rail_notify_icon_update; + window->NotifyIconDelete = wf_rail_notify_icon_delete; + window->MonitoredDesktop = wf_rail_monitored_desktop; + window->NonMonitoredDesktop = wf_rail_non_monitored_desktop; +} + +/* RemoteApp Virtual Channel Extension */ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_rail_server_execute_result(RailClientContext* context, + const RAIL_EXEC_RESULT_ORDER* execResult) +{ + WLog_DBG(TAG, "RailServerExecuteResult: 0x%08X", execResult->rawResult); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_rail_server_system_param(RailClientContext* context, + const RAIL_SYSPARAM_ORDER* sysparam) +{ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_rail_server_handshake(RailClientContext* context, + const RAIL_HANDSHAKE_ORDER* handshake) +{ + return client_rail_server_start_cmd(context); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_rail_server_handshake_ex(RailClientContext* context, + const RAIL_HANDSHAKE_EX_ORDER* handshakeEx) +{ + return client_rail_server_start_cmd(context); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_rail_server_local_move_size(RailClientContext* context, + const RAIL_LOCALMOVESIZE_ORDER* localMoveSize) +{ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_rail_server_min_max_info(RailClientContext* context, + const RAIL_MINMAXINFO_ORDER* minMaxInfo) +{ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_rail_server_language_bar_info(RailClientContext* context, + const RAIL_LANGBAR_INFO_ORDER* langBarInfo) +{ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT wf_rail_server_get_appid_response(RailClientContext* context, + const RAIL_GET_APPID_RESP_ORDER* getAppIdResp) +{ + return CHANNEL_RC_OK; +} + +void wf_rail_invalidate_region(wfContext* wfc, REGION16* invalidRegion) +{ + RECT updateRect; + RECTANGLE_16 windowRect; + ULONG_PTR* pKeys = nullptr; + wfRailWindow* railWindow; + const RECTANGLE_16* extents; + REGION16 windowInvalidRegion; + region16_init(&windowInvalidRegion); + size_t count = HashTable_GetKeys(wfc->railWindows, &pKeys); + + for (size_t index = 0; index < count; index++) + { + railWindow = (wfRailWindow*)HashTable_GetItemValue(wfc->railWindows, (void*)pKeys[index]); + + if (railWindow) + { + windowRect.left = railWindow->x; + windowRect.top = railWindow->y; + windowRect.right = railWindow->x + railWindow->width; + windowRect.bottom = railWindow->y + railWindow->height; + region16_clear(&windowInvalidRegion); + region16_intersect_rect(&windowInvalidRegion, invalidRegion, &windowRect); + + if (!region16_is_empty(&windowInvalidRegion)) + { + extents = region16_extents(&windowInvalidRegion); + updateRect.left = extents->left - railWindow->x; + updateRect.top = extents->top - railWindow->y; + updateRect.right = extents->right - railWindow->x; + updateRect.bottom = extents->bottom - railWindow->y; + InvalidateRect(railWindow->hWnd, &updateRect, FALSE); + } + } + } + + region16_uninit(&windowInvalidRegion); +} + +BOOL wf_rail_init(wfContext* wfc, RailClientContext* rail) +{ + rdpContext* context = (rdpContext*)wfc; + wfc->rail = rail; + rail->custom = (void*)wfc; + rail->ServerExecuteResult = wf_rail_server_execute_result; + rail->ServerSystemParam = wf_rail_server_system_param; + rail->ServerHandshake = wf_rail_server_handshake; + rail->ServerHandshakeEx = wf_rail_server_handshake_ex; + rail->ServerLocalMoveSize = wf_rail_server_local_move_size; + rail->ServerMinMaxInfo = wf_rail_server_min_max_info; + rail->ServerLanguageBarInfo = wf_rail_server_language_bar_info; + rail->ServerGetAppIdResponse = wf_rail_server_get_appid_response; + wf_rail_register_update_callbacks(context->update); + wfc->railWindows = HashTable_New(TRUE); + return (wfc->railWindows != nullptr); +} + +void wf_rail_uninit(wfContext* wfc, RailClientContext* rail) +{ + wfc->rail = nullptr; + rail->custom = nullptr; + HashTable_Free(wfc->railWindows); +} diff --git a/third_party/FreeRDP/client/Windows/wf_rail.h b/third_party/FreeRDP/client/Windows/wf_rail.h new file mode 100644 index 0000000..2b73821 --- /dev/null +++ b/third_party/FreeRDP/client/Windows/wf_rail.h @@ -0,0 +1,33 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2013-2014 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_WIN_RAIL_H +#define FREERDP_CLIENT_WIN_RAIL_H + +typedef struct wf_rail_window wfRailWindow; + +#include "wf_client.h" + +#include + +BOOL wf_rail_init(wfContext* wfc, RailClientContext* rail); +void wf_rail_uninit(wfContext* wfc, RailClientContext* rail); + +void wf_rail_invalidate_region(wfContext* wfc, REGION16* invalidRegion); + +#endif /* FREERDP_CLIENT_WIN_RAIL_H */ diff --git a/third_party/FreeRDP/client/X11/CMakeLists.txt b/third_party/FreeRDP/client/X11/CMakeLists.txt new file mode 100644 index 0000000..24bff5e --- /dev/null +++ b/third_party/FreeRDP/client/X11/CMakeLists.txt @@ -0,0 +1,198 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP X11 Client +# +# Copyright 2012 Marc-Andre Moreau +# Copyright 2013 Corey Clayton +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +cmake_minimum_required(VERSION 3.13) + +if(NOT FREERDP_DEFAULT_PROJECT_VERSION) + set(FREERDP_DEFAULT_PROJECT_VERSION "1.0.0.0") +endif() + +project(xfreerdp-client LANGUAGES C VERSION ${FREERDP_DEFAULT_PROJECT_VERSION}) + +message("project ${PROJECT_NAME} is using version ${PROJECT_VERSION}") + +set(MODULE_NAME "xfreerdp") + +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/) +include(ProjectCStandard) +include(CommonConfigOptions) + +include(ConfigureFreeRDP) + +find_package(X11 REQUIRED) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../resources) +include_directories(SYSTEM ${X11_INCLUDE_DIRS}) +include_directories(SYSTEM ${OPENSSL_INCLUDE_DIR}) + +set(SRCS + xf_types.h + xf_utils.h + xf_utils.c + xf_x11_utils.c + xf_gfx.c + xf_gfx.h + xf_rail.c + xf_rail.h + xf_input.c + xf_input.h + xf_debug.h + xf_event.c + xf_event.h + xf_floatbar.c + xf_floatbar.h + xf_input.c + xf_input.h + xf_channels.c + xf_channels.h + xf_cliprdr.c + xf_cliprdr.h + xf_monitor.c + xf_monitor.h + xf_disp.c + xf_disp.h + xf_graphics.c + xf_graphics.h + xf_keyboard.c + xf_keyboard.h + keyboard_x11.h + keyboard_x11.c + xkb_layout_ids.h + xkb_layout_ids.c + xf_video.c + xf_video.h + xf_window.c + xf_window.h + xf_client.c + xf_client.h +) + +if(CHANNEL_TSMF_CLIENT) + list(APPEND SRCS xf_tsmf.c xf_tsmf.h) +endif() + +if(CLIENT_INTERFACE_SHARED) + addtargetwithresourcefile(${PROJECT_NAME} "SHARED" "${PROJECT_VERSION}" SRCS) +else() + addtargetwithresourcefile(${PROJECT_NAME} "STATIC" "${PROJECT_VERSION}" SRCS) +endif() +target_include_directories(${PROJECT_NAME} INTERFACE $) + +set(PRIV_LIBS ${X11_LIBRARIES}) + +find_package(X11 REQUIRED) +if(X11_XShm_FOUND) + add_compile_definitions(WITH_XSHM) + include_directories(SYSTEM ${X11_XShm_INCLUDE_PATH}) + list(APPEND PRIV_LIBS ${X11_Xext_LIB}) +endif() + +option(WITH_XINERAMA "[X11] enable xinerama" ON) +if(WITH_XINERAMA) + find_package(X11 REQUIRED) + if(X11_Xinerama_FOUND) + add_compile_definitions(WITH_XINERAMA) + include_directories(SYSTEM ${X11_Xinerama_INCLUDE_PATH}) + list(APPEND PRIV_LIBS ${X11_Xinerama_LIB}) + endif() +endif() + +option(WITH_XEXT "[X11] enable Xext" ON) +if(WITH_XEXT) + find_package(X11 REQUIRED) + if(X11_Xext_FOUND) + add_compile_definitions(WITH_XEXT) + list(APPEND PRIV_LIBS ${X11_Xext_LIB}) + endif() +endif() + +option(WITH_XCURSOR "[X11] enable Xcursor" ON) +if(WITH_XCURSOR) + find_package(X11 REQUIRED) + if(X11_Xcursor_FOUND) + add_compile_definitions(WITH_XCURSOR) + include_directories(SYSTEM ${X11_Xcursor_INCLUDE_PATH}) + list(APPEND PRIV_LIBS ${X11_Xcursor_LIB}) + endif() +endif() + +option(WITH_XV "[X11] enable Xv" ON) +if(WITH_XV) + find_package(X11 REQUIRED) + if(X11_Xv_FOUND) + add_compile_definitions(WITH_XV) + include_directories(SYSTEM ${X11_Xv_INCLUDE_PATH}) + list(APPEND PRIV_LIBS ${X11_Xv_LIB}) + endif() +endif() + +option(WITH_XI "[X11] enable Xi" ON) +if(WITH_XI) + find_package(X11 REQUIRED) + if(X11_Xi_FOUND) + add_compile_definitions(WITH_XI) + include_directories(SYSTEM ${X11_Xi_INCLUDE_PATH}) + list(APPEND PRIV_LIBS ${X11_Xi_LIB}) + endif() +endif() + +option(WITH_XRENDER "[X11] enable XRender" ON) +if(WITH_XRENDER) + find_package(X11 REQUIRED) + if(X11_Xrender_FOUND) + add_compile_definitions(WITH_XRENDER) + include_directories(SYSTEM ${X11_Xrender_INCLUDE_PATH}) + list(APPEND PRIV_LIBS ${X11_Xrender_LIB}) + endif() +endif() + +option(WITH_XRANDR "[X11] enable XRandR" ON) +if(WITH_XRANDR) + find_package(X11 REQUIRED) + if(X11_Xrandr_FOUND) + add_compile_definitions(WITH_XRANDR) + include_directories(SYSTEM ${X11_Xrandr_INCLUDE_PATH}) + list(APPEND PRIV_LIBS ${X11_Xrandr_LIB}) + endif() +endif() + +option(WITH_XFIXES "[X11] enable Xfixes" ON) +if(WITH_XFIXES) + find_package(X11 REQUIRED) + if(X11_Xfixes_FOUND) + add_compile_definitions(WITH_XFIXES) + include_directories(SYSTEM ${X11_Xfixes_INCLUDE_PATH}) + list(APPEND PRIV_LIBS ${X11_Xfixes_LIB}) + endif() +endif() + +list(APPEND PUB_LIBS freerdp-client) + +list(APPEND PRIV_LIBS m) +if(NOT APPLE) + list(APPEND PRIV_LIBS rt) +endif() +target_link_libraries(${PROJECT_NAME} PUBLIC ${PUB_LIBS}) +target_link_libraries(${PROJECT_NAME} PRIVATE ${PRIV_LIBS}) + +if(WITH_CLIENT_INTERFACE) + installwithrpath(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT libraries) +endif() +add_subdirectory(cli) +add_subdirectory(man) + +set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER "Client/X11") diff --git a/third_party/FreeRDP/client/X11/ModuleOptions.cmake b/third_party/FreeRDP/client/X11/ModuleOptions.cmake new file mode 100644 index 0000000..de108a4 --- /dev/null +++ b/third_party/FreeRDP/client/X11/ModuleOptions.cmake @@ -0,0 +1,3 @@ +set(FREERDP_CLIENT_NAME "xfreerdp") +set(FREERDP_CLIENT_PLATFORM "X11") +set(FREERDP_CLIENT_VENDOR "FreeRDP") diff --git a/third_party/FreeRDP/client/X11/cli/CMakeLists.txt b/third_party/FreeRDP/client/X11/cli/CMakeLists.txt new file mode 100644 index 0000000..a668446 --- /dev/null +++ b/third_party/FreeRDP/client/X11/cli/CMakeLists.txt @@ -0,0 +1,39 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP X11 cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# +# 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 xfreerdp.c) + +addtargetwithresourcefile(${MODULE_NAME} TRUE "${PROJECT_VERSION}" SRCS) +set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "..") +set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER "Client/X11") + +list(APPEND LIBS xfreerdp-client) + +target_link_libraries(${MODULE_NAME} PRIVATE ${LIBS}) + +installwithrpath( + TARGETS + ${MODULE_NAME} + RUNTIME + DESTINATION + ${CMAKE_INSTALL_BINDIR} + COMPONENT + client +) +install_freerdp_desktop("${MODULE_NAME}") + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Client/X11") diff --git a/third_party/FreeRDP/client/X11/cli/xfreerdp.c b/third_party/FreeRDP/client/X11/cli/xfreerdp.c new file mode 100644 index 0000000..cd17335 --- /dev/null +++ b/third_party/FreeRDP/client/X11/cli/xfreerdp.c @@ -0,0 +1,120 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Client + * + * Copyright 2011 Marc-Andre Moreau + * Copyright 2012 HP Development Company, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include +#include + +#include "../xf_client.h" +#include "../xfreerdp.h" + +static void xfreerdp_print_help(void) +{ + printf("Keyboard Shortcuts:\n"); + printf("\t\n"); + printf("\t\treleases keyboard and mouse grab\n"); + printf("\t++\n"); + printf("\t\ttoggles fullscreen state of the application\n"); + printf("\t++c\n"); + printf("\t\ttoggles remote control in a remote assistance session\n"); + printf("\t++m\n"); + printf("\t\tminimizes the application\n"); + printf("\tAction Script\n"); + printf("\t\tExecutes a predefined script on key press.\n"); + printf("\t\tShould the script not exist it is ignored.\n"); + printf("\t\tScripts can be provided at the default location ~/.config/freerdp/action.sh or as " + "command line argument /action:script:\n"); + printf("\t\tThe script will receive the current key combination as argument.\n"); + printf("\t\tThe output of the script is parsed for 'key-local' which tells that the script " + "used the key combination, otherwise the combination is forwarded to the remote.\n"); +} + +int main(int argc, char* argv[]) +{ + int rc = 1; + int status = 0; + HANDLE thread = nullptr; + xfContext* xfc = nullptr; + DWORD dwExitCode = 0; + rdpContext* context = nullptr; + rdpSettings* settings = nullptr; + RDP_CLIENT_ENTRY_POINTS clientEntryPoints = WINPR_C_ARRAY_INIT; + + clientEntryPoints.Size = sizeof(RDP_CLIENT_ENTRY_POINTS); + clientEntryPoints.Version = RDP_CLIENT_INTERFACE_VERSION; + + RdpClientEntry(&clientEntryPoints); + + context = freerdp_client_context_new(&clientEntryPoints); + if (!context) + return 1; + + settings = context->settings; + xfc = (xfContext*)context; + + status = freerdp_client_settings_parse_command_line(context->settings, argc, argv, FALSE); + if (status) + { + rc = freerdp_client_settings_command_line_status_print(settings, status, argc, argv); + + if (freerdp_settings_get_bool(settings, FreeRDP_ListMonitors)) + xf_list_monitors(xfc); + 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: + xfreerdp_print_help(); + break; + } + } + goto out; + } + + if (!stream_dump_register_handlers(context, CONNECTION_STATE_MCS_CREATE_REQUEST, FALSE)) + goto out; + + if (freerdp_client_start(context) != 0) + goto out; + + thread = freerdp_client_get_thread(context); + + (void)WaitForSingleObject(thread, INFINITE); + GetExitCodeThread(thread, &dwExitCode); + rc = xf_exit_code_from_disconnect_reason(dwExitCode); + + freerdp_client_stop(context); + +out: + freerdp_client_context_free(context); + + return rc; +} diff --git a/third_party/FreeRDP/client/X11/keyboard_x11.c b/third_party/FreeRDP/client/X11/keyboard_x11.c new file mode 100644 index 0000000..235e0ec --- /dev/null +++ b/third_party/FreeRDP/client/X11/keyboard_x11.c @@ -0,0 +1,147 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Keyboard Mapping + * + * Copyright 2009-2012 Marc-Andre Moreau + * Copyright 2023 Bernhard Miklautz + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include "xf_debug.h" +#include "keyboard_x11.h" +#include "xkb_layout_ids.h" +#include "xf_utils.h" + +static BOOL parse_xkb_rule_names(char* xkb_rule, unsigned long num_bytes, char** layout, + char** variant) +{ + /* Sample output for "Canadian Multilingual Standard" + * + * _XKB_RULES_NAMES_BACKUP(STRING) = "xorg", "pc105", "ca", "multi", "magic" + * + * Format: "rules", "model", "layout", "variant", "options" + * + * Where "xorg" is the set of rules + * "pc105" the keyboard model + * "ca" the keyboard layout(s) (can also be something like 'us,uk') + * "multi" the keyboard layout variant(s) (in the examples, “,winkeys” - which means first + * layout uses some “default” variant and second uses “winkeys” variant) + * "magic" - configuration option (in the examples, + * “eurosign:e,lv3:ralt_switch,grp:rctrl_toggle” + * - three options) + */ + for (size_t i = 0, index = 0; i < num_bytes; i++, index++) + { + char* ptr = xkb_rule + i; + i += strnlen(ptr, num_bytes - i); + + switch (index) + { + case 0: // rules + break; + case 1: // model + break; + case 2: // layout + { + /* If multiple languages are present we just take the first one */ + char* delimiter = strchr(ptr, ','); + if (delimiter) + *delimiter = '\0'; + *layout = ptr; + break; + } + case 3: // variant + { + /* If multiple variants are present we just take the first one */ + char* delimiter = strchr(ptr, ','); + if (delimiter) + *delimiter = '\0'; + *variant = ptr; + } + break; + case 4: // option + break; + default: + break; + } + } + return TRUE; +} + +static DWORD kbd_layout_id_from_x_property(wLog* log, Display* display, Window root, + char* property_name) +{ + char* layout = nullptr; + char* variant = nullptr; + char* rule = nullptr; + Atom type = None; + int item_size = 0; + unsigned long items = 0; + unsigned long unread_items = 0; + DWORD layout_id = 0; + + Atom property = XInternAtom(display, property_name, False); + if (property == None) + return 0; + + if (LogDynAndXGetWindowProperty(log, display, root, property, 0, 1024, False, XA_STRING, &type, + &item_size, &items, &unread_items, + (unsigned char**)&rule) != Success) + return 0; + + if (type != XA_STRING || item_size != 8 || unread_items != 0) + { + XFree(rule); + return 0; + } + + parse_xkb_rule_names(rule, items, &layout, &variant); + + DEBUG_X11("%s layout: %s, variant: %s", property_name, layout, variant); + layout_id = xf_find_keyboard_layout_in_xorg_rules(layout, variant); + + XFree(rule); + + return layout_id; +} + +int xf_detect_keyboard_layout_from_xkb(wLog* log, DWORD* keyboardLayoutId) +{ + Display* display = XOpenDisplay(nullptr); + + if (!display) + return 0; + + Window root = DefaultRootWindow(display); + if (!root) + return 0; + + /* We start by looking for _XKB_RULES_NAMES_BACKUP which appears to be used by libxklavier */ + DWORD id = kbd_layout_id_from_x_property(log, display, root, "_XKB_RULES_NAMES_BACKUP"); + + if (0 == id) + id = kbd_layout_id_from_x_property(log, display, root, "_XKB_RULES_NAMES"); + + if (0 != id) + *keyboardLayoutId = id; + + LogDynAndXCloseDisplay(log, display); + return (int)id; +} diff --git a/third_party/FreeRDP/client/X11/keyboard_x11.h b/third_party/FreeRDP/client/X11/keyboard_x11.h new file mode 100644 index 0000000..1b0a65e --- /dev/null +++ b/third_party/FreeRDP/client/X11/keyboard_x11.h @@ -0,0 +1,25 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Keyboard Mapping + * + * Copyright 2009-2012 Marc-Andre Moreau + * + * 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 +#include + +int xf_detect_keyboard_layout_from_xkb(wLog* log, DWORD* keyboardLayoutId); diff --git a/third_party/FreeRDP/client/X11/man/CMakeLists.txt b/third_party/FreeRDP/client/X11/man/CMakeLists.txt new file mode 100644 index 0000000..452d147 --- /dev/null +++ b/third_party/FreeRDP/client/X11/man/CMakeLists.txt @@ -0,0 +1,14 @@ +set(DEPS + ../../common/man/freerdp-global-options.1 + xfreerdp-shortcuts.1 + ../../common/man/freerdp-global-envvar.1 + ../../common/man/freerdp-global-config.1 + xfreerdp-global-config.1 + xfreerdp-examples.1 + ../../common/man/freerdp-global-links.1 +) + +include(GetSysconfDir) +get_sysconf_dir("" SYSCONF_DIR) +set(VAR_NAMES "VENDOR" "PRODUCT" "VENDOR_PRODUCT" "SYSCONF_DIR") +generate_and_install_freerdp_man_from_xml(${MODULE_NAME} "1" "${DEPS}" "${VAR_NAMES}") diff --git a/third_party/FreeRDP/client/X11/man/xfreerdp-examples.1.in b/third_party/FreeRDP/client/X11/man/xfreerdp-examples.1.in new file mode 100644 index 0000000..5839941 --- /dev/null +++ b/third_party/FreeRDP/client/X11/man/xfreerdp-examples.1.in @@ -0,0 +1,181 @@ +.SH "EXAMPLES" +.PP +.RS 4 +.sp +.if n \{\ +.RS 4 +.\} +.nf +#!/bin/bash + +# we got a key combination +if [ "$1" = "key" ]; +then + # we only got one argument \*(Aqkey\*(Aq + # list all supported combinations with echo + if [ $# \-eq 1 ]; + then + echo "ctrl+alt+f1" + echo "ctrl+alt+f2" + else + # We want the action for a single combination + # use \*(Aqkey\-local\*(Aq to not forward to RDP session + if [ "$2" = "ctrl+alt+f1" ]; + then + echo "key\-local" + fi + if [ "$2" = "ctrl+alt+f2" ]; + then + echo "/usr/local/bin/somescript\&.sh" + fi + fi +fi +if [ "$1" = "xevent" ]; + then + if [ $# \-eq 1 ]; + then + echo "FocusIn" + echo "SelectionClear" + else + if [ "$2" = "SelectionNotify" ]; + then + echo "/usr/local/bin/someprogram" + fi + fi + fi +.fi +.if n \{\ +.RE +.\} +Example action script for key events, listing +\fIctrl+alt+f1\fR +to be handled by local window manager and +\fIctrl+alt+f2\fR +executing a script +.sp +The return value of the program determines if the key is handled locally or remotely (0 for local, > 0 for remote, < 0 for errors) +.RE +.PP +\fB@MANPAGE_NAME@ connection\&.rdp /p:Pwd123! /f\fR +.RS 4 +Connect in fullscreen mode using a stored configuration +\fIconnection\&.rdp\fR +and the password +\fIPwd123!\fR +.RE +.PP +\fB@MANPAGE_NAME@ /u:USER /size:50%h /v:rdp\&.contoso\&.com\fR +.RS 4 +Connect to host +\fIrdp\&.contoso\&.com\fR +with user +\fIUSER\fR +and a size of +\fI50 percent of the height\fR\&. If width (w) is set instead of height (h) like /size:50%w\&. 50 percent of the width is used\&. +.RE +.PP +\fB@MANPAGE_NAME@ /u:CONTOSO\e\eJohnDoe /p:Pwd123! /v:rdp\&.contoso\&.com\fR +.RS 4 +Connect to host +\fIrdp\&.contoso\&.com\fR +with user +\fICONTOSO\e\eJohnDoe\fR +and password +\fIPwd123!\fR +.RE +.PP +\fB@MANPAGE_NAME@ /u:JohnDoe /p:Pwd123! /w:1366 /h:768 /v:192\&.168\&.1\&.100:4489\fR +.RS 4 +Connect to host +\fI192\&.168\&.1\&.100\fR +on port +\fI4489\fR +with user +\fIJohnDoe\fR, password +\fIPwd123!\fR\&. The screen width is set to +\fI1366\fR +and the height to +\fI768\fR +.RE +.PP +\fB@MANPAGE_NAME@ /u:JohnDoe /p:Pwd123! /vmconnect:C824F53E\-95D2\-46C6\-9A18\-23A5BB403532 /v:192\&.168\&.1\&.100\fR +.RS 4 +Establish a connection to host +\fI192\&.168\&.1\&.100\fR +with user +\fIJohnDoe\fR, password +\fIPwd123!\fR +and connect to Hyper\-V console (use port 2179, disable negotiation) with VMID +\fIC824F53E\-95D2\-46C6\-9A18\-23A5BB403532\fR +.RE +.PP +\fB+clipboard\fR +.RS 4 +Activate clipboard redirection +.RE +.PP +\fB/drive:home,/home/user\fR +.RS 4 +Activate drive redirection of +\fI/home/user\fR +as home drive +.RE +.PP +\fB/smartcard:\fR +.RS 4 +Activate smartcard redirection for device +\fIdevice\fR +.RE +.PP +\fB/printer:,\fR +.RS 4 +Activate printer redirection for printer +\fIdevice\fR +using driver +\fIdriver\fR +.RE +.PP +\fB/serial:\fR +.RS 4 +Activate serial port redirection for port +\fIdevice\fR +.RE +.PP +\fB/parallel:\fR +.RS 4 +Activate parallel port redirection for port +\fIdevice\fR +.RE +.PP +\fB/sound:sys:alsa\fR +.RS 4 +Activate audio output redirection using device +\fIsys:alsa\fR +.RE +.PP +\fB/microphone:sys:alsa\fR +.RS 4 +Activate audio input redirection using device +\fIsys:alsa\fR +.RE +.PP +\fB/multimedia:sys:alsa\fR +.RS 4 +Activate multimedia redirection using device +\fIsys:alsa\fR +.RE +.PP +\fB/usb:id,dev:054c:0268\fR +.RS 4 +Activate USB device redirection for the device identified by +\fI054c:0268\fR +.RE +.PP +\fB/kbd:remap:29=58,remap:58=29\fR +.RS 4 +Swap left Control (\ +\fI29\fR\ +) and Caps Lock (\ +\fI58\fR\ +). +.RE diff --git a/third_party/FreeRDP/client/X11/man/xfreerdp-global-config.1.in b/third_party/FreeRDP/client/X11/man/xfreerdp-global-config.1.in new file mode 100644 index 0000000..fb84ed4 --- /dev/null +++ b/third_party/FreeRDP/client/X11/man/xfreerdp-global-config.1.in @@ -0,0 +1,22 @@ +.SH "GLOBAL CONFIGURATION (X11 client)" +.PP +The X11 client configuration location is +\fI@SYSCONF_DIR@/xfreerdp\&.json\fR +.br + +File format is JSON +.RE +.PP +Supported options: +.RS 4 +.PP +\fIisActionScriptAllowed\fR +.RS 4 +.PP +.RS 4 +\fIJSON boolean\fR +.br + +Allow or block action scripts. Default (if option is not set) is to allow action scripts. +.RE +.RE diff --git a/third_party/FreeRDP/client/X11/man/xfreerdp-shortcuts.1.in b/third_party/FreeRDP/client/X11/man/xfreerdp-shortcuts.1.in new file mode 100644 index 0000000..4d7004c --- /dev/null +++ b/third_party/FreeRDP/client/X11/man/xfreerdp-shortcuts.1.in @@ -0,0 +1,44 @@ +.SH "KEYBOARD SHORTCUTS" +.PP + +.RS 4 +releases keyboard and mouse grab. +.br +If keyboard is grabbed the local system shortcuts do no longer work and are sent to the remote system. +.br +If the Mouse is grabbed (optional) local gesture detection does not work and the mouse might not be able to leave the RDP window. Mouse events are not altered. +.RE +.PP +++ +.RS 4 +toggles fullscreen state of the application +.RE +.PP +++ +.RS 4 +Minimizes the application +.RE +.PP +++c +.RS 4 +toggles remote control in a remote assistance session +.RE +.PP +++ +.RS 4 +Disconnect the session and terminate application +.RE +.PP +Action Script +.RS 4 +executes a predefined script on key press\&. +Should the script not exist it is ignored\&. +Scripts can be provided at the default location +\fI$XDG_CONFIG_HOME/freerdp/action\&.sh\fR +or as command line argument +\fI/action:script:\fR\&. +The script will receive the current key combination as argument\&. +The output of the script is parsed for +\fIkey\-local\fR +which tells that the script used the key combination, otherwise the combination is forwarded to the remote\&. +.RE diff --git a/third_party/FreeRDP/client/X11/man/xfreerdp.1.in b/third_party/FreeRDP/client/X11/man/xfreerdp.1.in new file mode 100644 index 0000000..83538a0 --- /dev/null +++ b/third_party/FreeRDP/client/X11/man/xfreerdp.1.in @@ -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 X11 client +.SH "SYNOPSIS" +.PP +\fB@MANPAGE_NAME@\fR +[file] [options] [/v:server[:port]] +.SH "DESCRIPTION" +.PP +\fB@MANPAGE_NAME@\fR +is an X11 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)\&. diff --git a/third_party/FreeRDP/client/X11/resource/close.xbm b/third_party/FreeRDP/client/X11/resource/close.xbm new file mode 100644 index 0000000..45c60e3 --- /dev/null +++ b/third_party/FreeRDP/client/X11/resource/close.xbm @@ -0,0 +1,11 @@ +#define close_width 24 +#define close_height 24 +static unsigned char close_bits[] = +{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x7c, 0xfe, 0xff, 0x38, 0xfe, 0xff, 0x11, 0xff, 0xff, 0x83, 0xff, + 0xff, 0xc7, 0xff, 0xff, 0x83, 0xff, 0xff, 0x11, 0xff, 0xff, 0x38, 0xfe, + 0xff, 0x7c, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; diff --git a/third_party/FreeRDP/client/X11/resource/lock.xbm b/third_party/FreeRDP/client/X11/resource/lock.xbm new file mode 100644 index 0000000..12340f5 --- /dev/null +++ b/third_party/FreeRDP/client/X11/resource/lock.xbm @@ -0,0 +1,11 @@ +#define lock_width 24 +#define lock_height 24 +static unsigned char lock_bits[] = +{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x83, 0xff, + 0xff, 0x83, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xc7, 0xff, 0xff, 0xc7, 0xff, + 0xff, 0x00, 0xfe, 0xff, 0x00, 0xfe, 0xff, 0xef, 0xff, 0xff, 0xef, 0xff, + 0xff, 0xef, 0xff, 0xff, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; diff --git a/third_party/FreeRDP/client/X11/resource/minimize.xbm b/third_party/FreeRDP/client/X11/resource/minimize.xbm new file mode 100644 index 0000000..c69d861 --- /dev/null +++ b/third_party/FreeRDP/client/X11/resource/minimize.xbm @@ -0,0 +1,11 @@ +#define minimize_width 24 +#define minimize_height 24 +static unsigned char minimize_bits[] = +{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0xfc, + 0x3f, 0x00, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; diff --git a/third_party/FreeRDP/client/X11/resource/restore.xbm b/third_party/FreeRDP/client/X11/resource/restore.xbm new file mode 100644 index 0000000..e9909f5 --- /dev/null +++ b/third_party/FreeRDP/client/X11/resource/restore.xbm @@ -0,0 +1,11 @@ +#define restore_width 24 +#define restore_height 24 +static unsigned char restore_bits[] = +{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x03, 0xff, 0xff, 0x03, 0xff, 0xff, 0x3b, 0xff, 0x7f, 0x20, 0xff, + 0x7f, 0x20, 0xff, 0x7f, 0x07, 0xff, 0x7f, 0xe7, 0xff, 0x7f, 0xe7, 0xff, + 0x7f, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; diff --git a/third_party/FreeRDP/client/X11/resource/unlock.xbm b/third_party/FreeRDP/client/X11/resource/unlock.xbm new file mode 100644 index 0000000..a809126 --- /dev/null +++ b/third_party/FreeRDP/client/X11/resource/unlock.xbm @@ -0,0 +1,11 @@ +#define unlock_width 24 +#define unlock_height 24 +static unsigned char unlock_bits[] = +{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf3, 0xff, 0xff, 0xf3, 0xff, 0xff, 0x73, 0xfe, 0xff, 0x03, 0xfe, + 0x3f, 0x00, 0xfe, 0xff, 0x03, 0xfe, 0xff, 0x73, 0xfe, 0xff, 0xf3, 0xff, + 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; diff --git a/third_party/FreeRDP/client/X11/xf_channels.c b/third_party/FreeRDP/client/X11/xf_channels.c new file mode 100644 index 0000000..76d0f1f --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_channels.c @@ -0,0 +1,132 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Client Channels + * + * Copyright 2013 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include "xf_channels.h" + +#include "xf_client.h" +#include "xfreerdp.h" + +#include "xf_gfx.h" +#if defined(CHANNEL_TSMF_CLIENT) +#include "xf_tsmf.h" +#endif +#include "xf_rail.h" +#include "xf_cliprdr.h" +#include "xf_disp.h" +#include "xf_video.h" + +void xf_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e) +{ + xfContext* xfc = (xfContext*)context; + rdpSettings* settings = nullptr; + + WINPR_ASSERT(xfc); + WINPR_ASSERT(e); + WINPR_ASSERT(e->name); + + settings = xfc->common.context.settings; + WINPR_ASSERT(settings); + + if (FALSE) + { + } +#if defined(CHANNEL_TSMF_CLIENT) + else if (strcmp(e->name, TSMF_DVC_CHANNEL_NAME) == 0) + { + xf_tsmf_init(xfc, (TsmfClientContext*)e->pInterface); + } +#endif + else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0) + { + xf_graphics_pipeline_init(xfc, (RdpgfxClientContext*)e->pInterface); + } + else if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0) + { + xf_rail_init(xfc, (RailClientContext*)e->pInterface); + } + else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) + { + xf_cliprdr_init(xfc, (CliprdrClientContext*)e->pInterface); + } + else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0) + { + xf_disp_init(xfc->xfDisp, (DispClientContext*)e->pInterface); + } + else if (strcmp(e->name, VIDEO_CONTROL_DVC_CHANNEL_NAME) == 0) + { + if (freerdp_settings_get_bool(settings, FreeRDP_SoftwareGdi)) + gdi_video_control_init(xfc->common.context.gdi, (VideoClientContext*)e->pInterface); + else + xf_video_control_init(xfc, (VideoClientContext*)e->pInterface); + } + else + freerdp_client_OnChannelConnectedEventHandler(context, e); +} + +void xf_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e) +{ + xfContext* xfc = (xfContext*)context; + rdpSettings* settings = nullptr; + + WINPR_ASSERT(xfc); + WINPR_ASSERT(e); + WINPR_ASSERT(e->name); + + settings = xfc->common.context.settings; + WINPR_ASSERT(settings); + + if (FALSE) + { + } + else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0) + { + xf_disp_uninit(xfc->xfDisp, (DispClientContext*)e->pInterface); + } +#if defined(CHANNEL_TSMF_CLIENT) + else if (strcmp(e->name, TSMF_DVC_CHANNEL_NAME) == 0) + { + xf_tsmf_uninit(xfc, (TsmfClientContext*)e->pInterface); + } +#endif + else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0) + { + xf_graphics_pipeline_uninit(xfc, (RdpgfxClientContext*)e->pInterface); + } + else if (strcmp(e->name, RAIL_SVC_CHANNEL_NAME) == 0) + { + xf_rail_uninit(xfc, (RailClientContext*)e->pInterface); + } + else if (strcmp(e->name, CLIPRDR_SVC_CHANNEL_NAME) == 0) + { + xf_cliprdr_uninit(xfc, (CliprdrClientContext*)e->pInterface); + } + else if (strcmp(e->name, VIDEO_CONTROL_DVC_CHANNEL_NAME) == 0) + { + if (freerdp_settings_get_bool(settings, FreeRDP_SoftwareGdi)) + gdi_video_control_uninit(xfc->common.context.gdi, (VideoClientContext*)e->pInterface); + else + xf_video_control_uninit(xfc, (VideoClientContext*)e->pInterface); + } + else + freerdp_client_OnChannelDisconnectedEventHandler(context, e); +} diff --git a/third_party/FreeRDP/client/X11/xf_channels.h b/third_party/FreeRDP/client/X11/xf_channels.h new file mode 100644 index 0000000..86b00b9 --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_channels.h @@ -0,0 +1,37 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Client Channels + * + * Copyright 2013 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_X11_CHANNELS_H +#define FREERDP_CLIENT_X11_CHANNELS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void xf_OnChannelConnectedEventHandler(void* context, const ChannelConnectedEventArgs* e); +void xf_OnChannelDisconnectedEventHandler(void* context, const ChannelDisconnectedEventArgs* e); + +#endif /* FREERDP_CLIENT_X11_CHANNELS_H */ diff --git a/third_party/FreeRDP/client/X11/xf_client.c b/third_party/FreeRDP/client/X11/xf_client.c new file mode 100644 index 0000000..17d4e21 --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_client.c @@ -0,0 +1,2104 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Client Interface + * + * Copyright 2013 Marc-Andre Moreau + * Copyright 2013 Corey Clayton + * Copyright 2014 Thincast Technologies GmbH + * Copyright 2014 Norbert Federa + * Copyright 2016 Armin Novak + * Copyright 2016 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 + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#ifdef WITH_XRENDER +#include +#endif + +#ifdef WITH_XI +#include +#include +#endif + +#ifdef WITH_XCURSOR +#include +#endif + +#ifdef WITH_XINERAMA +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "xf_rail.h" +#if defined(CHANNEL_TSMF_CLIENT) +#include "xf_tsmf.h" +#endif +#include "xf_event.h" +#include "xf_input.h" +#include "xf_cliprdr.h" +#include "xf_disp.h" +#include "xf_video.h" +#include "xf_monitor.h" +#include "xf_graphics.h" +#include "xf_keyboard.h" +#include "xf_channels.h" +#include "xf_client.h" +#include "xfreerdp.h" +#include "xf_utils.h" + +#include +#define TAG CLIENT_TAG("x11") + +#define MIN_PIXEL_DIFF 0.001 + +struct xf_exit_code_map_t +{ + DWORD error; + int rc; +}; +static const struct xf_exit_code_map_t xf_exit_code_map[] = { + { FREERDP_ERROR_SUCCESS, XF_EXIT_SUCCESS }, + { FREERDP_ERROR_AUTHENTICATION_FAILED, XF_EXIT_AUTH_FAILURE }, + { FREERDP_ERROR_SECURITY_NEGO_CONNECT_FAILED, XF_EXIT_NEGO_FAILURE }, + { FREERDP_ERROR_CONNECT_LOGON_FAILURE, XF_EXIT_LOGON_FAILURE }, + { FREERDP_ERROR_CONNECT_ACCOUNT_LOCKED_OUT, XF_EXIT_ACCOUNT_LOCKED_OUT }, + { FREERDP_ERROR_PRE_CONNECT_FAILED, XF_EXIT_PRE_CONNECT_FAILED }, + { FREERDP_ERROR_CONNECT_UNDEFINED, XF_EXIT_CONNECT_UNDEFINED }, + { FREERDP_ERROR_POST_CONNECT_FAILED, XF_EXIT_POST_CONNECT_FAILED }, + { FREERDP_ERROR_DNS_ERROR, XF_EXIT_DNS_ERROR }, + { FREERDP_ERROR_DNS_NAME_NOT_FOUND, XF_EXIT_DNS_NAME_NOT_FOUND }, + { FREERDP_ERROR_CONNECT_FAILED, XF_EXIT_CONNECT_FAILED }, + { FREERDP_ERROR_MCS_CONNECT_INITIAL_ERROR, XF_EXIT_MCS_CONNECT_INITIAL_ERROR }, + { FREERDP_ERROR_TLS_CONNECT_FAILED, XF_EXIT_TLS_CONNECT_FAILED }, + { FREERDP_ERROR_INSUFFICIENT_PRIVILEGES, XF_EXIT_INSUFFICIENT_PRIVILEGES }, + { FREERDP_ERROR_CONNECT_CANCELLED, XF_EXIT_CONNECT_CANCELLED }, + { FREERDP_ERROR_CONNECT_TRANSPORT_FAILED, XF_EXIT_CONNECT_TRANSPORT_FAILED }, + { FREERDP_ERROR_CONNECT_PASSWORD_EXPIRED, XF_EXIT_CONNECT_PASSWORD_EXPIRED }, + { FREERDP_ERROR_CONNECT_PASSWORD_MUST_CHANGE, XF_EXIT_CONNECT_PASSWORD_MUST_CHANGE }, + { FREERDP_ERROR_CONNECT_KDC_UNREACHABLE, XF_EXIT_CONNECT_KDC_UNREACHABLE }, + { FREERDP_ERROR_CONNECT_ACCOUNT_DISABLED, XF_EXIT_CONNECT_ACCOUNT_DISABLED }, + { FREERDP_ERROR_CONNECT_PASSWORD_CERTAINLY_EXPIRED, + XF_EXIT_CONNECT_PASSWORD_CERTAINLY_EXPIRED }, + { FREERDP_ERROR_CONNECT_CLIENT_REVOKED, XF_EXIT_CONNECT_CLIENT_REVOKED }, + { FREERDP_ERROR_CONNECT_WRONG_PASSWORD, XF_EXIT_CONNECT_WRONG_PASSWORD }, + { FREERDP_ERROR_CONNECT_ACCESS_DENIED, XF_EXIT_CONNECT_ACCESS_DENIED }, + { FREERDP_ERROR_CONNECT_ACCOUNT_RESTRICTION, XF_EXIT_CONNECT_ACCOUNT_RESTRICTION }, + { FREERDP_ERROR_CONNECT_ACCOUNT_EXPIRED, XF_EXIT_CONNECT_ACCOUNT_EXPIRED }, + { FREERDP_ERROR_CONNECT_LOGON_TYPE_NOT_GRANTED, XF_EXIT_CONNECT_LOGON_TYPE_NOT_GRANTED }, + { FREERDP_ERROR_CONNECT_NO_OR_MISSING_CREDENTIALS, XF_EXIT_CONNECT_NO_OR_MISSING_CREDENTIALS }, + { FREERDP_ERROR_CONNECT_TARGET_BOOTING, XF_EXIT_CONNECT_TARGET_BOOTING } +}; + +static BOOL xf_setup_x11(xfContext* xfc); +static void xf_teardown_x11(xfContext* xfc); + +static int xf_map_error_to_exit_code(DWORD error) +{ + for (size_t x = 0; x < ARRAYSIZE(xf_exit_code_map); x++) + { + const struct xf_exit_code_map_t* cur = &xf_exit_code_map[x]; + if (cur->error == error) + return cur->rc; + } + + return XF_EXIT_CONN_FAILED; +} + +static int (*def_error_handler)(Display*, XErrorEvent*); +static int xf_error_handler_ex(Display* d, XErrorEvent* ev); +static void xf_check_extensions(xfContext* context); +static BOOL xf_get_pixmap_info(xfContext* xfc); + +#ifdef WITH_XRENDER +static void xf_draw_screen_scaled(xfContext* xfc, int x, int y, int w, int h) +{ + XTransform transform = WINPR_C_ARRAY_INIT; + Picture windowPicture = 0; + Picture primaryPicture = 0; + XRenderPictureAttributes pa; + XRenderPictFormat* picFormat = nullptr; + int x2 = 0; + int y2 = 0; + const char* filter = nullptr; + WINPR_ASSERT(xfc); + + rdpSettings* settings = xfc->common.context.settings; + WINPR_ASSERT(settings); + + if (xfc->scaledWidth <= 0 || xfc->scaledHeight <= 0) + { + WLog_ERR(TAG, "the current window dimensions are invalid"); + return; + } + + if (freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) <= 0 || + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) <= 0) + { + WLog_ERR(TAG, "the window dimensions are invalid"); + return; + } + + const double xScalingFactor = 1.0 * + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) / + (double)xfc->scaledWidth; + const double yScalingFactor = 1.0 * + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) / + (double)xfc->scaledHeight; + LogDynAndXSetFillStyle(xfc->log, xfc->display, xfc->gc, FillSolid); + LogDynAndXSetForeground(xfc->log, xfc->display, xfc->gc, 0); + /* Black out possible space between desktop and window borders */ + { + XRectangle box1 = { 0, 0, WINPR_ASSERTING_INT_CAST(UINT16, xfc->window->width), + WINPR_ASSERTING_INT_CAST(UINT16, xfc->window->height) }; + XRectangle box2 = { WINPR_ASSERTING_INT_CAST(INT16, xfc->offset_x), + WINPR_ASSERTING_INT_CAST(INT16, xfc->offset_y), + WINPR_ASSERTING_INT_CAST(UINT16, xfc->scaledWidth), + WINPR_ASSERTING_INT_CAST(UINT16, xfc->scaledHeight) }; + Region reg1 = XCreateRegion(); + Region reg2 = XCreateRegion(); + XUnionRectWithRegion(&box1, reg1, reg1); + XUnionRectWithRegion(&box2, reg2, reg2); + + if (XSubtractRegion(reg1, reg2, reg1) && !XEmptyRegion(reg1)) + { + LogDynAndXSetRegion(xfc->log, xfc->display, xfc->gc, reg1); + LogDynAndXFillRectangle(xfc->log, xfc->display, xfc->window->handle, xfc->gc, 0, 0, + WINPR_ASSERTING_INT_CAST(UINT16, xfc->window->width), + WINPR_ASSERTING_INT_CAST(UINT16, xfc->window->height)); + LogDynAndXSetClipMask(xfc->log, xfc->display, xfc->gc, None); + } + + XDestroyRegion(reg1); + XDestroyRegion(reg2); + } + picFormat = XRenderFindVisualFormat(xfc->display, xfc->visual); + pa.subwindow_mode = IncludeInferiors; + primaryPicture = + XRenderCreatePicture(xfc->display, xfc->primary, picFormat, CPSubwindowMode, &pa); + windowPicture = + XRenderCreatePicture(xfc->display, xfc->window->handle, picFormat, CPSubwindowMode, &pa); + /* avoid blurry filter when scaling factor is 2x, 3x, etc + * useful when the client has high-dpi monitor */ + filter = FilterBilinear; + if (fabs(xScalingFactor - yScalingFactor) < MIN_PIXEL_DIFF) + { + const double inverseX = 1.0 / xScalingFactor; + const double inverseRoundedX = round(inverseX); + const double absInverse = fabs(inverseX - inverseRoundedX); + + if (absInverse < MIN_PIXEL_DIFF) + filter = FilterNearest; + } + XRenderSetPictureFilter(xfc->display, primaryPicture, filter, nullptr, 0); + transform.matrix[0][0] = XDoubleToFixed(xScalingFactor); + transform.matrix[0][1] = XDoubleToFixed(0.0); + transform.matrix[0][2] = XDoubleToFixed(0.0); + transform.matrix[1][0] = XDoubleToFixed(0.0); + transform.matrix[1][1] = XDoubleToFixed(yScalingFactor); + transform.matrix[1][2] = XDoubleToFixed(0.0); + transform.matrix[2][0] = XDoubleToFixed(0.0); + transform.matrix[2][1] = XDoubleToFixed(0.0); + transform.matrix[2][2] = XDoubleToFixed(1.0); + /* calculate and fix up scaled coordinates */ + x2 = x + w; + y2 = y + h; + + const double dx1 = floor(x / xScalingFactor); + const double dy1 = floor(y / yScalingFactor); + const double dx2 = ceil(x2 / xScalingFactor); + const double dy2 = ceil(y2 / yScalingFactor); + x = ((int)dx1) - 1; + y = ((int)dy1) - 1; + w = ((int)dx2) + 1 - x; + h = ((int)dy2) + 1 - y; + XRenderSetPictureTransform(xfc->display, primaryPicture, &transform); + XRenderComposite(xfc->display, PictOpSrc, primaryPicture, 0, windowPicture, x, y, 0, 0, + xfc->offset_x + x, xfc->offset_y + y, WINPR_ASSERTING_INT_CAST(uint32_t, w), + WINPR_ASSERTING_INT_CAST(uint32_t, h)); + XRenderFreePicture(xfc->display, primaryPicture); + XRenderFreePicture(xfc->display, windowPicture); +} + +BOOL xf_picture_transform_required(xfContext* xfc) +{ + rdpSettings* settings = nullptr; + + WINPR_ASSERT(xfc); + + settings = xfc->common.context.settings; + WINPR_ASSERT(settings); + + return ( + (xfc->offset_x != 0) || (xfc->offset_y != 0) || + (xfc->scaledWidth != (INT64)freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth)) || + (xfc->scaledHeight != (INT64)freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight))); +} +#endif /* WITH_XRENDER defined */ + +void xf_draw_screen_(xfContext* xfc, int x, int y, int w, int h, const char* fkt, + WINPR_ATTR_UNUSED const char* file, WINPR_ATTR_UNUSED int line) +{ + if (!xfc) + { + WLog_DBG(TAG, "called from [%s] xfc=nullptr", fkt); + return; + } + + if (w == 0 || h == 0) + { + WLog_WARN(TAG, "invalid width and/or height specified: w=%d h=%d", w, h); + return; + } + + if (!xfc->window) + { + WLog_WARN(TAG, "invalid xfc->window=nullptr"); + return; + } + +#ifdef WITH_XRENDER + + if (xf_picture_transform_required(xfc)) + { + xf_draw_screen_scaled(xfc, x, y, w, h); + return; + } + +#endif + LogDynAndXCopyArea(xfc->log, xfc->display, xfc->primary, xfc->window->handle, xfc->gc, x, y, + WINPR_ASSERTING_INT_CAST(uint32_t, w), WINPR_ASSERTING_INT_CAST(uint32_t, h), + x, y); +} + +static BOOL xf_desktop_resize(rdpContext* context) +{ + xfContext* xfc = (xfContext*)context; + + WINPR_ASSERT(xfc); + + rdpSettings* settings = context->settings; + WINPR_ASSERT(settings); + + if (xfc->primary) + { + BOOL same = (xfc->primary == xfc->drawing); + LogDynAndXFreePixmap(xfc->log, xfc->display, xfc->primary); + + WINPR_ASSERT(xfc->depth != 0); + if (!(xfc->primary = LogDynAndXCreatePixmap( + xfc->log, xfc->display, xfc->drawable, + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight), + WINPR_ASSERTING_INT_CAST(uint32_t, xfc->depth)))) + return FALSE; + + if (same) + xfc->drawing = xfc->primary; + } + +#ifdef WITH_XRENDER + + if (!freerdp_settings_get_bool(settings, FreeRDP_SmartSizing)) + { + xfc->scaledWidth = WINPR_ASSERTING_INT_CAST( + int, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth)); + xfc->scaledHeight = WINPR_ASSERTING_INT_CAST( + int, freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)); + } + +#endif + + if (!xfc->fullscreen) + { + xf_ResizeDesktopWindow( + xfc, xfc->window, + WINPR_ASSERTING_INT_CAST(int, + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth)), + WINPR_ASSERTING_INT_CAST(int, + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight))); + } + else + { +#ifdef WITH_XRENDER + + if (!freerdp_settings_get_bool(settings, FreeRDP_SmartSizing)) +#endif + { + /* Update the saved width and height values the window will be + * resized to when toggling out of fullscreen */ + xfc->savedWidth = WINPR_ASSERTING_INT_CAST( + int, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth)); + xfc->savedHeight = WINPR_ASSERTING_INT_CAST( + int, freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)); + } + + LogDynAndXSetFunction(xfc->log, xfc->display, xfc->gc, GXcopy); + LogDynAndXSetFillStyle(xfc->log, xfc->display, xfc->gc, FillSolid); + LogDynAndXSetForeground(xfc->log, xfc->display, xfc->gc, 0); + LogDynAndXFillRectangle(xfc->log, xfc->display, xfc->drawable, xfc->gc, 0, 0, + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)); + } + + return TRUE; +} + +static BOOL xf_paint(xfContext* xfc, const GDI_RGN* region) +{ + WINPR_ASSERT(xfc); + WINPR_ASSERT(region); + + if (xfc->remote_app) + { + const RECTANGLE_16 rect = { .left = WINPR_ASSERTING_INT_CAST(UINT16, region->x), + .top = WINPR_ASSERTING_INT_CAST(UINT16, region->y), + .right = + WINPR_ASSERTING_INT_CAST(UINT16, region->x + region->w), + .bottom = + WINPR_ASSERTING_INT_CAST(UINT16, region->y + region->h) }; + xf_rail_paint(xfc, &rect); + } + else + { + LogDynAndXPutImage(xfc->log, xfc->display, xfc->primary, xfc->gc, xfc->image, region->x, + region->y, region->x, region->y, + WINPR_ASSERTING_INT_CAST(UINT16, region->w), + WINPR_ASSERTING_INT_CAST(UINT16, region->h)); + xf_draw_screen(xfc, region->x, region->y, region->w, region->h); + } + return TRUE; +} + +static BOOL xf_end_paint(rdpContext* context) +{ + xfContext* xfc = (xfContext*)context; + WINPR_ASSERT(xfc); + + rdpGdi* gdi = context->gdi; + WINPR_ASSERT(gdi); + + if (gdi->suppressOutput) + return TRUE; + + HGDI_DC hdc = gdi->primary->hdc; + if (!hdc->hwnd) + return TRUE; + + HGDI_WND hwnd = hdc->hwnd; + if (!xfc->complex_regions) + { + const GDI_RGN* rgn = hwnd->invalid; + if (rgn->null) + return TRUE; + xf_lock_x11(xfc); + if (!xf_paint(xfc, rgn)) + return FALSE; + xf_unlock_x11(xfc); + } + else + { + const INT32 ninvalid = hwnd->ninvalid; + const GDI_RGN* cinvalid = hwnd->cinvalid; + + if (hwnd->ninvalid < 1) + return TRUE; + + xf_lock_x11(xfc); + + for (INT32 i = 0; i < ninvalid; i++) + { + const GDI_RGN* rgn = &cinvalid[i]; + if (!xf_paint(xfc, rgn)) + return FALSE; + } + + LogDynAndXFlush(xfc->log, xfc->display); + xf_unlock_x11(xfc); + } + + hwnd->invalid->null = TRUE; + hwnd->ninvalid = 0; + return TRUE; +} + +static BOOL xf_sw_desktop_resize(rdpContext* context) +{ + WINPR_ASSERT(context); + + rdpGdi* gdi = context->gdi; + WINPR_ASSERT(gdi); + + xfContext* xfc = (xfContext*)context; + rdpSettings* settings = context->settings; + WINPR_ASSERT(settings); + + BOOL ret = FALSE; + + /* There is a possible race here. + * Ensure that the drawing thread does not update the screen during a + * resize. */ + const BOOL suppress = gdi->suppressOutput; + gdi->suppressOutput = TRUE; + + xf_lock_x11(xfc); + if (!gdi_resize(gdi, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight))) + goto out; + + if (xfc->image) + { + xfc->image->data = nullptr; + XDestroyImage(xfc->image); + } + + WINPR_ASSERT(xfc->depth != 0); + if (!(xfc->image = LogDynAndXCreateImage( + xfc->log, xfc->display, xfc->visual, WINPR_ASSERTING_INT_CAST(uint32_t, xfc->depth), + ZPixmap, 0, (char*)gdi->primary_buffer, + WINPR_ASSERTING_INT_CAST(uint32_t, gdi->width), + WINPR_ASSERTING_INT_CAST(uint32_t, gdi->height), xfc->scanline_pad, + WINPR_ASSERTING_INT_CAST(int, gdi->stride)))) + { + goto out; + } + + xfc->image->byte_order = LSBFirst; + xfc->image->bitmap_bit_order = LSBFirst; + ret = xf_desktop_resize(context); +out: + xf_unlock_x11(xfc); + gdi->suppressOutput = suppress; + return ret; +} + +static BOOL xf_process_x_events(freerdp* instance) +{ + BOOL status = TRUE; + int pending_status = 1; + xfContext* xfc = (xfContext*)instance->context; + + while (pending_status) + { + xf_lock_x11(xfc); + pending_status = XPending(xfc->display); + + if (pending_status) + { + XEvent xevent = WINPR_C_ARRAY_INIT; + + XNextEvent(xfc->display, &xevent); + status = xf_event_process(instance, &xevent); + } + xf_unlock_x11(xfc); + if (!status) + break; + } + + return status; +} + +static char* xf_window_get_title(rdpSettings* settings) +{ + BOOL port = 0; + char* windowTitle = nullptr; + size_t size = 0; + const char* prefix = "FreeRDP:"; + + if (!settings) + return nullptr; + + const char* name = freerdp_settings_get_string(settings, FreeRDP_ServerHostname); + const char* title = freerdp_settings_get_string(settings, FreeRDP_WindowTitle); + + if (title) + return _strdup(title); + + port = (freerdp_settings_get_uint32(settings, FreeRDP_ServerPort) != 3389); + /* Just assume a window title is never longer than a filename... */ + size = strnlen(name, MAX_PATH) + 16; + windowTitle = calloc(size, sizeof(char)); + + if (!windowTitle) + return nullptr; + + if (!port) + (void)sprintf_s(windowTitle, size, "%s %s", prefix, name); + else + (void)sprintf_s(windowTitle, size, "%s %s:%" PRIu32, prefix, name, + freerdp_settings_get_uint32(settings, FreeRDP_ServerPort)); + + return windowTitle; +} + +BOOL xf_create_window(xfContext* xfc) +{ + XGCValues gcv = WINPR_C_ARRAY_INIT; + XEvent xevent = WINPR_C_ARRAY_INIT; + char* windowTitle = nullptr; + + WINPR_ASSERT(xfc); + rdpSettings* settings = xfc->common.context.settings; + WINPR_ASSERT(settings); + + int width = + WINPR_ASSERTING_INT_CAST(int, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth)); + int height = + WINPR_ASSERTING_INT_CAST(int, freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)); + + const XSetWindowAttributes empty = WINPR_C_ARRAY_INIT; + xfc->attribs = empty; + + if (xfc->remote_app) + xfc->depth = 32; + else + xfc->depth = DefaultDepthOfScreen(xfc->screen); + + XVisualInfo vinfo = WINPR_C_ARRAY_INIT; + if (XMatchVisualInfo(xfc->display, xfc->screen_number, xfc->depth, TrueColor, &vinfo)) + { + Window root = XDefaultRootWindow(xfc->display); + xfc->visual = vinfo.visual; + xfc->attribs.colormap = xfc->colormap = + XCreateColormap(xfc->display, root, vinfo.visual, AllocNone); + } + else + { + if (xfc->remote_app) + { + WLog_WARN(TAG, "running in remote app mode, but XServer does not support transparency"); + WLog_WARN(TAG, "display of remote applications might be distorted (black frames, ...)"); + } + xfc->depth = DefaultDepthOfScreen(xfc->screen); + xfc->visual = DefaultVisual(xfc->display, xfc->screen_number); + xfc->attribs.colormap = xfc->colormap = DefaultColormap(xfc->display, xfc->screen_number); + } + + /* + * Detect if the server visual has an inverted colormap + * (BGR vs RGB, or red being the least significant byte) + */ + if (vinfo.red_mask & 0xFF) + { + xfc->invert = FALSE; + } + + if (!xfc->remote_app) + { + xfc->attribs.background_pixel = BlackPixelOfScreen(xfc->screen); + xfc->attribs.border_pixel = WhitePixelOfScreen(xfc->screen); + xfc->attribs.backing_store = xfc->primary ? NotUseful : Always; + xfc->attribs.override_redirect = False; + + xfc->attribs.bit_gravity = NorthWestGravity; + xfc->attribs.win_gravity = NorthWestGravity; + xfc->attribs_mask = CWBackPixel | CWBackingStore | CWOverrideRedirect | CWColormap | + CWBorderPixel | CWWinGravity | CWBitGravity; + +#ifdef WITH_XRENDER + xfc->offset_x = 0; + xfc->offset_y = 0; +#endif + windowTitle = xf_window_get_title(settings); + + if (!windowTitle) + return FALSE; + +#ifdef WITH_XRENDER + + if (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) && !xfc->fullscreen) + { + if (freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingWidth) > 0) + width = WINPR_ASSERTING_INT_CAST( + int, freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingWidth)); + + if (freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingHeight) > 0) + height = WINPR_ASSERTING_INT_CAST( + int, freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingHeight)); + + xfc->scaledWidth = width; + xfc->scaledHeight = height; + } + +#endif + xfc->window = xf_CreateDesktopWindow(xfc, windowTitle, width, height); + free(windowTitle); + + if (xfc->fullscreen) + xf_SetWindowFullscreen(xfc, xfc->window, xfc->fullscreen); + + xfc->unobscured = (xevent.xvisibility.state == VisibilityUnobscured); + XSetWMProtocols(xfc->display, xfc->window->handle, &(xfc->WM_DELETE_WINDOW), 1); + xfc->drawable = xfc->window->handle; + } + else + { + xfc->attribs.border_pixel = 0; + xfc->attribs.background_pixel = 0; + xfc->attribs.backing_store = xfc->primary ? NotUseful : Always; + xfc->attribs.override_redirect = False; + + xfc->attribs.bit_gravity = NorthWestGravity; + xfc->attribs.win_gravity = NorthWestGravity; + xfc->attribs_mask = CWBackPixel | CWBackingStore | CWOverrideRedirect | CWColormap | + CWBorderPixel | CWWinGravity | CWBitGravity; + + xfc->drawable = xf_CreateDummyWindow(xfc); + } + + if (!xfc->gc) + xfc->gc = + LogDynAndXCreateGC(xfc->log, xfc->display, xfc->drawable, GCGraphicsExposures, &gcv); + + WINPR_ASSERT(xfc->depth != 0); + if (!xfc->primary) + xfc->primary = + LogDynAndXCreatePixmap(xfc->log, xfc->display, xfc->drawable, + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight), + WINPR_ASSERTING_INT_CAST(uint32_t, xfc->depth)); + + xfc->drawing = xfc->primary; + + if (!xfc->bitmap_mono) + xfc->bitmap_mono = LogDynAndXCreatePixmap(xfc->log, xfc->display, xfc->drawable, 8, 8, 1); + + if (!xfc->gc_mono) + xfc->gc_mono = + LogDynAndXCreateGC(xfc->log, xfc->display, xfc->bitmap_mono, GCGraphicsExposures, &gcv); + + LogDynAndXSetFunction(xfc->log, xfc->display, xfc->gc, GXcopy); + LogDynAndXSetFillStyle(xfc->log, xfc->display, xfc->gc, FillSolid); + LogDynAndXSetForeground(xfc->log, xfc->display, xfc->gc, BlackPixelOfScreen(xfc->screen)); + LogDynAndXFillRectangle(xfc->log, xfc->display, xfc->primary, xfc->gc, 0, 0, + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)); + LogDynAndXFlush(xfc->log, xfc->display); + + return TRUE; +} + +BOOL xf_create_image(xfContext* xfc) +{ + WINPR_ASSERT(xfc); + if (!xfc->image) + { + const rdpSettings* settings = xfc->common.context.settings; + rdpGdi* cgdi = xfc->common.context.gdi; + WINPR_ASSERT(cgdi); + + WINPR_ASSERT(xfc->depth != 0); + xfc->image = LogDynAndXCreateImage( + xfc->log, xfc->display, xfc->visual, WINPR_ASSERTING_INT_CAST(uint32_t, xfc->depth), + ZPixmap, 0, (char*)cgdi->primary_buffer, + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight), xfc->scanline_pad, + WINPR_ASSERTING_INT_CAST(int, cgdi->stride)); + xfc->image->byte_order = LSBFirst; + xfc->image->bitmap_bit_order = LSBFirst; + } + return TRUE; +} + +void xf_destroy_window(xfContext* xfc) +{ + if (xfc->window) + { + xf_DestroyDesktopWindow(xfc, xfc->window); + xfc->window = nullptr; + } + +#if defined(CHANNEL_TSMF_CLIENT) + if (xfc->xv_context) + { + xf_tsmf_uninit(xfc, nullptr); + xfc->xv_context = nullptr; + } +#endif + + if (xfc->image) + { + xfc->image->data = nullptr; + XDestroyImage(xfc->image); + xfc->image = nullptr; + } + + if (xfc->bitmap_mono) + { + LogDynAndXFreePixmap(xfc->log, xfc->display, xfc->bitmap_mono); + xfc->bitmap_mono = 0; + } + + if (xfc->gc_mono) + { + LogDynAndXFreeGC(xfc->log, xfc->display, xfc->gc_mono); + xfc->gc_mono = nullptr; + } + + if (xfc->primary) + { + LogDynAndXFreePixmap(xfc->log, xfc->display, xfc->primary); + xfc->primary = 0; + } + + if (xfc->gc) + { + LogDynAndXFreeGC(xfc->log, xfc->display, xfc->gc); + xfc->gc = nullptr; + } +} + +void xf_toggle_fullscreen(xfContext* xfc) +{ + WindowStateChangeEventArgs e = WINPR_C_ARRAY_INIT; + rdpContext* context = (rdpContext*)xfc; + rdpSettings* settings = context->settings; + + /* + when debugging, ungrab keyboard when toggling fullscreen + to allow keyboard usage on the debugger + */ + if (xfc->debug) + xf_ungrab(xfc); + + xfc->fullscreen = !((xfc->fullscreen)); + xfc->decorations = + (xfc->fullscreen) ? FALSE : freerdp_settings_get_bool(settings, FreeRDP_Decorations); + xf_SetWindowFullscreen(xfc, xfc->window, xfc->fullscreen); + EventArgsInit(&e, "xfreerdp"); + e.state = xfc->fullscreen ? FREERDP_WINDOW_STATE_FULLSCREEN : 0; + PubSub_OnWindowStateChange(context->pubSub, context, &e); +} + +void xf_minimize(xfContext* xfc) +{ + WindowStateChangeEventArgs e = WINPR_C_ARRAY_INIT; + rdpContext* context = (rdpContext*)xfc; + WINPR_ASSERT(context); + + /* + when debugging, ungrab keyboard when toggling fullscreen + to allow keyboard usage on the debugger + */ + if (xfc->debug) + xf_ungrab(xfc); + + xf_SetWindowMinimized(xfc, xfc->window); + + e.state = xfc->fullscreen ? FREERDP_WINDOW_STATE_FULLSCREEN : 0; + PubSub_OnWindowStateChange(context->pubSub, context, &e); +} + +void xf_lock_x11_(xfContext* xfc, WINPR_ATTR_UNUSED const char* fkt) +{ + if (!xfc->UseXThreads) + (void)WaitForSingleObject(xfc->mutex, INFINITE); + else + XLockDisplay(xfc->display); + + xfc->locked++; +} + +void xf_unlock_x11_(xfContext* xfc, WINPR_ATTR_UNUSED const char* fkt) +{ + if (xfc->locked == 0) + WLog_WARN(TAG, "X11: trying to unlock although not locked!"); + else + xfc->locked--; + + if (!xfc->UseXThreads) + (void)ReleaseMutex(xfc->mutex); + else + XUnlockDisplay(xfc->display); +} + +static BOOL xf_get_pixmap_info(xfContext* xfc) +{ + int pf_count = 0; + XPixmapFormatValues* pfs = nullptr; + + WINPR_ASSERT(xfc->display); + pfs = XListPixmapFormats(xfc->display, &pf_count); + + if (!pfs) + { + WLog_ERR(TAG, "XListPixmapFormats failed"); + return 1; + } + + WINPR_ASSERT(xfc->depth != 0); + for (int i = 0; i < pf_count; i++) + { + const XPixmapFormatValues* pf = &pfs[i]; + + if (pf->depth == xfc->depth) + { + xfc->scanline_pad = pf->scanline_pad; + break; + } + } + + XFree(pfs); + return !((xfc->visual == nullptr) || (xfc->scanline_pad == 0)); +} + +static int xf_error_handler(Display* d, XErrorEvent* ev) +{ + char buf[256] = WINPR_C_ARRAY_INIT; + XGetErrorText(d, ev->error_code, buf, sizeof(buf)); + const char* what = request_code_2_str(ev->request_code); + WLog_ERR(TAG, "%s: %s", what, buf); + winpr_log_backtrace(TAG, WLOG_ERROR, 20); + return 0; +} + +static int xf_error_handler_ex(Display* d, XErrorEvent* ev) +{ + /* + * ungrab the keyboard, in case a debugger is running in + * another window. This make xf_error_handler() a potential + * debugger breakpoint. + */ +#if defined(WITH_DEBUG_X11) + XUngrabKeyboard(d, CurrentTime); + XUngrabPointer(d, CurrentTime); +#endif + return xf_error_handler(d, ev); +} + +static BOOL xf_play_sound(rdpContext* context, const PLAY_SOUND_UPDATE* play_sound) +{ + xfContext* xfc = (xfContext*)context; + WINPR_UNUSED(play_sound); + XkbBell(xfc->display, None, 100, 0); + return TRUE; +} + +static void xf_check_extensions(xfContext* context) +{ + int xkb_opcode = 0; + int xkb_event = 0; + int xkb_error = 0; + int xkb_major = XkbMajorVersion; + int xkb_minor = XkbMinorVersion; + + if (XkbLibraryVersion(&xkb_major, &xkb_minor) && + XkbQueryExtension(context->display, &xkb_opcode, &xkb_event, &xkb_error, &xkb_major, + &xkb_minor)) + { + context->xkbAvailable = TRUE; + } + +#ifdef WITH_XRENDER + { + int xrender_event_base = 0; + int xrender_error_base = 0; + + if (XRenderQueryExtension(context->display, &xrender_event_base, &xrender_error_base)) + { + context->xrenderAvailable = TRUE; + } + } +#endif +} + +#ifdef WITH_XI +/* Input device which does NOT have the correct mapping. We must disregard */ +/* this device when trying to find the input device which is the pointer. */ +static const char TEST_PTR_STR[] = "Virtual core XTEST pointer"; +static const size_t TEST_PTR_LEN = sizeof(TEST_PTR_STR) / sizeof(char); +#endif /* WITH_XI */ + +static void xf_get_x11_button_map(xfContext* xfc, unsigned char* x11_map) +{ +#ifdef WITH_XI + int opcode = 0; + int event = 0; + int error = 0; + XDevice* ptr_dev = nullptr; + XExtensionVersion* version = nullptr; + XDeviceInfo* devices1 = nullptr; + XIDeviceInfo* devices2 = nullptr; + int num_devices = 0; + + if (XQueryExtension(xfc->display, "XInputExtension", &opcode, &event, &error)) + { + WLog_DBG(TAG, "Searching for XInput pointer device"); + ptr_dev = nullptr; + /* loop through every device, looking for a pointer */ + version = XGetExtensionVersion(xfc->display, INAME); + + if (version->major_version >= 2) + { + /* XID of pointer device using XInput version 2 */ + devices2 = XIQueryDevice(xfc->display, XIAllDevices, &num_devices); + + if (devices2) + { + for (int i = 0; i < num_devices; ++i) + { + XIDeviceInfo* dev = &devices2[i]; + if ((dev->use == XISlavePointer) && + (strncmp(dev->name, TEST_PTR_STR, TEST_PTR_LEN) != 0)) + { + ptr_dev = XOpenDevice(xfc->display, + WINPR_ASSERTING_INT_CAST(uint32_t, dev->deviceid)); + if (ptr_dev) + break; + } + } + + XIFreeDeviceInfo(devices2); + } + } + else + { + /* XID of pointer device using XInput version 1 */ + devices1 = XListInputDevices(xfc->display, &num_devices); + + if (devices1) + { + for (int i = 0; i < num_devices; ++i) + { + if ((devices1[i].use == IsXExtensionPointer) && + (strncmp(devices1[i].name, TEST_PTR_STR, TEST_PTR_LEN) != 0)) + { + ptr_dev = XOpenDevice(xfc->display, devices1[i].id); + if (ptr_dev) + break; + } + } + + XFreeDeviceList(devices1); + } + } + + XFree(version); + + /* get button mapping from input extension if there is a pointer device; */ + /* otherwise leave unchanged. */ + if (ptr_dev) + { + WLog_DBG(TAG, "Pointer device: %" PRIu32, + WINPR_CXX_COMPAT_CAST(uint32_t, ptr_dev->device_id)); + XGetDeviceButtonMapping(xfc->display, ptr_dev, x11_map, NUM_BUTTONS_MAPPED); + XCloseDevice(xfc->display, ptr_dev); + } + else + { + WLog_DBG(TAG, "No pointer device found!"); + } + } + else +#endif /* WITH_XI */ + { + WLog_DBG(TAG, "Get global pointer mapping (no XInput)"); + XGetPointerMapping(xfc->display, x11_map, NUM_BUTTONS_MAPPED); + } +} + +/* Assignment of physical (not logical) mouse buttons to wire flags. */ +/* Notice that the middle button is 2 in X11, but 3 in RDP. */ +static const button_map xf_button_flags[NUM_BUTTONS_MAPPED] = { + { Button1, PTR_FLAGS_BUTTON1 }, + { Button2, PTR_FLAGS_BUTTON3 }, + { Button3, PTR_FLAGS_BUTTON2 }, + { Button4, PTR_FLAGS_WHEEL | 0x78 }, + /* Negative value is 9bit twos complement */ + { Button5, PTR_FLAGS_WHEEL | PTR_FLAGS_WHEEL_NEGATIVE | (0x100 - 0x78) }, + { 6, PTR_FLAGS_HWHEEL | PTR_FLAGS_WHEEL_NEGATIVE | (0x100 - 0x78) }, + { 7, PTR_FLAGS_HWHEEL | 0x78 }, + { 8, PTR_XFLAGS_BUTTON1 }, + { 9, PTR_XFLAGS_BUTTON2 }, + { 97, PTR_XFLAGS_BUTTON1 }, + { 112, PTR_XFLAGS_BUTTON2 } +}; + +static UINT16 get_flags_for_button(size_t button) +{ + for (size_t x = 0; x < ARRAYSIZE(xf_button_flags); x++) + { + const button_map* map = &xf_button_flags[x]; + + if (map->button == button) + return map->flags; + } + + return 0; +} + +void xf_button_map_init(xfContext* xfc) +{ + size_t pos = 0; + /* loop counter for array initialization */ + + /* logical mouse button which is used for each physical mouse */ + /* button (indexed from zero). This is the default map. */ + unsigned char x11_map[112] = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(xfc); + WINPR_ASSERT(xfc->common.context.settings); + + x11_map[0] = Button1; + x11_map[1] = Button2; + x11_map[2] = Button3; + x11_map[3] = Button4; + x11_map[4] = Button5; + x11_map[5] = 6; + x11_map[6] = 7; + x11_map[7] = 8; + x11_map[8] = 9; + x11_map[96] = 97; + x11_map[111] = 112; + + /* query system for actual remapping */ + if (freerdp_settings_get_bool(xfc->common.context.settings, FreeRDP_UnmapButtons)) + { + xf_get_x11_button_map(xfc, x11_map); + } + + /* iterate over all (mapped) physical buttons; for each of them */ + /* find the logical button in X11, and assign to this the */ + /* appropriate value to send over the RDP wire. */ + for (size_t physical = 0; physical < ARRAYSIZE(x11_map); ++physical) + { + const unsigned char logical = x11_map[physical]; + const UINT16 flags = get_flags_for_button(logical); + + if ((logical != 0) && (flags != 0)) + { + if (pos >= NUM_BUTTONS_MAPPED) + { + WLog_ERR(TAG, "Failed to map mouse button to RDP button, no space"); + } + else + { + button_map* map = &xfc->button_map[pos++]; + map->button = logical; + map->flags = get_flags_for_button(physical + Button1); + } + } + } +} + +/** + * Callback given to freerdp_connect() to process the pre-connect operations. + * It will fill the rdp_freerdp structure (instance) with the appropriate options to use for the + * connection. + * + * @param instance - pointer to the rdp_freerdp structure that contains the connection's parameters, + * and will be filled with the appropriate information. + * + * @return TRUE if successful. FALSE otherwise. + * Can exit with error code XF_EXIT_PARSE_ARGUMENTS if there is an error in the parameters. + */ +static BOOL xf_pre_connect(freerdp* instance) +{ + UINT32 maxWidth = 0; + UINT32 maxHeight = 0; + + WINPR_ASSERT(instance); + + rdpContext* context = instance->context; + WINPR_ASSERT(context); + xfContext* xfc = (xfContext*)context; + + rdpSettings* settings = context->settings; + WINPR_ASSERT(settings); + + if (!freerdp_settings_set_bool(settings, FreeRDP_CertificateCallbackPreferPEM, TRUE)) + return FALSE; + + if (!freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly)) + { + if (!xf_setup_x11(xfc)) + return FALSE; + } + + if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMajorType, OSMAJORTYPE_UNIX)) + return FALSE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMinorType, OSMINORTYPE_NATIVE_XSERVER)) + return FALSE; + if (PubSub_SubscribeChannelConnected(context->pubSub, xf_OnChannelConnectedEventHandler) < 0) + return FALSE; + if (PubSub_SubscribeChannelDisconnected(context->pubSub, xf_OnChannelDisconnectedEventHandler) < + 0) + return FALSE; + + if (!freerdp_settings_get_string(settings, FreeRDP_Username) && + !freerdp_settings_get_bool(settings, FreeRDP_CredentialsFromStdin) && + !freerdp_settings_get_bool(settings, FreeRDP_SmartcardLogon)) + { + char login_name[MAX_PATH] = WINPR_C_ARRAY_INIT; + ULONG size = sizeof(login_name) - 1; + + if (GetUserNameExA(NameSamCompatible, login_name, &size)) + { + if (!freerdp_settings_set_string(settings, FreeRDP_Username, login_name)) + return FALSE; + + WLog_INFO(TAG, "No user name set. - Using login name: %s", + freerdp_settings_get_string(settings, FreeRDP_Username)); + } + } + + if (freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly)) + { + /* Check +auth-only has a username and password. */ + if (!freerdp_settings_get_string(settings, FreeRDP_Password)) + { + WLog_INFO(TAG, "auth-only, but no password set. Please provide one."); + return FALSE; + } + + WLog_INFO(TAG, "Authentication only. Don't connect to X."); + } + + if (!freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly)) + { + const char* KeyboardRemappingList = freerdp_settings_get_string( + xfc->common.context.settings, FreeRDP_KeyboardRemappingList); + + xfc->remap_table = freerdp_keyboard_remap_string_to_list(KeyboardRemappingList); + if (!xfc->remap_table) + return FALSE; + if (!xf_keyboard_init(xfc)) + return FALSE; + if (!xf_keyboard_action_script_init(xfc)) + return FALSE; + if (!xf_detect_monitors(xfc, &maxWidth, &maxHeight)) + return FALSE; + } + + if (maxWidth && maxHeight && !freerdp_settings_get_bool(settings, FreeRDP_SmartSizing)) + { + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, maxWidth)) + return FALSE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, maxHeight)) + return FALSE; + } + +#ifdef WITH_XRENDER + + /** + * If /f is specified in combination with /smart-sizing:widthxheight then + * we run the session in the /smart-sizing dimensions scaled to full screen + */ + if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen) && + freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) && + (freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingWidth) > 0) && + (freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingHeight) > 0)) + { + if (!freerdp_settings_set_uint32( + settings, FreeRDP_DesktopWidth, + freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingWidth))) + return FALSE; + if (!freerdp_settings_set_uint32( + settings, FreeRDP_DesktopHeight, + freerdp_settings_get_uint32(settings, FreeRDP_SmartSizingHeight))) + return FALSE; + } + +#endif + xfc->fullscreen = freerdp_settings_get_bool(settings, FreeRDP_Fullscreen); + xfc->decorations = freerdp_settings_get_bool(settings, FreeRDP_Decorations); + xfc->grab_keyboard = freerdp_settings_get_bool(settings, FreeRDP_GrabKeyboard); + xfc->fullscreen_toggle = freerdp_settings_get_bool(settings, FreeRDP_ToggleFullscreen); + if (!freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly)) + xf_button_map_init(xfc); + return TRUE; +} + +static BOOL xf_inject_keypress(rdpContext* context, const char* buffer, size_t size) +{ + WCHAR wbuffer[64] = WINPR_C_ARRAY_INIT; + const SSIZE_T len = ConvertUtf8NToWChar(buffer, size, wbuffer, ARRAYSIZE(wbuffer)); + if (len < 0) + return FALSE; + + rdpInput* input = context->input; + WINPR_ASSERT(input); + + for (SSIZE_T x = 0; x < len; x++) + { + const WCHAR code = wbuffer[x]; + freerdp_input_send_unicode_keyboard_event(input, 0, code); + Sleep(5); + freerdp_input_send_unicode_keyboard_event(input, KBD_FLAGS_RELEASE, code); + Sleep(5); + } + return TRUE; +} + +static BOOL xf_process_pipe(rdpContext* context, const char* pipe) +{ + int fd = open(pipe, O_NONBLOCK | O_RDONLY); + if (fd < 0) + { + char ebuffer[256] = WINPR_C_ARRAY_INIT; + WLog_ERR(TAG, "pipe '%s' open returned %s [%d]", pipe, + winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno); + return FALSE; + } + while (!freerdp_shall_disconnect_context(context)) + { + char buffer[64] = WINPR_C_ARRAY_INIT; + ssize_t rd = read(fd, buffer, sizeof(buffer) - 1); + if (rd == 0) + { + char ebuffer[256] = WINPR_C_ARRAY_INIT; + if ((errno == EAGAIN) || (errno == 0)) + { + Sleep(100); + continue; + } + + // EOF, abort reading. + WLog_ERR(TAG, "pipe '%s' read returned %s [%d]", pipe, + winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno); + break; + } + else if (rd < 0) + { + char ebuffer[256] = WINPR_C_ARRAY_INIT; + WLog_ERR(TAG, "pipe '%s' read returned %s [%d]", pipe, + winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno); + break; + } + else + { + if (!xf_inject_keypress(context, buffer, WINPR_ASSERTING_INT_CAST(size_t, rd))) + break; + } + } + close(fd); + return TRUE; +} + +static void cleanup_pipe(WINPR_ATTR_UNUSED int signum, WINPR_ATTR_UNUSED const char* signame, + void* context) +{ + const char* pipe = context; + if (!pipe) + return; + unlink(pipe); +} + +static DWORD WINAPI xf_handle_pipe(void* arg) +{ + xfContext* xfc = arg; + WINPR_ASSERT(xfc); + + rdpContext* context = &xfc->common.context; + WINPR_ASSERT(context); + + rdpSettings* settings = context->settings; + WINPR_ASSERT(settings); + + const char* pipe = freerdp_settings_get_string(settings, FreeRDP_KeyboardPipeName); + WINPR_ASSERT(pipe); + + const int rc = mkfifo(pipe, S_IWUSR | S_IRUSR); + if (rc != 0) + { + char ebuffer[256] = WINPR_C_ARRAY_INIT; + WLog_ERR(TAG, "Failed to create named pipe '%s': %s [%d]", pipe, + winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno); + return 0; + } + + void* ctx = WINPR_CAST_CONST_PTR_AWAY(pipe, void*); + if (freerdp_add_signal_cleanup_handler(ctx, cleanup_pipe)) + { + + xf_process_pipe(context, pipe); + + freerdp_del_signal_cleanup_handler(ctx, cleanup_pipe); + } + + unlink(pipe); + return 0; +} + +/** + * Callback given to freerdp_connect() to perform post-connection operations. + * It will be called only if the connection was initialized properly, and will continue the + * initialization based on the newly created connection. + */ +static BOOL xf_post_connect(freerdp* instance) +{ + ResizeWindowEventArgs e = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(instance); + xfContext* xfc = (xfContext*)instance->context; + rdpContext* context = instance->context; + WINPR_ASSERT(context); + + rdpSettings* settings = context->settings; + WINPR_ASSERT(settings); + + rdpUpdate* update = context->update; + WINPR_ASSERT(update); + + if (freerdp_settings_get_bool(settings, FreeRDP_RemoteApplicationMode)) + xfc->remote_app = TRUE; + + if (!xf_create_window(xfc)) + return FALSE; + + if (!xf_get_pixmap_info(xfc)) + return FALSE; + + if (!gdi_init(instance, xf_get_local_color_format(xfc, TRUE))) + return FALSE; + + if (!xf_create_image(xfc)) + return FALSE; + + if (!xf_register_pointer(context->graphics)) + return FALSE; + +#ifdef WITH_XRENDER + xfc->scaledWidth = + WINPR_ASSERTING_INT_CAST(int, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth)); + xfc->scaledHeight = + WINPR_ASSERTING_INT_CAST(int, freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)); + xfc->offset_x = 0; + xfc->offset_y = 0; +#endif + + if (!xfc->xrenderAvailable) + { + if (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing)) + { + WLog_ERR(TAG, "XRender not available: disabling smart-sizing"); + if (!freerdp_settings_set_bool(settings, FreeRDP_SmartSizing, FALSE)) + return FALSE; + } + + if (freerdp_settings_get_bool(settings, FreeRDP_MultiTouchGestures)) + { + WLog_ERR(TAG, "XRender not available: disabling local multi-touch gestures"); + if (!freerdp_settings_set_bool(settings, FreeRDP_MultiTouchGestures, FALSE)) + return FALSE; + } + } + + update->DesktopResize = xf_sw_desktop_resize; + update->EndPaint = xf_end_paint; + update->PlaySound = xf_play_sound; + update->SetKeyboardIndicators = xf_keyboard_set_indicators; + update->SetKeyboardImeStatus = xf_keyboard_set_ime_status; + + const BOOL serverIsWindowsPlatform = + (freerdp_settings_get_uint32(settings, FreeRDP_OsMajorType) == OSMAJORTYPE_WINDOWS); + if (freerdp_settings_get_bool(settings, FreeRDP_RedirectClipboard) && + !(xfc->clipboard = xf_clipboard_new(xfc, !serverIsWindowsPlatform))) + return FALSE; + + if (!(xfc->xfDisp = xf_disp_new(xfc))) + return FALSE; + + const char* pipe = freerdp_settings_get_string(settings, FreeRDP_KeyboardPipeName); + if (pipe) + { + xfc->pipethread = CreateThread(nullptr, 0, xf_handle_pipe, xfc, 0, nullptr); + if (!xfc->pipethread) + return FALSE; + } + + EventArgsInit(&e, "xfreerdp"); + e.width = + WINPR_ASSERTING_INT_CAST(int, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth)); + e.height = + WINPR_ASSERTING_INT_CAST(int, freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)); + PubSub_OnResizeWindow(context->pubSub, xfc, &e); + return TRUE; +} + +static void xf_post_disconnect(freerdp* instance) +{ + xfContext* xfc = nullptr; + rdpContext* context = nullptr; + + if (!instance || !instance->context) + return; + + context = instance->context; + xfc = (xfContext*)context; + PubSub_UnsubscribeChannelConnected(instance->context->pubSub, + xf_OnChannelConnectedEventHandler); + PubSub_UnsubscribeChannelDisconnected(instance->context->pubSub, + xf_OnChannelDisconnectedEventHandler); + gdi_free(instance); + + if (xfc->pipethread) + { + (void)WaitForSingleObject(xfc->pipethread, INFINITE); + (void)CloseHandle(xfc->pipethread); + xfc->pipethread = nullptr; + } + if (xfc->clipboard) + { + xf_clipboard_free(xfc->clipboard); + xfc->clipboard = nullptr; + } + + if (xfc->xfDisp) + { + xf_disp_free(xfc->xfDisp); + xfc->xfDisp = nullptr; + } + + if ((xfc->window != nullptr) && (xfc->drawable == xfc->window->handle)) + xfc->drawable = 0; + else + xf_DestroyDummyWindow(xfc, xfc->drawable); + + freerdp_keyboard_remap_free(xfc->remap_table); + xfc->remap_table = nullptr; + + xf_destroy_window(xfc); +} + +static void xf_post_final_disconnect(freerdp* instance) +{ + xfContext* xfc = nullptr; + rdpContext* context = nullptr; + + if (!instance || !instance->context) + return; + + context = instance->context; + xfc = (xfContext*)context; + + xf_keyboard_free(xfc); + xf_teardown_x11(xfc); +} + +static int xf_logon_error_info(freerdp* instance, UINT32 data, UINT32 type) +{ + xfContext* xfc = (xfContext*)instance->context; + const char* str_data = freerdp_get_logon_error_info_data(data); + const char* str_type = freerdp_get_logon_error_info_type(type); + WLog_INFO(TAG, "Logon Error Info %s [%s]", str_data, str_type); + if (type != LOGON_MSG_SESSION_CONTINUE) + { + xf_rail_disable_remoteapp_mode(xfc); + } + return 1; +} + +static BOOL handle_window_events(freerdp* instance) +{ + if (!xf_process_x_events(instance)) + { + WLog_DBG(TAG, "Closed from X11"); + freerdp_abort_connect_context(instance->context); + return FALSE; + } + + return TRUE; +} + +/** Main loop for the rdp connection. + * It will be run from the thread's entry point (thread_func()). + * It initiates the connection, and will continue to run until the session ends, + * processing events as they are received. + * + * @param param - pointer to the rdp_freerdp structure that contains the session's settings + * @return A code from the enum XF_EXIT_CODE (0 if successful) + */ +static DWORD WINAPI xf_client_thread(LPVOID param) +{ + DWORD exit_code = 0; + DWORD waitStatus = 0; + HANDLE inputEvent = nullptr; + + freerdp* instance = (freerdp*)param; + WINPR_ASSERT(instance); + + const BOOL status = freerdp_connect(instance); + rdpContext* context = instance->context; + WINPR_ASSERT(context); + xfContext* xfc = (xfContext*)instance->context; + WINPR_ASSERT(xfc); + + rdpSettings* settings = context->settings; + WINPR_ASSERT(settings); + + if (!status) + { + UINT32 error = freerdp_get_last_error(instance->context); + exit_code = (uint32_t)xf_map_error_to_exit_code(error); + } + else + exit_code = XF_EXIT_SUCCESS; + + if (!status) + goto end; + + /* --authonly ? */ + if (freerdp_settings_get_bool(settings, FreeRDP_AuthenticationOnly)) + { + WLog_ERR(TAG, "Authentication only, exit status %" PRId32 "", !status); + goto disconnect; + } + + if (!status) + { + WLog_ERR(TAG, "Freerdp connect error exit status %" PRId32 "", !status); + exit_code = freerdp_error_info(instance); + + if (freerdp_get_last_error(instance->context) == FREERDP_ERROR_AUTHENTICATION_FAILED) + exit_code = XF_EXIT_AUTH_FAILURE; + else if (exit_code == ERRINFO_SUCCESS) + exit_code = XF_EXIT_CONN_FAILED; + + goto disconnect; + } + + inputEvent = xfc->x11event; + + while (!freerdp_shall_disconnect_context(instance->context)) + { + HANDLE handles[MAXIMUM_WAIT_OBJECTS] = WINPR_C_ARRAY_INIT; + DWORD nCount = 0; + handles[nCount++] = inputEvent; + + /* + * win8 and server 2k12 seem to have some timing issue/race condition + * when a initial sync request is send to sync the keyboard indicators + * sending the sync event twice fixed this problem + */ + if (freerdp_focus_required(instance)) + { + xf_keyboard_focus_in(xfc); + xf_keyboard_focus_in(xfc); + } + + { + DWORD tmp = + freerdp_get_event_handles(context, &handles[nCount], ARRAYSIZE(handles) - nCount); + + if (tmp == 0) + { + WLog_ERR(TAG, "freerdp_get_event_handles failed"); + break; + } + + nCount += tmp; + } + + if (xfc->window) + xf_floatbar_hide_and_show(xfc->window->floatbar); + + waitStatus = WaitForMultipleObjects(nCount, handles, FALSE, INFINITE); + + if (waitStatus == WAIT_FAILED) + break; + + { + if (!freerdp_check_event_handles(context)) + { + if (client_auto_reconnect_ex(instance, handle_window_events)) + continue; + else + { + /* + * Indicate an unsuccessful connection attempt if reconnect + * did not succeed and no other error was specified. + */ + const UINT32 error = freerdp_get_last_error(instance->context); + + if (freerdp_error_info(instance) == 0) + exit_code = (uint32_t)xf_map_error_to_exit_code(error); + } + + if (freerdp_get_last_error(context) == FREERDP_ERROR_SUCCESS) + WLog_ERR(TAG, "Failed to check FreeRDP file descriptor"); + + break; + } + } + + if (!handle_window_events(instance)) + break; + } + + if (!exit_code) + { + exit_code = freerdp_error_info(instance); + + if (exit_code == XF_EXIT_DISCONNECT && + freerdp_get_disconnect_ultimatum(context) == Disconnect_Ultimatum_user_requested) + { + /* This situation might be limited to Windows XP. */ + WLog_INFO(TAG, "Error info says user did not initiate but disconnect ultimatum says " + "they did; treat this as a user logoff"); + exit_code = XF_EXIT_LOGOFF; + } + } + +disconnect: + + freerdp_disconnect(instance); +end: + ExitThread(exit_code); + return exit_code; +} + +int xf_exit_code_from_disconnect_reason(DWORD reason) +{ + if ((reason == 0) || ((reason >= XF_EXIT_PARSE_ARGUMENTS) && (reason <= XF_EXIT_CODE_LAST))) + return WINPR_ASSERTING_INT_CAST(int, reason); + /* License error set */ + else if (reason >= 0x100 && reason <= 0x10A) + reason -= 0x100 + XF_EXIT_LICENSE_INTERNAL; + /* RDP protocol error set */ + else if (reason >= 0x10c9 && reason <= 0x1193) + reason = XF_EXIT_RDP; + /* There's no need to test protocol-independent codes: they match */ + else if (!(reason <= 0xC)) + reason = XF_EXIT_UNKNOWN; + + return WINPR_ASSERTING_INT_CAST(int, reason); +} + +static void xf_TerminateEventHandler(void* context, const TerminateEventArgs* e) +{ + rdpContext* ctx = (rdpContext*)context; + WINPR_UNUSED(e); + freerdp_abort_connect_context(ctx); +} + +#ifdef WITH_XRENDER +static void xf_ZoomingChangeEventHandler(void* context, const ZoomingChangeEventArgs* e) +{ + int w = 0; + int h = 0; + rdpSettings* settings = nullptr; + xfContext* xfc = (xfContext*)context; + + WINPR_ASSERT(xfc); + + settings = xfc->common.context.settings; + WINPR_ASSERT(settings); + + w = xfc->scaledWidth + e->dx; + h = xfc->scaledHeight + e->dy; + + if (e->dx == 0 && e->dy == 0) + return; + + if (w < 10) + w = 10; + + if (h < 10) + h = 10; + + if (w == xfc->scaledWidth && h == xfc->scaledHeight) + return; + + xfc->scaledWidth = w; + xfc->scaledHeight = h; + xf_draw_screen(xfc, 0, 0, + WINPR_ASSERTING_INT_CAST( + int32_t, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth)), + WINPR_ASSERTING_INT_CAST( + int32_t, freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight))); +} + +static void xf_PanningChangeEventHandler(void* context, const PanningChangeEventArgs* e) +{ + xfContext* xfc = (xfContext*)context; + rdpSettings* settings = nullptr; + + WINPR_ASSERT(xfc); + WINPR_ASSERT(e); + + settings = xfc->common.context.settings; + WINPR_ASSERT(settings); + + if (e->dx == 0 && e->dy == 0) + return; + + xfc->offset_x += e->dx; + xfc->offset_y += e->dy; + xf_draw_screen(xfc, 0, 0, + WINPR_ASSERTING_INT_CAST( + int32_t, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth)), + WINPR_ASSERTING_INT_CAST( + int32_t, freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight))); +} +#endif + +/** + * Client Interface + */ + +static BOOL xfreerdp_client_global_init(void) +{ + // NOLINTNEXTLINE(concurrency-mt-unsafe) + (void)setlocale(LC_ALL, ""); + + return (freerdp_handle_signals() == 0); +} + +static void xfreerdp_client_global_uninit(void) +{ +} + +static int xfreerdp_client_start(rdpContext* context) +{ + xfContext* xfc = (xfContext*)context; + rdpSettings* settings = context->settings; + + if (!freerdp_settings_get_string(settings, FreeRDP_ServerHostname)) + { + WLog_ERR(TAG, "error: server hostname was not specified with /v:[:port]"); + return -1; + } + + if (!(xfc->common.thread = + CreateThread(nullptr, 0, xf_client_thread, context->instance, 0, nullptr))) + { + WLog_ERR(TAG, "failed to create client thread"); + return -1; + } + + return 0; +} + +static Atom get_supported_atom(xfContext* xfc, const char* atomName) +{ + const Atom atom = Logging_XInternAtom(xfc->log, xfc->display, atomName, False); + + for (unsigned long i = 0; i < xfc->supportedAtomCount; i++) + { + if (xfc->supportedAtoms[i] == atom) + return atom; + } + + return None; +} + +void xf_teardown_x11(xfContext* xfc) +{ + WINPR_ASSERT(xfc); + + if (xfc->display) + { + LogDynAndXCloseDisplay(xfc->log, xfc->display); + xfc->display = nullptr; + } + + if (xfc->x11event) + { + (void)CloseHandle(xfc->x11event); + xfc->x11event = nullptr; + } + + if (xfc->mutex) + { + (void)CloseHandle(xfc->mutex); + xfc->mutex = nullptr; + } + + if (xfc->vscreen.monitors) + { + free(xfc->vscreen.monitors); + xfc->vscreen.monitors = nullptr; + } + xfc->vscreen.nmonitors = 0; + + free(xfc->supportedAtoms); + xfc->supportedAtoms = nullptr; + xfc->supportedAtomCount = 0; +} + +BOOL xf_setup_x11(xfContext* xfc) +{ + + WINPR_ASSERT(xfc); + xfc->UseXThreads = TRUE; + +#if !defined(NDEBUG) + /* uncomment below if debugging to prevent keyboard grab */ + xfc->debug = TRUE; +#endif + + if (xfc->UseXThreads) + { + if (!XInitThreads()) + { + WLog_WARN(TAG, "XInitThreads() failure"); + xfc->UseXThreads = FALSE; + } + } + + xfc->display = XOpenDisplay(nullptr); + + if (!xfc->display) + { + WLog_ERR(TAG, "failed to open display: %s", XDisplayName(nullptr)); + WLog_ERR(TAG, "Please check that the $DISPLAY environment variable is properly set."); + goto fail; + } + if (xfc->debug) + { + WLog_INFO(TAG, "Enabling X11 debug mode."); + XSynchronize(xfc->display, TRUE); + } + def_error_handler = XSetErrorHandler(xf_error_handler_ex); + + xfc->mutex = CreateMutex(nullptr, FALSE, nullptr); + + if (!xfc->mutex) + { + WLog_ERR(TAG, "Could not create mutex!"); + goto fail; + } + + xfc->xfds = ConnectionNumber(xfc->display); + xfc->screen_number = DefaultScreen(xfc->display); + xfc->screen = ScreenOfDisplay(xfc->display, xfc->screen_number); + xfc->big_endian = (ImageByteOrder(xfc->display) == MSBFirst); + xfc->invert = TRUE; + xfc->complex_regions = TRUE; + xfc->NET_SUPPORTED = Logging_XInternAtom(xfc->log, xfc->display, "_NET_SUPPORTED", True); + xfc->NET_SUPPORTING_WM_CHECK = + Logging_XInternAtom(xfc->log, xfc->display, "_NET_SUPPORTING_WM_CHECK", True); + + if ((xfc->NET_SUPPORTED != None) && (xfc->NET_SUPPORTING_WM_CHECK != None)) + { + Atom actual_type = 0; + int actual_format = 0; + unsigned long nitems = 0; + unsigned long after = 0; + unsigned char* data = nullptr; + int status = LogDynAndXGetWindowProperty( + xfc->log, xfc->display, RootWindowOfScreen(xfc->screen), xfc->NET_SUPPORTED, 0, 1024, + False, XA_ATOM, &actual_type, &actual_format, &nitems, &after, &data); + + if ((status == Success) && (actual_type == XA_ATOM) && (actual_format == 32)) + { + xfc->supportedAtomCount = nitems; + xfc->supportedAtoms = calloc(xfc->supportedAtomCount, sizeof(Atom)); + WINPR_ASSERT(xfc->supportedAtoms); + memcpy(xfc->supportedAtoms, data, nitems * sizeof(Atom)); + } + + if (data) + XFree(data); + } + + xfc->XWAYLAND_MAY_GRAB_KEYBOARD = + Logging_XInternAtom(xfc->log, xfc->display, "_XWAYLAND_MAY_GRAB_KEYBOARD", False); + xfc->NET_WM_ICON = Logging_XInternAtom(xfc->log, xfc->display, "_NET_WM_ICON", False); + xfc->MOTIF_WM_HINTS = Logging_XInternAtom(xfc->log, xfc->display, "_MOTIF_WM_HINTS", False); + xfc->NET_NUMBER_OF_DESKTOPS = + Logging_XInternAtom(xfc->log, xfc->display, "_NET_NUMBER_OF_DESKTOPS", False); + xfc->NET_CURRENT_DESKTOP = + Logging_XInternAtom(xfc->log, xfc->display, "_NET_CURRENT_DESKTOP", False); + xfc->NET_WORKAREA = Logging_XInternAtom(xfc->log, xfc->display, "_NET_WORKAREA", False); + xfc->NET_WM_STATE = get_supported_atom(xfc, "_NET_WM_STATE"); + xfc->NET_WM_STATE_MODAL = get_supported_atom(xfc, "_NET_WM_STATE_MODAL"); + xfc->NET_WM_STATE_STICKY = get_supported_atom(xfc, "_NET_WM_STATE_STICKY"); + xfc->NET_WM_STATE_MAXIMIZED_HORZ = + Logging_XInternAtom(xfc->log, xfc->display, "_NET_WM_STATE_MAXIMIZED_HORZ", False); + xfc->NET_WM_STATE_MAXIMIZED_VERT = + Logging_XInternAtom(xfc->log, xfc->display, "_NET_WM_STATE_MAXIMIZED_VERT", False); + xfc->NET_WM_STATE_SHADED = get_supported_atom(xfc, "_NET_WM_STATE_SHADED"); + xfc->NET_WM_STATE_SKIP_TASKBAR = get_supported_atom(xfc, "_NET_WM_STATE_SKIP_TASKBAR"); + xfc->NET_WM_STATE_SKIP_PAGER = get_supported_atom(xfc, "_NET_WM_STATE_SKIP_PAGER"); + xfc->NET_WM_STATE_HIDDEN = get_supported_atom(xfc, "_NET_WM_STATE_HIDDEN"); + xfc->NET_WM_STATE_FULLSCREEN = get_supported_atom(xfc, "_NET_WM_STATE_FULLSCREEN"); + xfc->NET_WM_STATE_ABOVE = get_supported_atom(xfc, "_NET_WM_STATE_ABOVE"); + xfc->NET_WM_STATE_BELOW = get_supported_atom(xfc, "_NET_WM_STATE_BELOW"); + xfc->NET_WM_STATE_DEMANDS_ATTENTION = + get_supported_atom(xfc, "_NET_WM_STATE_DEMANDS_ATTENTION"); + xfc->NET_WM_FULLSCREEN_MONITORS = get_supported_atom(xfc, "_NET_WM_FULLSCREEN_MONITORS"); + xfc->NET_WM_NAME = Logging_XInternAtom(xfc->log, xfc->display, "_NET_WM_NAME", False); + xfc->NET_WM_PID = Logging_XInternAtom(xfc->log, xfc->display, "_NET_WM_PID", False); + xfc->NET_WM_WINDOW_TYPE = + Logging_XInternAtom(xfc->log, xfc->display, "_NET_WM_WINDOW_TYPE", False); + xfc->NET_WM_WINDOW_TYPE_NORMAL = + Logging_XInternAtom(xfc->log, xfc->display, "_NET_WM_WINDOW_TYPE_NORMAL", False); + xfc->NET_WM_WINDOW_TYPE_DIALOG = + Logging_XInternAtom(xfc->log, xfc->display, "_NET_WM_WINDOW_TYPE_DIALOG", False); + xfc->NET_WM_WINDOW_TYPE_POPUP = + Logging_XInternAtom(xfc->log, xfc->display, "_NET_WM_WINDOW_TYPE_POPUP", False); + xfc->NET_WM_WINDOW_TYPE_POPUP_MENU = + Logging_XInternAtom(xfc->log, xfc->display, "_NET_WM_WINDOW_TYPE_POPUP_MENU", False); + xfc->NET_WM_WINDOW_TYPE_UTILITY = + Logging_XInternAtom(xfc->log, xfc->display, "_NET_WM_WINDOW_TYPE_UTILITY", False); + xfc->NET_WM_WINDOW_TYPE_DROPDOWN_MENU = + Logging_XInternAtom(xfc->log, xfc->display, "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU", False); + xfc->NET_WM_STATE_SKIP_TASKBAR = + Logging_XInternAtom(xfc->log, xfc->display, "_NET_WM_STATE_SKIP_TASKBAR", False); + xfc->NET_WM_STATE_SKIP_PAGER = + Logging_XInternAtom(xfc->log, xfc->display, "_NET_WM_STATE_SKIP_PAGER", False); + xfc->NET_WM_MOVERESIZE = + Logging_XInternAtom(xfc->log, xfc->display, "_NET_WM_MOVERESIZE", False); + xfc->NET_MOVERESIZE_WINDOW = + Logging_XInternAtom(xfc->log, xfc->display, "_NET_MOVERESIZE_WINDOW", False); + xfc->UTF8_STRING = Logging_XInternAtom(xfc->log, xfc->display, "UTF8_STRING", FALSE); + xfc->WM_PROTOCOLS = Logging_XInternAtom(xfc->log, xfc->display, "WM_PROTOCOLS", False); + xfc->WM_DELETE_WINDOW = Logging_XInternAtom(xfc->log, xfc->display, "WM_DELETE_WINDOW", False); + xfc->WM_STATE = Logging_XInternAtom(xfc->log, xfc->display, "WM_STATE", False); + xfc->x11event = CreateFileDescriptorEvent(nullptr, FALSE, FALSE, xfc->xfds, WINPR_FD_READ); + + xfc->NET_WM_ALLOWED_ACTIONS = + Logging_XInternAtom(xfc->log, xfc->display, "_NET_WM_ALLOWED_ACTIONS", False); + + xfc->NET_WM_ACTION_CLOSE = + Logging_XInternAtom(xfc->log, xfc->display, "_NET_WM_ACTION_CLOSE", False); + xfc->NET_WM_ACTION_MINIMIZE = + Logging_XInternAtom(xfc->log, xfc->display, "_NET_WM_ACTION_MINIMIZE", False); + xfc->NET_WM_ACTION_MOVE = + Logging_XInternAtom(xfc->log, xfc->display, "_NET_WM_ACTION_MOVE", False); + xfc->NET_WM_ACTION_RESIZE = + Logging_XInternAtom(xfc->log, xfc->display, "_NET_WM_ACTION_RESIZE", False); + xfc->NET_WM_ACTION_MAXIMIZE_HORZ = + Logging_XInternAtom(xfc->log, xfc->display, "_NET_WM_ACTION_MAXIMIZE_HORZ", False); + xfc->NET_WM_ACTION_MAXIMIZE_VERT = + Logging_XInternAtom(xfc->log, xfc->display, "_NET_WM_ACTION_MAXIMIZE_VERT", False); + xfc->NET_WM_ACTION_FULLSCREEN = + Logging_XInternAtom(xfc->log, xfc->display, "_NET_WM_ACTION_FULLSCREEN", False); + xfc->NET_WM_ACTION_CHANGE_DESKTOP = + Logging_XInternAtom(xfc->log, xfc->display, "_NET_WM_ACTION_CHANGE_DESKTOP", False); + + if (!xfc->x11event) + { + WLog_ERR(TAG, "Could not create xfds event"); + goto fail; + } + + xf_check_extensions(xfc); + + xfc->vscreen.monitors = calloc(16, sizeof(MONITOR_INFO)); + + if (!xfc->vscreen.monitors) + goto fail; + return TRUE; + +fail: + xf_teardown_x11(xfc); + return FALSE; +} + +static BOOL xfreerdp_client_new(freerdp* instance, rdpContext* context) +{ + xfContext* xfc = (xfContext*)instance->context; + WINPR_ASSERT(context); + WINPR_ASSERT(xfc); + WINPR_ASSERT(!xfc->display); + WINPR_ASSERT(!xfc->mutex); + WINPR_ASSERT(!xfc->x11event); + instance->PreConnect = xf_pre_connect; + instance->PostConnect = xf_post_connect; + instance->PostDisconnect = xf_post_disconnect; + instance->PostFinalDisconnect = xf_post_final_disconnect; + instance->LogonErrorInfo = xf_logon_error_info; + instance->GetAccessToken = client_cli_get_access_token; + if (PubSub_SubscribeTerminate(context->pubSub, xf_TerminateEventHandler) < 0) + return FALSE; +#ifdef WITH_XRENDER + if (PubSub_SubscribeZoomingChange(context->pubSub, xf_ZoomingChangeEventHandler) < 0) + return FALSE; + if (PubSub_SubscribePanningChange(context->pubSub, xf_PanningChangeEventHandler) < 0) + return FALSE; +#endif + xfc->log = WLog_Get(TAG); + + return TRUE; +} + +static void xfreerdp_client_free(WINPR_ATTR_UNUSED freerdp* instance, rdpContext* context) +{ + if (!context) + return; + + if (context->pubSub) + { + PubSub_UnsubscribeTerminate(context->pubSub, xf_TerminateEventHandler); +#ifdef WITH_XRENDER + PubSub_UnsubscribeZoomingChange(context->pubSub, xf_ZoomingChangeEventHandler); + PubSub_UnsubscribePanningChange(context->pubSub, xf_PanningChangeEventHandler); +#endif + } +} + +int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints) +{ + pEntryPoints->Version = 1; + pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1); + pEntryPoints->GlobalInit = xfreerdp_client_global_init; + pEntryPoints->GlobalUninit = xfreerdp_client_global_uninit; + pEntryPoints->ContextSize = sizeof(xfContext); + pEntryPoints->ClientNew = xfreerdp_client_new; + pEntryPoints->ClientFree = xfreerdp_client_free; + pEntryPoints->ClientStart = xfreerdp_client_start; + pEntryPoints->ClientStop = freerdp_client_common_stop; + return 0; +} diff --git a/third_party/FreeRDP/client/X11/xf_client.h b/third_party/FreeRDP/client/X11/xf_client.h new file mode 100644 index 0000000..c9bc21b --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_client.h @@ -0,0 +1,53 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Client Interface + * + * Copyright 2013 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_X11_CLIENT_H +#define FREERDP_CLIENT_X11_CLIENT_H + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + + /** + * Client Interface + */ + + FREERDP_API int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_CLIENT_X11_CLIENT_H */ diff --git a/third_party/FreeRDP/client/X11/xf_cliprdr.c b/third_party/FreeRDP/client/X11/xf_cliprdr.c new file mode 100644 index 0000000..9753d95 --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_cliprdr.c @@ -0,0 +1,2717 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Clipboard Redirection + * + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +#include +#include + +#ifdef WITH_XFIXES +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "xf_cliprdr.h" +#include "xf_event.h" +#include "xf_utils.h" + +#define TAG CLIENT_TAG("x11.cliprdr") + +#define MAX_CLIPBOARD_FORMATS 255 + +#define DEBUG_CLIPRDR(...) WLog_DBG(TAG, __VA_ARGS__) + +typedef struct +{ + Atom atom; + UINT32 formatToRequest; + UINT32 localFormat; + char* formatName; + BOOL isImage; +} xfCliprdrFormat; + +typedef struct +{ + BYTE* data; + UINT32 data_length; +} xfCachedData; + +typedef struct +{ + UINT32 localFormat; + UINT32 formatToRequest; + char* formatName; +} RequestedFormat; + +struct xf_clipboard +{ + xfContext* xfc; + rdpChannels* channels; + CliprdrClientContext* context; + + wClipboard* system; + + Window root_window; + Atom clipboard_atom; + Atom property_atom; + + Atom timestamp_property_atom; + Time selection_ownership_timestamp; + + Atom raw_transfer_atom; + Atom raw_format_list_atom; + + UINT32 numClientFormats; + xfCliprdrFormat clientFormats[20]; + + UINT32 numServerFormats; + CLIPRDR_FORMAT* serverFormats; + + size_t numTargets; + Atom targets[20]; + + UINT32 requestedFormatId; + + wHashTable* cachedData; + wHashTable* cachedRawData; + + BOOL data_raw_format; + + RequestedFormat* requestedFormat; + + XSelectionEvent* respond; + + Window owner; + BOOL sync; + + /* INCR mechanism */ + Atom incr_atom; + BOOL incr_starts; + BYTE* incr_data; + size_t incr_data_length; + long event_mask; + + /* XFixes extension */ + int xfixes_event_base; + int xfixes_error_base; + BOOL xfixes_supported; + + /* last sent data */ + CLIPRDR_FORMAT* lastSentFormats; + UINT32 lastSentNumFormats; + CliprdrFileContext* file; + BOOL isImageContent; +}; + +static const char mime_text_plain[] = "text/plain"; +static const char mime_uri_list[] = "text/uri-list"; +static const char mime_html[] = "text/html"; +static const char* mime_bitmap[] = { "image/bmp", "image/x-bmp", "image/x-MS-bmp", + "image/x-win-bitmap" }; +static const char mime_webp[] = "image/webp"; +static const char mime_png[] = "image/png"; +static const char mime_jpeg[] = "image/jpeg"; +static const char mime_tiff[] = "image/tiff"; +static const char* mime_images[] = { mime_webp, mime_png, mime_jpeg, mime_tiff }; + +static const char mime_gnome_copied_files[] = "x-special/gnome-copied-files"; +static const char mime_mate_copied_files[] = "x-special/mate-copied-files"; + +static const char type_FileGroupDescriptorW[] = "FileGroupDescriptorW"; +static const char type_HtmlFormat[] = "HTML Format"; + +static void xf_cliprdr_clear_cached_data(xfClipboard* clipboard); +static UINT xf_cliprdr_send_client_format_list(xfClipboard* clipboard, BOOL force); +static void xf_cliprdr_set_selection_owner(xfContext* xfc, xfClipboard* clipboard, Time timestamp); + +static void requested_format_free(RequestedFormat** ppRequestedFormat) +{ + if (!ppRequestedFormat) + return; + if (!(*ppRequestedFormat)) + return; + + free((*ppRequestedFormat)->formatName); + free(*ppRequestedFormat); + *ppRequestedFormat = nullptr; +} + +static BOOL requested_format_replace(RequestedFormat** ppRequestedFormat, UINT32 remoteFormatId, + UINT32 localFormatId, const char* formatName) +{ + if (!ppRequestedFormat) + return FALSE; + + requested_format_free(ppRequestedFormat); + RequestedFormat* requested = calloc(1, sizeof(RequestedFormat)); + if (!requested) + return FALSE; + requested->localFormat = localFormatId; + requested->formatToRequest = remoteFormatId; + if (formatName) + { + requested->formatName = _strdup(formatName); + if (!requested->formatName) + { + free(requested); + return FALSE; + } + } + + *ppRequestedFormat = requested; + return TRUE; +} + +static void xf_cached_data_free(void* ptr) +{ + xfCachedData* cached_data = ptr; + if (!cached_data) + return; + + free(cached_data->data); + free(cached_data); +} + +static xfCachedData* xf_cached_data_new(BYTE* data, size_t data_length) +{ + if (data_length > UINT32_MAX) + return nullptr; + + xfCachedData* cached_data = calloc(1, sizeof(xfCachedData)); + if (!cached_data) + return nullptr; + + cached_data->data = data; + cached_data->data_length = (UINT32)data_length; + + return cached_data; +} + +static xfCachedData* xf_cached_data_new_copy(const BYTE* data, size_t data_length) +{ + BYTE* copy = nullptr; + if (data_length > 0) + { + copy = calloc(data_length + 1, sizeof(BYTE)); + if (!copy) + return nullptr; + memcpy(copy, data, data_length); + } + + xfCachedData* cache = xf_cached_data_new(copy, data_length); + if (!cache) + free(copy); + return cache; +} + +static void xf_clipboard_free_server_formats(xfClipboard* clipboard) +{ + WINPR_ASSERT(clipboard); + if (clipboard->serverFormats) + { + for (size_t i = 0; i < clipboard->numServerFormats; i++) + { + CLIPRDR_FORMAT* format = &clipboard->serverFormats[i]; + free(format->formatName); + } + + free(clipboard->serverFormats); + clipboard->serverFormats = nullptr; + } +} + +static BOOL xf_cliprdr_update_owner(xfClipboard* clipboard) +{ + WINPR_ASSERT(clipboard); + + xfContext* xfc = clipboard->xfc; + WINPR_ASSERT(xfc); + + if (!clipboard->sync) + return FALSE; + + Window owner = LogDynAndXGetSelectionOwner(xfc->log, xfc->display, clipboard->clipboard_atom); + if (clipboard->owner == owner) + return FALSE; + + clipboard->owner = owner; + return TRUE; +} + +static void xf_cliprdr_check_owner(xfClipboard* clipboard) +{ + if (xf_cliprdr_update_owner(clipboard)) + xf_cliprdr_send_client_format_list(clipboard, FALSE); +} + +static BOOL xf_cliprdr_is_self_owned(xfClipboard* clipboard) +{ + xfContext* xfc = nullptr; + + WINPR_ASSERT(clipboard); + + xfc = clipboard->xfc; + WINPR_ASSERT(xfc); + return LogDynAndXGetSelectionOwner(xfc->log, xfc->display, clipboard->clipboard_atom) == + xfc->drawable; +} + +static void xf_cliprdr_set_raw_transfer_enabled(xfClipboard* clipboard, BOOL enabled) +{ + UINT32 data = WINPR_ASSERTING_INT_CAST(uint32_t, enabled); + xfContext* xfc = nullptr; + + WINPR_ASSERT(clipboard); + + xfc = clipboard->xfc; + WINPR_ASSERT(xfc); + LogDynAndXChangeProperty(xfc->log, xfc->display, xfc->drawable, clipboard->raw_transfer_atom, + XA_INTEGER, 32, PropModeReplace, (const BYTE*)&data, 1); +} + +static BOOL xf_cliprdr_is_raw_transfer_available(xfClipboard* clipboard) +{ + Atom type = 0; + int format = 0; + int result = 0; + unsigned long length = 0; + unsigned long bytes_left = 0; + UINT32* data = nullptr; + UINT32 is_enabled = 0; + Window owner = None; + xfContext* xfc = nullptr; + + WINPR_ASSERT(clipboard); + + xfc = clipboard->xfc; + WINPR_ASSERT(xfc); + + owner = LogDynAndXGetSelectionOwner(xfc->log, xfc->display, clipboard->clipboard_atom); + + if (owner != None) + { + result = LogDynAndXGetWindowProperty(xfc->log, xfc->display, owner, + clipboard->raw_transfer_atom, 0, 4, 0, XA_INTEGER, + &type, &format, &length, &bytes_left, (BYTE**)&data); + } + + if (data) + { + is_enabled = *data; + XFree(data); + } + + if ((owner == None) || (owner == xfc->drawable)) + return FALSE; + + if (result != Success) + return FALSE; + + return is_enabled != 0; +} + +static BOOL xf_cliprdr_formats_equal(const CLIPRDR_FORMAT* server, const xfCliprdrFormat* client) +{ + WINPR_ASSERT(server); + WINPR_ASSERT(client); + + if (server->formatName && client->formatName) + { + /* The server may be using short format names while we store them in full form. */ + return (0 == strncmp(server->formatName, client->formatName, strlen(server->formatName))); + } + + if (!server->formatName && !client->formatName) + { + return (server->formatId == client->formatToRequest); + } + + return FALSE; +} + +static const xfCliprdrFormat* xf_cliprdr_get_client_format_by_id(xfClipboard* clipboard, + UINT32 formatId) +{ + WINPR_ASSERT(clipboard); + + const BOOL formatIsHtml = formatId == ClipboardGetFormatId(clipboard->system, type_HtmlFormat); + const BOOL fetchImage = clipboard->isImageContent && formatIsHtml; + for (size_t index = 0; index < clipboard->numClientFormats; index++) + { + const xfCliprdrFormat* format = &(clipboard->clientFormats[index]); + + if (fetchImage && format->isImage) + return format; + + if (format->formatToRequest == formatId) + return format; + } + + return nullptr; +} + +static const xfCliprdrFormat* xf_cliprdr_get_client_format_by_atom(xfClipboard* clipboard, + Atom atom) +{ + WINPR_ASSERT(clipboard); + + for (UINT32 i = 0; i < clipboard->numClientFormats; i++) + { + const xfCliprdrFormat* format = &(clipboard->clientFormats[i]); + + if (format->atom == atom) + return format; + } + + return nullptr; +} + +static const CLIPRDR_FORMAT* xf_cliprdr_get_server_format_by_atom(xfClipboard* clipboard, Atom atom) +{ + WINPR_ASSERT(clipboard); + + for (size_t i = 0; i < clipboard->numClientFormats; i++) + { + const xfCliprdrFormat* client_format = &(clipboard->clientFormats[i]); + + if (client_format->atom == atom) + { + for (size_t j = 0; j < clipboard->numServerFormats; j++) + { + const CLIPRDR_FORMAT* server_format = &(clipboard->serverFormats[j]); + + if (xf_cliprdr_formats_equal(server_format, client_format)) + return server_format; + } + } + } + + return nullptr; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_cliprdr_send_data_request(xfClipboard* clipboard, UINT32 formatId, + WINPR_ATTR_UNUSED const xfCliprdrFormat* cformat) +{ + CLIPRDR_FORMAT_DATA_REQUEST request = WINPR_C_ARRAY_INIT; + request.requestedFormatId = formatId; + + DEBUG_CLIPRDR("requesting format 0x%08" PRIx32 " [%s] {local 0x%08" PRIx32 "} [%s]", formatId, + ClipboardGetFormatIdString(formatId), cformat->localFormat, cformat->formatName); + + WINPR_ASSERT(clipboard); + WINPR_ASSERT(clipboard->context); + WINPR_ASSERT(clipboard->context->ClientFormatDataRequest); + return clipboard->context->ClientFormatDataRequest(clipboard->context, &request); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_cliprdr_send_data_response(xfClipboard* clipboard, const xfCliprdrFormat* format, + const BYTE* data, size_t size) +{ + CLIPRDR_FORMAT_DATA_RESPONSE response = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(clipboard); + + /* No request currently pending, do not send a response. */ + if (clipboard->requestedFormatId == UINT32_MAX) + return CHANNEL_RC_OK; + + if (size == 0) + { + if (format) + DEBUG_CLIPRDR("send CB_RESPONSE_FAIL response {format 0x%08" PRIx32 + " [%s] {local 0x%08" PRIx32 "} [%s]", + format->formatToRequest, + ClipboardGetFormatIdString(format->formatToRequest), format->localFormat, + format->formatName); + else + DEBUG_CLIPRDR("send CB_RESPONSE_FAIL response"); + } + else + { + WINPR_ASSERT(format); + DEBUG_CLIPRDR("send response format 0x%08" PRIx32 " [%s] {local 0x%08" PRIx32 " [%s]} [%s]", + format->formatToRequest, ClipboardGetFormatIdString(format->formatToRequest), + format->localFormat, + ClipboardGetFormatName(clipboard->system, format->localFormat), + format->formatName); + } + /* Request handled, reset to invalid */ + clipboard->requestedFormatId = UINT32_MAX; + + response.common.msgFlags = (data) ? CB_RESPONSE_OK : CB_RESPONSE_FAIL; + + WINPR_ASSERT(size <= UINT32_MAX); + response.common.dataLen = (UINT32)size; + response.requestedFormatData = data; + + WINPR_ASSERT(clipboard->context); + WINPR_ASSERT(clipboard->context->ClientFormatDataResponse); + return clipboard->context->ClientFormatDataResponse(clipboard->context, &response); +} + +static wStream* xf_cliprdr_serialize_server_format_list(xfClipboard* clipboard) +{ + UINT32 formatCount = 0; + wStream* s = nullptr; + + WINPR_ASSERT(clipboard); + + /* Typical MS Word format list is about 80 bytes long. */ + if (!(s = Stream_New(nullptr, 128))) + { + WLog_ERR(TAG, "failed to allocate serialized format list"); + goto error; + } + + /* If present, the last format is always synthetic CF_RAW. Do not include it. */ + formatCount = (clipboard->numServerFormats > 0) ? clipboard->numServerFormats - 1 : 0; + Stream_Write_UINT32(s, formatCount); + + for (UINT32 i = 0; i < formatCount; i++) + { + CLIPRDR_FORMAT* format = &clipboard->serverFormats[i]; + size_t name_length = format->formatName ? strlen(format->formatName) : 0; + + DEBUG_CLIPRDR("server announced 0x%08" PRIx32 " [%s][%s]", format->formatId, + ClipboardGetFormatIdString(format->formatId), format->formatName); + if (!Stream_EnsureRemainingCapacity(s, sizeof(UINT32) + name_length + 1)) + { + WLog_ERR(TAG, "failed to expand serialized format list"); + goto error; + } + + Stream_Write_UINT32(s, format->formatId); + + if (format->formatName) + Stream_Write(s, format->formatName, name_length); + + Stream_Write_UINT8(s, '\0'); + } + + Stream_SealLength(s); + return s; +error: + Stream_Free(s, TRUE); + return nullptr; +} + +static CLIPRDR_FORMAT* xf_cliprdr_parse_server_format_list(BYTE* data, size_t length, + UINT32* numFormats) +{ + wStream* s = nullptr; + CLIPRDR_FORMAT* formats = nullptr; + + WINPR_ASSERT(data || (length == 0)); + WINPR_ASSERT(numFormats); + + if (!(s = Stream_New(data, length))) + { + WLog_ERR(TAG, "failed to allocate stream for parsing serialized format list"); + goto error; + } + + if (!Stream_CheckAndLogRequiredLength(TAG, s, sizeof(UINT32))) + goto error; + + Stream_Read_UINT32(s, *numFormats); + + if (*numFormats > MAX_CLIPBOARD_FORMATS) + { + WLog_ERR(TAG, "unexpectedly large number of formats: %" PRIu32 "", *numFormats); + goto error; + } + + if (!(formats = (CLIPRDR_FORMAT*)calloc(*numFormats, sizeof(CLIPRDR_FORMAT)))) + { + WLog_ERR(TAG, "failed to allocate format list"); + goto error; + } + + for (UINT32 i = 0; i < *numFormats; i++) + { + const char* formatName = nullptr; + size_t formatNameLength = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, sizeof(UINT32))) + goto error; + + Stream_Read_UINT32(s, formats[i].formatId); + formatName = (const char*)Stream_Pointer(s); + formatNameLength = strnlen(formatName, Stream_GetRemainingLength(s)); + + if (formatNameLength == Stream_GetRemainingLength(s)) + { + WLog_ERR(TAG, "missing terminating null byte, %" PRIuz " bytes left to read", + formatNameLength); + goto error; + } + + formats[i].formatName = strndup(formatName, formatNameLength); + Stream_Seek(s, formatNameLength + 1); + } + + Stream_Free(s, FALSE); + return formats; +error: + Stream_Free(s, FALSE); + free(formats); + *numFormats = 0; + return nullptr; +} + +static void xf_cliprdr_free_formats(CLIPRDR_FORMAT* formats, UINT32 numFormats) +{ + WINPR_ASSERT(formats || (numFormats == 0)); + + for (UINT32 i = 0; i < numFormats; i++) + { + free(formats[i].formatName); + } + + free(formats); +} + +static CLIPRDR_FORMAT* xf_cliprdr_get_raw_server_formats(xfClipboard* clipboard, UINT32* numFormats) +{ + Atom type = None; + int format = 0; + unsigned long length = 0; + unsigned long remaining = 0; + BYTE* data = nullptr; + CLIPRDR_FORMAT* formats = nullptr; + xfContext* xfc = nullptr; + + WINPR_ASSERT(clipboard); + WINPR_ASSERT(numFormats); + + xfc = clipboard->xfc; + WINPR_ASSERT(xfc); + + *numFormats = 0; + + Window owner = LogDynAndXGetSelectionOwner(xfc->log, xfc->display, clipboard->clipboard_atom); + LogDynAndXGetWindowProperty(xfc->log, xfc->display, owner, clipboard->raw_format_list_atom, 0, + 4096, False, clipboard->raw_format_list_atom, &type, &format, + &length, &remaining, &data); + + if (data && length > 0 && format == 8 && type == clipboard->raw_format_list_atom) + { + formats = xf_cliprdr_parse_server_format_list(data, length, numFormats); + } + else + { + WLog_ERR(TAG, + "failed to retrieve raw format list: data=%p, length=%lu, format=%d, type=%lu " + "(expected=%lu)", + (void*)data, length, format, (unsigned long)type, + (unsigned long)clipboard->raw_format_list_atom); + } + + if (data) + XFree(data); + + return formats; +} + +static BOOL xf_cliprdr_should_add_format(const CLIPRDR_FORMAT* formats, size_t count, + const xfCliprdrFormat* xformat) +{ + WINPR_ASSERT(formats); + + if (!xformat) + return FALSE; + + for (size_t x = 0; x < count; x++) + { + const CLIPRDR_FORMAT* format = &formats[x]; + if (format->formatId == xformat->formatToRequest) + return FALSE; + } + return TRUE; +} + +static CLIPRDR_FORMAT* xf_cliprdr_get_formats_from_targets(xfClipboard* clipboard, + UINT32* numFormats) +{ + Atom atom = None; + BYTE* data = nullptr; + int format_property = 0; + unsigned long proplength = 0; + unsigned long bytes_left = 0; + CLIPRDR_FORMAT* formats = nullptr; + + WINPR_ASSERT(clipboard); + WINPR_ASSERT(numFormats); + + xfContext* xfc = clipboard->xfc; + WINPR_ASSERT(xfc); + + *numFormats = 0; + LogDynAndXGetWindowProperty(xfc->log, xfc->display, xfc->drawable, clipboard->property_atom, 0, + 200, 0, XA_ATOM, &atom, &format_property, &proplength, &bytes_left, + &data); + + if (proplength > 0) + { + unsigned long length = proplength + 1; + if (!data) + { + WLog_ERR(TAG, "XGetWindowProperty set length = %lu but data is nullptr", length); + goto out; + } + + if (!(formats = (CLIPRDR_FORMAT*)calloc(length, sizeof(CLIPRDR_FORMAT)))) + { + WLog_ERR(TAG, "failed to allocate %lu CLIPRDR_FORMAT structs", length); + goto out; + } + } + + { + BOOL isImage = FALSE; + BOOL hasHtml = FALSE; + const uint32_t htmlFormatId = ClipboardRegisterFormat(clipboard->system, type_HtmlFormat); + for (unsigned long i = 0; i < proplength; i++) + { + Atom tatom = ((Atom*)data)[i]; + const xfCliprdrFormat* format = xf_cliprdr_get_client_format_by_atom(clipboard, tatom); + + if (xf_cliprdr_should_add_format(formats, *numFormats, format)) + { + CLIPRDR_FORMAT* cformat = &formats[*numFormats]; + cformat->formatId = format->formatToRequest; + + /* We do not want to double register a format, so check if HTML was already + * registered. + */ + if (cformat->formatId == htmlFormatId) + hasHtml = TRUE; + + /* These are standard image types that will always be registered regardless of + * actual image format. */ + if (cformat->formatId == CF_TIFF) + isImage = TRUE; + else if (cformat->formatId == CF_DIB) + isImage = TRUE; + else if (cformat->formatId == CF_DIBV5) + isImage = TRUE; + + if (format->formatName) + { + cformat->formatName = _strdup(format->formatName); + WINPR_ASSERT(cformat->formatName); + } + else + cformat->formatName = nullptr; + + *numFormats += 1; + } + } + + clipboard->isImageContent = isImage; + if (isImage && !hasHtml) + { + CLIPRDR_FORMAT* cformat = &formats[*numFormats]; + cformat->formatId = htmlFormatId; + cformat->formatName = _strdup(type_HtmlFormat); + + *numFormats += 1; + } + } +out: + + if (data) + XFree(data); + + return formats; +} + +static CLIPRDR_FORMAT* xf_cliprdr_get_client_formats(xfClipboard* clipboard, UINT32* numFormats) +{ + CLIPRDR_FORMAT* formats = nullptr; + + WINPR_ASSERT(clipboard); + WINPR_ASSERT(numFormats); + + *numFormats = 0; + + if (xf_cliprdr_is_raw_transfer_available(clipboard)) + { + formats = xf_cliprdr_get_raw_server_formats(clipboard, numFormats); + } + + if (*numFormats == 0) + { + xf_cliprdr_free_formats(formats, *numFormats); + formats = xf_cliprdr_get_formats_from_targets(clipboard, numFormats); + } + + return formats; +} + +static void xf_cliprdr_provide_server_format_list(xfClipboard* clipboard) +{ + wStream* formats = nullptr; + xfContext* xfc = nullptr; + + WINPR_ASSERT(clipboard); + + xfc = clipboard->xfc; + WINPR_ASSERT(xfc); + + formats = xf_cliprdr_serialize_server_format_list(clipboard); + + if (formats) + { + const size_t len = Stream_Length(formats); + WINPR_ASSERT(len <= INT32_MAX); + LogDynAndXChangeProperty(xfc->log, xfc->display, xfc->drawable, + clipboard->raw_format_list_atom, clipboard->raw_format_list_atom, + 8, PropModeReplace, Stream_Buffer(formats), (int)len); + } + else + { + LogDynAndXDeleteProperty(xfc->log, xfc->display, xfc->drawable, + clipboard->raw_format_list_atom); + } + + Stream_Free(formats, TRUE); +} + +static BOOL xf_clipboard_format_equal(const CLIPRDR_FORMAT* a, const CLIPRDR_FORMAT* b) +{ + WINPR_ASSERT(a); + WINPR_ASSERT(b); + + if (a->formatId != b->formatId) + return FALSE; + if (!a->formatName && !b->formatName) + return TRUE; + if (!a->formatName || !b->formatName) + return FALSE; + return strcmp(a->formatName, b->formatName) == 0; +} + +static BOOL xf_clipboard_changed(xfClipboard* clipboard, const CLIPRDR_FORMAT* formats, + UINT32 numFormats) +{ + WINPR_ASSERT(clipboard); + WINPR_ASSERT(formats || (numFormats == 0)); + + if (clipboard->lastSentNumFormats != numFormats) + return TRUE; + + for (UINT32 x = 0; x < numFormats; x++) + { + const CLIPRDR_FORMAT* cur = &clipboard->lastSentFormats[x]; + BOOL contained = FALSE; + for (UINT32 y = 0; y < numFormats; y++) + { + if (xf_clipboard_format_equal(cur, &formats[y])) + { + contained = TRUE; + break; + } + } + if (!contained) + return TRUE; + } + + return FALSE; +} + +static void xf_clipboard_formats_free(xfClipboard* clipboard) +{ + WINPR_ASSERT(clipboard); + + /* Synchronize RDP/X11 thread with channel thread */ + xf_lock_x11(clipboard->xfc); + xf_cliprdr_free_formats(clipboard->lastSentFormats, clipboard->lastSentNumFormats); + xf_unlock_x11(clipboard->xfc); + + clipboard->lastSentFormats = nullptr; + clipboard->lastSentNumFormats = 0; +} + +static BOOL xf_clipboard_copy_formats(xfClipboard* clipboard, const CLIPRDR_FORMAT* formats, + UINT32 numFormats) +{ + WINPR_ASSERT(clipboard); + WINPR_ASSERT(formats || (numFormats == 0)); + + xf_clipboard_formats_free(clipboard); + if (numFormats > 0) + clipboard->lastSentFormats = calloc(numFormats, sizeof(CLIPRDR_FORMAT)); + if (!clipboard->lastSentFormats) + return FALSE; + clipboard->lastSentNumFormats = numFormats; + for (UINT32 x = 0; x < numFormats; x++) + { + CLIPRDR_FORMAT* lcur = &clipboard->lastSentFormats[x]; + const CLIPRDR_FORMAT* cur = &formats[x]; + *lcur = *cur; + if (cur->formatName) + lcur->formatName = _strdup(cur->formatName); + } + return FALSE; +} + +static UINT xf_cliprdr_send_format_list(xfClipboard* clipboard, const CLIPRDR_FORMAT* formats, + UINT32 numFormats, BOOL force) +{ + union + { + const CLIPRDR_FORMAT* cpv; + CLIPRDR_FORMAT* pv; + } cnv = { .cpv = formats }; + const CLIPRDR_FORMAT_LIST formatList = { .common.msgFlags = 0, + .numFormats = numFormats, + .formats = cnv.pv, + .common.msgType = CB_FORMAT_LIST }; + UINT ret = 0; + + WINPR_ASSERT(clipboard); + WINPR_ASSERT(formats || (numFormats == 0)); + + if (!force && !xf_clipboard_changed(clipboard, formats, numFormats)) + return CHANNEL_RC_OK; + +#if defined(WITH_DEBUG_CLIPRDR) + for (UINT32 x = 0; x < numFormats; x++) + { + const CLIPRDR_FORMAT* format = &formats[x]; + DEBUG_CLIPRDR("announcing format 0x%08" PRIx32 " [%s] [%s]", format->formatId, + ClipboardGetFormatIdString(format->formatId), format->formatName); + } +#endif + + xf_clipboard_copy_formats(clipboard, formats, numFormats); + /* Ensure all pending requests are answered. */ + xf_cliprdr_send_data_response(clipboard, nullptr, nullptr, 0); + + xf_cliprdr_clear_cached_data(clipboard); + + ret = cliprdr_file_context_notify_new_client_format_list(clipboard->file); + if (ret) + return ret; + + WINPR_ASSERT(clipboard->context); + WINPR_ASSERT(clipboard->context->ClientFormatList); + return clipboard->context->ClientFormatList(clipboard->context, &formatList); +} + +static void xf_cliprdr_get_requested_targets(xfClipboard* clipboard) +{ + UINT32 numFormats = 0; + CLIPRDR_FORMAT* formats = xf_cliprdr_get_client_formats(clipboard, &numFormats); + xf_cliprdr_send_format_list(clipboard, formats, numFormats, FALSE); + xf_cliprdr_free_formats(formats, numFormats); +} + +static void xf_cliprdr_process_requested_data(xfClipboard* clipboard, BOOL hasData, + const BYTE* data, size_t size) +{ + BOOL bSuccess = 0; + UINT32 SrcSize = 0; + UINT32 DstSize = 0; + INT64 srcFormatId = -1; + BYTE* pDstData = nullptr; + const xfCliprdrFormat* format = nullptr; + + WINPR_ASSERT(clipboard); + + if (clipboard->incr_starts && hasData) + return; + + /* Reset incr_data_length, as we've reached the end of a possible incremental update. + * this ensures on next event that the buffer is not reused. */ + clipboard->incr_data_length = 0; + + format = xf_cliprdr_get_client_format_by_id(clipboard, clipboard->requestedFormatId); + + if (!hasData || !data || !format) + { + xf_cliprdr_send_data_response(clipboard, format, nullptr, 0); + return; + } + + switch (format->formatToRequest) + { + case CF_RAW: + srcFormatId = CF_RAW; + break; + + case CF_TEXT: + case CF_OEMTEXT: + case CF_UNICODETEXT: + srcFormatId = format->localFormat; + break; + + default: + srcFormatId = format->localFormat; + break; + } + + if (srcFormatId < 0) + { + xf_cliprdr_send_data_response(clipboard, format, nullptr, 0); + return; + } + + ClipboardLock(clipboard->system); + SrcSize = (UINT32)size; + bSuccess = ClipboardSetData(clipboard->system, (UINT32)srcFormatId, data, SrcSize); + + if (bSuccess) + { + DstSize = 0; + pDstData = + (BYTE*)ClipboardGetData(clipboard->system, clipboard->requestedFormatId, &DstSize); + } + ClipboardUnlock(clipboard->system); + + if (!pDstData) + { + xf_cliprdr_send_data_response(clipboard, format, nullptr, 0); + return; + } + + /* + * File lists require a bit of postprocessing to convert them from WinPR's FILDESCRIPTOR + * format to CLIPRDR_FILELIST expected by the server. + * + * We check for "FileGroupDescriptorW" format being registered (i.e., nonzero) in order + * to not process CF_RAW as a file list in case WinPR does not support file transfers. + */ + ClipboardLock(clipboard->system); + if (format->formatToRequest && + (format->formatToRequest == + ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW))) + { + UINT error = NO_ERROR; + FILEDESCRIPTORW* file_array = (FILEDESCRIPTORW*)pDstData; + UINT32 file_count = DstSize / sizeof(FILEDESCRIPTORW); + pDstData = nullptr; + DstSize = 0; + + const UINT32 flags = cliprdr_file_context_remote_get_flags(clipboard->file); + error = cliprdr_serialize_file_list_ex(flags, file_array, file_count, &pDstData, &DstSize); + + if (error) + WLog_ERR(TAG, "failed to serialize CLIPRDR_FILELIST: 0x%08X", error); + else + { + UINT32 formatId = ClipboardGetFormatId(clipboard->system, mime_uri_list); + UINT32 url_size = 0; + + char* url = ClipboardGetData(clipboard->system, formatId, &url_size); + cliprdr_file_context_update_client_data(clipboard->file, url, url_size); + free(url); + } + + free(file_array); + } + ClipboardUnlock(clipboard->system); + + xf_cliprdr_send_data_response(clipboard, format, pDstData, DstSize); + free(pDstData); +} + +static BOOL xf_restore_input_flags(xfClipboard* clipboard) +{ + WINPR_ASSERT(clipboard); + + xfContext* xfc = clipboard->xfc; + WINPR_ASSERT(xfc); + + if (clipboard->event_mask != 0) + { + XSelectInput(xfc->display, xfc->drawable, clipboard->event_mask); + clipboard->event_mask = 0; + } + return TRUE; +} + +static BOOL append(xfClipboard* clipboard, const void* sdata, size_t length) +{ + WINPR_ASSERT(clipboard); + + const size_t size = length + clipboard->incr_data_length + 2; + BYTE* data = realloc(clipboard->incr_data, size); + if (!data) + return FALSE; + clipboard->incr_data = data; + memcpy(&data[clipboard->incr_data_length], sdata, length); + clipboard->incr_data_length += length; + clipboard->incr_data[clipboard->incr_data_length + 0] = '\0'; + clipboard->incr_data[clipboard->incr_data_length + 1] = '\0'; + return TRUE; +} + +static BOOL xf_cliprdr_stop_incr(xfClipboard* clipboard) +{ + clipboard->incr_starts = FALSE; + clipboard->incr_data_length = 0; + return xf_restore_input_flags(clipboard); +} + +static BOOL xf_cliprdr_get_requested_data(xfClipboard* clipboard, Atom target) +{ + WINPR_ASSERT(clipboard); + + xfContext* xfc = clipboard->xfc; + WINPR_ASSERT(xfc); + + const xfCliprdrFormat* format = + xf_cliprdr_get_client_format_by_id(clipboard, clipboard->requestedFormatId); + + if (!format || (format->atom != target)) + { + xf_cliprdr_send_data_response(clipboard, format, nullptr, 0); + return FALSE; + } + + Atom type = 0; + BOOL has_data = FALSE; + int format_property = 0; + unsigned long length = 0; + unsigned long total_bytes = 0; + BYTE* property_data = nullptr; + const int rc = LogDynAndXGetWindowProperty( + xfc->log, xfc->display, xfc->drawable, clipboard->property_atom, 0, 0, False, target, &type, + &format_property, &length, &total_bytes, &property_data); + if (property_data) + XFree(property_data); + if (rc != Success) + { + xf_cliprdr_send_data_response(clipboard, format, nullptr, 0); + return FALSE; + } + + size_t len = 0; + + /* No data, empty return */ + if ((total_bytes <= 0) && !clipboard->incr_starts) + { + xf_cliprdr_stop_incr(clipboard); + } + /* We have to read incremental updates */ + else if (type == clipboard->incr_atom) + { + xf_cliprdr_stop_incr(clipboard); + clipboard->incr_starts = TRUE; + has_data = TRUE; /* data will follow in PropertyNotify event */ + } + else + { + BYTE* incremental_data = nullptr; + unsigned long incremental_len = 0; + + /* Incremental updates completed, pass data */ + len = clipboard->incr_data_length; + if (total_bytes <= 0) + { + xf_cliprdr_stop_incr(clipboard); + has_data = TRUE; + } + /* Read incremental data batch */ + else if (LogDynAndXGetWindowProperty( + xfc->log, xfc->display, xfc->drawable, clipboard->property_atom, 0, + WINPR_ASSERTING_INT_CAST(int32_t, total_bytes), False, target, &type, + &format_property, &incremental_len, &length, &incremental_data) == Success) + { + has_data = append(clipboard, incremental_data, incremental_len); + len = clipboard->incr_data_length; + } + + if (incremental_data) + XFree(incremental_data); + } + + LogDynAndXDeleteProperty(xfc->log, xfc->display, xfc->drawable, clipboard->property_atom); + xf_cliprdr_process_requested_data(clipboard, has_data, clipboard->incr_data, len); + + return TRUE; +} + +static void xf_cliprdr_append_target(xfClipboard* clipboard, Atom target) +{ + WINPR_ASSERT(clipboard); + + if (clipboard->numTargets >= ARRAYSIZE(clipboard->targets)) + return; + + for (size_t i = 0; i < clipboard->numTargets; i++) + { + if (clipboard->targets[i] == target) + return; + } + + clipboard->targets[clipboard->numTargets++] = target; +} + +static void xf_cliprdr_provide_targets(xfClipboard* clipboard, const XSelectionEvent* respond) +{ + xfContext* xfc = nullptr; + + WINPR_ASSERT(clipboard); + + xfc = clipboard->xfc; + WINPR_ASSERT(xfc); + + if (respond->property != None) + { + WINPR_ASSERT(clipboard->numTargets <= INT32_MAX); + LogDynAndXChangeProperty(xfc->log, xfc->display, respond->requestor, respond->property, + XA_ATOM, 32, PropModeReplace, (const BYTE*)clipboard->targets, + (int)clipboard->numTargets); + } +} + +static void xf_cliprdr_provide_timestamp(xfClipboard* clipboard, const XSelectionEvent* respond) +{ + xfContext* xfc = nullptr; + + WINPR_ASSERT(clipboard); + + xfc = clipboard->xfc; + WINPR_ASSERT(xfc); + + if (respond->property != None) + { + LogDynAndXChangeProperty(xfc->log, xfc->display, respond->requestor, respond->property, + XA_INTEGER, 32, PropModeReplace, + (const BYTE*)&clipboard->selection_ownership_timestamp, 1); + } +} + +#define xf_cliprdr_provide_data(clipboard, respond, data, size) \ + xf_cliprdr_provide_data_((clipboard), (respond), (data), (size), __FILE__, __func__, __LINE__) +static void xf_cliprdr_provide_data_(xfClipboard* clipboard, const XSelectionEvent* respond, + const BYTE* data, UINT32 size, const char* file, + const char* fkt, size_t line) +{ + WINPR_ASSERT(clipboard); + + xfContext* xfc = clipboard->xfc; + WINPR_ASSERT(xfc); + + if (respond->property != None) + { + LogDynAndXChangeProperty_ex(xfc->log, file, fkt, line, xfc->display, respond->requestor, + respond->property, respond->target, 8, PropModeReplace, data, + WINPR_ASSERTING_INT_CAST(int32_t, size)); + } +} + +static void log_selection_event(xfContext* xfc, const XEvent* event) +{ + const DWORD level = WLOG_TRACE; + static wLog* _log_cached_ptr = nullptr; + if (!_log_cached_ptr) + _log_cached_ptr = WLog_Get(TAG); + if (WLog_IsLevelActive(_log_cached_ptr, level)) + { + + switch (event->type) + { + case SelectionClear: + { + const XSelectionClearEvent* xevent = &event->xselectionclear; + char* selection = + Safe_XGetAtomName(_log_cached_ptr, xfc->display, xevent->selection); + WLog_Print(_log_cached_ptr, level, "got event %s [selection %s]", + x11_event_string(event->type), selection); + XFree(selection); + } + break; + case SelectionNotify: + { + const XSelectionEvent* xevent = &event->xselection; + char* selection = + Safe_XGetAtomName(_log_cached_ptr, xfc->display, xevent->selection); + char* target = Safe_XGetAtomName(_log_cached_ptr, xfc->display, xevent->target); + char* property = Safe_XGetAtomName(_log_cached_ptr, xfc->display, xevent->property); + WLog_Print(_log_cached_ptr, level, + "got event %s [selection %s, target %s, property %s]", + x11_event_string(event->type), selection, target, property); + XFree(selection); + XFree(target); + XFree(property); + } + break; + case SelectionRequest: + { + const XSelectionRequestEvent* xevent = &event->xselectionrequest; + char* selection = + Safe_XGetAtomName(_log_cached_ptr, xfc->display, xevent->selection); + char* target = Safe_XGetAtomName(_log_cached_ptr, xfc->display, xevent->target); + char* property = Safe_XGetAtomName(_log_cached_ptr, xfc->display, xevent->property); + WLog_Print(_log_cached_ptr, level, + "got event %s [selection %s, target %s, property %s]", + x11_event_string(event->type), selection, target, property); + XFree(selection); + XFree(target); + XFree(property); + } + break; + case PropertyNotify: + { + const XPropertyEvent* xevent = &event->xproperty; + char* atom = Safe_XGetAtomName(_log_cached_ptr, xfc->display, xevent->atom); + WLog_Print(_log_cached_ptr, level, "got event %s [atom %s]", + x11_event_string(event->type), atom); + XFree(atom); + } + break; + default: + break; + } + } +} + +static BOOL xf_cliprdr_process_selection_notify(xfClipboard* clipboard, + const XSelectionEvent* xevent) +{ + WINPR_ASSERT(clipboard); + WINPR_ASSERT(xevent); + + if (xevent->target == clipboard->targets[1]) + { + if (xevent->property == None) + { + xf_cliprdr_send_client_format_list(clipboard, FALSE); + } + else + { + xf_cliprdr_get_requested_targets(clipboard); + } + + return TRUE; + } + else + { + return xf_cliprdr_get_requested_data(clipboard, xevent->target); + } +} + +void xf_cliprdr_clear_cached_data(xfClipboard* clipboard) +{ + WINPR_ASSERT(clipboard); + + ClipboardLock(clipboard->system); + ClipboardEmpty(clipboard->system); + + HashTable_Clear(clipboard->cachedData); + HashTable_Clear(clipboard->cachedRawData); + + cliprdr_file_context_clear(clipboard->file); + + xf_cliprdr_stop_incr(clipboard); + ClipboardUnlock(clipboard->system); +} + +static void* format_to_cache_slot(UINT32 format) +{ + union + { + uintptr_t uptr; + void* vptr; + } cnv; + cnv.uptr = 0x100000000ULL + format; + return cnv.vptr; +} + +static UINT32 get_dst_format_id_for_local_request(xfClipboard* clipboard, + const xfCliprdrFormat* format) +{ + UINT32 dstFormatId = 0; + + WINPR_ASSERT(format); + + if (!format->formatName) + return format->localFormat; + + ClipboardLock(clipboard->system); + if (strcmp(format->formatName, type_HtmlFormat) == 0) + dstFormatId = ClipboardGetFormatId(clipboard->system, mime_html); + ClipboardUnlock(clipboard->system); + + if (strcmp(format->formatName, type_FileGroupDescriptorW) == 0) + dstFormatId = format->localFormat; + + return dstFormatId; +} + +static void get_src_format_info_for_local_request(xfClipboard* clipboard, + const xfCliprdrFormat* format, + UINT32* srcFormatId, BOOL* nullTerminated) +{ + *srcFormatId = 0; + *nullTerminated = FALSE; + + if (format->formatName) + { + ClipboardLock(clipboard->system); + if (strcmp(format->formatName, type_HtmlFormat) == 0) + { + *srcFormatId = ClipboardGetFormatId(clipboard->system, type_HtmlFormat); + *nullTerminated = TRUE; + } + else if (strcmp(format->formatName, type_FileGroupDescriptorW) == 0) + { + *srcFormatId = ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW); + *nullTerminated = TRUE; + } + ClipboardUnlock(clipboard->system); + } + else + { + *srcFormatId = format->formatToRequest; + switch (format->formatToRequest) + { + case CF_TEXT: + case CF_OEMTEXT: + case CF_UNICODETEXT: + *nullTerminated = TRUE; + break; + case CF_DIB: + *srcFormatId = CF_DIB; + break; + case CF_TIFF: + *srcFormatId = CF_TIFF; + break; + default: + break; + } + } +} + +static xfCachedData* convert_data_from_existing_raw_data(xfClipboard* clipboard, + xfCachedData* cached_raw_data, + UINT32 srcFormatId, BOOL nullTerminated, + UINT32 dstFormatId) +{ + UINT32 dst_size = 0; + + WINPR_ASSERT(clipboard); + WINPR_ASSERT(cached_raw_data); + WINPR_ASSERT(cached_raw_data->data); + + ClipboardLock(clipboard->system); + BOOL success = ClipboardSetData(clipboard->system, srcFormatId, cached_raw_data->data, + cached_raw_data->data_length); + if (!success) + { + WLog_WARN(TAG, "Failed to set clipboard data (formatId: %u, data: %p, data_length: %u)", + srcFormatId, WINPR_CXX_COMPAT_CAST(const void*, cached_raw_data->data), + cached_raw_data->data_length); + ClipboardUnlock(clipboard->system); + return nullptr; + } + + BYTE* dst_data = ClipboardGetData(clipboard->system, dstFormatId, &dst_size); + if (!dst_data) + { + WLog_WARN(TAG, "Failed to get converted clipboard data"); + ClipboardUnlock(clipboard->system); + return nullptr; + } + ClipboardUnlock(clipboard->system); + + if (nullTerminated) + { + BYTE* nullTerminator = memchr(dst_data, '\0', dst_size); + if (nullTerminator) + { + const intptr_t diff = nullTerminator - dst_data; + WINPR_ASSERT(diff >= 0); + WINPR_ASSERT(diff <= UINT32_MAX); + dst_size = (UINT32)diff; + } + } + + xfCachedData* cached_data = xf_cached_data_new(dst_data, dst_size); + if (!cached_data) + { + WLog_WARN(TAG, "Failed to allocate cache entry"); + free(dst_data); + return nullptr; + } + + if (!HashTable_Insert(clipboard->cachedData, format_to_cache_slot(dstFormatId), cached_data)) + { + WLog_WARN(TAG, "Failed to cache clipboard data"); + xf_cached_data_free(cached_data); + return nullptr; + } + + return cached_data; +} + +static BOOL xf_cliprdr_process_selection_request(xfClipboard* clipboard, + const XSelectionRequestEvent* xevent) +{ + int fmt = 0; + Atom type = 0; + UINT32 formatId = 0; + XSelectionEvent* respond = nullptr; + BYTE* data = nullptr; + BOOL delayRespond = 0; + BOOL rawTransfer = 0; + unsigned long length = 0; + unsigned long bytes_left = 0; + xfContext* xfc = nullptr; + + WINPR_ASSERT(clipboard); + WINPR_ASSERT(xevent); + + xfc = clipboard->xfc; + WINPR_ASSERT(xfc); + + if (xevent->owner != xfc->drawable) + return FALSE; + + delayRespond = FALSE; + + if (!(respond = (XSelectionEvent*)calloc(1, sizeof(XSelectionEvent)))) + { + WLog_ERR(TAG, "failed to allocate XEvent data"); + return FALSE; + } + + respond->property = None; + respond->type = SelectionNotify; + respond->display = xevent->display; + respond->requestor = xevent->requestor; + respond->selection = xevent->selection; + respond->target = xevent->target; + respond->time = xevent->time; + + if (xevent->target == clipboard->targets[0]) /* TIMESTAMP */ + { + /* Someone else requests the selection's timestamp */ + respond->property = xevent->property; + xf_cliprdr_provide_timestamp(clipboard, respond); + } + else if (xevent->target == clipboard->targets[1]) /* TARGETS */ + { + /* Someone else requests our available formats */ + respond->property = xevent->property; + xf_cliprdr_provide_targets(clipboard, respond); + } + else + { + const CLIPRDR_FORMAT* format = + xf_cliprdr_get_server_format_by_atom(clipboard, xevent->target); + const xfCliprdrFormat* cformat = + xf_cliprdr_get_client_format_by_atom(clipboard, xevent->target); + + if (format && (xevent->requestor != xfc->drawable)) + { + formatId = format->formatId; + rawTransfer = FALSE; + xfCachedData* cached_data = nullptr; + + if (formatId == CF_RAW) + { + if (LogDynAndXGetWindowProperty( + xfc->log, xfc->display, xevent->requestor, clipboard->property_atom, 0, 4, + 0, XA_INTEGER, &type, &fmt, &length, &bytes_left, &data) != Success) + { + } + + if (data) + { + rawTransfer = TRUE; + CopyMemory(&formatId, data, 4); + XFree(data); + } + } + + const UINT32 dstFormatId = get_dst_format_id_for_local_request(clipboard, cformat); + DEBUG_CLIPRDR("formatId: 0x%08" PRIx32 ", dstFormatId: 0x%08" PRIx32 "", formatId, + dstFormatId); + + wHashTable* table = clipboard->cachedData; + if (rawTransfer) + table = clipboard->cachedRawData; + + HashTable_Lock(table); + + if (!rawTransfer) + cached_data = HashTable_GetItemValue(table, format_to_cache_slot(dstFormatId)); + else + cached_data = HashTable_GetItemValue(table, format_to_cache_slot(formatId)); + + DEBUG_CLIPRDR("hasCachedData: %u, rawTransfer: %d", cached_data ? 1u : 0u, rawTransfer); + + if (!cached_data && !rawTransfer) + { + UINT32 srcFormatId = 0; + BOOL nullTerminated = FALSE; + xfCachedData* cached_raw_data = nullptr; + + get_src_format_info_for_local_request(clipboard, cformat, &srcFormatId, + &nullTerminated); + cached_raw_data = + HashTable_GetItemValue(clipboard->cachedRawData, (void*)(UINT_PTR)srcFormatId); + + DEBUG_CLIPRDR("hasCachedRawData: %u, rawDataLength: %u", cached_raw_data ? 1u : 0u, + cached_raw_data ? cached_raw_data->data_length : 0); + + if (cached_raw_data && cached_raw_data->data_length != 0) + cached_data = convert_data_from_existing_raw_data( + clipboard, cached_raw_data, srcFormatId, nullTerminated, dstFormatId); + } + + DEBUG_CLIPRDR("hasCachedData: %u", cached_data ? 1u : 0u); + + if (cached_data) + { + /* Cached clipboard data available. Send it now */ + respond->property = xevent->property; + + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc) + xf_cliprdr_provide_data(clipboard, respond, cached_data->data, + cached_data->data_length); + } + else if (clipboard->respond) + { + /* duplicate request */ + } + else + { + WINPR_ASSERT(cformat); + + /** + * Send clipboard data request to the server. + * Response will be postponed after receiving the data + */ + respond->property = xevent->property; + clipboard->respond = respond; + requested_format_replace(&clipboard->requestedFormat, formatId, dstFormatId, + cformat->formatName); + clipboard->data_raw_format = rawTransfer; + delayRespond = TRUE; + xf_cliprdr_send_data_request(clipboard, formatId, cformat); + } + HashTable_Unlock(table); + } + } + + if (!delayRespond) + { + union + { + XEvent* ev; + XSelectionEvent* sev; + } conv; + + conv.sev = respond; + LogDynAndXSendEvent(xfc->log, xfc->display, xevent->requestor, 0, 0, conv.ev); + LogDynAndXFlush(xfc->log, xfc->display); + free(respond); + } + + return TRUE; +} + +static BOOL xf_cliprdr_process_selection_clear(xfClipboard* clipboard, + const XSelectionClearEvent* xevent) +{ + xfContext* xfc = nullptr; + + WINPR_ASSERT(clipboard); + WINPR_ASSERT(xevent); + + xfc = clipboard->xfc; + WINPR_ASSERT(xfc); + + WINPR_UNUSED(xevent); + + if (xf_cliprdr_is_self_owned(clipboard)) + return FALSE; + + LogDynAndXDeleteProperty(xfc->log, xfc->display, clipboard->root_window, + clipboard->property_atom); + return TRUE; +} + +static BOOL xf_cliprdr_process_property_notify(xfClipboard* clipboard, const XPropertyEvent* xevent) +{ + const xfCliprdrFormat* format = nullptr; + xfContext* xfc = nullptr; + + if (!clipboard) + return TRUE; + + xfc = clipboard->xfc; + WINPR_ASSERT(xfc); + WINPR_ASSERT(xevent); + + if (xevent->atom == clipboard->timestamp_property_atom) + { + /* This is the response to the property change we did + * in xf_cliprdr_prepare_to_set_selection_owner. Now + * we can set ourselves as the selection owner. (See + * comments in those functions below.) */ + xf_cliprdr_set_selection_owner(xfc, clipboard, xevent->time); + return TRUE; + } + + if (xevent->atom != clipboard->property_atom) + return FALSE; /* Not cliprdr-related */ + + if (xevent->window == clipboard->root_window) + { + xf_cliprdr_send_client_format_list(clipboard, FALSE); + } + else if ((xevent->window == xfc->drawable) && (xevent->state == PropertyNewValue) && + clipboard->incr_starts) + { + format = xf_cliprdr_get_client_format_by_id(clipboard, clipboard->requestedFormatId); + + if (format) + xf_cliprdr_get_requested_data(clipboard, format->atom); + } + + return TRUE; +} + +void xf_cliprdr_handle_xevent(xfContext* xfc, const XEvent* event) +{ + xfClipboard* clipboard = nullptr; + + if (!xfc || !event) + return; + + clipboard = xfc->clipboard; + + if (!clipboard) + return; + +#ifdef WITH_XFIXES + + if (clipboard->xfixes_supported && + event->type == XFixesSelectionNotify + clipboard->xfixes_event_base) + { + const XFixesSelectionNotifyEvent* se = (const XFixesSelectionNotifyEvent*)event; + + if (se->subtype == XFixesSetSelectionOwnerNotify) + { + if (se->selection != clipboard->clipboard_atom) + return; + + if (LogDynAndXGetSelectionOwner(xfc->log, xfc->display, se->selection) == xfc->drawable) + return; + + clipboard->owner = None; + xf_cliprdr_check_owner(clipboard); + } + + return; + } + +#endif + + switch (event->type) + { + case SelectionNotify: + log_selection_event(xfc, event); + xf_cliprdr_process_selection_notify(clipboard, &event->xselection); + break; + + case SelectionRequest: + log_selection_event(xfc, event); + xf_cliprdr_process_selection_request(clipboard, &event->xselectionrequest); + break; + + case SelectionClear: + log_selection_event(xfc, event); + xf_cliprdr_process_selection_clear(clipboard, &event->xselectionclear); + break; + + case PropertyNotify: + log_selection_event(xfc, event); + xf_cliprdr_process_property_notify(clipboard, &event->xproperty); + break; + + case FocusIn: + if (!clipboard->xfixes_supported) + { + xf_cliprdr_check_owner(clipboard); + } + + break; + default: + break; + } +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_cliprdr_send_client_capabilities(xfClipboard* clipboard) +{ + CLIPRDR_CAPABILITIES capabilities = WINPR_C_ARRAY_INIT; + CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(clipboard); + + capabilities.cCapabilitiesSets = 1; + capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET*)&(generalCapabilitySet); + generalCapabilitySet.capabilitySetType = CB_CAPSTYPE_GENERAL; + generalCapabilitySet.capabilitySetLength = 12; + generalCapabilitySet.version = CB_CAPS_VERSION_2; + generalCapabilitySet.generalFlags = CB_USE_LONG_FORMAT_NAMES; + + WINPR_ASSERT(clipboard); + generalCapabilitySet.generalFlags |= cliprdr_file_context_current_flags(clipboard->file); + + WINPR_ASSERT(clipboard->context); + WINPR_ASSERT(clipboard->context->ClientCapabilities); + return clipboard->context->ClientCapabilities(clipboard->context, &capabilities); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_cliprdr_send_client_format_list(xfClipboard* clipboard, BOOL force) +{ + WINPR_ASSERT(clipboard); + + xfContext* xfc = clipboard->xfc; + WINPR_ASSERT(xfc); + + UINT32 numFormats = 0; + CLIPRDR_FORMAT* formats = xf_cliprdr_get_client_formats(clipboard, &numFormats); + + const UINT ret = xf_cliprdr_send_format_list(clipboard, formats, numFormats, force); + + if (clipboard->owner && clipboard->owner != xfc->drawable) + { + /* Request the owner for TARGETS, and wait for SelectionNotify event */ + LogDynAndXConvertSelection(xfc->log, xfc->display, clipboard->clipboard_atom, + clipboard->targets[1], clipboard->property_atom, xfc->drawable, + CurrentTime); + } + + xf_cliprdr_free_formats(formats, numFormats); + + return ret; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_cliprdr_send_client_format_list_response(xfClipboard* clipboard, BOOL status) +{ + CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse = WINPR_C_ARRAY_INIT; + + formatListResponse.common.msgType = CB_FORMAT_LIST_RESPONSE; + formatListResponse.common.msgFlags = status ? CB_RESPONSE_OK : CB_RESPONSE_FAIL; + formatListResponse.common.dataLen = 0; + + WINPR_ASSERT(clipboard); + WINPR_ASSERT(clipboard->context); + WINPR_ASSERT(clipboard->context->ClientFormatListResponse); + return clipboard->context->ClientFormatListResponse(clipboard->context, &formatListResponse); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_cliprdr_monitor_ready(CliprdrClientContext* context, + const CLIPRDR_MONITOR_READY* monitorReady) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(monitorReady); + + xfClipboard* clipboard = cliprdr_file_context_get_context(context->custom); + WINPR_ASSERT(clipboard); + + WINPR_UNUSED(monitorReady); + + const UINT ret = xf_cliprdr_send_client_capabilities(clipboard); + if (ret != CHANNEL_RC_OK) + return ret; + + xf_clipboard_formats_free(clipboard); + + const UINT ret2 = xf_cliprdr_send_client_format_list(clipboard, TRUE); + if (ret2 != CHANNEL_RC_OK) + return ret2; + + clipboard->sync = TRUE; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_cliprdr_server_capabilities(CliprdrClientContext* context, + const CLIPRDR_CAPABILITIES* capabilities) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(capabilities); + + xfClipboard* clipboard = cliprdr_file_context_get_context(context->custom); + WINPR_ASSERT(clipboard); + + const BYTE* capsPtr = (const BYTE*)capabilities->capabilitySets; + WINPR_ASSERT(capsPtr); + + if (!cliprdr_file_context_remote_set_flags(clipboard->file, 0)) + return ERROR_INTERNAL_ERROR; + + for (UINT32 i = 0; i < capabilities->cCapabilitiesSets; i++) + { + const CLIPRDR_CAPABILITY_SET* caps = (const CLIPRDR_CAPABILITY_SET*)capsPtr; + + if (caps->capabilitySetType == CB_CAPSTYPE_GENERAL) + { + const CLIPRDR_GENERAL_CAPABILITY_SET* generalCaps = + (const CLIPRDR_GENERAL_CAPABILITY_SET*)caps; + + if (!cliprdr_file_context_remote_set_flags(clipboard->file, generalCaps->generalFlags)) + return ERROR_INTERNAL_ERROR; + } + + capsPtr += caps->capabilitySetLength; + } + + return CHANNEL_RC_OK; +} + +static void xf_cliprdr_prepare_to_set_selection_owner(xfContext* xfc, xfClipboard* clipboard) +{ + WINPR_ASSERT(xfc); + WINPR_ASSERT(clipboard); + /* + * When you're writing to the selection in response to a + * normal X event like a mouse click or keyboard action, you + * get the selection timestamp by copying the time field out + * of that X event. Here, we're doing it on our own + * initiative, so we have to _request_ the X server time. + * + * There isn't a GetServerTime request in the X protocol, so I + * work around it by setting a property on our own window, and + * waiting for a PropertyNotify event to come back telling me + * it's been done - which will have a timestamp we can use. + */ + + /* We have to set the property to some value, but it doesn't + * matter what. Set it to its own name, which we have here + * anyway! */ + Atom value = clipboard->timestamp_property_atom; + + LogDynAndXChangeProperty(xfc->log, xfc->display, xfc->drawable, + clipboard->timestamp_property_atom, XA_ATOM, 32, PropModeReplace, + (const BYTE*)&value, 1); + LogDynAndXFlush(xfc->log, xfc->display); +} + +static void xf_cliprdr_set_selection_owner(xfContext* xfc, xfClipboard* clipboard, Time timestamp) +{ + WINPR_ASSERT(xfc); + WINPR_ASSERT(clipboard); + /* + * Actually set ourselves up as the selection owner, now that + * we have a timestamp to use. + */ + + clipboard->selection_ownership_timestamp = timestamp; + LogDynAndXSetSelectionOwner(xfc->log, xfc->display, clipboard->clipboard_atom, xfc->drawable, + timestamp); + LogDynAndXFlush(xfc->log, xfc->display); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_cliprdr_server_format_list(CliprdrClientContext* context, + const CLIPRDR_FORMAT_LIST* formatList) +{ + xfContext* xfc = nullptr; + UINT ret = 0; + xfClipboard* clipboard = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(formatList); + + clipboard = cliprdr_file_context_get_context(context->custom); + WINPR_ASSERT(clipboard); + + xfc = clipboard->xfc; + WINPR_ASSERT(xfc); + + xf_lock_x11(xfc); + + /* Clear the active SelectionRequest, as it is now invalid */ + free(clipboard->respond); + clipboard->respond = nullptr; + + xf_clipboard_formats_free(clipboard); + xf_cliprdr_clear_cached_data(clipboard); + requested_format_free(&clipboard->requestedFormat); + + xf_clipboard_free_server_formats(clipboard); + + clipboard->numServerFormats = formatList->numFormats + 1; /* +1 for CF_RAW */ + + if (!(clipboard->serverFormats = + (CLIPRDR_FORMAT*)calloc(clipboard->numServerFormats, sizeof(CLIPRDR_FORMAT)))) + { + WLog_ERR(TAG, "failed to allocate %" PRIu32 " CLIPRDR_FORMAT structs", + clipboard->numServerFormats); + ret = CHANNEL_RC_NO_MEMORY; + goto out; + } + + for (size_t i = 0; i < formatList->numFormats; i++) + { + const CLIPRDR_FORMAT* format = &formatList->formats[i]; + CLIPRDR_FORMAT* srvFormat = &clipboard->serverFormats[i]; + + srvFormat->formatId = format->formatId; + + if (format->formatName) + { + srvFormat->formatName = _strdup(format->formatName); + + if (!srvFormat->formatName) + { + for (UINT32 k = 0; k < i; k++) + free(clipboard->serverFormats[k].formatName); + + clipboard->numServerFormats = 0; + free(clipboard->serverFormats); + clipboard->serverFormats = nullptr; + ret = CHANNEL_RC_NO_MEMORY; + goto out; + } + } + } + + ClipboardLock(clipboard->system); + ret = cliprdr_file_context_notify_new_server_format_list(clipboard->file); + ClipboardUnlock(clipboard->system); + if (ret) + goto out; + + /* CF_RAW is always implicitly supported by the server */ + { + CLIPRDR_FORMAT* format = &clipboard->serverFormats[formatList->numFormats]; + format->formatId = CF_RAW; + format->formatName = nullptr; + } + xf_cliprdr_provide_server_format_list(clipboard); + clipboard->numTargets = 2; + + for (size_t i = 0; i < formatList->numFormats; i++) + { + const CLIPRDR_FORMAT* format = &formatList->formats[i]; + + for (size_t j = 0; j < clipboard->numClientFormats; j++) + { + const xfCliprdrFormat* clientFormat = &clipboard->clientFormats[j]; + if (xf_cliprdr_formats_equal(format, clientFormat)) + { + if ((clientFormat->formatName != nullptr) && + (strcmp(type_FileGroupDescriptorW, clientFormat->formatName) == 0)) + { + if (!cliprdr_file_context_has_local_support(clipboard->file)) + continue; + } + xf_cliprdr_append_target(clipboard, clientFormat->atom); + } + } + } + + ret = xf_cliprdr_send_client_format_list_response(clipboard, TRUE); + if (xfc->remote_app) + xf_cliprdr_set_selection_owner(xfc, clipboard, CurrentTime); + else + xf_cliprdr_prepare_to_set_selection_owner(xfc, clipboard); + +out: + xf_unlock_x11(xfc); + + return ret; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_cliprdr_server_format_list_response( + WINPR_ATTR_UNUSED CliprdrClientContext* context, + WINPR_ATTR_UNUSED const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(formatListResponse); + // xfClipboard* clipboard = (xfClipboard*) context->custom; + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +xf_cliprdr_server_format_data_request(CliprdrClientContext* context, + const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest) +{ + const xfCliprdrFormat* format = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(formatDataRequest); + + xfClipboard* clipboard = cliprdr_file_context_get_context(context->custom); + WINPR_ASSERT(clipboard); + + xfContext* xfc = clipboard->xfc; + WINPR_ASSERT(xfc); + + const uint32_t formatId = formatDataRequest->requestedFormatId; + + const BOOL rawTransfer = xf_cliprdr_is_raw_transfer_available(clipboard); + + if (rawTransfer) + { + format = xf_cliprdr_get_client_format_by_id(clipboard, CF_RAW); + LogDynAndXChangeProperty(xfc->log, xfc->display, xfc->drawable, clipboard->property_atom, + XA_INTEGER, 32, PropModeReplace, (const BYTE*)&formatId, 1); + } + else + format = xf_cliprdr_get_client_format_by_id(clipboard, formatId); + + clipboard->requestedFormatId = rawTransfer ? CF_RAW : formatId; + if (!format) + return xf_cliprdr_send_data_response(clipboard, format, nullptr, 0); + + DEBUG_CLIPRDR("requested format 0x%08" PRIx32 " [%s] {local 0x%08" PRIx32 " [%s]} [%s]", + format->formatToRequest, ClipboardGetFormatIdString(format->formatToRequest), + format->localFormat, + ClipboardGetFormatName(clipboard->system, format->localFormat), + format->formatName); + LogDynAndXConvertSelection(xfc->log, xfc->display, clipboard->clipboard_atom, format->atom, + clipboard->property_atom, xfc->drawable, CurrentTime); + LogDynAndXFlush(xfc->log, xfc->display); + /* After this point, we expect a SelectionNotify event from the clipboard owner. */ + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +xf_cliprdr_server_format_data_response(CliprdrClientContext* context, + const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse) +{ + BOOL bSuccess = 0; + BYTE* pDstData = nullptr; + UINT32 DstSize = 0; + UINT32 SrcSize = 0; + UINT32 srcFormatId = 0; + UINT32 dstFormatId = 0; + BOOL nullTerminated = FALSE; + xfCachedData* cached_data = nullptr; + + WINPR_ASSERT(context); + WINPR_ASSERT(formatDataResponse); + + xfClipboard* clipboard = cliprdr_file_context_get_context(context->custom); + WINPR_ASSERT(clipboard); + + xfContext* xfc = clipboard->xfc; + WINPR_ASSERT(xfc); + + const UINT32 size = formatDataResponse->common.dataLen; + const BYTE* data = formatDataResponse->requestedFormatData; + + if (formatDataResponse->common.msgFlags == CB_RESPONSE_FAIL) + { + WLog_WARN(TAG, "Format Data Response PDU msgFlags is CB_RESPONSE_FAIL"); + free(clipboard->respond); + clipboard->respond = nullptr; + return CHANNEL_RC_OK; + } + + if (!clipboard->respond) + return CHANNEL_RC_OK; + + const RequestedFormat* format = clipboard->requestedFormat; + if (clipboard->data_raw_format) + { + srcFormatId = CF_RAW; + dstFormatId = CF_RAW; + } + else if (!format) + return ERROR_INTERNAL_ERROR; + else if (format->formatName) + { + dstFormatId = format->localFormat; + + ClipboardLock(clipboard->system); + if (strcmp(format->formatName, type_HtmlFormat) == 0) + { + srcFormatId = ClipboardGetFormatId(clipboard->system, type_HtmlFormat); + dstFormatId = ClipboardGetFormatId(clipboard->system, mime_html); + nullTerminated = TRUE; + } + + if (strcmp(format->formatName, type_FileGroupDescriptorW) == 0) + { + if (!cliprdr_file_context_update_server_data(clipboard->file, clipboard->system, data, + size)) + WLog_WARN(TAG, "failed to update file descriptors"); + + srcFormatId = ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW); + const xfCliprdrFormat* dstTargetFormat = + xf_cliprdr_get_client_format_by_atom(clipboard, clipboard->respond->target); + if (!dstTargetFormat) + { + dstFormatId = ClipboardGetFormatId(clipboard->system, mime_uri_list); + } + else + { + dstFormatId = dstTargetFormat->localFormat; + } + + nullTerminated = TRUE; + } + ClipboardUnlock(clipboard->system); + } + else + { + srcFormatId = format->formatToRequest; + dstFormatId = format->localFormat; + switch (format->formatToRequest) + { + case CF_TEXT: + nullTerminated = TRUE; + break; + + case CF_OEMTEXT: + nullTerminated = TRUE; + break; + + case CF_UNICODETEXT: + nullTerminated = TRUE; + break; + + case CF_DIB: + srcFormatId = CF_DIB; + break; + + case CF_TIFF: + srcFormatId = CF_TIFF; + break; + + default: + break; + } + } + + DEBUG_CLIPRDR("requested format 0x%08" PRIx32 " [%s] {local 0x%08" PRIx32 " [%s]} [%s]", + format->formatToRequest, ClipboardGetFormatIdString(format->formatToRequest), + format->localFormat, + ClipboardGetFormatName(clipboard->system, format->localFormat), + format->formatName); + SrcSize = size; + + DEBUG_CLIPRDR("srcFormatId: 0x%08" PRIx32 ", dstFormatId: 0x%08" PRIx32 "", srcFormatId, + dstFormatId); + + ClipboardLock(clipboard->system); + bSuccess = ClipboardSetData(clipboard->system, srcFormatId, data, SrcSize); + + BOOL willQuit = FALSE; + if (bSuccess) + { + if (SrcSize == 0) + { + WLog_DBG(TAG, "skipping, empty data detected!"); + free(clipboard->respond); + clipboard->respond = nullptr; + willQuit = TRUE; + } + else + { + pDstData = (BYTE*)ClipboardGetData(clipboard->system, dstFormatId, &DstSize); + + if (!pDstData) + { + WLog_WARN(TAG, "failed to get clipboard data in format %s [source format %s]", + ClipboardGetFormatName(clipboard->system, dstFormatId), + ClipboardGetFormatName(clipboard->system, srcFormatId)); + } + + if (nullTerminated && pDstData) + { + BYTE* nullTerminator = memchr(pDstData, '\0', DstSize); + if (nullTerminator) + { + const intptr_t diff = nullTerminator - pDstData; + WINPR_ASSERT(diff >= 0); + WINPR_ASSERT(diff <= UINT32_MAX); + DstSize = (UINT32)diff; + } + } + } + } + ClipboardUnlock(clipboard->system); + if (willQuit) + return CHANNEL_RC_OK; + + /* Cache converted and original data to avoid doing a possibly costly + * conversion again on subsequent requests */ + if (pDstData) + { + cached_data = xf_cached_data_new(pDstData, DstSize); + if (!cached_data) + { + WLog_WARN(TAG, "Failed to allocate cache entry"); + free(pDstData); + return CHANNEL_RC_OK; + } + if (!HashTable_Insert(clipboard->cachedData, format_to_cache_slot(dstFormatId), + cached_data)) + { + WLog_WARN(TAG, "Failed to cache clipboard data"); + xf_cached_data_free(cached_data); + return CHANNEL_RC_OK; + } + } + + /* We have to copy the original data again, as pSrcData is now owned + * by clipboard->system. Memory allocation failure is not fatal here + * as this is only a cached value. */ + { + // clipboard->cachedData owns cached_data + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc + xfCachedData* cached_raw_data = xf_cached_data_new_copy(data, size); + if (!cached_raw_data) + WLog_WARN(TAG, "Failed to allocate cache entry"); + else + { + if (!HashTable_Insert(clipboard->cachedRawData, (void*)(UINT_PTR)srcFormatId, + cached_raw_data)) + { + WLog_WARN(TAG, "Failed to cache clipboard data"); + xf_cached_data_free(cached_raw_data); + } + } + } + + // clipboard->cachedRawData owns cached_raw_data + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc) + xf_cliprdr_provide_data(clipboard, clipboard->respond, pDstData, DstSize); + { + union + { + XEvent* ev; + XSelectionEvent* sev; + } conv; + + conv.sev = clipboard->respond; + + LogDynAndXSendEvent(xfc->log, xfc->display, clipboard->respond->requestor, 0, 0, conv.ev); + LogDynAndXFlush(xfc->log, xfc->display); + } + free(clipboard->respond); + clipboard->respond = nullptr; + return CHANNEL_RC_OK; +} + +static BOOL xf_cliprdr_is_valid_unix_filename(LPCWSTR filename) +{ + if (!filename) + return FALSE; + + if (filename[0] == L'\0') + return FALSE; + + /* Reserved characters */ + for (const WCHAR* c = filename; *c; ++c) + { + if (*c == L'/') + return FALSE; + } + + return TRUE; +} + +xfClipboard* xf_clipboard_new(xfContext* xfc, BOOL relieveFilenameRestriction) +{ + int n = 0; + rdpChannels* channels = nullptr; + xfClipboard* clipboard = nullptr; + const char* selectionAtom = nullptr; + xfCliprdrFormat* clientFormat = nullptr; + wObject* obj = nullptr; + + WINPR_ASSERT(xfc); + WINPR_ASSERT(xfc->common.context.settings); + + if (!(clipboard = (xfClipboard*)calloc(1, sizeof(xfClipboard)))) + { + WLog_ERR(TAG, "failed to allocate xfClipboard data"); + return nullptr; + } + + clipboard->file = cliprdr_file_context_new(clipboard); + if (!clipboard->file) + goto fail; + + xfc->clipboard = clipboard; + clipboard->xfc = xfc; + channels = xfc->common.context.channels; + clipboard->channels = channels; + clipboard->system = ClipboardCreate(); + clipboard->requestedFormatId = UINT32_MAX; + clipboard->root_window = DefaultRootWindow(xfc->display); + + selectionAtom = + freerdp_settings_get_string(xfc->common.context.settings, FreeRDP_ClipboardUseSelection); + if (!selectionAtom) + selectionAtom = "CLIPBOARD"; + + clipboard->clipboard_atom = Logging_XInternAtom(xfc->log, xfc->display, selectionAtom, FALSE); + + if (clipboard->clipboard_atom == None) + { + WLog_ERR(TAG, "unable to get %s atom", selectionAtom); + goto fail; + } + + clipboard->timestamp_property_atom = + Logging_XInternAtom(xfc->log, xfc->display, "_FREERDP_TIMESTAMP_PROPERTY", FALSE); + clipboard->property_atom = + Logging_XInternAtom(xfc->log, xfc->display, "_FREERDP_CLIPRDR", FALSE); + clipboard->raw_transfer_atom = + Logging_XInternAtom(xfc->log, xfc->display, "_FREERDP_CLIPRDR_RAW", FALSE); + clipboard->raw_format_list_atom = + Logging_XInternAtom(xfc->log, xfc->display, "_FREERDP_CLIPRDR_FORMATS", FALSE); + xf_cliprdr_set_raw_transfer_enabled(clipboard, TRUE); + XSelectInput(xfc->display, clipboard->root_window, PropertyChangeMask); +#ifdef WITH_XFIXES + + if (XFixesQueryExtension(xfc->display, &clipboard->xfixes_event_base, + &clipboard->xfixes_error_base)) + { + int xfmajor = 0; + int xfminor = 0; + + if (XFixesQueryVersion(xfc->display, &xfmajor, &xfminor)) + { + XFixesSelectSelectionInput(xfc->display, clipboard->root_window, + clipboard->clipboard_atom, + XFixesSetSelectionOwnerNotifyMask); + clipboard->xfixes_supported = TRUE; + } + else + { + WLog_ERR(TAG, "Error querying X Fixes extension version"); + } + } + else + { + WLog_ERR(TAG, "Error loading X Fixes extension"); + } + +#else + WLog_ERR( + TAG, + "Warning: Using clipboard redirection without XFIXES extension is strongly discouraged!"); +#endif + clientFormat = &clipboard->clientFormats[n++]; + clientFormat->atom = Logging_XInternAtom(xfc->log, xfc->display, "_FREERDP_RAW", False); + clientFormat->localFormat = clientFormat->formatToRequest = CF_RAW; + + clientFormat = &clipboard->clientFormats[n++]; + clientFormat->atom = Logging_XInternAtom(xfc->log, xfc->display, "UTF8_STRING", False); + clientFormat->formatToRequest = CF_UNICODETEXT; + clientFormat->localFormat = ClipboardGetFormatId(xfc->clipboard->system, mime_text_plain); + + clientFormat = &clipboard->clientFormats[n++]; + clientFormat->atom = XA_STRING; + clientFormat->formatToRequest = CF_TEXT; + clientFormat->localFormat = ClipboardGetFormatId(xfc->clipboard->system, mime_text_plain); + + clientFormat = &clipboard->clientFormats[n++]; + clientFormat->atom = Logging_XInternAtom(xfc->log, xfc->display, mime_tiff, False); + clientFormat->formatToRequest = clientFormat->localFormat = CF_TIFF; + + for (size_t x = 0; x < ARRAYSIZE(mime_bitmap); x++) + { + const char* mime_bmp = mime_bitmap[x]; + const DWORD format = ClipboardGetFormatId(xfc->clipboard->system, mime_bmp); + if (format == 0) + { + WLog_DBG(TAG, "skipping local bitmap format %s [NOT SUPPORTED]", mime_bmp); + continue; + } + + WLog_DBG(TAG, "register local bitmap format %s [0x%08" PRIx32 "]", mime_bmp, format); + clientFormat = &clipboard->clientFormats[n++]; + clientFormat->localFormat = format; + clientFormat->atom = Logging_XInternAtom(xfc->log, xfc->display, mime_bmp, False); + clientFormat->formatToRequest = CF_DIB; + clientFormat->isImage = TRUE; + } + + for (size_t x = 0; x < ARRAYSIZE(mime_images); x++) + { + const char* mime_bmp = mime_images[x]; + const DWORD format = ClipboardGetFormatId(xfc->clipboard->system, mime_bmp); + if (format == 0) + { + WLog_DBG(TAG, "skipping local bitmap format %s [NOT SUPPORTED]", mime_bmp); + continue; + } + + WLog_DBG(TAG, "register local bitmap format %s [0x%08" PRIx32 "]", mime_bmp, format); + clientFormat = &clipboard->clientFormats[n++]; + clientFormat->localFormat = format; + clientFormat->atom = Logging_XInternAtom(xfc->log, xfc->display, mime_bmp, False); + clientFormat->formatToRequest = CF_DIB; + clientFormat->isImage = TRUE; + } + + clientFormat = &clipboard->clientFormats[n++]; + clientFormat->atom = Logging_XInternAtom(xfc->log, xfc->display, mime_html, False); + clientFormat->formatToRequest = ClipboardGetFormatId(xfc->clipboard->system, type_HtmlFormat); + clientFormat->localFormat = ClipboardGetFormatId(xfc->clipboard->system, mime_html); + clientFormat->formatName = _strdup(type_HtmlFormat); + + if (!clientFormat->formatName) + goto fail; + + clientFormat = &clipboard->clientFormats[n++]; + + /* + * Existence of registered format IDs for file formats does not guarantee that they are + * in fact supported by wClipboard (as further initialization may have failed after format + * registration). However, they are definitely not supported if there are no registered + * formats. In this case we should not list file formats in TARGETS. + */ + { + const UINT32 fgid = ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW); + { + const UINT32 uid = ClipboardGetFormatId(clipboard->system, mime_uri_list); + if (uid) + { + if (!cliprdr_file_context_set_locally_available(clipboard->file, TRUE)) + goto fail; + clientFormat->atom = + Logging_XInternAtom(xfc->log, xfc->display, mime_uri_list, False); + clientFormat->localFormat = uid; + clientFormat->formatToRequest = fgid; + clientFormat->formatName = _strdup(type_FileGroupDescriptorW); + + if (!clientFormat->formatName) + goto fail; + + clientFormat = &clipboard->clientFormats[n++]; + } + } + + { + const UINT32 gid = ClipboardGetFormatId(clipboard->system, mime_gnome_copied_files); + if (gid != 0) + { + if (!cliprdr_file_context_set_locally_available(clipboard->file, TRUE)) + goto fail; + clientFormat->atom = + Logging_XInternAtom(xfc->log, xfc->display, mime_gnome_copied_files, False); + clientFormat->localFormat = gid; + clientFormat->formatToRequest = fgid; + clientFormat->formatName = _strdup(type_FileGroupDescriptorW); + + if (!clientFormat->formatName) + goto fail; + + clientFormat = &clipboard->clientFormats[n++]; + } + } + + { + const UINT32 mid = ClipboardGetFormatId(clipboard->system, mime_mate_copied_files); + if (mid != 0) + { + if (!cliprdr_file_context_set_locally_available(clipboard->file, TRUE)) + goto fail; + clientFormat->atom = + Logging_XInternAtom(xfc->log, xfc->display, mime_mate_copied_files, False); + clientFormat->localFormat = mid; + clientFormat->formatToRequest = fgid; + clientFormat->formatName = _strdup(type_FileGroupDescriptorW); + + if (!clientFormat->formatName) + goto fail; + } + } + } + + clipboard->numClientFormats = WINPR_ASSERTING_INT_CAST(uint32_t, n); + clipboard->targets[0] = Logging_XInternAtom(xfc->log, xfc->display, "TIMESTAMP", FALSE); + clipboard->targets[1] = Logging_XInternAtom(xfc->log, xfc->display, "TARGETS", FALSE); + clipboard->numTargets = 2; + clipboard->incr_atom = Logging_XInternAtom(xfc->log, xfc->display, "INCR", FALSE); + + if (relieveFilenameRestriction) + { + WLog_DBG(TAG, "Relieving CLIPRDR filename restriction"); + ClipboardGetDelegate(clipboard->system)->IsFileNameComponentValid = + xf_cliprdr_is_valid_unix_filename; + } + + clipboard->cachedData = HashTable_New(TRUE); + if (!clipboard->cachedData) + goto fail; + + obj = HashTable_ValueObject(clipboard->cachedData); + obj->fnObjectFree = xf_cached_data_free; + + clipboard->cachedRawData = HashTable_New(TRUE); + if (!clipboard->cachedRawData) + goto fail; + + obj = HashTable_ValueObject(clipboard->cachedRawData); + obj->fnObjectFree = xf_cached_data_free; + + return clipboard; + +fail: + WINPR_PRAGMA_DIAG_PUSH + WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC + xf_clipboard_free(clipboard); + WINPR_PRAGMA_DIAG_POP + return nullptr; +} + +void xf_clipboard_free(xfClipboard* clipboard) +{ + if (!clipboard) + return; + + xf_clipboard_free_server_formats(clipboard); + + if (clipboard->numClientFormats) + { + for (UINT32 i = 0; i < clipboard->numClientFormats; i++) + { + xfCliprdrFormat* format = &clipboard->clientFormats[i]; + free(format->formatName); + } + } + + cliprdr_file_context_free(clipboard->file); + + ClipboardDestroy(clipboard->system); + xf_clipboard_formats_free(clipboard); + HashTable_Free(clipboard->cachedRawData); + HashTable_Free(clipboard->cachedData); + requested_format_free(&clipboard->requestedFormat); + free(clipboard->respond); + free(clipboard->incr_data); + free(clipboard); +} + +void xf_cliprdr_init(xfContext* xfc, CliprdrClientContext* cliprdr) +{ + WINPR_ASSERT(xfc); + WINPR_ASSERT(cliprdr); + + xfc->cliprdr = cliprdr; + xfc->clipboard->context = cliprdr; + + cliprdr->MonitorReady = xf_cliprdr_monitor_ready; + cliprdr->ServerCapabilities = xf_cliprdr_server_capabilities; + cliprdr->ServerFormatList = xf_cliprdr_server_format_list; + cliprdr->ServerFormatListResponse = xf_cliprdr_server_format_list_response; + cliprdr->ServerFormatDataRequest = xf_cliprdr_server_format_data_request; + cliprdr->ServerFormatDataResponse = xf_cliprdr_server_format_data_response; + + cliprdr_file_context_init(xfc->clipboard->file, cliprdr); +} + +void xf_cliprdr_uninit(xfContext* xfc, CliprdrClientContext* cliprdr) +{ + WINPR_ASSERT(xfc); + WINPR_ASSERT(cliprdr); + + xfc->cliprdr = nullptr; + + if (xfc->clipboard) + { + ClipboardLock(xfc->clipboard->system); + cliprdr_file_context_uninit(xfc->clipboard->file, cliprdr); + ClipboardUnlock(xfc->clipboard->system); + xfc->clipboard->context = nullptr; + } +} diff --git a/third_party/FreeRDP/client/X11/xf_cliprdr.h b/third_party/FreeRDP/client/X11/xf_cliprdr.h new file mode 100644 index 0000000..3d0a153 --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_cliprdr.h @@ -0,0 +1,43 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Clipboard Redirection + * + * Copyright 2010-2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_X11_CLIPRDR_H +#define FREERDP_CLIENT_X11_CLIPRDR_H + +#include +#include + +#include + +#include "xf_types.h" + +typedef struct xf_clipboard xfClipboard; + +void xf_clipboard_free(xfClipboard* clipboard); + +WINPR_ATTR_MALLOC(xf_clipboard_free, 1) +WINPR_ATTR_NODISCARD +xfClipboard* xf_clipboard_new(xfContext* xfc, BOOL relieveFilenameRestriction); + +void xf_cliprdr_init(xfContext* xfc, CliprdrClientContext* cliprdr); +void xf_cliprdr_uninit(xfContext* xfc, CliprdrClientContext* cliprdr); + +void xf_cliprdr_handle_xevent(xfContext* xfc, const XEvent* event); + +#endif /* FREERDP_CLIENT_X11_CLIPRDR_H */ diff --git a/third_party/FreeRDP/client/X11/xf_debug.h b/third_party/FreeRDP/client/X11/xf_debug.h new file mode 100644 index 0000000..9f5cba9 --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_debug.h @@ -0,0 +1,35 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 debug helper header + * + * Copyright 2025 Armin Novak + * 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 +#include + +#define DBG_TAG CLIENT_TAG("x11") + +#ifdef WITH_DEBUG_X11 +#define DEBUG_X11(...) WLog_DBG(DBG_TAG, __VA_ARGS__) +#else +#define DEBUG_X11(...) \ + do \ + { \ + } while (0) +#endif diff --git a/third_party/FreeRDP/client/X11/xf_disp.c b/third_party/FreeRDP/client/X11/xf_disp.c new file mode 100644 index 0000000..ede54fd --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_disp.c @@ -0,0 +1,589 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Display Control channel + * + * Copyright 2017 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include + +#include + +#ifdef WITH_XRANDR +#include +#include + +#if (RANDR_MAJOR * 100 + RANDR_MINOR) >= 105 +#define USABLE_XRANDR +#endif + +#endif + +#include "xfreerdp.h" +#include "xf_disp.h" +#include "xf_monitor.h" + +#include +#define TAG CLIENT_TAG("x11disp") +#define RESIZE_MIN_DELAY_NS 200000000UL /* minimum delay in ms between two resizes */ + +struct s_xfDispContext +{ + xfContext* xfc; + DispClientContext* disp; + BOOL haveXRandr; + int eventBase; + int errorBase; + UINT32 lastSentWidth; + UINT32 lastSentHeight; + BYTE reserved[4]; + UINT64 lastSentDate; + UINT32 targetWidth; + UINT32 targetHeight; + BOOL activated; + BOOL fullscreen; + UINT16 lastSentDesktopOrientation; + BYTE reserved2[2]; + UINT32 lastSentDesktopScaleFactor; + UINT32 lastSentDeviceScaleFactor; + BYTE reserved3[4]; + FreeRDP_TimerID timerID; +}; + +static BOOL xf_disp_check_context(void* context, xfContext** ppXfc, xfDispContext** ppXfDisp, + rdpSettings** ppSettings); +static BOOL xf_disp_sendResize(xfDispContext* xfDisp, BOOL fromTimer); +static UINT xf_disp_sendLayout(DispClientContext* disp, const rdpMonitor* monitors, + UINT32 nmonitors); + +static BOOL xf_disp_settings_changed(xfDispContext* xfDisp) +{ + rdpSettings* settings = nullptr; + + WINPR_ASSERT(xfDisp); + WINPR_ASSERT(xfDisp->xfc); + + settings = xfDisp->xfc->common.context.settings; + WINPR_ASSERT(settings); + + if (xfDisp->lastSentWidth != xfDisp->targetWidth) + return TRUE; + + if (xfDisp->lastSentHeight != xfDisp->targetHeight) + return TRUE; + + if (xfDisp->lastSentDesktopOrientation != + freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation)) + return TRUE; + + if (xfDisp->lastSentDesktopScaleFactor != + freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor)) + return TRUE; + + if (xfDisp->lastSentDeviceScaleFactor != + freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor)) + return TRUE; + + if (xfDisp->fullscreen != xfDisp->xfc->fullscreen) + return TRUE; + + return FALSE; +} + +static BOOL xf_update_last_sent(xfDispContext* xfDisp) +{ + rdpSettings* settings = nullptr; + + WINPR_ASSERT(xfDisp); + WINPR_ASSERT(xfDisp->xfc); + + settings = xfDisp->xfc->common.context.settings; + WINPR_ASSERT(settings); + + xfDisp->lastSentWidth = xfDisp->targetWidth; + xfDisp->lastSentHeight = xfDisp->targetHeight; + xfDisp->lastSentDesktopOrientation = + freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation); + xfDisp->lastSentDesktopScaleFactor = + freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor); + xfDisp->lastSentDeviceScaleFactor = + freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor); + xfDisp->fullscreen = xfDisp->xfc->fullscreen; + return TRUE; +} + +static uint64_t xf_disp_OnTimer(rdpContext* context, WINPR_ATTR_UNUSED void* userdata, + WINPR_ATTR_UNUSED FreeRDP_TimerID timerID, + WINPR_ATTR_UNUSED uint64_t timestamp, + WINPR_ATTR_UNUSED uint64_t interval) + +{ + xfContext* xfc = nullptr; + xfDispContext* xfDisp = nullptr; + rdpSettings* settings = nullptr; + + if (!xf_disp_check_context(context, &xfc, &xfDisp, &settings)) + return interval; + + if (!xfDisp->activated) + return interval; + + xf_disp_sendResize(xfDisp, TRUE); + xfDisp->timerID = 0; + return 0; +} + +static BOOL update_timer(xfDispContext* xfDisp, uint64_t intervalNS) +{ + WINPR_ASSERT(xfDisp); + + if (xfDisp->timerID == 0) + { + rdpContext* context = &xfDisp->xfc->common.context; + + xfDisp->timerID = freerdp_timer_add(context, intervalNS, xf_disp_OnTimer, nullptr, true); + } + return TRUE; +} + +BOOL xf_disp_sendResize(xfDispContext* xfDisp, BOOL fromTimer) +{ + DISPLAY_CONTROL_MONITOR_LAYOUT layout = WINPR_C_ARRAY_INIT; + + if (!xfDisp || !xfDisp->xfc) + return FALSE; + + /* If there is already a timer running skip the update and wait for the timer to expire. */ + if ((xfDisp->timerID != 0) && !fromTimer) + return TRUE; + + xfContext* xfc = xfDisp->xfc; + rdpSettings* settings = xfc->common.context.settings; + + if (!settings) + return FALSE; + + if (!xfDisp->activated || !xfDisp->disp) + return update_timer(xfDisp, RESIZE_MIN_DELAY_NS); + + const uint64_t diff = winpr_GetTickCount64NS() - xfDisp->lastSentDate; + if (diff < RESIZE_MIN_DELAY_NS) + { + const uint64_t interval = RESIZE_MIN_DELAY_NS - diff; + return update_timer(xfDisp, interval); + } + + if (!xf_disp_settings_changed(xfDisp)) + return TRUE; + + xfDisp->lastSentDate = winpr_GetTickCount64NS(); + + const UINT32 mcount = freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount); + if (mcount > 1) + { + const rdpMonitor* monitors = + freerdp_settings_get_pointer(settings, FreeRDP_MonitorDefArray); + if (xf_disp_sendLayout(xfDisp->disp, monitors, mcount) != CHANNEL_RC_OK) + return FALSE; + } + else + { + layout.Flags = DISPLAY_CONTROL_MONITOR_PRIMARY; + layout.Top = layout.Left = 0; + layout.Width = xfDisp->targetWidth; + layout.Height = xfDisp->targetHeight; + layout.Orientation = freerdp_settings_get_uint16(settings, FreeRDP_DesktopOrientation); + layout.DesktopScaleFactor = + freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor); + layout.DeviceScaleFactor = freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor); + + const double dw = xfDisp->targetWidth / 75.0 * 25.4; + const double dh = xfDisp->targetHeight / 75.0 * 25.4; + layout.PhysicalWidth = (UINT32)lround(dw); + layout.PhysicalHeight = (UINT32)lround(dh); + + if (IFCALLRESULT(CHANNEL_RC_OK, xfDisp->disp->SendMonitorLayout, xfDisp->disp, 1, + &layout) != CHANNEL_RC_OK) + return FALSE; + } + + return xf_update_last_sent(xfDisp); +} + +static BOOL xf_disp_queueResize(xfDispContext* xfDisp, UINT32 width, UINT32 height) +{ + if ((xfDisp->targetWidth == (INT64)width) && (xfDisp->targetHeight == (INT64)height)) + return TRUE; + xfDisp->targetWidth = width; + xfDisp->targetHeight = height; + return xf_disp_sendResize(xfDisp, FALSE); +} + +static BOOL xf_disp_set_window_resizable(xfDispContext* xfDisp) +{ + XSizeHints* size_hints = nullptr; + + if (!(size_hints = XAllocSizeHints())) + return FALSE; + + size_hints->flags = PMinSize | PMaxSize | PWinGravity; + size_hints->win_gravity = NorthWestGravity; + size_hints->min_width = size_hints->min_height = 320; + size_hints->max_width = size_hints->max_height = 8192; + + if (xfDisp->xfc->window) + XSetWMNormalHints(xfDisp->xfc->display, xfDisp->xfc->window->handle, size_hints); + + XFree(size_hints); + return TRUE; +} + +BOOL xf_disp_check_context(void* context, xfContext** ppXfc, xfDispContext** ppXfDisp, + rdpSettings** ppSettings) +{ + xfContext* xfc = nullptr; + + if (!context) + return FALSE; + + xfc = (xfContext*)context; + + if (!(xfc->xfDisp)) + return FALSE; + + if (!xfc->common.context.settings) + return FALSE; + + *ppXfc = xfc; + *ppXfDisp = xfc->xfDisp; + *ppSettings = xfc->common.context.settings; + return TRUE; +} + +static void xf_disp_OnActivated(void* context, const ActivatedEventArgs* e) +{ + xfContext* xfc = nullptr; + xfDispContext* xfDisp = nullptr; + rdpSettings* settings = nullptr; + + if (!xf_disp_check_context(context, &xfc, &xfDisp, &settings)) + return; + + if (xfDisp->activated && !xfc->fullscreen) + { + xf_disp_set_window_resizable(xfDisp); + + if (e->firstActivation) + return; + + xf_disp_sendResize(xfDisp, FALSE); + } +} + +static void xf_disp_OnGraphicsReset(void* context, const GraphicsResetEventArgs* e) +{ + xfContext* xfc = nullptr; + xfDispContext* xfDisp = nullptr; + rdpSettings* settings = nullptr; + + WINPR_UNUSED(e); + + if (!xf_disp_check_context(context, &xfc, &xfDisp, &settings)) + return; + + if (xfDisp->activated && !freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)) + { + xf_disp_set_window_resizable(xfDisp); + xf_disp_sendResize(xfDisp, FALSE); + } +} + +static void xf_disp_OnWindowStateChange(void* context, const WindowStateChangeEventArgs* e) +{ + xfContext* xfc = nullptr; + xfDispContext* xfDisp = nullptr; + rdpSettings* settings = nullptr; + + WINPR_UNUSED(e); + + if (!xf_disp_check_context(context, &xfc, &xfDisp, &settings)) + return; + + if (!xfDisp->activated || !xfc->fullscreen) + return; + + xf_disp_sendResize(xfDisp, FALSE); +} + +xfDispContext* xf_disp_new(xfContext* xfc) +{ + WINPR_ASSERT(xfc); + + wPubSub* pubSub = xfc->common.context.pubSub; + WINPR_ASSERT(pubSub); + + const rdpSettings* settings = xfc->common.context.settings; + WINPR_ASSERT(settings); + + if (PubSub_SubscribeActivated(pubSub, xf_disp_OnActivated) < 0) + return nullptr; + if (PubSub_SubscribeGraphicsReset(pubSub, xf_disp_OnGraphicsReset) < 0) + return nullptr; + if (PubSub_SubscribeWindowStateChange(pubSub, xf_disp_OnWindowStateChange) < 0) + return nullptr; + + xfDispContext* ret = calloc(1, sizeof(xfDispContext)); + + if (!ret) + return nullptr; + + ret->xfc = xfc; +#ifdef USABLE_XRANDR + + if (XRRQueryExtension(xfc->display, &ret->eventBase, &ret->errorBase)) + { + ret->haveXRandr = TRUE; + } + +#endif + ret->lastSentWidth = ret->targetWidth = + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + ret->lastSentHeight = ret->targetHeight = + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + return ret; +} + +void xf_disp_free(xfDispContext* disp) +{ + if (!disp) + return; + + if (disp->xfc) + { + wPubSub* pubSub = disp->xfc->common.context.pubSub; + PubSub_UnsubscribeActivated(pubSub, xf_disp_OnActivated); + PubSub_UnsubscribeGraphicsReset(pubSub, xf_disp_OnGraphicsReset); + PubSub_UnsubscribeWindowStateChange(pubSub, xf_disp_OnWindowStateChange); + } + + free(disp); +} + +UINT xf_disp_sendLayout(DispClientContext* disp, const rdpMonitor* monitors, UINT32 nmonitors) +{ + UINT ret = CHANNEL_RC_OK; + xfDispContext* xfDisp = nullptr; + rdpSettings* settings = nullptr; + DISPLAY_CONTROL_MONITOR_LAYOUT* layouts = nullptr; + + WINPR_ASSERT(disp); + WINPR_ASSERT(monitors); + WINPR_ASSERT(nmonitors > 0); + + xfDisp = (xfDispContext*)disp->custom; + WINPR_ASSERT(xfDisp); + WINPR_ASSERT(xfDisp->xfc); + + settings = xfDisp->xfc->common.context.settings; + WINPR_ASSERT(settings); + + layouts = calloc(nmonitors, sizeof(DISPLAY_CONTROL_MONITOR_LAYOUT)); + + if (!layouts) + return CHANNEL_RC_NO_MEMORY; + + for (UINT32 i = 0; i < nmonitors; i++) + { + const rdpMonitor* monitor = &monitors[i]; + DISPLAY_CONTROL_MONITOR_LAYOUT* layout = &layouts[i]; + + layout->Flags = (monitor->is_primary ? DISPLAY_CONTROL_MONITOR_PRIMARY : 0); + layout->Left = monitor->x; + layout->Top = monitor->y; + layout->Width = WINPR_ASSERTING_INT_CAST(uint32_t, monitor->width); + layout->Height = WINPR_ASSERTING_INT_CAST(uint32_t, monitor->height); + layout->Orientation = ORIENTATION_LANDSCAPE; + layout->PhysicalWidth = monitor->attributes.physicalWidth; + layout->PhysicalHeight = monitor->attributes.physicalHeight; + + switch (monitor->attributes.orientation) + { + case 90: + layout->Orientation = ORIENTATION_PORTRAIT; + break; + + case 180: + layout->Orientation = ORIENTATION_LANDSCAPE_FLIPPED; + break; + + case 270: + layout->Orientation = ORIENTATION_PORTRAIT_FLIPPED; + break; + + case 0: + default: + /* MS-RDPEDISP - 2.2.2.2.1: + * Orientation (4 bytes): A 32-bit unsigned integer that specifies the + * orientation of the monitor in degrees. Valid values are 0, 90, 180 + * or 270 + * + * So we default to ORIENTATION_LANDSCAPE + */ + layout->Orientation = ORIENTATION_LANDSCAPE; + break; + } + + layout->DesktopScaleFactor = + freerdp_settings_get_uint32(settings, FreeRDP_DesktopScaleFactor); + layout->DeviceScaleFactor = + freerdp_settings_get_uint32(settings, FreeRDP_DeviceScaleFactor); + } + + ret = IFCALLRESULT(CHANNEL_RC_OK, disp->SendMonitorLayout, disp, nmonitors, layouts); + free(layouts); + return ret; +} + +BOOL xf_disp_handle_xevent(xfContext* xfc, const XEvent* event) +{ + xfDispContext* xfDisp = nullptr; + rdpSettings* settings = nullptr; + UINT32 maxWidth = 0; + UINT32 maxHeight = 0; + + if (!xfc || !event) + return FALSE; + + xfDisp = xfc->xfDisp; + + if (!xfDisp) + return FALSE; + + settings = xfc->common.context.settings; + + if (!settings) + return FALSE; + + if (!xfDisp->haveXRandr || !xfDisp->disp) + return TRUE; + +#ifdef USABLE_XRANDR + + if (event->type != xfDisp->eventBase + RRScreenChangeNotify) + return TRUE; + +#endif + + WLog_DBG(TAG, "RRScreenChangeNotify event"); + + xf_detect_monitors(xfc, &maxWidth, &maxHeight); + const rdpMonitor* monitors = freerdp_settings_get_pointer(settings, FreeRDP_MonitorDefArray); + const UINT32 mcount = freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount); + return xf_disp_sendLayout(xfDisp->disp, monitors, mcount) == CHANNEL_RC_OK; +} + +BOOL xf_disp_handle_configureNotify(xfContext* xfc, int width, int height) +{ + xfDispContext* xfDisp = nullptr; + + if (!xfc) + return FALSE; + + xfDisp = xfc->xfDisp; + + if (!xfDisp) + return FALSE; + + WLog_DBG(TAG, "ConfigureNotify (%dx%d)", width, height); + return xf_disp_queueResize(xfDisp, WINPR_ASSERTING_INT_CAST(uint32_t, width), + WINPR_ASSERTING_INT_CAST(uint32_t, height)); +} + +static UINT xf_DisplayControlCaps(DispClientContext* disp, UINT32 maxNumMonitors, + UINT32 maxMonitorAreaFactorA, UINT32 maxMonitorAreaFactorB) +{ + /* we're called only if dynamic resolution update is activated */ + xfDispContext* xfDisp = nullptr; + rdpSettings* settings = nullptr; + + WINPR_ASSERT(disp); + + xfDisp = (xfDispContext*)disp->custom; + WINPR_ASSERT(xfDisp); + WINPR_ASSERT(xfDisp->xfc); + + settings = xfDisp->xfc->common.context.settings; + WINPR_ASSERT(settings); + + WLog_DBG(TAG, + "DisplayControlCapsPdu: MaxNumMonitors: %" PRIu32 " MaxMonitorAreaFactorA: %" PRIu32 + " MaxMonitorAreaFactorB: %" PRIu32 "", + maxNumMonitors, maxMonitorAreaFactorA, maxMonitorAreaFactorB); + xfDisp->activated = TRUE; + + if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)) + return CHANNEL_RC_OK; + + WLog_DBG(TAG, "DisplayControlCapsPdu: setting the window as resizable"); + return xf_disp_set_window_resizable(xfDisp) ? CHANNEL_RC_OK : CHANNEL_RC_NO_MEMORY; +} + +BOOL xf_disp_init(xfDispContext* xfDisp, DispClientContext* disp) +{ + rdpSettings* settings = nullptr; + + if (!xfDisp || !xfDisp->xfc || !disp) + return FALSE; + + settings = xfDisp->xfc->common.context.settings; + + if (!settings) + return FALSE; + + xfDisp->disp = disp; + disp->custom = (void*)xfDisp; + + if (freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate)) + { + disp->DisplayControlCaps = xf_DisplayControlCaps; +#ifdef USABLE_XRANDR + + if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)) + { + /* ask X11 to notify us of screen changes */ + XRRSelectInput(xfDisp->xfc->display, DefaultRootWindow(xfDisp->xfc->display), + RRScreenChangeNotifyMask); + } + +#endif + } + + WLog_DBG(TAG, "Channel %s opened", DISP_CHANNEL_NAME); + return TRUE; +} + +BOOL xf_disp_uninit(xfDispContext* xfDisp, DispClientContext* disp) +{ + if (!xfDisp || !disp) + return FALSE; + + WLog_DBG(TAG, "Channel %s closed", DISP_CHANNEL_NAME); + xfDisp->disp = nullptr; + return TRUE; +} diff --git a/third_party/FreeRDP/client/X11/xf_disp.h b/third_party/FreeRDP/client/X11/xf_disp.h new file mode 100644 index 0000000..e28e1e2 --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_disp.h @@ -0,0 +1,44 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Display Control channel + * + * Copyright 2017 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef FREERDP_CLIENT_X11_DISP_H +#define FREERDP_CLIENT_X11_DISP_H + +#include +#include + +#include + +#include "xf_types.h" + +typedef struct s_xfDispContext xfDispContext; + +FREERDP_API BOOL xf_disp_init(xfDispContext* xfDisp, DispClientContext* disp); +FREERDP_API BOOL xf_disp_uninit(xfDispContext* xfDisp, DispClientContext* disp); + +void xf_disp_free(xfDispContext* disp); + +WINPR_ATTR_MALLOC(xf_disp_free, 1) +WINPR_ATTR_NODISCARD +xfDispContext* xf_disp_new(xfContext* xfc); + +BOOL xf_disp_handle_xevent(xfContext* xfc, const XEvent* event); +BOOL xf_disp_handle_configureNotify(xfContext* xfc, int width, int height); +void xf_disp_resized(xfDispContext* disp); + +#endif /* FREERDP_CLIENT_X11_DISP_H */ diff --git a/third_party/FreeRDP/client/X11/xf_event.c b/third_party/FreeRDP/client/X11/xf_event.c new file mode 100644 index 0000000..1a05b42 --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_event.c @@ -0,0 +1,1433 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Event Handling + * + * Copyright 2011 Marc-Andre Moreau + * Copyright 2023 HP Development Company, L.P. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +#include +#include + +#include +#include + +#include +#include + +#include "xf_rail.h" +#include "xf_window.h" +#include "xf_cliprdr.h" +#include "xf_disp.h" +#include "xf_input.h" +#include "xf_gfx.h" +#include "xf_graphics.h" +#include "xf_utils.h" + +#include "xf_debug.h" +#include "xf_event.h" + +#define CLAMP_COORDINATES(x, y) \ + do \ + { \ + if ((x) < 0) \ + (x) = 0; \ + if ((y) < 0) \ + (y) = 0; \ + } while (0) + +static const DWORD mouseLogLevel = WLOG_TRACE; + +const char* x11_event_string(int event) +{ + switch (event) + { + case KeyPress: + return "KeyPress"; + + case KeyRelease: + return "KeyRelease"; + + case ButtonPress: + return "ButtonPress"; + + case ButtonRelease: + return "ButtonRelease"; + + case MotionNotify: + return "MotionNotify"; + + case EnterNotify: + return "EnterNotify"; + + case LeaveNotify: + return "LeaveNotify"; + + case FocusIn: + return "FocusIn"; + + case FocusOut: + return "FocusOut"; + + case KeymapNotify: + return "KeymapNotify"; + + case Expose: + return "Expose"; + + case GraphicsExpose: + return "GraphicsExpose"; + + case NoExpose: + return "NoExpose"; + + case VisibilityNotify: + return "VisibilityNotify"; + + case CreateNotify: + return "CreateNotify"; + + case DestroyNotify: + return "DestroyNotify"; + + case UnmapNotify: + return "UnmapNotify"; + + case MapNotify: + return "MapNotify"; + + case MapRequest: + return "MapRequest"; + + case ReparentNotify: + return "ReparentNotify"; + + case ConfigureNotify: + return "ConfigureNotify"; + + case ConfigureRequest: + return "ConfigureRequest"; + + case GravityNotify: + return "GravityNotify"; + + case ResizeRequest: + return "ResizeRequest"; + + case CirculateNotify: + return "CirculateNotify"; + + case CirculateRequest: + return "CirculateRequest"; + + case PropertyNotify: + return "PropertyNotify"; + + case SelectionClear: + return "SelectionClear"; + + case SelectionRequest: + return "SelectionRequest"; + + case SelectionNotify: + return "SelectionNotify"; + + case ColormapNotify: + return "ColormapNotify"; + + case ClientMessage: + return "ClientMessage"; + + case MappingNotify: + return "MappingNotify"; + + case GenericEvent: + return "GenericEvent"; + + default: + return "UNKNOWN"; + } +} + +static BOOL xf_action_script_append(xfContext* xfc, const char* buffer, size_t size, + WINPR_ATTR_UNUSED void* user, const char* what, const char* arg) +{ + WINPR_ASSERT(xfc); + WINPR_UNUSED(what); + WINPR_UNUSED(arg); + + if (buffer || (size == 0)) + return TRUE; + + if (!ArrayList_Append(xfc->xevents, buffer)) + { + ArrayList_Clear(xfc->xevents); + return FALSE; + } + return TRUE; +} + +BOOL xf_event_action_script_init(xfContext* xfc) +{ + WINPR_ASSERT(xfc); + + xf_event_action_script_free(xfc); + + char* val = getConfigOption(TRUE, "isActionScriptAllowed"); + + /* We default to enabled if there is no global config file. */ + xfc->isActionScriptAllowed = !val || (_stricmp(val, "true") == 0); + free(val); + + if (!xfc->isActionScriptAllowed) + return TRUE; + + xfc->xevents = ArrayList_New(TRUE); + + if (!xfc->xevents) + return FALSE; + + wObject* obj = ArrayList_Object(xfc->xevents); + WINPR_ASSERT(obj); + obj->fnObjectNew = winpr_ObjectStringClone; + obj->fnObjectFree = winpr_ObjectStringFree; + + return run_action_script(xfc, "xevent", nullptr, xf_action_script_append, nullptr); +} + +void xf_event_action_script_free(xfContext* xfc) +{ + if (xfc->xevents) + { + ArrayList_Free(xfc->xevents); + xfc->xevents = nullptr; + } +} + +static BOOL action_script_run(xfContext* xfc, const char* buffer, size_t size, void* user, + const char* what, const char* arg) +{ + WINPR_UNUSED(xfc); + if (!xfc->isActionScriptAllowed) + return TRUE; + + WINPR_UNUSED(what); + WINPR_UNUSED(arg); + WINPR_ASSERT(user); + int* pstatus = user; + + if (size == 0) + { + WLog_Print(xfc->log, WLOG_WARN, "ActionScript xevent: script did not return data"); + return FALSE; + } + + if (winpr_PathFileExists(buffer)) + { + char* cmd = nullptr; + size_t cmdlen = 0; + winpr_asprintf(&cmd, &cmdlen, "%s %s %s", buffer, what, arg); + if (!cmd) + return FALSE; + + FILE* fp = popen(cmd, "w"); + free(cmd); + if (!fp) + { + WLog_Print(xfc->log, WLOG_ERROR, "Failed to execute '%s'", buffer); + return FALSE; + } + + *pstatus = pclose(fp); + if (*pstatus < 0) + { + WLog_Print(xfc->log, WLOG_ERROR, "Command '%s' returned %d", buffer, *pstatus); + return FALSE; + } + } + else + { + WLog_Print(xfc->log, WLOG_WARN, "ActionScript xevent: No such file '%s'", buffer); + return FALSE; + } + + return TRUE; +} + +static BOOL xf_event_execute_action_script(xfContext* xfc, const XEvent* event) +{ + size_t count = 0; + char* name = nullptr; + BOOL match = FALSE; + const char* xeventName = nullptr; + + if (!xfc->actionScriptExists || !xfc->xevents || !xfc->window) + return FALSE; + + if (event->type > LASTEvent) + return FALSE; + + xeventName = x11_event_string(event->type); + count = ArrayList_Count(xfc->xevents); + + for (size_t index = 0; index < count; index++) + { + name = (char*)ArrayList_GetItem(xfc->xevents, index); + + if (_stricmp(name, xeventName) == 0) + { + match = TRUE; + break; + } + } + + if (!match) + return FALSE; + + char command[2048] = WINPR_C_ARRAY_INIT; + char arg[2048] = WINPR_C_ARRAY_INIT; + (void)_snprintf(command, sizeof(command), "xevent %s", xeventName); + (void)_snprintf(arg, sizeof(arg), "%lu", (unsigned long)xfc->window->handle); + return run_action_script(xfc, command, arg, action_script_run, nullptr); +} + +void xf_adjust_coordinates_to_screen(xfContext* xfc, UINT32* x, UINT32* y) +{ + if (!xfc || !xfc->common.context.settings || !y || !x) + return; + + rdpSettings* settings = xfc->common.context.settings; + INT64 tx = *x; + INT64 ty = *y; + if (!xfc->remote_app) + { +#ifdef WITH_XRENDER + + if (xf_picture_transform_required(xfc)) + { + const double dw = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + const double dh = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + double xScalingFactor = xfc->scaledWidth / dw; + double yScalingFactor = xfc->scaledHeight / dh; + tx = (INT64)lround((1.0 * (*x) + xfc->offset_x) * xScalingFactor); + ty = (INT64)lround((1.0 * (*y) + xfc->offset_y) * yScalingFactor); + } + +#endif + } + + CLAMP_COORDINATES(tx, ty); + *x = (UINT32)tx; + *y = (UINT32)ty; +} + +void xf_event_adjust_coordinates(xfContext* xfc, int* x, int* y) +{ + if (!xfc || !xfc->common.context.settings || !y || !x) + return; + + if (!xfc->remote_app) + { +#ifdef WITH_XRENDER + rdpSettings* settings = xfc->common.context.settings; + if (xf_picture_transform_required(xfc)) + { + double xScalingFactor = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) / + (double)xfc->scaledWidth; + double yScalingFactor = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) / + (double)xfc->scaledHeight; + *x = (int)((*x - xfc->offset_x) * xScalingFactor); + *y = (int)((*y - xfc->offset_y) * yScalingFactor); + } + +#endif + } + + CLAMP_COORDINATES(*x, *y); +} + +static BOOL xf_event_Expose(xfContext* xfc, const XExposeEvent* event, BOOL app) +{ + int x = 0; + int y = 0; + int w = 0; + int h = 0; + rdpSettings* settings = nullptr; + + WINPR_ASSERT(xfc); + WINPR_ASSERT(event); + + settings = xfc->common.context.settings; + WINPR_ASSERT(settings); + + if (!app && (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) || + freerdp_settings_get_bool(settings, FreeRDP_MultiTouchGestures))) + { + x = 0; + y = 0; + w = WINPR_ASSERTING_INT_CAST(int, + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth)); + h = WINPR_ASSERTING_INT_CAST(int, + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)); + } + else + { + x = event->x; + y = event->y; + w = event->width; + h = event->height; + } + + if (!app) + { + if (xfc->common.context.gdi->gfx) + { + xf_OutputExpose( + xfc, WINPR_ASSERTING_INT_CAST(uint32_t, x), WINPR_ASSERTING_INT_CAST(uint32_t, y), + WINPR_ASSERTING_INT_CAST(uint32_t, w), WINPR_ASSERTING_INT_CAST(uint32_t, h)); + return TRUE; + } + xf_draw_screen(xfc, x, y, w, h); + } + else + { + xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, event->window); + if (appWindow) + xf_UpdateWindowArea(xfc, appWindow, x, y, w, h); + xf_rail_return_window(appWindow, FALSE); + } + + return TRUE; +} + +static BOOL xf_event_VisibilityNotify(xfContext* xfc, const XVisibilityEvent* event, BOOL app) +{ + WINPR_UNUSED(app); + xfc->unobscured = event->state == VisibilityUnobscured; + return TRUE; +} + +BOOL xf_generic_MotionNotify_(xfContext* xfc, int x, int y, Window window, BOOL app, + const char* file, const char* fkt, size_t line) +{ + Window childWindow = None; + + if (WLog_IsLevelActive(xfc->log, mouseLogLevel)) + WLog_PrintTextMessage(xfc->log, mouseLogLevel, line, file, fkt, + "%s: x=%d, y=%d, window=0x%08lx, app=%d", __func__, x, y, window, + app); + + WINPR_ASSERT(xfc); + WINPR_ASSERT(xfc->common.context.settings); + + if (app) + { + /* make sure window exists */ + xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, window); + xf_rail_return_window(appWindow, FALSE); + if (!appWindow) + return TRUE; + + /* Translate to desktop coordinates */ + XTranslateCoordinates(xfc->display, window, RootWindowOfScreen(xfc->screen), x, y, &x, &y, + &childWindow); + } + + xf_event_adjust_coordinates(xfc, &x, &y); + freerdp_client_send_button_event(&xfc->common, FALSE, PTR_FLAGS_MOVE, x, y); + + if (xfc->fullscreen && !app) + { + if (xfc->window) + XSetInputFocus(xfc->display, xfc->window->handle, RevertToPointerRoot, CurrentTime); + } + + return TRUE; +} + +BOOL xf_generic_RawMotionNotify_(xfContext* xfc, int x, int y, WINPR_ATTR_UNUSED Window window, + BOOL app, const char* file, const char* fkt, size_t line) +{ + WINPR_ASSERT(xfc); + + if (WLog_IsLevelActive(xfc->log, mouseLogLevel)) + WLog_PrintTextMessage(xfc->log, mouseLogLevel, line, file, fkt, + "%s: x=%d, y=%d, window=0x%08lx, app=%d", __func__, x, y, window, + app); + + if (app) + { + WLog_Print(xfc->log, WLOG_ERROR, + "Relative mouse input is not supported with remoate app mode!"); + return FALSE; + } + + return freerdp_client_send_button_event(&xfc->common, TRUE, PTR_FLAGS_MOVE, x, y); +} + +static BOOL xf_event_MotionNotify(xfContext* xfc, const XMotionEvent* event, BOOL app) +{ + WINPR_ASSERT(xfc); + + if (xfc->window) + xf_floatbar_set_root_y(xfc->window->floatbar, event->y); + + if (xfc->xi_event || xfc->xi_rawevent || (xfc->common.mouse_grabbed && xf_use_rel_mouse(xfc))) + return TRUE; + + return xf_generic_MotionNotify(xfc, event->x, event->y, event->window, app); +} + +BOOL xf_generic_ButtonEvent_(xfContext* xfc, int x, int y, int button, Window window, BOOL app, + BOOL down, const char* file, const char* fkt, size_t line) +{ + UINT16 flags = 0; + Window childWindow = None; + + if (WLog_IsLevelActive(xfc->log, mouseLogLevel)) + WLog_PrintTextMessage(xfc->log, mouseLogLevel, line, file, fkt, + "%s: x=%d, y=%d, button=%d, window=0x%08lx, app=%d, down=%d", + __func__, x, y, button, window, app, down); + + WINPR_ASSERT(xfc); + if (button < 0) + return FALSE; + + for (size_t i = 0; i < ARRAYSIZE(xfc->button_map); i++) + { + const button_map* cur = &xfc->button_map[i]; + + if (cur->button == (UINT32)button) + { + flags = cur->flags; + break; + } + } + + if (flags != 0) + { + if (flags & (PTR_FLAGS_WHEEL | PTR_FLAGS_HWHEEL)) + { + if (down) + freerdp_client_send_wheel_event(&xfc->common, flags); + } + else + { + BOOL extended = FALSE; + + if (flags & (PTR_XFLAGS_BUTTON1 | PTR_XFLAGS_BUTTON2)) + { + extended = TRUE; + + if (down) + flags |= PTR_XFLAGS_DOWN; + } + else if (flags & (PTR_FLAGS_BUTTON1 | PTR_FLAGS_BUTTON2 | PTR_FLAGS_BUTTON3)) + { + if (down) + flags |= PTR_FLAGS_DOWN; + } + + if (app) + { + /* make sure window exists */ + xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, window); + xf_rail_return_window(appWindow, FALSE); + if (!appWindow) + return TRUE; + + /* Translate to desktop coordinates */ + XTranslateCoordinates(xfc->display, window, RootWindowOfScreen(xfc->screen), x, y, + &x, &y, &childWindow); + } + + xf_event_adjust_coordinates(xfc, &x, &y); + + if (extended) + freerdp_client_send_extended_button_event(&xfc->common, FALSE, flags, x, y); + else + freerdp_client_send_button_event(&xfc->common, FALSE, flags, x, y); + } + } + + return TRUE; +} + +static BOOL xf_grab_mouse(xfContext* xfc) +{ + WINPR_ASSERT(xfc); + + if (!xfc->window) + return FALSE; + + if (freerdp_settings_get_bool(xfc->common.context.settings, FreeRDP_GrabMouse)) + { + XGrabPointer(xfc->display, xfc->window->handle, False, + ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask | + EnterWindowMask | LeaveWindowMask, + GrabModeAsync, GrabModeAsync, xfc->window->handle, None, CurrentTime); + xfc->common.mouse_grabbed = TRUE; + } + return TRUE; +} + +static BOOL xf_grab_kbd(xfContext* xfc) +{ + WINPR_ASSERT(xfc); + + if (!xfc->window) + return FALSE; + + XGrabKeyboard(xfc->display, xfc->window->handle, TRUE, GrabModeAsync, GrabModeAsync, + CurrentTime); + return TRUE; +} + +static BOOL xf_event_ButtonPress(xfContext* xfc, const XButtonEvent* event, BOOL app) +{ + xf_grab_mouse(xfc); + + if (xfc->xi_event || xfc->xi_rawevent || (xfc->common.mouse_grabbed && xf_use_rel_mouse(xfc))) + return TRUE; + if (!app && xfc_is_floatbar_window(xfc, event->window)) + return TRUE; + return xf_generic_ButtonEvent(xfc, event->x, event->y, + WINPR_ASSERTING_INT_CAST(int, event->button), event->window, app, + TRUE); +} + +static BOOL xf_event_ButtonRelease(xfContext* xfc, const XButtonEvent* event, BOOL app) +{ + xf_grab_mouse(xfc); + + if (xfc->xi_event || xfc->xi_rawevent || (xfc->common.mouse_grabbed && xf_use_rel_mouse(xfc))) + return TRUE; + return xf_generic_ButtonEvent(xfc, event->x, event->y, + WINPR_ASSERTING_INT_CAST(int, event->button), event->window, app, + FALSE); +} + +static BOOL xf_event_KeyPress(xfContext* xfc, const XKeyEvent* event, BOOL app) +{ + KeySym keysym = 0; + char str[256] = WINPR_C_ARRAY_INIT; + union + { + const XKeyEvent* cev; + XKeyEvent* ev; + } cnv; + cnv.cev = event; + WINPR_UNUSED(app); + XLookupString(cnv.ev, str, sizeof(str), &keysym, nullptr); + xf_keyboard_key_press(xfc, event, keysym); + return TRUE; +} + +static BOOL xf_event_KeyRelease(xfContext* xfc, const XKeyEvent* event, BOOL app) +{ + KeySym keysym = 0; + char str[256] = WINPR_C_ARRAY_INIT; + union + { + const XKeyEvent* cev; + XKeyEvent* ev; + } cnv; + cnv.cev = event; + + WINPR_UNUSED(app); + XLookupString(cnv.ev, str, sizeof(str), &keysym, nullptr); + xf_keyboard_key_release(xfc, event, keysym); + return TRUE; +} + +/* Release a key, but ignore the event in case of autorepeat. + */ +static BOOL xf_event_KeyReleaseOrIgnore(xfContext* xfc, const XKeyEvent* event, BOOL app) +{ + WINPR_ASSERT(xfc); + WINPR_ASSERT(event); + + if ((event->type == KeyRelease) && XEventsQueued(xfc->display, QueuedAfterReading)) + { + XEvent nev = WINPR_C_ARRAY_INIT; + XPeekEvent(xfc->display, &nev); + + if ((nev.type == KeyPress) && (nev.xkey.time == event->time) && + (nev.xkey.keycode == event->keycode)) + { + /* Key wasn’t actually released */ + return TRUE; + } + } + + return xf_event_KeyRelease(xfc, event, app); +} + +static BOOL xf_event_FocusIn(xfContext* xfc, const XFocusInEvent* event, BOOL app) +{ + if (event->mode == NotifyGrab) + return TRUE; + + xfc->focused = TRUE; + + if (xfc->mouse_active && !app) + { + xf_grab_mouse(xfc); + if (!xf_grab_kbd(xfc)) + return FALSE; + } + + /* Release all keys, should already be done at FocusOut but might be missed + * if the WM decided to use an alternate event order */ + if (!app) + xf_keyboard_release_all_keypress(xfc); + else + { + if (!xf_rail_send_activate(xfc, event->window, TRUE)) + return FALSE; + } + + xf_pointer_update_scale(xfc); + + if (app) + { + xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, event->window); + + /* Update the server with any window changes that occurred while the window was not focused. + */ + if (appWindow) + xf_rail_adjust_position(xfc, appWindow); + xf_rail_return_window(appWindow, FALSE); + } + + xf_keyboard_focus_in(xfc); + return TRUE; +} + +static BOOL xf_event_FocusOut(xfContext* xfc, const XFocusOutEvent* event, BOOL app) +{ + if (event->mode == NotifyUngrab) + return TRUE; + + xfc->focused = FALSE; + + if (event->mode == NotifyWhileGrabbed) + XUngrabKeyboard(xfc->display, CurrentTime); + + xf_keyboard_release_all_keypress(xfc); + if (app) + return xf_rail_send_activate(xfc, event->window, FALSE); + + return TRUE; +} + +static BOOL xf_event_MappingNotify(xfContext* xfc, const XMappingEvent* event, BOOL app) +{ + WINPR_UNUSED(app); + + switch (event->request) + { + case MappingModifier: + return xf_keyboard_update_modifier_map(xfc); + case MappingKeyboard: + WLog_Print(xfc->log, WLOG_TRACE, "[%d] MappingKeyboard", event->request); + return xf_keyboard_init(xfc); + case MappingPointer: + WLog_Print(xfc->log, WLOG_TRACE, "[%d] MappingPointer", event->request); + xf_button_map_init(xfc); + return TRUE; + default: + WLog_Print(xfc->log, WLOG_WARN, + "[%d] Unsupported MappingNotify::request, must be one " + "of[MappingModifier(%d), MappingKeyboard(%d), MappingPointer(%d)]", + event->request, MappingModifier, MappingKeyboard, MappingPointer); + return FALSE; + } +} + +static BOOL xf_event_ClientMessage(xfContext* xfc, const XClientMessageEvent* event, BOOL app) +{ + if ((event->message_type == xfc->WM_PROTOCOLS) && + ((Atom)event->data.l[0] == xfc->WM_DELETE_WINDOW)) + { + if (app) + { + BOOL rc = TRUE; + xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, event->window); + + if (appWindow) + rc = xf_rail_send_client_system_command(xfc, appWindow->windowId, SC_CLOSE); + xf_rail_return_window(appWindow, FALSE); + return rc; + } + else + { + DEBUG_X11("Main window closed"); + return FALSE; + } + } + + return TRUE; +} + +static BOOL xf_event_EnterNotify(xfContext* xfc, const XEnterWindowEvent* event, BOOL app) +{ + if (!app) + { + if (!xfc->window) + return FALSE; + + xfc->mouse_active = TRUE; + + if (xfc->fullscreen) + XSetInputFocus(xfc->display, xfc->window->handle, RevertToPointerRoot, CurrentTime); + + if (xfc->focused) + xf_grab_kbd(xfc); + } + else + { + xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, event->window); + + /* keep track of which window has focus so that we can apply pointer updates */ + xfc->appWindow = appWindow; + xf_rail_return_window(appWindow, FALSE); + } + + return TRUE; +} + +static BOOL xf_event_LeaveNotify(xfContext* xfc, const XLeaveWindowEvent* event, BOOL app) +{ + if (event->mode == NotifyGrab || event->mode == NotifyUngrab) + return TRUE; + if (!app) + { + xfc->mouse_active = FALSE; + XUngrabKeyboard(xfc->display, CurrentTime); + } + else + { + xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, event->window); + + /* keep track of which window has focus so that we can apply pointer updates */ + if (xfc->appWindow == appWindow) + xfc->appWindow = nullptr; + xf_rail_return_window(appWindow, FALSE); + } + return TRUE; +} + +static BOOL xf_event_ConfigureNotify(xfContext* xfc, const XConfigureEvent* event, BOOL app) +{ + Window childWindow = None; + xfAppWindow* appWindow = nullptr; + + WINPR_ASSERT(xfc); + WINPR_ASSERT(event); + + const rdpSettings* settings = xfc->common.context.settings; + WINPR_ASSERT(settings); + + WLog_Print(xfc->log, WLOG_DEBUG, "x=%" PRId32 ", y=%" PRId32 ", w=%" PRId32 ", h=%" PRId32, + event->x, event->y, event->width, event->height); + + if (!app) + { + if (!xfc->window) + return FALSE; + + if (xfc->window->left != event->x) + xfc->window->left = event->x; + + if (xfc->window->top != event->y) + xfc->window->top = event->y; + + if (xfc->window->width != event->width || xfc->window->height != event->height) + { + xfc->window->width = event->width; + xfc->window->height = event->height; +#ifdef WITH_XRENDER + xfc->offset_x = 0; + xfc->offset_y = 0; + + if (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) || + freerdp_settings_get_bool(settings, FreeRDP_MultiTouchGestures)) + { + xfc->scaledWidth = xfc->window->width; + xfc->scaledHeight = xfc->window->height; + xf_draw_screen( + xfc, 0, 0, + WINPR_ASSERTING_INT_CAST( + int32_t, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth)), + WINPR_ASSERTING_INT_CAST( + int32_t, freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight))); + } + else + { + xfc->scaledWidth = WINPR_ASSERTING_INT_CAST( + int, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth)); + xfc->scaledHeight = WINPR_ASSERTING_INT_CAST( + int, freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)); + } + +#endif + } + + if (freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate)) + { + const int alignedWidth = (xfc->window->width / 2) * 2; + const int alignedHeight = (xfc->window->height / 2) * 2; + /* ask the server to resize using the display channel */ + xf_disp_handle_configureNotify(xfc, alignedWidth, alignedHeight); + } + } + else + { + appWindow = xf_AppWindowFromX11Window(xfc, event->window); + + if (appWindow) + { + /* + * ConfigureNotify coordinates are expressed relative to the window parent. + * Translate these to root window coordinates. + */ + XTranslateCoordinates(xfc->display, appWindow->handle, RootWindowOfScreen(xfc->screen), + 0, 0, &appWindow->x, &appWindow->y, &childWindow); + appWindow->width = event->width; + appWindow->height = event->height; + + xf_AppWindowResize(xfc, appWindow); + + /* + * Additional checks for not in a local move and not ignoring configure to send + * position update to server, also should the window not be focused then do not + * send to server yet (i.e. resizing using window decoration). + * The server will be updated when the window gets refocused. + */ + if (appWindow->decorations) + { + /* moving resizing using window decoration */ + xf_rail_adjust_position(xfc, appWindow); + } + else + { + if ((!event->send_event || appWindow->local_move.state == LMS_NOT_ACTIVE) && + !appWindow->rail_ignore_configure && xfc->focused) + xf_rail_adjust_position(xfc, appWindow); + } + } + xf_rail_return_window(appWindow, FALSE); + } + return xf_pointer_update_scale(xfc); +} + +static BOOL xf_event_MapNotify(xfContext* xfc, const XMapEvent* event, BOOL app) +{ + WINPR_ASSERT(xfc); + if (!app) + { + if (!gdi_send_suppress_output(xfc->common.context.gdi, FALSE)) + return FALSE; + } + else + { + xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, event->window); + + if (appWindow) + { + /* local restore event */ + /* This is now handled as part of the PropertyNotify + * Doing this here would inhibit the ability to restore a maximized window + * that is minimized back to the maximized state + */ + // xf_rail_send_client_system_command(xfc, appWindow->windowId, SC_RESTORE); + appWindow->is_mapped = TRUE; + } + xf_rail_return_window(appWindow, FALSE); + } + + return TRUE; +} + +static BOOL xf_event_UnmapNotify(xfContext* xfc, const XUnmapEvent* event, BOOL app) +{ + WINPR_ASSERT(xfc); + WINPR_ASSERT(event); + + if (!app) + xf_keyboard_release_all_keypress(xfc); + + if (!app) + return gdi_send_suppress_output(xfc->common.context.gdi, TRUE); + + { + xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, event->window); + + if (appWindow) + appWindow->is_mapped = FALSE; + xf_rail_return_window(appWindow, FALSE); + } + + return TRUE; +} + +static BOOL xf_event_PropertyNotify(xfContext* xfc, const XPropertyEvent* event, BOOL app) +{ + BOOL rc = TRUE; + WINPR_ASSERT(xfc); + WINPR_ASSERT(event); + + /* + * This section handles sending the appropriate commands to the rail server + * when the window has been minimized, maximized, restored locally + * ie. not using the buttons on the rail window itself + */ + if (((event->atom == xfc->NET_WM_STATE) && (event->state != PropertyDelete)) || + ((event->atom == xfc->WM_STATE) && (event->state != PropertyDelete))) + { + BOOL status = FALSE; + BOOL minimized = FALSE; + BOOL minimizedChanged = FALSE; + unsigned long nitems = 0; + unsigned long bytes = 0; + unsigned char* prop = nullptr; + xfAppWindow* appWindow = nullptr; + + if (app) + { + appWindow = xf_AppWindowFromX11Window(xfc, event->window); + + if (!appWindow) + goto fail; + } + + if (event->atom == xfc->NET_WM_STATE) + { + status = xf_GetWindowProperty(xfc, event->window, xfc->NET_WM_STATE, 12, &nitems, + &bytes, &prop); + + if (status) + { + if (appWindow) + { + appWindow->maxVert = FALSE; + appWindow->maxHorz = FALSE; + } + for (unsigned long i = 0; i < nitems; i++) + { + if ((Atom)((UINT16**)prop)[i] == + Logging_XInternAtom(xfc->log, xfc->display, "_NET_WM_STATE_MAXIMIZED_VERT", + False)) + { + if (appWindow) + appWindow->maxVert = TRUE; + } + + if ((Atom)((UINT16**)prop)[i] == + Logging_XInternAtom(xfc->log, xfc->display, "_NET_WM_STATE_MAXIMIZED_HORZ", + False)) + { + if (appWindow) + appWindow->maxHorz = TRUE; + } + } + + XFree(prop); + } + } + + if (event->atom == xfc->WM_STATE) + { + status = + xf_GetWindowProperty(xfc, event->window, xfc->WM_STATE, 1, &nitems, &bytes, &prop); + + if (status) + { + /* If the window is in the iconic state */ + if (((UINT32)*prop == 3) && !IsGnome()) + { + minimized = TRUE; + if (appWindow) + appWindow->minimized = TRUE; + } + else + { + minimized = FALSE; + if (appWindow) + appWindow->minimized = FALSE; + } + + minimizedChanged = TRUE; + XFree(prop); + } + } + + if (app) + { + WINPR_ASSERT(appWindow); + if (appWindow->maxVert && appWindow->maxHorz && !appWindow->minimized) + { + if (appWindow->rail_state != WINDOW_SHOW_MAXIMIZED) + { + appWindow->rail_state = WINDOW_SHOW_MAXIMIZED; + rc = xf_rail_send_client_system_command(xfc, appWindow->windowId, SC_MAXIMIZE); + } + } + else if (appWindow->minimized) + { + if (appWindow->rail_state != WINDOW_SHOW_MINIMIZED) + { + appWindow->rail_state = WINDOW_SHOW_MINIMIZED; + rc = xf_rail_send_client_system_command(xfc, appWindow->windowId, SC_MINIMIZE); + } + } + else + { + if (appWindow->rail_state != WINDOW_SHOW && appWindow->rail_state != WINDOW_HIDE) + { + appWindow->rail_state = WINDOW_SHOW; + rc = xf_rail_send_client_system_command(xfc, appWindow->windowId, SC_RESTORE); + } + } + } + else if (minimizedChanged) + rc = gdi_send_suppress_output(xfc->common.context.gdi, minimized); + + fail: + xf_rail_return_window(appWindow, FALSE); + } + + return rc; +} + +static BOOL xf_event_suppress_events(xfContext* xfc, xfAppWindow* appWindow, const XEvent* event) +{ + if (!xfc->remote_app) + return FALSE; + + switch (appWindow->local_move.state) + { + case LMS_NOT_ACTIVE: + + /* No local move in progress, nothing to do */ + + /* Prevent Configure from happening during indeterminant state of Horz or Vert Max only + */ + if ((event->type == ConfigureNotify) && appWindow->rail_ignore_configure) + { + appWindow->rail_ignore_configure = FALSE; + return TRUE; + } + + break; + + case LMS_STARTING: + + /* Local move initiated by RDP server, but we have not yet seen any updates from the X + * server */ + switch (event->type) + { + case ConfigureNotify: + /* Starting to see move events from the X server. Local move is now in progress. + */ + appWindow->local_move.state = LMS_ACTIVE; + /* Allow these events to be processed during move to keep our state up to date. + */ + break; + + case ButtonPress: + case ButtonRelease: + case KeyPress: + case KeyRelease: + case UnmapNotify: + /* + * A button release event means the X window server did not grab the + * mouse before the user released it. In this case we must cancel the + * local move. The event will be processed below as normal, below. + */ + break; + + case VisibilityNotify: + case PropertyNotify: + case Expose: + /* Allow these events to pass */ + break; + + default: + /* Eat any other events */ + return TRUE; + } + + break; + + case LMS_ACTIVE: + + /* Local move is in progress */ + switch (event->type) + { + case ConfigureNotify: + case VisibilityNotify: + case PropertyNotify: + case Expose: + case GravityNotify: + /* Keep us up to date on position */ + break; + + default: + /* Any other event terminates move */ + xf_rail_end_local_move(xfc, appWindow); + break; + } + + break; + + case LMS_TERMINATING: + /* Already sent RDP end move to server. Allow events to pass. */ + break; + default: + break; + } + + return FALSE; +} + +BOOL xf_event_process(freerdp* instance, const XEvent* event) +{ + BOOL status = TRUE; + + WINPR_ASSERT(instance); + WINPR_ASSERT(event); + + xfContext* xfc = (xfContext*)instance->context; + WINPR_ASSERT(xfc); + + rdpSettings* settings = xfc->common.context.settings; + WINPR_ASSERT(settings); + + if (xfc->remote_app) + { + xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, event->xany.window); + + if (appWindow) + { + /* Update "current" window for cursor change orders */ + xfc->appWindow = appWindow; + + const BOOL rc = xf_event_suppress_events(xfc, appWindow, event); + xf_rail_return_window(appWindow, FALSE); + if (rc) + return TRUE; + } + } + + if (xfc->window) + { + xfFloatbar* floatbar = xfc->window->floatbar; + if (xf_floatbar_check_event(floatbar, event)) + { + xf_floatbar_event_process(floatbar, event); + return TRUE; + } + + if (xf_floatbar_is_locked(floatbar)) + { + /* Filter input events, floatbar is locked do not forward anything to the session */ + switch (event->type) + { + case MotionNotify: + case ButtonPress: + case ButtonRelease: + case KeyPress: + case KeyRelease: + case FocusIn: + case FocusOut: + case EnterNotify: + case LeaveNotify: + return TRUE; + default: + break; + } + } + } + + xf_event_execute_action_script(xfc, event); + + if (event->type != MotionNotify) + { + DEBUG_X11("%s Event(%d): wnd=0x%08lX", x11_event_string(event->type), event->type, + (unsigned long)event->xany.window); + } + + switch (event->type) + { + case Expose: + status = xf_event_Expose(xfc, &event->xexpose, xfc->remote_app); + break; + + case VisibilityNotify: + status = xf_event_VisibilityNotify(xfc, &event->xvisibility, xfc->remote_app); + break; + + case MotionNotify: + status = xf_event_MotionNotify(xfc, &event->xmotion, xfc->remote_app); + break; + + case ButtonPress: + status = xf_event_ButtonPress(xfc, &event->xbutton, xfc->remote_app); + break; + + case ButtonRelease: + status = xf_event_ButtonRelease(xfc, &event->xbutton, xfc->remote_app); + break; + + case KeyPress: + status = xf_event_KeyPress(xfc, &event->xkey, xfc->remote_app); + break; + + case KeyRelease: + status = xf_event_KeyReleaseOrIgnore(xfc, &event->xkey, xfc->remote_app); + break; + + case FocusIn: + status = xf_event_FocusIn(xfc, &event->xfocus, xfc->remote_app); + break; + + case FocusOut: + status = xf_event_FocusOut(xfc, &event->xfocus, xfc->remote_app); + break; + + case EnterNotify: + status = xf_event_EnterNotify(xfc, &event->xcrossing, xfc->remote_app); + break; + + case LeaveNotify: + status = xf_event_LeaveNotify(xfc, &event->xcrossing, xfc->remote_app); + break; + + case NoExpose: + break; + + case GraphicsExpose: + break; + + case ConfigureNotify: + status = xf_event_ConfigureNotify(xfc, &event->xconfigure, xfc->remote_app); + break; + + case MapNotify: + status = xf_event_MapNotify(xfc, &event->xmap, xfc->remote_app); + break; + + case UnmapNotify: + status = xf_event_UnmapNotify(xfc, &event->xunmap, xfc->remote_app); + break; + + case ReparentNotify: + break; + + case MappingNotify: + status = xf_event_MappingNotify(xfc, &event->xmapping, xfc->remote_app); + break; + + case ClientMessage: + status = xf_event_ClientMessage(xfc, &event->xclient, xfc->remote_app); + break; + + case PropertyNotify: + status = xf_event_PropertyNotify(xfc, &event->xproperty, xfc->remote_app); + break; + + default: + if (freerdp_settings_get_bool(settings, FreeRDP_SupportDisplayControl)) + xf_disp_handle_xevent(xfc, event); + + break; + } + + xfWindow* window = xfc->window; + xfFloatbar* floatbar = nullptr; + if (window) + floatbar = window->floatbar; + + xf_cliprdr_handle_xevent(xfc, event); + if (!xf_floatbar_check_event(floatbar, event) && !xf_floatbar_is_locked(floatbar)) + xf_input_handle_event(xfc, event); + + LogDynAndXSync(xfc->log, xfc->display, FALSE); + return status; +} + +BOOL xf_generic_RawButtonEvent_(xfContext* xfc, int button, BOOL app, BOOL down, const char* file, + const char* fkt, size_t line) +{ + UINT16 flags = 0; + + if (WLog_IsLevelActive(xfc->log, mouseLogLevel)) + WLog_PrintTextMessage(xfc->log, mouseLogLevel, line, file, fkt, + "%s: button=%d, app=%d, down=%d", __func__, button, app, down); + + if (app || (button < 0)) + return FALSE; + + for (size_t i = 0; i < ARRAYSIZE(xfc->button_map); i++) + { + const button_map* cur = &xfc->button_map[i]; + + if (cur->button == (UINT32)button) + { + flags = cur->flags; + break; + } + } + + if (flags != 0) + { + if (flags & (PTR_FLAGS_WHEEL | PTR_FLAGS_HWHEEL)) + { + if (down) + freerdp_client_send_wheel_event(&xfc->common, flags); + } + else + { + BOOL extended = FALSE; + + if (flags & (PTR_XFLAGS_BUTTON1 | PTR_XFLAGS_BUTTON2)) + { + extended = TRUE; + + if (down) + flags |= PTR_XFLAGS_DOWN; + } + else if (flags & (PTR_FLAGS_BUTTON1 | PTR_FLAGS_BUTTON2 | PTR_FLAGS_BUTTON3)) + { + if (down) + flags |= PTR_FLAGS_DOWN; + } + + if (extended) + freerdp_client_send_extended_button_event(&xfc->common, TRUE, flags, 0, 0); + else + freerdp_client_send_button_event(&xfc->common, TRUE, flags, 0, 0); + } + } + + return TRUE; +} diff --git a/third_party/FreeRDP/client/X11/xf_event.h b/third_party/FreeRDP/client/X11/xf_event.h new file mode 100644 index 0000000..778f739 --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_event.h @@ -0,0 +1,61 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Event Handling + * + * Copyright 2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_X11_EVENT_H +#define FREERDP_CLIENT_X11_EVENT_H + +#include "xf_keyboard.h" + +#include "xf_client.h" +#include "xfreerdp.h" + +const char* x11_event_string(int event); + +BOOL xf_event_action_script_init(xfContext* xfc); +void xf_event_action_script_free(xfContext* xfc); + +BOOL xf_event_process(freerdp* instance, const XEvent* event); +void xf_event_SendClientEvent(xfContext* xfc, xfWindow* window, Atom atom, unsigned int numArgs, + ...); + +void xf_event_adjust_coordinates(xfContext* xfc, int* x, int* y); +void xf_adjust_coordinates_to_screen(xfContext* xfc, UINT32* x, UINT32* y); + +#define xf_generic_MotionNotify(xfc, x, y, window, app) \ + xf_generic_MotionNotify_((xfc), (x), (y), (window), (app), __FILE__, __func__, __LINE__) +BOOL xf_generic_MotionNotify_(xfContext* xfc, int x, int y, Window window, BOOL app, + const char* file, const char* fkt, size_t line); + +#define xf_generic_RawMotionNotify(xfc, x, y, window, app) \ + xf_generic_RawMotionNotify_((xfc), (x), (y), (window), (app), __FILE__, __func__, __LINE__) +BOOL xf_generic_RawMotionNotify_(xfContext* xfc, int x, int y, Window window, BOOL app, + const char* file, const char* fkt, size_t line); + +#define xf_generic_ButtonEvent(xfc, x, y, button, window, app, down) \ + xf_generic_ButtonEvent_((xfc), (x), (y), (button), (window), (app), (down), __FILE__, \ + __func__, __LINE__) +BOOL xf_generic_ButtonEvent_(xfContext* xfc, int x, int y, int button, Window window, BOOL app, + BOOL down, const char* file, const char* fkt, size_t line); + +#define xf_generic_RawButtonEvent(xfc, button, app, down) \ + xf_generic_RawButtonEvent_((xfc), (button), (app), (down), __FILE__, __func__, __LINE__) +BOOL xf_generic_RawButtonEvent_(xfContext* xfc, int button, BOOL app, BOOL down, const char* file, + const char* fkt, size_t line); + +#endif /* FREERDP_CLIENT_X11_EVENT_H */ diff --git a/third_party/FreeRDP/client/X11/xf_floatbar.c b/third_party/FreeRDP/client/X11/xf_floatbar.c new file mode 100644 index 0000000..8d525cd --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_floatbar.c @@ -0,0 +1,959 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Windows + * + * Licensed under the Apache License, Version 2.0 (the "License");n + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include "xf_floatbar.h" +#include "xf_utils.h" +#include "resource/close.xbm" +#include "resource/lock.xbm" +#include "resource/unlock.xbm" +#include "resource/minimize.xbm" +#include "resource/restore.xbm" + +#include +#define TAG CLIENT_TAG("x11") + +#define FLOATBAR_HEIGHT 26 +#define FLOATBAR_DEFAULT_WIDTH 576 +#define FLOATBAR_MIN_WIDTH 200 +#define FLOATBAR_BORDER 24 +#define FLOATBAR_BUTTON_WIDTH 24 +#define FLOATBAR_COLOR_BACKGROUND "RGB:31/6c/a9" +#define FLOATBAR_COLOR_BORDER "RGB:75/9a/c8" +#define FLOATBAR_COLOR_FOREGROUND "RGB:FF/FF/FF" + +#define XF_FLOATBAR_MODE_NONE 0 +#define XF_FLOATBAR_MODE_DRAGGING 1 +#define XF_FLOATBAR_MODE_RESIZE_LEFT 2 +#define XF_FLOATBAR_MODE_RESIZE_RIGHT 3 + +#define XF_FLOATBAR_BUTTON_CLOSE 1 +#define XF_FLOATBAR_BUTTON_RESTORE 2 +#define XF_FLOATBAR_BUTTON_MINIMIZE 3 +#define XF_FLOATBAR_BUTTON_LOCKED 4 + +typedef BOOL (*OnClick)(xfFloatbar*); + +typedef struct +{ + int x; + int y; + int type; + bool focus; + bool clicked; + OnClick onclick; + Window handle; +} xfFloatbarButton; + +struct xf_floatbar +{ + int x; + int y; + int width; + int height; + int mode; + int last_motion_x_root; + int last_motion_y_root; + BOOL locked; + xfFloatbarButton* buttons[4]; + Window handle; + BOOL hasCursor; + xfContext* xfc; + DWORD flags; + BOOL created; + Window root_window; + char* title; + XFontSet fontSet; +}; + +static xfFloatbarButton* xf_floatbar_new_button(xfFloatbar* floatbar, int type); + +static BOOL xf_floatbar_button_onclick_close(xfFloatbar* floatbar) +{ + if (!floatbar) + return FALSE; + + return freerdp_abort_connect_context(&floatbar->xfc->common.context); +} + +static BOOL xf_floatbar_button_onclick_minimize(xfFloatbar* floatbar) +{ + xfContext* xfc = nullptr; + + if (!floatbar || !floatbar->xfc) + return FALSE; + + xfc = floatbar->xfc; + xf_SetWindowMinimized(xfc, xfc->window); + return TRUE; +} + +static BOOL xf_floatbar_button_onclick_restore(xfFloatbar* floatbar) +{ + if (!floatbar) + return FALSE; + + xf_toggle_fullscreen(floatbar->xfc); + return TRUE; +} + +static BOOL xf_floatbar_button_onclick_locked(xfFloatbar* floatbar) +{ + if (!floatbar) + return FALSE; + + floatbar->locked = !((floatbar->locked)); + return xf_floatbar_hide_and_show(floatbar); +} + +BOOL xf_floatbar_set_root_y(xfFloatbar* floatbar, int y) +{ + if (!floatbar) + return FALSE; + + floatbar->last_motion_y_root = y; + return TRUE; +} + +BOOL xf_floatbar_hide_and_show(xfFloatbar* floatbar) +{ + xfContext* xfc = nullptr; + + if (!floatbar || !floatbar->xfc) + return FALSE; + + if (!floatbar->created) + return TRUE; + + xfc = floatbar->xfc; + WINPR_ASSERT(xfc); + WINPR_ASSERT(xfc->display); + + if (!floatbar->locked) + { + if ((floatbar->mode == XF_FLOATBAR_MODE_NONE) && (floatbar->last_motion_y_root > 10) && + (floatbar->y > (FLOATBAR_HEIGHT * -1))) + { + floatbar->y = floatbar->y - 1; + LogDynAndXMoveWindow(xfc->log, xfc->display, floatbar->handle, floatbar->x, + floatbar->y); + } + else if (floatbar->y < 0 && (floatbar->last_motion_y_root < 10)) + { + floatbar->y = floatbar->y + 1; + LogDynAndXMoveWindow(xfc->log, xfc->display, floatbar->handle, floatbar->x, + floatbar->y); + } + } + + return TRUE; +} + +static BOOL create_floatbar(xfFloatbar* floatbar) +{ + xfContext* xfc = nullptr; + Status status = 0; + XWindowAttributes attr = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(floatbar); + if (floatbar->created) + return TRUE; + + xfc = floatbar->xfc; + WINPR_ASSERT(xfc); + WINPR_ASSERT(xfc->display); + + status = XGetWindowAttributes(xfc->display, floatbar->root_window, &attr); + if (status == 0) + { + WLog_WARN(TAG, "XGetWindowAttributes failed"); + return FALSE; + } + floatbar->x = attr.x + attr.width / 2 - FLOATBAR_DEFAULT_WIDTH / 2; + floatbar->y = 0; + + if (((floatbar->flags & 0x0004) == 0) && !floatbar->locked) + floatbar->y = -FLOATBAR_HEIGHT + 1; + + floatbar->handle = LogDynAndXCreateWindow( + xfc->log, xfc->display, floatbar->root_window, floatbar->x, 0, FLOATBAR_DEFAULT_WIDTH, + FLOATBAR_HEIGHT, 0, CopyFromParent, InputOutput, CopyFromParent, 0, nullptr); + floatbar->width = FLOATBAR_DEFAULT_WIDTH; + floatbar->height = FLOATBAR_HEIGHT; + floatbar->mode = XF_FLOATBAR_MODE_NONE; + floatbar->buttons[0] = xf_floatbar_new_button(floatbar, XF_FLOATBAR_BUTTON_CLOSE); + floatbar->buttons[1] = xf_floatbar_new_button(floatbar, XF_FLOATBAR_BUTTON_RESTORE); + floatbar->buttons[2] = xf_floatbar_new_button(floatbar, XF_FLOATBAR_BUTTON_MINIMIZE); + floatbar->buttons[3] = xf_floatbar_new_button(floatbar, XF_FLOATBAR_BUTTON_LOCKED); + XSelectInput(xfc->display, floatbar->handle, + ExposureMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | + FocusChangeMask | LeaveWindowMask | EnterWindowMask | StructureNotifyMask | + PropertyChangeMask); + floatbar->created = TRUE; + return TRUE; +} + +BOOL xf_floatbar_toggle_fullscreen(xfFloatbar* floatbar, bool fullscreen) +{ + int size = 0; + bool visible = False; + xfContext* xfc = nullptr; + + if (!floatbar || !floatbar->xfc) + return FALSE; + + xfc = floatbar->xfc; + WINPR_ASSERT(xfc->display); + + /* Only visible if enabled */ + if (floatbar->flags & 0x0001) + { + /* Visible if fullscreen and flag visible in fullscreen mode */ + visible |= ((floatbar->flags & 0x0010) != 0) && fullscreen; + /* Visible if window and flag visible in window mode */ + visible |= ((floatbar->flags & 0x0020) != 0) && !fullscreen; + } + + if (visible) + { + if (!create_floatbar(floatbar)) + return FALSE; + + LogDynAndXMapWindow(xfc->log, xfc->display, floatbar->handle); + size = ARRAYSIZE(floatbar->buttons); + + for (int i = 0; i < size; i++) + { + xfFloatbarButton* button = floatbar->buttons[i]; + LogDynAndXMapWindow(xfc->log, xfc->display, button->handle); + } + + /* If default is hidden (and not sticky) don't show on fullscreen state changes */ + if (((floatbar->flags & 0x0004) == 0) && !floatbar->locked) + floatbar->y = -FLOATBAR_HEIGHT + 1; + + xf_floatbar_hide_and_show(floatbar); + } + else if (floatbar->created) + { + XUnmapSubwindows(xfc->display, floatbar->handle); + LogDynAndXUnmapWindow(xfc->log, xfc->display, floatbar->handle); + } + + return TRUE; +} + +xfFloatbarButton* xf_floatbar_new_button(xfFloatbar* floatbar, int type) +{ + xfFloatbarButton* button = nullptr; + + WINPR_ASSERT(floatbar); + WINPR_ASSERT(floatbar->xfc); + WINPR_ASSERT(floatbar->xfc->display); + WINPR_ASSERT(floatbar->handle); + + button = (xfFloatbarButton*)calloc(1, sizeof(xfFloatbarButton)); + button->type = type; + + switch (type) + { + case XF_FLOATBAR_BUTTON_CLOSE: + button->x = floatbar->width - FLOATBAR_BORDER - FLOATBAR_BUTTON_WIDTH * type; + button->onclick = xf_floatbar_button_onclick_close; + break; + + case XF_FLOATBAR_BUTTON_RESTORE: + button->x = floatbar->width - FLOATBAR_BORDER - FLOATBAR_BUTTON_WIDTH * type; + button->onclick = xf_floatbar_button_onclick_restore; + break; + + case XF_FLOATBAR_BUTTON_MINIMIZE: + button->x = floatbar->width - FLOATBAR_BORDER - FLOATBAR_BUTTON_WIDTH * type; + button->onclick = xf_floatbar_button_onclick_minimize; + break; + + case XF_FLOATBAR_BUTTON_LOCKED: + button->x = FLOATBAR_BORDER; + button->onclick = xf_floatbar_button_onclick_locked; + break; + + default: + break; + } + + button->y = 0; + button->focus = FALSE; + button->handle = + LogDynAndXCreateWindow(floatbar->xfc->log, floatbar->xfc->display, floatbar->handle, + button->x, 0, FLOATBAR_BUTTON_WIDTH, FLOATBAR_BUTTON_WIDTH, 0, + CopyFromParent, InputOutput, CopyFromParent, 0, nullptr); + XSelectInput(floatbar->xfc->display, button->handle, + ExposureMask | ButtonPressMask | ButtonReleaseMask | FocusChangeMask | + LeaveWindowMask | EnterWindowMask | StructureNotifyMask); + return button; +} + +xfFloatbar* xf_floatbar_new(xfContext* xfc, Window window, const char* title, DWORD flags) +{ + WINPR_ASSERT(xfc); + WINPR_ASSERT(xfc->display); + WINPR_ASSERT(title); + + /* Floatbar not enabled */ + if ((flags & 0x0001) == 0) + return nullptr; + + if (!xfc) + return nullptr; + + /* Force disable with remote app */ + if (xfc->remote_app) + return nullptr; + + xfFloatbar* floatbar = (xfFloatbar*)calloc(1, sizeof(xfFloatbar)); + + if (!floatbar) + return nullptr; + + floatbar->title = _strdup(title); + + if (!floatbar->title) + goto fail; + + floatbar->root_window = window; + floatbar->flags = flags; + floatbar->xfc = xfc; + floatbar->locked = (flags & 0x0002) != 0; + xf_floatbar_toggle_fullscreen(floatbar, FALSE); + + { + char** missingList = nullptr; + int missingCount = 0; + char* defString = nullptr; + floatbar->fontSet = XCreateFontSet(floatbar->xfc->display, "-*-*-*-*-*-*-*-*-*-*-*-*-*-*", + &missingList, &missingCount, &defString); + + if (floatbar->fontSet == nullptr) + { + WLog_ERR(TAG, "Failed to create fontset"); + } + XFreeStringList(missingList); + } + return floatbar; +fail: + WINPR_PRAGMA_DIAG_PUSH + WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC + xf_floatbar_free(floatbar); + WINPR_PRAGMA_DIAG_POP + return nullptr; +} + +static unsigned long xf_floatbar_get_color(xfFloatbar* floatbar, char* rgb_value) +{ + XColor color; + + WINPR_ASSERT(floatbar); + WINPR_ASSERT(floatbar->xfc); + + Display* display = floatbar->xfc->display; + WINPR_ASSERT(display); + + Colormap cmap = DefaultColormap(display, XDefaultScreen(display)); + XParseColor(display, cmap, rgb_value, &color); + XAllocColor(display, cmap, &color); + return color.pixel; +} + +static void xf_floatbar_event_expose(xfFloatbar* floatbar) +{ + GC gc = nullptr; + GC shape_gc = nullptr; + Pixmap pmap = 0; + XPoint shape[5] = WINPR_C_ARRAY_INIT; + XPoint border[5] = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(floatbar); + WINPR_ASSERT(floatbar->xfc); + + Display* display = floatbar->xfc->display; + WINPR_ASSERT(display); + + /* create the pixmap that we'll use for shaping the window */ + pmap = LogDynAndXCreatePixmap(floatbar->xfc->log, display, floatbar->handle, + WINPR_ASSERTING_INT_CAST(uint32_t, floatbar->width), + WINPR_ASSERTING_INT_CAST(uint32_t, floatbar->height), 1); + gc = LogDynAndXCreateGC(floatbar->xfc->log, display, floatbar->handle, 0, 0); + shape_gc = LogDynAndXCreateGC(floatbar->xfc->log, display, pmap, 0, 0); + /* points for drawing the floatbar */ + shape[0].x = 0; + shape[0].y = 0; + shape[1].x = WINPR_ASSERTING_INT_CAST(short, floatbar->width); + shape[1].y = 0; + shape[2].x = WINPR_ASSERTING_INT_CAST(short, shape[1].x - FLOATBAR_BORDER); + shape[2].y = FLOATBAR_HEIGHT; + shape[3].x = WINPR_ASSERTING_INT_CAST(short, shape[0].x + FLOATBAR_BORDER); + shape[3].y = FLOATBAR_HEIGHT; + shape[4].x = shape[0].x; + shape[4].y = shape[0].y; + /* points for drawing the border of the floatbar */ + border[0].x = shape[0].x; + border[0].y = WINPR_ASSERTING_INT_CAST(short, shape[0].y - 1); + border[1].x = WINPR_ASSERTING_INT_CAST(short, shape[1].x - 1); + border[1].y = WINPR_ASSERTING_INT_CAST(short, shape[1].y - 1); + border[2].x = shape[2].x; + border[2].y = WINPR_ASSERTING_INT_CAST(short, shape[2].y - 1); + border[3].x = WINPR_ASSERTING_INT_CAST(short, shape[3].x - 1); + border[3].y = WINPR_ASSERTING_INT_CAST(short, shape[3].y - 1); + border[4].x = border[0].x; + border[4].y = border[0].y; + /* Fill all pixels with 0 */ + LogDynAndXSetForeground(floatbar->xfc->log, display, shape_gc, 0); + LogDynAndXFillRectangle(floatbar->xfc->log, display, pmap, shape_gc, 0, 0, + WINPR_ASSERTING_INT_CAST(uint32_t, floatbar->width), + WINPR_ASSERTING_INT_CAST(uint32_t, floatbar->height)); + /* Fill all pixels which should be shown with 1 */ + LogDynAndXSetForeground(floatbar->xfc->log, display, shape_gc, 1); + XFillPolygon(display, pmap, shape_gc, shape, 5, 0, CoordModeOrigin); + XShapeCombineMask(display, floatbar->handle, ShapeBounding, 0, 0, pmap, ShapeSet); + /* draw the float bar */ + LogDynAndXSetForeground(floatbar->xfc->log, display, gc, + xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_BACKGROUND)); + XFillPolygon(display, floatbar->handle, gc, shape, 4, 0, CoordModeOrigin); + /* draw an border for the floatbar */ + LogDynAndXSetForeground(floatbar->xfc->log, display, gc, + xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_BORDER)); + XDrawLines(display, floatbar->handle, gc, border, 5, CoordModeOrigin); + /* draw the host name connected to (limit to maximum file name) */ + const size_t len = strnlen(floatbar->title, MAX_PATH); + LogDynAndXSetForeground(floatbar->xfc->log, display, gc, + xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_FOREGROUND)); + + WINPR_ASSERT(len <= INT32_MAX / 2); + const int fx = floatbar->width / 2 - (int)len * 2; + if (floatbar->fontSet != nullptr) + { + XmbDrawString(display, floatbar->handle, floatbar->fontSet, gc, fx, 15, floatbar->title, + (int)len); + } + else + { + XDrawString(display, floatbar->handle, gc, fx, 15, floatbar->title, (int)len); + } + LogDynAndXFreeGC(floatbar->xfc->log, display, gc); + LogDynAndXFreeGC(floatbar->xfc->log, display, shape_gc); +} + +static xfFloatbarButton* xf_floatbar_get_button(xfFloatbar* floatbar, Window window) +{ + WINPR_ASSERT(floatbar); + const size_t size = ARRAYSIZE(floatbar->buttons); + + for (size_t i = 0; i < size; i++) + { + xfFloatbarButton* button = floatbar->buttons[i]; + if (button->handle == window) + { + return button; + } + } + + return nullptr; +} + +static void xf_floatbar_button_update_positon(xfFloatbar* floatbar) +{ + xfFloatbarButton* button = nullptr; + WINPR_ASSERT(floatbar); + xfContext* xfc = floatbar->xfc; + const size_t size = ARRAYSIZE(floatbar->buttons); + + for (size_t i = 0; i < size; i++) + { + button = floatbar->buttons[i]; + + switch (button->type) + { + case XF_FLOATBAR_BUTTON_CLOSE: + button->x = + floatbar->width - FLOATBAR_BORDER - FLOATBAR_BUTTON_WIDTH * button->type; + break; + + case XF_FLOATBAR_BUTTON_RESTORE: + button->x = + floatbar->width - FLOATBAR_BORDER - FLOATBAR_BUTTON_WIDTH * button->type; + break; + + case XF_FLOATBAR_BUTTON_MINIMIZE: + button->x = + floatbar->width - FLOATBAR_BORDER - FLOATBAR_BUTTON_WIDTH * button->type; + break; + + default: + break; + } + + WINPR_ASSERT(xfc); + WINPR_ASSERT(xfc->display); + LogDynAndXMoveWindow(xfc->log, xfc->display, button->handle, button->x, button->y); + xf_floatbar_event_expose(floatbar); + } +} + +static void xf_floatbar_button_event_expose(xfFloatbar* floatbar, Window window) +{ + xfFloatbarButton* button = xf_floatbar_get_button(floatbar, window); + static unsigned char* bits; + GC gc = nullptr; + Pixmap pattern = 0; + xfContext* xfc = floatbar->xfc; + + if (!button) + return; + + WINPR_ASSERT(xfc); + WINPR_ASSERT(xfc->display); + WINPR_ASSERT(xfc->window); + + gc = LogDynAndXCreateGC(xfc->log, xfc->display, button->handle, 0, 0); + floatbar = xfc->window->floatbar; + WINPR_ASSERT(floatbar); + + switch (button->type) + { + case XF_FLOATBAR_BUTTON_CLOSE: + bits = close_bits; + break; + + case XF_FLOATBAR_BUTTON_RESTORE: + bits = restore_bits; + break; + + case XF_FLOATBAR_BUTTON_MINIMIZE: + bits = minimize_bits; + break; + + case XF_FLOATBAR_BUTTON_LOCKED: + if (floatbar->locked) + bits = lock_bits; + else + bits = unlock_bits; + + break; + + default: + break; + } + + pattern = XCreateBitmapFromData(xfc->display, button->handle, (const char*)bits, + FLOATBAR_BUTTON_WIDTH, FLOATBAR_BUTTON_WIDTH); + + if (!(button->focus)) + LogDynAndXSetForeground(floatbar->xfc->log, xfc->display, gc, + xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_BACKGROUND)); + else + LogDynAndXSetForeground(floatbar->xfc->log, xfc->display, gc, + xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_BORDER)); + + LogDynAndXSetBackground(xfc->log, xfc->display, gc, + xf_floatbar_get_color(floatbar, FLOATBAR_COLOR_FOREGROUND)); + XCopyPlane(xfc->display, pattern, button->handle, gc, 0, 0, FLOATBAR_BUTTON_WIDTH, + FLOATBAR_BUTTON_WIDTH, 0, 0, 1); + LogDynAndXFreePixmap(xfc->log, xfc->display, pattern); + LogDynAndXFreeGC(xfc->log, xfc->display, gc); +} + +static void xf_floatbar_button_event_buttonpress(xfFloatbar* floatbar, const XButtonEvent* event) +{ + WINPR_ASSERT(event); + xfFloatbarButton* button = xf_floatbar_get_button(floatbar, event->window); + + if (button) + button->clicked = TRUE; +} + +static void xf_floatbar_button_event_buttonrelease(xfFloatbar* floatbar, const XButtonEvent* event) +{ + xfFloatbarButton* button = nullptr; + + WINPR_ASSERT(floatbar); + WINPR_ASSERT(event); + + button = xf_floatbar_get_button(floatbar, event->window); + + if (button) + { + if (button->clicked) + button->onclick(floatbar); + button->clicked = FALSE; + } +} + +static void xf_floatbar_event_buttonpress(xfFloatbar* floatbar, const XButtonEvent* event) +{ + WINPR_ASSERT(floatbar); + WINPR_ASSERT(event); + + switch (event->button) + { + case Button1: + if (event->x <= FLOATBAR_BORDER) + floatbar->mode = XF_FLOATBAR_MODE_RESIZE_LEFT; + else if (event->x >= (floatbar->width - FLOATBAR_BORDER)) + floatbar->mode = XF_FLOATBAR_MODE_RESIZE_RIGHT; + else + floatbar->mode = XF_FLOATBAR_MODE_DRAGGING; + + break; + + default: + break; + } +} + +static void xf_floatbar_event_buttonrelease(xfFloatbar* floatbar, const XButtonEvent* event) +{ + WINPR_ASSERT(floatbar); + WINPR_ASSERT(event); + + switch (event->button) + { + case Button1: + floatbar->mode = XF_FLOATBAR_MODE_NONE; + break; + + default: + break; + } +} + +static void xf_floatbar_resize(xfFloatbar* floatbar, const XMotionEvent* event) +{ + int x = 0; + int width = 0; + int movement = 0; + + WINPR_ASSERT(floatbar); + WINPR_ASSERT(event); + + xfContext* xfc = floatbar->xfc; + WINPR_ASSERT(xfc); + WINPR_ASSERT(xfc->display); + + /* calculate movement which happened on the root window */ + movement = event->x_root - floatbar->last_motion_x_root; + + /* set x and width depending if movement happens on the left or right */ + if (floatbar->mode == XF_FLOATBAR_MODE_RESIZE_LEFT) + { + x = floatbar->x + movement; + width = floatbar->width + movement * -1; + } + else + { + x = floatbar->x; + width = floatbar->width + movement; + } + + /* only resize and move window if still above minimum width */ + if (FLOATBAR_MIN_WIDTH < width) + { + LogDynAndXMoveResizeWindow(xfc->log, xfc->display, floatbar->handle, x, 0, + WINPR_ASSERTING_INT_CAST(uint32_t, width), + WINPR_ASSERTING_INT_CAST(uint32_t, floatbar->height)); + floatbar->x = x; + floatbar->width = width; + } +} + +static void xf_floatbar_dragging(xfFloatbar* floatbar, const XMotionEvent* event) +{ + int x = 0; + int movement = 0; + + WINPR_ASSERT(floatbar); + WINPR_ASSERT(event); + xfContext* xfc = floatbar->xfc; + WINPR_ASSERT(xfc); + WINPR_ASSERT(xfc->window); + WINPR_ASSERT(xfc->display); + + /* calculate movement and new x position */ + movement = event->x_root - floatbar->last_motion_x_root; + x = floatbar->x + movement; + + /* do nothing if floatbar would be moved out of the window */ + if (x < 0 || (x + floatbar->width) > xfc->window->width) + return; + + /* move window to new x position */ + LogDynAndXMoveWindow(xfc->log, xfc->display, floatbar->handle, x, 0); + /* update struct values for the next event */ + floatbar->last_motion_x_root = floatbar->last_motion_x_root + movement; + floatbar->x = x; +} + +static void xf_floatbar_event_motionnotify(xfFloatbar* floatbar, const XMotionEvent* event) +{ + int mode = 0; + Cursor cursor = 0; + + WINPR_ASSERT(floatbar); + WINPR_ASSERT(event); + + xfContext* xfc = floatbar->xfc; + WINPR_ASSERT(xfc); + WINPR_ASSERT(xfc->display); + + mode = floatbar->mode; + cursor = XCreateFontCursor(xfc->display, XC_arrow); + + if ((event->state & Button1Mask) && (mode > XF_FLOATBAR_MODE_DRAGGING)) + { + xf_floatbar_resize(floatbar, event); + } + else if ((event->state & Button1Mask) && (mode == XF_FLOATBAR_MODE_DRAGGING)) + { + xf_floatbar_dragging(floatbar, event); + } + else + { + if (event->x <= FLOATBAR_BORDER || event->x >= floatbar->width - FLOATBAR_BORDER) + cursor = XCreateFontCursor(xfc->display, XC_sb_h_double_arrow); + } + + XDefineCursor(xfc->display, xfc->window->handle, cursor); + XFreeCursor(xfc->display, cursor); + floatbar->last_motion_x_root = event->x_root; +} + +static void xf_floatbar_button_event_focusin(xfFloatbar* floatbar, const XAnyEvent* event) +{ + xfFloatbarButton* button = nullptr; + + WINPR_ASSERT(floatbar); + WINPR_ASSERT(event); + + button = xf_floatbar_get_button(floatbar, event->window); + + if (button) + { + button->focus = TRUE; + xf_floatbar_button_event_expose(floatbar, event->window); + } +} + +static void xf_floatbar_button_event_focusout(xfFloatbar* floatbar, const XAnyEvent* event) +{ + xfFloatbarButton* button = nullptr; + + WINPR_ASSERT(floatbar); + WINPR_ASSERT(event); + + button = xf_floatbar_get_button(floatbar, event->window); + + if (button) + { + button->focus = FALSE; + xf_floatbar_button_event_expose(floatbar, event->window); + } +} + +static void xf_floatbar_event_focusout(xfFloatbar* floatbar) +{ + WINPR_ASSERT(floatbar); + xfContext* xfc = floatbar->xfc; + WINPR_ASSERT(xfc); + + if (xfc->pointer) + { + WINPR_ASSERT(xfc->window); + WINPR_ASSERT(xfc->pointer); + XDefineCursor(xfc->display, xfc->window->handle, xfc->pointer->cursor); + } +} + +BOOL xf_floatbar_check_event(xfFloatbar* floatbar, const XEvent* event) +{ + if (!floatbar || !floatbar->xfc || !event) + return FALSE; + + if (!floatbar->created) + return FALSE; + + if (event->xany.window == floatbar->handle) + return TRUE; + + size_t size = ARRAYSIZE(floatbar->buttons); + + for (size_t i = 0; i < size; i++) + { + const xfFloatbarButton* button = floatbar->buttons[i]; + + if (event->xany.window == button->handle) + return TRUE; + } + + return FALSE; +} + +BOOL xf_floatbar_event_process(xfFloatbar* floatbar, const XEvent* event) +{ + if (!floatbar || !floatbar->xfc || !event) + return FALSE; + + if (!floatbar->created) + return FALSE; + + switch (event->type) + { + case Expose: + if (event->xexpose.window == floatbar->handle) + xf_floatbar_event_expose(floatbar); + else + xf_floatbar_button_event_expose(floatbar, event->xexpose.window); + + break; + + case MotionNotify: + xf_floatbar_event_motionnotify(floatbar, &event->xmotion); + break; + + case ButtonPress: + if (event->xany.window == floatbar->handle) + xf_floatbar_event_buttonpress(floatbar, &event->xbutton); + else + xf_floatbar_button_event_buttonpress(floatbar, &event->xbutton); + + break; + + case ButtonRelease: + if (event->xany.window == floatbar->handle) + xf_floatbar_event_buttonrelease(floatbar, &event->xbutton); + else + xf_floatbar_button_event_buttonrelease(floatbar, &event->xbutton); + + break; + + case EnterNotify: + case FocusIn: + if (event->xany.window != floatbar->handle) + xf_floatbar_button_event_focusin(floatbar, &event->xany); + + break; + + case LeaveNotify: + case FocusOut: + if (event->xany.window == floatbar->handle) + xf_floatbar_event_focusout(floatbar); + else + xf_floatbar_button_event_focusout(floatbar, &event->xany); + + break; + + case ConfigureNotify: + if (event->xany.window == floatbar->handle) + xf_floatbar_button_update_positon(floatbar); + + break; + + case PropertyNotify: + if (event->xany.window == floatbar->handle) + xf_floatbar_button_update_positon(floatbar); + + break; + + default: + break; + } + + return floatbar->handle == event->xany.window; +} + +static void xf_floatbar_button_free(xfContext* xfc, xfFloatbarButton* button) +{ + if (!button) + return; + + if (button->handle) + { + WINPR_ASSERT(xfc); + WINPR_ASSERT(xfc->display); + LogDynAndXUnmapWindow(xfc->log, xfc->display, button->handle); + LogDynAndXDestroyWindow(xfc->log, xfc->display, button->handle); + } + + free(button); +} + +void xf_floatbar_free(xfFloatbar* floatbar) +{ + size_t size = 0; + xfContext* xfc = nullptr; + + if (!floatbar) + return; + + free(floatbar->title); + xfc = floatbar->xfc; + WINPR_ASSERT(xfc); + + size = ARRAYSIZE(floatbar->buttons); + + for (size_t i = 0; i < size; i++) + { + xf_floatbar_button_free(xfc, floatbar->buttons[i]); + floatbar->buttons[i] = nullptr; + } + + if (floatbar->handle) + { + WINPR_ASSERT(xfc->display); + LogDynAndXUnmapWindow(xfc->log, xfc->display, floatbar->handle); + LogDynAndXDestroyWindow(xfc->log, xfc->display, floatbar->handle); + } + + free(floatbar); +} + +BOOL xf_floatbar_is_locked(xfFloatbar* floatbar) +{ + if (!floatbar) + return FALSE; + return floatbar->mode != XF_FLOATBAR_MODE_NONE; +} + +BOOL xf_floatbar_is_window(xfFloatbar* floatbar, Window window) +{ + if (!floatbar) + return FALSE; + return floatbar->handle == window; +} + +BOOL xfc_is_floatbar_window(xfContext* xfc, Window window) +{ + if (!xfc || !xfc->window) + return FALSE; + return xf_floatbar_is_window(xfc->window->floatbar, window); +} diff --git a/third_party/FreeRDP/client/X11/xf_floatbar.h b/third_party/FreeRDP/client/X11/xf_floatbar.h new file mode 100644 index 0000000..41cac37 --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_floatbar.h @@ -0,0 +1,45 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Windows + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_X11_FLOATBAR_H +#define FREERDP_CLIENT_X11_FLOATBAR_H + +#include + +#include + +#include "xf_types.h" + +typedef struct xf_floatbar xfFloatbar; + +void xf_floatbar_free(xfFloatbar* floatbar); + +WINPR_ATTR_MALLOC(xf_floatbar_free, 1) +WINPR_ATTR_NODISCARD +xfFloatbar* xf_floatbar_new(xfContext* xfc, Window window, const char* title, DWORD flags); + +BOOL xf_floatbar_is_window(xfFloatbar* floatbar, Window window); +BOOL xf_floatbar_is_locked(xfFloatbar* floatbar); +BOOL xf_floatbar_event_process(xfFloatbar* floatbar, const XEvent* event); +BOOL xf_floatbar_check_event(xfFloatbar* floatbar, const XEvent* event); +BOOL xf_floatbar_toggle_fullscreen(xfFloatbar* floatbar, bool fullscreen); +BOOL xf_floatbar_hide_and_show(xfFloatbar* floatbar); +BOOL xf_floatbar_set_root_y(xfFloatbar* floatbar, int y); + +BOOL xfc_is_floatbar_window(xfContext* xfc, Window window); + +#endif /* FREERDP_CLIENT_X11_FLOATBAR_H */ diff --git a/third_party/FreeRDP/client/X11/xf_gfx.c b/third_party/FreeRDP/client/X11/xf_gfx.c new file mode 100644 index 0000000..ef2cf01 --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_gfx.c @@ -0,0 +1,537 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Graphics Pipeline + * + * Copyright 2014 Marc-Andre Moreau + * Copyright 2016 Armin Novak + * Copyright 2016 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 + +#include + +#include +#include + +#include +#include "xf_gfx.h" +#include "xf_rail.h" +#include "xf_utils.h" +#include "xf_window.h" + +#include + +#define TAG CLIENT_TAG("x11") + +static UINT xf_OutputUpdate(xfContext* xfc, xfGfxSurface* surface) +{ + UINT rc = ERROR_INTERNAL_ERROR; + UINT32 surfaceX = 0; + UINT32 surfaceY = 0; + RECTANGLE_16 surfaceRect = WINPR_C_ARRAY_INIT; + UINT32 nbRects = 0; + const RECTANGLE_16* rects = nullptr; + + WINPR_ASSERT(xfc); + WINPR_ASSERT(surface); + + rdpGdi* gdi = xfc->common.context.gdi; + WINPR_ASSERT(gdi); + + rdpSettings* settings = xfc->common.context.settings; + WINPR_ASSERT(settings); + + surfaceX = surface->gdi.outputOriginX; + surfaceY = surface->gdi.outputOriginY; + surfaceRect.left = 0; + surfaceRect.top = 0; + surfaceRect.right = WINPR_ASSERTING_INT_CAST(UINT16, surface->gdi.mappedWidth); + surfaceRect.bottom = WINPR_ASSERTING_INT_CAST(UINT16, surface->gdi.mappedHeight); + LogDynAndXSetClipMask(xfc->log, xfc->display, xfc->gc, None); + LogDynAndXSetFunction(xfc->log, xfc->display, xfc->gc, GXcopy); + LogDynAndXSetFillStyle(xfc->log, xfc->display, xfc->gc, FillSolid); + if (!region16_intersect_rect(&(surface->gdi.invalidRegion), &(surface->gdi.invalidRegion), + &surfaceRect)) + return ERROR_INTERNAL_ERROR; + + WINPR_ASSERT(surface->gdi.mappedWidth); + WINPR_ASSERT(surface->gdi.mappedHeight); + const double sx = 1.0 * surface->gdi.outputTargetWidth / (double)surface->gdi.mappedWidth; + const double sy = 1.0 * surface->gdi.outputTargetHeight / (double)surface->gdi.mappedHeight; + + if (!(rects = region16_rects(&surface->gdi.invalidRegion, &nbRects))) + return CHANNEL_RC_OK; + + xf_lock_x11(xfc); + for (UINT32 x = 0; x < nbRects; x++) + { + const RECTANGLE_16* rect = &rects[x]; + const UINT32 nXSrc = rect->left; + const UINT32 nYSrc = rect->top; + const UINT32 swidth = rect->right - nXSrc; + const UINT32 sheight = rect->bottom - nYSrc; + const UINT32 nXDst = (UINT32)lround(1.0 * surfaceX + nXSrc * sx); + const UINT32 nYDst = (UINT32)lround(1.0 * surfaceY + nYSrc * sy); + const UINT32 dwidth = (UINT32)lround(1.0 * swidth * sx); + const UINT32 dheight = (UINT32)lround(1.0 * sheight * sy); + + if (surface->stage) + { + if (!freerdp_image_scale(surface->stage, gdi->dstFormat, surface->stageScanline, nXSrc, + nYSrc, dwidth, dheight, surface->gdi.data, surface->gdi.format, + surface->gdi.scanline, nXSrc, nYSrc, swidth, sheight)) + goto fail; + } + + if (xfc->remote_app) + { + LogDynAndXPutImage(xfc->log, xfc->display, xfc->primary, xfc->gc, surface->image, + WINPR_ASSERTING_INT_CAST(int, nXSrc), + WINPR_ASSERTING_INT_CAST(int, nYSrc), + WINPR_ASSERTING_INT_CAST(int, nXDst), + WINPR_ASSERTING_INT_CAST(int, nYDst), dwidth, dheight); + xf_rail_paint_surface(xfc, surface->gdi.windowId, rect); + } + else +#ifdef WITH_XRENDER + if (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) || + freerdp_settings_get_bool(settings, FreeRDP_MultiTouchGestures)) + { + LogDynAndXPutImage(xfc->log, xfc->display, xfc->primary, xfc->gc, surface->image, + WINPR_ASSERTING_INT_CAST(int, nXSrc), + WINPR_ASSERTING_INT_CAST(int, nYSrc), + WINPR_ASSERTING_INT_CAST(int, nXDst), + WINPR_ASSERTING_INT_CAST(int, nYDst), dwidth, dheight); + xf_draw_screen(xfc, WINPR_ASSERTING_INT_CAST(int32_t, nXDst), + WINPR_ASSERTING_INT_CAST(int32_t, nYDst), + WINPR_ASSERTING_INT_CAST(int32_t, dwidth), + WINPR_ASSERTING_INT_CAST(int32_t, dheight)); + } + else +#endif + { + LogDynAndXPutImage(xfc->log, xfc->display, xfc->drawable, xfc->gc, surface->image, + WINPR_ASSERTING_INT_CAST(int, nXSrc), + WINPR_ASSERTING_INT_CAST(int, nYSrc), + WINPR_ASSERTING_INT_CAST(int, nXDst), + WINPR_ASSERTING_INT_CAST(int, nYDst), dwidth, dheight); + } + } + + rc = CHANNEL_RC_OK; +fail: + region16_clear(&surface->gdi.invalidRegion); + LogDynAndXSetClipMask(xfc->log, xfc->display, xfc->gc, None); + LogDynAndXSync(xfc->log, xfc->display, False); + xf_unlock_x11(xfc); + return rc; +} + +static UINT xf_WindowUpdate(RdpgfxClientContext* context, xfGfxSurface* surface) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(surface); + return IFCALLRESULT(CHANNEL_RC_OK, context->UpdateWindowFromSurface, context, &surface->gdi); +} + +static UINT xf_UpdateSurfaces(RdpgfxClientContext* context) +{ + UINT16 count = 0; + UINT status = CHANNEL_RC_OK; + UINT16* pSurfaceIds = nullptr; + rdpGdi* gdi = (rdpGdi*)context->custom; + xfContext* xfc = nullptr; + + if (!gdi) + return status; + + if (gdi->suppressOutput) + return CHANNEL_RC_OK; + + xfc = (xfContext*)gdi->context; + EnterCriticalSection(&context->mux); + status = context->GetSurfaceIds(context, &pSurfaceIds, &count); + if (status != CHANNEL_RC_OK) + goto fail; + + for (UINT32 index = 0; index < count; index++) + { + xfGfxSurface* surface = (xfGfxSurface*)context->GetSurfaceData(context, pSurfaceIds[index]); + + if (!surface) + continue; + + /* If UpdateSurfaceArea callback is available, the output has already been updated. */ + if (context->UpdateSurfaceArea) + { + if (surface->gdi.handleInUpdateSurfaceArea) + continue; + } + + if (surface->gdi.outputMapped) + status = xf_OutputUpdate(xfc, surface); + else if (surface->gdi.windowMapped) + status = xf_WindowUpdate(context, surface); + + if (status != CHANNEL_RC_OK) + break; + } + +fail: + free(pSurfaceIds); + LeaveCriticalSection(&context->mux); + return status; +} + +UINT xf_OutputExpose(xfContext* xfc, UINT32 x, UINT32 y, UINT32 width, UINT32 height) +{ + UINT16 count = 0; + UINT status = ERROR_INTERNAL_ERROR; + RECTANGLE_16 invalidRect = WINPR_C_ARRAY_INIT; + RECTANGLE_16 intersection = WINPR_C_ARRAY_INIT; + UINT16* pSurfaceIds = nullptr; + RdpgfxClientContext* context = nullptr; + + WINPR_ASSERT(xfc); + WINPR_ASSERT(xfc->common.context.gdi); + + context = xfc->common.context.gdi->gfx; + WINPR_ASSERT(context); + + invalidRect.left = WINPR_ASSERTING_INT_CAST(UINT16, x); + invalidRect.top = WINPR_ASSERTING_INT_CAST(UINT16, y); + invalidRect.right = WINPR_ASSERTING_INT_CAST(UINT16, x + width); + invalidRect.bottom = WINPR_ASSERTING_INT_CAST(UINT16, y + height); + status = context->GetSurfaceIds(context, &pSurfaceIds, &count); + + if (status != CHANNEL_RC_OK) + goto fail; + + if (!TryEnterCriticalSection(&context->mux)) + { + free(pSurfaceIds); + return CHANNEL_RC_OK; + } + for (UINT32 index = 0; index < count; index++) + { + RECTANGLE_16 surfaceRect = WINPR_C_ARRAY_INIT; + xfGfxSurface* surface = (xfGfxSurface*)context->GetSurfaceData(context, pSurfaceIds[index]); + + if (!surface || (!surface->gdi.outputMapped && !surface->gdi.windowMapped)) + continue; + + surfaceRect.left = WINPR_ASSERTING_INT_CAST(UINT16, surface->gdi.outputOriginX); + surfaceRect.top = WINPR_ASSERTING_INT_CAST(UINT16, surface->gdi.outputOriginY); + surfaceRect.right = WINPR_ASSERTING_INT_CAST(UINT16, surface->gdi.outputOriginX + + surface->gdi.outputTargetWidth); + surfaceRect.bottom = WINPR_ASSERTING_INT_CAST(UINT16, surface->gdi.outputOriginY + + surface->gdi.outputTargetHeight); + + if (rectangles_intersection(&invalidRect, &surfaceRect, &intersection)) + { + /* Invalid rects are specified relative to surface origin */ + intersection.left -= surfaceRect.left; + intersection.top -= surfaceRect.top; + intersection.right -= surfaceRect.left; + intersection.bottom -= surfaceRect.top; + if (!region16_union_rect(&surface->gdi.invalidRegion, &surface->gdi.invalidRegion, + &intersection)) + { + free(pSurfaceIds); + LeaveCriticalSection(&context->mux); + + goto fail; + } + } + } + + free(pSurfaceIds); + LeaveCriticalSection(&context->mux); + IFCALLRET(context->UpdateSurfaces, status, context); + + if (status != CHANNEL_RC_OK) + goto fail; + +fail: + return status; +} + +static UINT32 x11_pad_scanline(UINT32 scanline, UINT32 inPad) +{ + /* Ensure X11 alignment is met */ + if (inPad > 0) + { + const UINT32 align = inPad / 8; + const UINT32 pad = align - scanline % align; + + if (align != pad) + scanline += pad; + } + + /* 16 byte alignment is required for ASM optimized code */ + if (scanline % 16) + scanline += 16 - scanline % 16; + + return scanline; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_CreateSurface(RdpgfxClientContext* context, + const RDPGFX_CREATE_SURFACE_PDU* createSurface) +{ + UINT ret = CHANNEL_RC_NO_MEMORY; + size_t size = 0; + xfGfxSurface* surface = nullptr; + rdpGdi* gdi = (rdpGdi*)context->custom; + xfContext* xfc = (xfContext*)gdi->context; + surface = (xfGfxSurface*)calloc(1, sizeof(xfGfxSurface)); + + if (!surface) + return CHANNEL_RC_NO_MEMORY; + + surface->gdi.codecs = context->codecs; + + if (!surface->gdi.codecs) + { + WLog_ERR(TAG, "global GDI codecs aren't set"); + goto out_free; + } + + surface->gdi.surfaceId = createSurface->surfaceId; + surface->gdi.width = x11_pad_scanline(createSurface->width, 0); + surface->gdi.height = x11_pad_scanline(createSurface->height, 0); + surface->gdi.mappedWidth = createSurface->width; + surface->gdi.mappedHeight = createSurface->height; + surface->gdi.outputTargetWidth = createSurface->width; + surface->gdi.outputTargetHeight = createSurface->height; + + switch (createSurface->pixelFormat) + { + case GFX_PIXEL_FORMAT_ARGB_8888: + surface->gdi.format = PIXEL_FORMAT_BGRA32; + break; + + case GFX_PIXEL_FORMAT_XRGB_8888: + surface->gdi.format = PIXEL_FORMAT_BGRX32; + break; + + default: + WLog_ERR(TAG, "unknown pixelFormat 0x%" PRIx32 "", createSurface->pixelFormat); + ret = ERROR_INTERNAL_ERROR; + goto out_free; + } + + surface->gdi.scanline = surface->gdi.width * FreeRDPGetBytesPerPixel(surface->gdi.format); + surface->gdi.scanline = x11_pad_scanline(surface->gdi.scanline, + WINPR_ASSERTING_INT_CAST(uint32_t, xfc->scanline_pad)); + size = 1ull * surface->gdi.scanline * surface->gdi.height; + surface->gdi.data = (BYTE*)winpr_aligned_malloc(size, 16); + + if (!surface->gdi.data) + { + WLog_ERR(TAG, "unable to allocate GDI data"); + goto out_free; + } + + ZeroMemory(surface->gdi.data, size); + + if (FreeRDPAreColorFormatsEqualNoAlpha(gdi->dstFormat, surface->gdi.format)) + { + WINPR_ASSERT(xfc->depth != 0); + surface->image = LogDynAndXCreateImage( + xfc->log, xfc->display, xfc->visual, WINPR_ASSERTING_INT_CAST(uint32_t, xfc->depth), + ZPixmap, 0, (char*)surface->gdi.data, surface->gdi.mappedWidth, + surface->gdi.mappedHeight, xfc->scanline_pad, + WINPR_ASSERTING_INT_CAST(int, surface->gdi.scanline)); + } + else + { + UINT32 width = surface->gdi.width; + UINT32 bytes = FreeRDPGetBytesPerPixel(gdi->dstFormat); + surface->stageScanline = width * bytes; + surface->stageScanline = x11_pad_scanline( + surface->stageScanline, WINPR_ASSERTING_INT_CAST(uint32_t, xfc->scanline_pad)); + size = 1ull * surface->stageScanline * surface->gdi.height; + surface->stage = (BYTE*)winpr_aligned_malloc(size, 16); + + if (!surface->stage) + { + WLog_ERR(TAG, "unable to allocate stage buffer"); + goto out_free_gdidata; + } + + ZeroMemory(surface->stage, size); + WINPR_ASSERT(xfc->depth != 0); + surface->image = LogDynAndXCreateImage( + xfc->log, xfc->display, xfc->visual, WINPR_ASSERTING_INT_CAST(uint32_t, xfc->depth), + ZPixmap, 0, (char*)surface->stage, surface->gdi.mappedWidth, surface->gdi.mappedHeight, + xfc->scanline_pad, WINPR_ASSERTING_INT_CAST(int, surface->stageScanline)); + } + + if (!surface->image) + { + WLog_ERR(TAG, "an error occurred when creating the XImage"); + goto error_surface_image; + } + + surface->image->byte_order = LSBFirst; + surface->image->bitmap_bit_order = LSBFirst; + + region16_init(&surface->gdi.invalidRegion); + + if (context->SetSurfaceData(context, surface->gdi.surfaceId, (void*)surface) != CHANNEL_RC_OK) + { + WLog_ERR(TAG, "an error occurred during SetSurfaceData"); + goto error_set_surface_data; + } + + return CHANNEL_RC_OK; +error_set_surface_data: + surface->image->data = nullptr; + XDestroyImage(surface->image); +error_surface_image: + winpr_aligned_free(surface->stage); +out_free_gdidata: + winpr_aligned_free(surface->gdi.data); +out_free: + free(surface); + return ret; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_DeleteSurface(RdpgfxClientContext* context, + const RDPGFX_DELETE_SURFACE_PDU* deleteSurface) +{ + rdpCodecs* codecs = nullptr; + + UINT status = 0; + EnterCriticalSection(&context->mux); + xfGfxSurface* surface = + (xfGfxSurface*)context->GetSurfaceData(context, deleteSurface->surfaceId); + + if (surface) + { + if (surface->gdi.windowMapped) + { + status = IFCALLRESULT(CHANNEL_RC_OK, context->UnmapWindowForSurface, context, + surface->gdi.windowId); + if (status != CHANNEL_RC_OK) + goto fail; + } + +#ifdef WITH_GFX_H264 + h264_context_free(surface->gdi.h264); +#endif + surface->image->data = nullptr; + XDestroyImage(surface->image); + winpr_aligned_free(surface->gdi.data); + winpr_aligned_free(surface->stage); + region16_uninit(&surface->gdi.invalidRegion); + codecs = surface->gdi.codecs; + free(surface); + } + + status = context->SetSurfaceData(context, deleteSurface->surfaceId, nullptr); + + if (codecs && codecs->progressive) + progressive_delete_surface_context(codecs->progressive, deleteSurface->surfaceId); + +fail: + LeaveCriticalSection(&context->mux); + return status; +} + +static UINT xf_UnmapWindowForSurface(RdpgfxClientContext* context, UINT64 windowID) +{ + WINPR_ASSERT(context); + rdpGdi* gdi = (rdpGdi*)context->custom; + WINPR_ASSERT(gdi); + + xfContext* xfc = (xfContext*)gdi->context; + WINPR_ASSERT(gdi->context); + + if (freerdp_settings_get_bool(gdi->context->settings, FreeRDP_RemoteApplicationMode)) + { + xfAppWindow* appWindow = xf_rail_get_window(xfc, windowID, FALSE); + if (appWindow) + xf_AppWindowDestroyImage(appWindow); + xf_rail_return_window(appWindow, FALSE); + } + + WLog_WARN(TAG, "function not implemented"); + return CHANNEL_RC_OK; +} + +static UINT xf_UpdateWindowFromSurface(RdpgfxClientContext* context, gdiGfxSurface* surface) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(surface); + + rdpGdi* gdi = (rdpGdi*)context->custom; + WINPR_ASSERT(gdi); + + xfContext* xfc = (xfContext*)gdi->context; + WINPR_ASSERT(gdi->context); + + if (freerdp_settings_get_bool(gdi->context->settings, FreeRDP_RemoteApplicationMode)) + return xf_AppUpdateWindowFromSurface(xfc, surface); + + WLog_WARN(TAG, "function not implemented"); + return CHANNEL_RC_OK; +} + +void xf_graphics_pipeline_init(xfContext* xfc, RdpgfxClientContext* gfx) +{ + rdpGdi* gdi = nullptr; + const rdpSettings* settings = nullptr; + WINPR_ASSERT(xfc); + WINPR_ASSERT(gfx); + + settings = xfc->common.context.settings; + WINPR_ASSERT(settings); + + gdi = xfc->common.context.gdi; + + gdi_graphics_pipeline_init(gdi, gfx); + + if (!freerdp_settings_get_bool(settings, FreeRDP_SoftwareGdi)) + { + gfx->UpdateSurfaces = xf_UpdateSurfaces; + gfx->CreateSurface = xf_CreateSurface; + gfx->DeleteSurface = xf_DeleteSurface; + } + + gfx->UpdateWindowFromSurface = xf_UpdateWindowFromSurface; + gfx->UnmapWindowForSurface = xf_UnmapWindowForSurface; +} + +void xf_graphics_pipeline_uninit(xfContext* xfc, RdpgfxClientContext* gfx) +{ + rdpGdi* gdi = nullptr; + + WINPR_ASSERT(xfc); + + gdi = xfc->common.context.gdi; + gdi_graphics_pipeline_uninit(gdi, gfx); +} diff --git a/third_party/FreeRDP/client/X11/xf_gfx.h b/third_party/FreeRDP/client/X11/xf_gfx.h new file mode 100644 index 0000000..934e85a --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_gfx.h @@ -0,0 +1,45 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Graphics Pipeline + * + * Copyright 2014 Marc-Andre Moreau + * Copyright 2016 Thincast Technologies GmbH + * Copyright 2016 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_X11_GFX_H +#define FREERDP_CLIENT_X11_GFX_H + +#include "xf_client.h" +#include "xfreerdp.h" + +#include + +struct xf_gfx_surface +{ + gdiGfxSurface gdi; + BYTE* stage; + UINT32 stageScanline; + XImage* image; +}; +typedef struct xf_gfx_surface xfGfxSurface; + +UINT xf_OutputExpose(xfContext* xfc, UINT32 x, UINT32 y, UINT32 width, UINT32 height); + +void xf_graphics_pipeline_init(xfContext* xfc, RdpgfxClientContext* gfx); + +void xf_graphics_pipeline_uninit(xfContext* xfc, RdpgfxClientContext* gfx); + +#endif /* FREERDP_CLIENT_X11_GFX_H */ diff --git a/third_party/FreeRDP/client/X11/xf_graphics.c b/third_party/FreeRDP/client/X11/xf_graphics.c new file mode 100644 index 0000000..7be34d8 --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_graphics.c @@ -0,0 +1,532 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Graphical Objects + * + * Copyright 2011 Marc-Andre Moreau + * Copyright 2016 Thincast Technologies GmbH + * Copyright 2016 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +#ifdef WITH_XCURSOR +#include +#endif + +#include +#include + +#include +#include + +#include +#include + +#include "xf_graphics.h" +#include "xf_event.h" +#include "xf_utils.h" + +#include +#define TAG CLIENT_TAG("x11") + +static BOOL xf_Pointer_Set(rdpContext* context, rdpPointer* pointer); + +BOOL xf_decode_color(xfContext* xfc, const UINT32 srcColor, XColor* color) +{ + UINT32 SrcFormat = 0; + BYTE r = 0; + BYTE g = 0; + BYTE b = 0; + BYTE a = 0; + + if (!xfc || !color) + return FALSE; + + rdpGdi* gdi = xfc->common.context.gdi; + + if (!gdi) + return FALSE; + + rdpSettings* settings = xfc->common.context.settings; + + if (!settings) + return FALSE; + + switch (freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth)) + { + case 32: + case 24: + SrcFormat = PIXEL_FORMAT_BGR24; + break; + + case 16: + SrcFormat = PIXEL_FORMAT_RGB16; + break; + + case 15: + SrcFormat = PIXEL_FORMAT_RGB15; + break; + + case 8: + SrcFormat = PIXEL_FORMAT_RGB8; + break; + + default: + return FALSE; + } + + FreeRDPSplitColor(srcColor, SrcFormat, &r, &g, &b, &a, &gdi->palette); + color->blue = (unsigned short)(b << 8); + color->green = (unsigned short)(g << 8); + color->red = (unsigned short)(r << 8); + color->flags = DoRed | DoGreen | DoBlue; + + return (XAllocColor(xfc->display, xfc->colormap, color) != 0); +} + +static BOOL xf_Pointer_GetCursorForCurrentScale(rdpContext* context, rdpPointer* pointer, + Cursor* cursor) +{ +#if defined(WITH_XCURSOR) && defined(WITH_XRENDER) + xfContext* xfc = (xfContext*)context; + xfPointer* xpointer = (xfPointer*)pointer; + XcursorImage ci = WINPR_C_ARRAY_INIT; + int cursorIndex = -1; + + if (!context || !pointer || !context->gdi) + return FALSE; + + rdpSettings* settings = xfc->common.context.settings; + + if (!settings) + return FALSE; + + const double dw = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + const double xscale = + (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) ? 1.0 * xfc->scaledWidth / dw + : 1); + const double dh = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + const double yscale = + (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) ? 1.0 * xfc->scaledHeight / dh + : 1); + const UINT32 xTargetSize = MAX(1, (UINT32)lround(1.0 * pointer->width * xscale)); + const UINT32 yTargetSize = MAX(1, (UINT32)lround(1.0 * pointer->height * yscale)); + + WLog_DBG(TAG, "scaled: %" PRId32 "x%" PRId32 ", desktop: %" PRIu32 "x%" PRIu32, + xfc->scaledWidth, xfc->scaledHeight, + freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth), + freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight)); + for (UINT32 i = 0; i < xpointer->nCursors; i++) + { + if ((xpointer->cursorWidths[i] == xTargetSize) && + (xpointer->cursorHeights[i] == yTargetSize)) + { + cursorIndex = WINPR_ASSERTING_INT_CAST(int, i); + } + } + + if (cursorIndex == -1) + { + UINT32 CursorFormat = 0; + xf_lock_x11(xfc); + + if (!xfc->invert) + CursorFormat = (!xfc->big_endian) ? PIXEL_FORMAT_RGBA32 : PIXEL_FORMAT_ABGR32; + else + CursorFormat = (!xfc->big_endian) ? PIXEL_FORMAT_BGRA32 : PIXEL_FORMAT_ARGB32; + + if (xpointer->nCursors == xpointer->mCursors) + { + void* tmp2 = nullptr; + xpointer->mCursors = (xpointer->mCursors == 0 ? 1 : xpointer->mCursors * 2); + + tmp2 = realloc(xpointer->cursorWidths, sizeof(UINT32) * xpointer->mCursors); + if (!tmp2) + { + xf_unlock_x11(xfc); + return FALSE; + } + xpointer->cursorWidths = tmp2; + + tmp2 = realloc(xpointer->cursorHeights, sizeof(UINT32) * xpointer->mCursors); + if (!tmp2) + { + xf_unlock_x11(xfc); + return FALSE; + } + xpointer->cursorHeights = (UINT32*)tmp2; + + tmp2 = realloc(xpointer->cursors, sizeof(Cursor) * xpointer->mCursors); + if (!tmp2) + { + xf_unlock_x11(xfc); + return FALSE; + } + xpointer->cursors = (Cursor*)tmp2; + } + + ci.version = XCURSOR_IMAGE_VERSION; + ci.size = sizeof(ci); + ci.width = xTargetSize; + ci.height = yTargetSize; + ci.xhot = (XcursorDim)lround(1.0 * pointer->xPos * xscale); + ci.yhot = (XcursorDim)lround(1.0 * pointer->yPos * yscale); + const size_t size = 1ull * ci.height * ci.width * FreeRDPGetBytesPerPixel(CursorFormat); + + void* tmp = winpr_aligned_malloc(size, 16); + if (!tmp) + { + xf_unlock_x11(xfc); + return FALSE; + } + ci.pixels = (XcursorPixel*)tmp; + + const double xs = fabs(fabs(xscale) - 1.0); + const double ys = fabs(fabs(yscale) - 1.0); + + WLog_DBG(TAG, + "cursorIndex %" PRId32 " scaling pointer %" PRIu32 "x%" PRIu32 " --> %" PRIu32 + "x%" PRIu32 " [%lfx%lf]", + cursorIndex, pointer->width, pointer->height, ci.width, ci.height, xscale, yscale); + if ((xs > DBL_EPSILON) || (ys > DBL_EPSILON)) + { + if (!freerdp_image_scale((BYTE*)ci.pixels, CursorFormat, 0, 0, 0, ci.width, ci.height, + (BYTE*)xpointer->cursorPixels, CursorFormat, 0, 0, 0, + pointer->width, pointer->height)) + { + winpr_aligned_free(tmp); + xf_unlock_x11(xfc); + return FALSE; + } + } + else + { + ci.pixels = xpointer->cursorPixels; + } + + const size_t idx = xpointer->nCursors; + xpointer->cursorWidths[idx] = ci.width; + xpointer->cursorHeights[idx] = ci.height; + xpointer->cursors[idx] = XcursorImageLoadCursor(xfc->display, &ci); + cursorIndex = WINPR_ASSERTING_INT_CAST(int, idx); + xpointer->nCursors += 1; + + winpr_aligned_free(tmp); + + xf_unlock_x11(xfc); + } + else + { + WLog_DBG(TAG, "using cached cursor %" PRId32, cursorIndex); + } + + cursor[0] = xpointer->cursors[cursorIndex]; +#endif + return TRUE; +} + +/* Pointer Class */ +static Window xf_Pointer_get_window(xfContext* xfc) +{ + if (!xfc) + { + WLog_WARN(TAG, "xf_Pointer: Invalid context"); + return 0; + } + if (xfc->remote_app) + { + Window w = 0; + xf_AppWindowsLock(xfc); + if (!xfc->appWindow) + WLog_WARN(TAG, "xf_Pointer: Invalid appWindow"); + else + w = xfc->appWindow->handle; + xf_AppWindowsUnlock(xfc); + return w; + } + else + { + if (!xfc->window) + { + WLog_WARN(TAG, "xf_Pointer: Invalid window"); + return 0; + } + return xfc->window->handle; + } +} + +BOOL xf_pointer_update_scale(xfContext* xfc) +{ + xfPointer* pointer = nullptr; + WINPR_ASSERT(xfc); + + pointer = xfc->pointer; + if (!pointer) + return TRUE; + + return xf_Pointer_Set(&xfc->common.context, &xfc->pointer->pointer); +} + +static BOOL xf_Pointer_New(rdpContext* context, rdpPointer* pointer) +{ + BOOL rc = FALSE; + +#ifdef WITH_XCURSOR + UINT32 CursorFormat = 0; + xfContext* xfc = (xfContext*)context; + xfPointer* xpointer = (xfPointer*)pointer; + + if (!context || !pointer || !context->gdi) + goto fail; + + if (!xfc->invert) + CursorFormat = (!xfc->big_endian) ? PIXEL_FORMAT_RGBA32 : PIXEL_FORMAT_ABGR32; + else + CursorFormat = (!xfc->big_endian) ? PIXEL_FORMAT_BGRA32 : PIXEL_FORMAT_ARGB32; + + xpointer->nCursors = 0; + xpointer->mCursors = 0; + + { + const size_t size = + 1ull * pointer->height * pointer->width * FreeRDPGetBytesPerPixel(CursorFormat); + + xpointer->cursorPixels = (XcursorPixel*)winpr_aligned_malloc(size, 16); + if (!xpointer->cursorPixels) + goto fail; + } + + if (!freerdp_image_copy_from_pointer_data( + (BYTE*)xpointer->cursorPixels, CursorFormat, 0, 0, 0, pointer->width, pointer->height, + pointer->xorMaskData, pointer->lengthXorMask, pointer->andMaskData, + pointer->lengthAndMask, pointer->xorBpp, &context->gdi->palette)) + goto fail; + +#endif + + rc = TRUE; + +fail: + WLog_DBG(TAG, "%p", WINPR_CXX_COMPAT_CAST(const void*, rc ? pointer : nullptr)); + return rc; +} + +static void xf_Pointer_Free(rdpContext* context, rdpPointer* pointer) +{ + WLog_DBG(TAG, "%p", WINPR_CXX_COMPAT_CAST(const void*, pointer)); + +#ifdef WITH_XCURSOR + xfContext* xfc = (xfContext*)context; + xfPointer* xpointer = (xfPointer*)pointer; + + xf_lock_x11(xfc); + + winpr_aligned_free(xpointer->cursorPixels); + free(xpointer->cursorWidths); + free(xpointer->cursorHeights); + + for (UINT32 i = 0; i < xpointer->nCursors; i++) + { + XFreeCursor(xfc->display, xpointer->cursors[i]); + } + + free(xpointer->cursors); + xpointer->nCursors = 0; + xpointer->mCursors = 0; + + xf_unlock_x11(xfc); +#endif +} + +static BOOL xf_Pointer_Set(rdpContext* context, rdpPointer* pointer) +{ + WLog_DBG(TAG, "%p", WINPR_CXX_COMPAT_CAST(const void*, pointer)); +#ifdef WITH_XCURSOR + xfContext* xfc = (xfContext*)context; + Window handle = xf_Pointer_get_window(xfc); + + WINPR_ASSERT(xfc); + WINPR_ASSERT(pointer); + + xfc->pointer = (xfPointer*)pointer; + + /* in RemoteApp mode, window can be null if none has had focus */ + + if (handle) + { + if (!xf_Pointer_GetCursorForCurrentScale(context, pointer, &(xfc->pointer->cursor))) + return FALSE; + xf_lock_x11(xfc); + XDefineCursor(xfc->display, handle, xfc->pointer->cursor); + xf_unlock_x11(xfc); + } + else + { + WLog_WARN(TAG, "handle=%lu", handle); + } + xfc->isCursorHidden = false; +#endif + return TRUE; +} + +static BOOL xf_Pointer_SetNull(rdpContext* context) +{ + WLog_DBG(TAG, "called"); +#ifdef WITH_XCURSOR + xfContext* xfc = (xfContext*)context; + static Cursor nullcursor = None; + Window handle = xf_Pointer_get_window(xfc); + xf_lock_x11(xfc); + + if (nullcursor == None) + { + XcursorImage ci = WINPR_C_ARRAY_INIT; + XcursorPixel xp = 0; + + ci.version = XCURSOR_IMAGE_VERSION; + ci.size = sizeof(ci); + ci.width = ci.height = 1; + ci.xhot = ci.yhot = 0; + ci.pixels = &xp; + nullcursor = XcursorImageLoadCursor(xfc->display, &ci); + } + + xfc->pointer = nullptr; + + if ((handle) && (nullcursor != None)) + XDefineCursor(xfc->display, handle, nullcursor); + + xf_unlock_x11(xfc); + xfc->isCursorHidden = true; +#endif + return TRUE; +} + +static BOOL xf_Pointer_SetDefault(rdpContext* context) +{ + WLog_DBG(TAG, "called"); +#ifdef WITH_XCURSOR + xfContext* xfc = (xfContext*)context; + Window handle = xf_Pointer_get_window(xfc); + xf_lock_x11(xfc); + xfc->pointer = nullptr; + + if (handle) + XUndefineCursor(xfc->display, handle); + + xf_unlock_x11(xfc); + xfc->isCursorHidden = false; +#endif + return TRUE; +} + +static BOOL xf_Pointer_SetPosition(rdpContext* context, UINT32 x, UINT32 y) +{ + xfContext* xfc = (xfContext*)context; + XWindowAttributes current = WINPR_C_ARRAY_INIT; + XSetWindowAttributes tmp = WINPR_C_ARRAY_INIT; + BOOL ret = FALSE; + Status rc = 0; + Window handle = xf_Pointer_get_window(xfc); + + if (!handle) + { + WLog_WARN(TAG, "focus %d, handle%lu", xfc->focused, handle); + return TRUE; + } + + WLog_DBG(TAG, "%" PRIu32 "x%" PRIu32, x, y); + if (!xfc->focused) + return TRUE; + + xf_adjust_coordinates_to_screen(xfc, &x, &y); + + xf_lock_x11(xfc); + + rc = XGetWindowAttributes(xfc->display, handle, ¤t); + if (rc == 0) + { + WLog_WARN(TAG, "XGetWindowAttributes==%d", rc); + goto out; + } + + tmp.event_mask = (current.your_event_mask & ~(PointerMotionMask)); + + rc = LogDynAndXChangeWindowAttributes(xfc->log, xfc->display, handle, CWEventMask, &tmp); + if (rc == 0) + goto out; + + rc = XWarpPointer(xfc->display, handle, handle, 0, 0, 0, 0, WINPR_ASSERTING_INT_CAST(int, x), + WINPR_ASSERTING_INT_CAST(int, y)); + if (rc == 0) + WLog_WARN(TAG, "XWarpPointer==%d", rc); + tmp.event_mask = current.your_event_mask; + LogDynAndXChangeWindowAttributes(xfc->log, xfc->display, handle, CWEventMask, &tmp); + ret = TRUE; +out: + xf_unlock_x11(xfc); + return ret; +} + +/* Graphics Module */ +BOOL xf_register_pointer(rdpGraphics* graphics) +{ + rdpPointer pointer = WINPR_C_ARRAY_INIT; + + pointer.size = sizeof(xfPointer); + pointer.New = xf_Pointer_New; + pointer.Free = xf_Pointer_Free; + pointer.Set = xf_Pointer_Set; + pointer.SetNull = xf_Pointer_SetNull; + pointer.SetDefault = xf_Pointer_SetDefault; + pointer.SetPosition = xf_Pointer_SetPosition; + graphics_register_pointer(graphics, &pointer); + return TRUE; +} + +UINT32 xf_get_local_color_format(xfContext* xfc, BOOL aligned) +{ + UINT32 DstFormat = 0; + BOOL invert = FALSE; + + if (!xfc) + return 0; + + invert = xfc->invert; + + WINPR_ASSERT(xfc->depth != 0); + if (xfc->depth == 32) + DstFormat = (!invert) ? PIXEL_FORMAT_RGBA32 : PIXEL_FORMAT_BGRA32; + else if (xfc->depth == 30) + DstFormat = (!invert) ? PIXEL_FORMAT_RGBX32_DEPTH30 : PIXEL_FORMAT_BGRX32_DEPTH30; + else if (xfc->depth == 24) + { + if (aligned) + DstFormat = (!invert) ? PIXEL_FORMAT_RGBX32 : PIXEL_FORMAT_BGRX32; + else + DstFormat = (!invert) ? PIXEL_FORMAT_RGB24 : PIXEL_FORMAT_BGR24; + } + else if (xfc->depth == 16) + DstFormat = PIXEL_FORMAT_RGB16; + else if (xfc->depth == 15) + DstFormat = PIXEL_FORMAT_RGB15; + else + DstFormat = (!invert) ? PIXEL_FORMAT_RGBX32 : PIXEL_FORMAT_BGRX32; + + return DstFormat; +} diff --git a/third_party/FreeRDP/client/X11/xf_graphics.h b/third_party/FreeRDP/client/X11/xf_graphics.h new file mode 100644 index 0000000..a91e325 --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_graphics.h @@ -0,0 +1,33 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Graphical Objects + * + * Copyright 2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_X11_GRAPHICS_H +#define FREERDP_CLIENT_X11_GRAPHICS_H + +#include "xf_client.h" +#include "xfreerdp.h" + +BOOL xf_register_pointer(rdpGraphics* graphics); + +BOOL xf_decode_color(xfContext* xfc, UINT32 srcColor, XColor* color); +UINT32 xf_get_local_color_format(xfContext* xfc, BOOL aligned); + +BOOL xf_pointer_update_scale(xfContext* xfc); + +#endif /* FREERDP_CLIENT_X11_GRAPHICS_H */ diff --git a/third_party/FreeRDP/client/X11/xf_input.c b/third_party/FreeRDP/client/X11/xf_input.c new file mode 100644 index 0000000..dba6e0f --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_input.c @@ -0,0 +1,920 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Input + * + * Copyright 2013 Corey Clayton + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +#ifdef WITH_XCURSOR +#include +#endif + +#ifdef WITH_XI +#include +#endif + +#include +#include +#include + +#include "xf_event.h" +#include "xf_input.h" +#include "xf_utils.h" + +#include +#include +#include +#define TAG CLIENT_TAG("x11") + +#ifdef WITH_XI + +#define PAN_THRESHOLD 50 +#define ZOOM_THRESHOLD 10 + +#define MIN_FINGER_DIST 5 + +static int xf_input_event(xfContext* xfc, WINPR_ATTR_UNUSED const XEvent* xevent, + XIDeviceEvent* event, int evtype); + +#ifdef DEBUG_XINPUT +static const char* xf_input_get_class_string(int class) +{ + if (class == XIKeyClass) + return "XIKeyClass"; + else if (class == XIButtonClass) + return "XIButtonClass"; + else if (class == XIValuatorClass) + return "XIValuatorClass"; + else if (class == XIScrollClass) + return "XIScrollClass"; + else if (class == XITouchClass) + return "XITouchClass"; + + return "XIUnknownClass"; +} +#endif + +static BOOL register_input_events(xfContext* xfc, Window window) +{ +#define MAX_NR_MASKS 64 + int ndevices = 0; + int nmasks = 0; + XIEventMask evmasks[MAX_NR_MASKS] = WINPR_C_ARRAY_INIT; + BYTE masks[MAX_NR_MASKS][XIMaskLen(XI_LASTEVENT)] = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(xfc); + + rdpSettings* settings = xfc->common.context.settings; + WINPR_ASSERT(settings); + + XIDeviceInfo* info = XIQueryDevice(xfc->display, XIAllDevices, &ndevices); + + for (int i = 0; i < MIN(ndevices, MAX_NR_MASKS); i++) + { + BOOL used = FALSE; + XIDeviceInfo* dev = &info[i]; + + evmasks[nmasks].mask = masks[nmasks]; + evmasks[nmasks].mask_len = sizeof(masks[0]); + evmasks[nmasks].deviceid = dev->deviceid; + + /* Ignore virtual core pointer */ + if (strcmp(dev->name, "Virtual core pointer") == 0) + continue; + + for (int j = 0; j < dev->num_classes; j++) + { + const XIAnyClassInfo* c_class = dev->classes[j]; + + switch (c_class->type) + { + case XITouchClass: + if (freerdp_settings_get_bool(settings, FreeRDP_MultiTouchInput)) + { + const XITouchClassInfo* t = (const XITouchClassInfo*)c_class; + if (t->mode == XIDirectTouch) + { + WLog_DBG( + TAG, + "%s %s touch device (id: %d, mode: %d), supporting %d touches.", + dev->name, (t->mode == XIDirectTouch) ? "direct" : "dependent", + dev->deviceid, t->mode, t->num_touches); + XISetMask(masks[nmasks], XI_TouchBegin); + XISetMask(masks[nmasks], XI_TouchUpdate); + XISetMask(masks[nmasks], XI_TouchEnd); + } + } + break; + case XIButtonClass: + { + const XIButtonClassInfo* t = (const XIButtonClassInfo*)c_class; + WLog_DBG(TAG, "%s button device (id: %d, mode: %d)", dev->name, dev->deviceid, + t->num_buttons); + XISetMask(masks[nmasks], XI_ButtonPress); + XISetMask(masks[nmasks], XI_ButtonRelease); + XISetMask(masks[nmasks], XI_Motion); + used = TRUE; + break; + } + case XIValuatorClass: + { + static wLog* log = nullptr; + if (!log) + log = WLog_Get(TAG); + + const XIValuatorClassInfo* t = (const XIValuatorClassInfo*)c_class; + char* name = + t->label ? Safe_XGetAtomName(log, xfc->display, t->label) : nullptr; + + WLog_Print(log, WLOG_DEBUG, + "%s device (id: %d) valuator %d label %s range %f - %f", dev->name, + dev->deviceid, t->number, name ? name : "None", t->min, t->max); + free(name); + + if (t->number == 2) + { + double max_pressure = t->max; + + char devName[200] = WINPR_C_ARRAY_INIT; + strncpy(devName, dev->name, ARRAYSIZE(devName) - 1); + CharLowerBuffA(devName, ARRAYSIZE(devName)); + + if (strstr(devName, "eraser") != nullptr) + { + if (freerdp_client_handle_pen(&xfc->common, + FREERDP_PEN_REGISTER | + FREERDP_PEN_IS_INVERTED | + FREERDP_PEN_HAS_PRESSURE, + dev->deviceid, max_pressure)) + WLog_DBG(TAG, "registered eraser"); + } + else if (strstr(devName, "stylus") != nullptr || + strstr(devName, "pen") != nullptr) + { + if (freerdp_client_handle_pen( + &xfc->common, FREERDP_PEN_REGISTER | FREERDP_PEN_HAS_PRESSURE, + dev->deviceid, max_pressure)) + WLog_DBG(TAG, "registered pen"); + } + } + break; + } + default: + break; + } + } + if (used) + nmasks++; + } + + XIFreeDeviceInfo(info); + + if (nmasks > 0) + { + Status xstatus = XISelectEvents(xfc->display, window, evmasks, nmasks); + if (xstatus != 0) + WLog_WARN(TAG, "XISelectEvents returned %d", xstatus); + } + + return TRUE; +} + +static BOOL register_raw_events(xfContext* xfc, Window window) +{ + XIEventMask mask; + unsigned char mask_bytes[XIMaskLen(XI_LASTEVENT)] = WINPR_C_ARRAY_INIT; + rdpSettings* settings = nullptr; + + WINPR_ASSERT(xfc); + + settings = xfc->common.context.settings; + WINPR_ASSERT(settings); + + if (freerdp_settings_get_bool(settings, FreeRDP_MouseUseRelativeMove)) + { + XISetMask(mask_bytes, XI_RawMotion); + XISetMask(mask_bytes, XI_RawButtonPress); + XISetMask(mask_bytes, XI_RawButtonRelease); + + mask.deviceid = XIAllMasterDevices; + mask.mask_len = sizeof(mask_bytes); + mask.mask = mask_bytes; + + XISelectEvents(xfc->display, window, &mask, 1); + } + + return TRUE; +} + +static BOOL register_device_events(xfContext* xfc, Window window) +{ + XIEventMask mask = WINPR_C_ARRAY_INIT; + unsigned char mask_bytes[XIMaskLen(XI_LASTEVENT)] = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(xfc); + + XISetMask(mask_bytes, XI_DeviceChanged); + XISetMask(mask_bytes, XI_HierarchyChanged); + + mask.deviceid = XIAllDevices; + mask.mask_len = sizeof(mask_bytes); + mask.mask = mask_bytes; + + XISelectEvents(xfc->display, window, &mask, 1); + + return TRUE; +} + +int xf_input_init(xfContext* xfc, Window window) +{ + int major = XI_2_Major; + int minor = XI_2_Minor; + int opcode = 0; + int event = 0; + int error = 0; + + WINPR_ASSERT(xfc); + + xfc->firstDist = -1.0; + xfc->z_vector = 0; + xfc->px_vector = 0; + xfc->py_vector = 0; + xfc->active_contacts = 0; + + if (!XQueryExtension(xfc->display, "XInputExtension", &opcode, &event, &error)) + { + WLog_WARN(TAG, "XInput extension not available."); + return -1; + } + + xfc->XInputOpcode = opcode; + XIQueryVersion(xfc->display, &major, &minor); + + if ((major < XI_2_Major) || ((major == XI_2_Major) && (minor < 2))) + { + WLog_WARN(TAG, "Server does not support XI 2.2"); + return -1; + } + else + { + int scr = DefaultScreen(xfc->display); + Window root = RootWindow(xfc->display, scr); + + if (!register_raw_events(xfc, root)) + return -1; + if (!register_input_events(xfc, window)) + return -1; + if (!register_device_events(xfc, window)) + return -1; + } + + return 0; +} + +static BOOL xf_input_is_duplicate(xfContext* xfc, const XGenericEventCookie* cookie) +{ + const XIDeviceEvent* event = nullptr; + + WINPR_ASSERT(xfc); + WINPR_ASSERT(cookie); + + event = cookie->data; + WINPR_ASSERT(event); + + return ((xfc->lastEvent.time == event->time) && (xfc->lastEvType == cookie->evtype) && + (xfc->lastEvent.detail == event->detail) && + (fabs(xfc->lastEvent.event_x - event->event_x) < DBL_EPSILON) && + (fabs(xfc->lastEvent.event_y - event->event_y) < DBL_EPSILON)); +} + +static void xf_input_save_last_event(xfContext* xfc, const XGenericEventCookie* cookie) +{ + const XIDeviceEvent* event = nullptr; + + WINPR_ASSERT(xfc); + WINPR_ASSERT(cookie); + + event = cookie->data; + WINPR_ASSERT(event); + + xfc->lastEvType = cookie->evtype; + xfc->lastEvent.time = event->time; + xfc->lastEvent.detail = event->detail; + xfc->lastEvent.event_x = event->event_x; + xfc->lastEvent.event_y = event->event_y; +} + +static void xf_input_detect_pan(xfContext* xfc) +{ + WINPR_ASSERT(xfc); + rdpContext* ctx = &xfc->common.context; + WINPR_ASSERT(ctx); + + if (xfc->active_contacts != 2) + { + return; + } + + const double dx[] = { xfc->contacts[0].pos_x - xfc->contacts[0].last_x, + xfc->contacts[1].pos_x - xfc->contacts[1].last_x }; + const double dy[] = { xfc->contacts[0].pos_y - xfc->contacts[0].last_y, + xfc->contacts[1].pos_y - xfc->contacts[1].last_y }; + const double px = fabs(dx[0]) < fabs(dx[1]) ? dx[0] : dx[1]; + const double py = fabs(dy[0]) < fabs(dy[1]) ? dy[0] : dy[1]; + xfc->px_vector += px; + xfc->py_vector += py; + const double dist_x = fabs(xfc->contacts[0].pos_x - xfc->contacts[1].pos_x); + const double dist_y = fabs(xfc->contacts[0].pos_y - xfc->contacts[1].pos_y); + + if (dist_y > MIN_FINGER_DIST) + { + if (xfc->px_vector > PAN_THRESHOLD) + { + { + PanningChangeEventArgs e; + EventArgsInit(&e, "xfreerdp"); + e.dx = 5; + e.dy = 0; + PubSub_OnPanningChange(ctx->pubSub, xfc, &e); + } + xfc->px_vector = 0; + xfc->py_vector = 0; + xfc->z_vector = 0; + } + else if (xfc->px_vector < -PAN_THRESHOLD) + { + { + PanningChangeEventArgs e; + EventArgsInit(&e, "xfreerdp"); + e.dx = -5; + e.dy = 0; + PubSub_OnPanningChange(ctx->pubSub, xfc, &e); + } + xfc->px_vector = 0; + xfc->py_vector = 0; + xfc->z_vector = 0; + } + } + + if (dist_x > MIN_FINGER_DIST) + { + if (xfc->py_vector > PAN_THRESHOLD) + { + { + PanningChangeEventArgs e; + EventArgsInit(&e, "xfreerdp"); + e.dx = 0; + e.dy = 5; + PubSub_OnPanningChange(ctx->pubSub, xfc, &e); + } + xfc->py_vector = 0; + xfc->px_vector = 0; + xfc->z_vector = 0; + } + else if (xfc->py_vector < -PAN_THRESHOLD) + { + { + PanningChangeEventArgs e; + EventArgsInit(&e, "xfreerdp"); + e.dx = 0; + e.dy = -5; + PubSub_OnPanningChange(ctx->pubSub, xfc, &e); + } + xfc->py_vector = 0; + xfc->px_vector = 0; + xfc->z_vector = 0; + } + } +} + +static void xf_input_detect_pinch(xfContext* xfc) +{ + ZoomingChangeEventArgs e = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(xfc); + rdpContext* ctx = &xfc->common.context; + WINPR_ASSERT(ctx); + + if (xfc->active_contacts != 2) + { + xfc->firstDist = -1.0; + return; + } + + /* first calculate the distance */ + const double dist = sqrt(pow(xfc->contacts[1].pos_x - xfc->contacts[0].last_x, 2.0) + + pow(xfc->contacts[1].pos_y - xfc->contacts[0].last_y, 2.0)); + + /* if this is the first 2pt touch */ + if (xfc->firstDist <= 0) + { + xfc->firstDist = dist; + xfc->lastDist = xfc->firstDist; + xfc->z_vector = 0; + xfc->px_vector = 0; + xfc->py_vector = 0; + } + else + { + double delta = xfc->lastDist - dist; + + if (delta > 1.0) + delta = 1.0; + + if (delta < -1.0) + delta = -1.0; + + /* compare the current distance to the first one */ + xfc->z_vector += delta; + xfc->lastDist = dist; + + if (xfc->z_vector > ZOOM_THRESHOLD) + { + EventArgsInit(&e, "xfreerdp"); + e.dx = e.dy = -10; + PubSub_OnZoomingChange(ctx->pubSub, xfc, &e); + xfc->z_vector = 0; + xfc->px_vector = 0; + xfc->py_vector = 0; + } + + if (xfc->z_vector < -ZOOM_THRESHOLD) + { + EventArgsInit(&e, "xfreerdp"); + e.dx = e.dy = 10; + PubSub_OnZoomingChange(ctx->pubSub, xfc, &e); + xfc->z_vector = 0; + xfc->px_vector = 0; + xfc->py_vector = 0; + } + } +} + +static void xf_input_touch_begin(xfContext* xfc, const XIDeviceEvent* event) +{ + WINPR_UNUSED(xfc); + for (int i = 0; i < MAX_CONTACTS; i++) + { + if (xfc->contacts[i].id == 0) + { + xfc->contacts[i].id = event->detail; + xfc->contacts[i].count = 1; + xfc->contacts[i].pos_x = event->event_x; + xfc->contacts[i].pos_y = event->event_y; + xfc->active_contacts++; + break; + } + } +} + +static void xf_input_touch_update(xfContext* xfc, const XIDeviceEvent* event) +{ + WINPR_ASSERT(xfc); + WINPR_ASSERT(event); + + for (int i = 0; i < MAX_CONTACTS; i++) + { + if (xfc->contacts[i].id == event->detail) + { + xfc->contacts[i].count++; + xfc->contacts[i].last_x = xfc->contacts[i].pos_x; + xfc->contacts[i].last_y = xfc->contacts[i].pos_y; + xfc->contacts[i].pos_x = event->event_x; + xfc->contacts[i].pos_y = event->event_y; + xf_input_detect_pinch(xfc); + xf_input_detect_pan(xfc); + break; + } + } +} + +static void xf_input_touch_end(xfContext* xfc, const XIDeviceEvent* event) +{ + WINPR_UNUSED(xfc); + for (int i = 0; i < MAX_CONTACTS; i++) + { + if (xfc->contacts[i].id == event->detail) + { + xfc->contacts[i].id = 0; + xfc->contacts[i].count = 0; + xfc->active_contacts--; + break; + } + } +} + +static int xf_input_handle_event_local(xfContext* xfc, const XEvent* event) +{ + union + { + const XGenericEventCookie* cc; + XGenericEventCookie* vc; + } cookie; + cookie.cc = &event->xcookie; + XGetEventData(xfc->display, cookie.vc); + + if ((cookie.cc->type == GenericEvent) && (cookie.cc->extension == xfc->XInputOpcode)) + { + switch (cookie.cc->evtype) + { + case XI_TouchBegin: + if (xf_input_is_duplicate(xfc, cookie.cc) == FALSE) + xf_input_touch_begin(xfc, cookie.cc->data); + + xf_input_save_last_event(xfc, cookie.cc); + break; + + case XI_TouchUpdate: + if (xf_input_is_duplicate(xfc, cookie.cc) == FALSE) + xf_input_touch_update(xfc, cookie.cc->data); + + xf_input_save_last_event(xfc, cookie.cc); + break; + + case XI_TouchEnd: + if (xf_input_is_duplicate(xfc, cookie.cc) == FALSE) + xf_input_touch_end(xfc, cookie.cc->data); + + xf_input_save_last_event(xfc, cookie.cc); + break; + + default: + xf_input_event(xfc, event, cookie.cc->data, cookie.cc->evtype); + break; + } + } + + XFreeEventData(xfc->display, cookie.vc); + return 0; +} + +static void xf_input_hide_cursor(xfContext* xfc) +{ +#ifdef WITH_XCURSOR + + if (!xfc->cursorHidden) + { + XcursorImage ci = WINPR_C_ARRAY_INIT; + XcursorPixel xp = 0; + static Cursor nullcursor = None; + xf_lock_x11(xfc); + ci.version = XCURSOR_IMAGE_VERSION; + ci.size = sizeof(ci); + ci.width = ci.height = 1; + ci.xhot = ci.yhot = 0; + ci.pixels = &xp; + nullcursor = XcursorImageLoadCursor(xfc->display, &ci); + + if ((xfc->window) && (nullcursor != None)) + XDefineCursor(xfc->display, xfc->window->handle, nullcursor); + + xfc->cursorHidden = TRUE; + xf_unlock_x11(xfc); + } + +#endif +} + +static void xf_input_show_cursor(xfContext* xfc) +{ +#ifdef WITH_XCURSOR + xf_lock_x11(xfc); + + if (xfc->cursorHidden) + { + if (xfc->window) + { + if (!xfc->pointer) + XUndefineCursor(xfc->display, xfc->window->handle); + else + XDefineCursor(xfc->display, xfc->window->handle, xfc->pointer->cursor); + } + + xfc->cursorHidden = FALSE; + } + + xf_unlock_x11(xfc); +#endif +} + +static int xf_input_touch_remote(xfContext* xfc, XIDeviceEvent* event, int evtype) +{ + int x = 0; + int y = 0; + int touchId = 0; + RdpeiClientContext* rdpei = xfc->common.rdpei; + + if (!rdpei) + return 0; + + xf_input_hide_cursor(xfc); + touchId = event->detail; + x = (int)event->event_x; + y = (int)event->event_y; + xf_event_adjust_coordinates(xfc, &x, &y); + + switch (evtype) + { + case XI_TouchBegin: + freerdp_client_handle_touch(&xfc->common, FREERDP_TOUCH_DOWN, touchId, 0, x, y); + break; + case XI_TouchUpdate: + freerdp_client_handle_touch(&xfc->common, FREERDP_TOUCH_MOTION, touchId, 0, x, y); + break; + case XI_TouchEnd: + freerdp_client_handle_touch(&xfc->common, FREERDP_TOUCH_UP, touchId, 0, x, y); + break; + default: + break; + } + + return 0; +} + +static BOOL xf_input_pen_remote(xfContext* xfc, XIDeviceEvent* event, int evtype, int deviceid) +{ + int x = 0; + int y = 0; + RdpeiClientContext* rdpei = xfc->common.rdpei; + + if (!rdpei) + return FALSE; + + xf_input_hide_cursor(xfc); + x = (int)event->event_x; + y = (int)event->event_y; + xf_event_adjust_coordinates(xfc, &x, &y); + + double pressure = 0.0; + double* val = event->valuators.values; + for (int i = 0; i < MIN(event->valuators.mask_len * 8, 3); i++) + { + if (XIMaskIsSet(event->valuators.mask, i)) + { + double value = *val++; + if (i == 2) + pressure = value; + } + } + + UINT32 flags = FREERDP_PEN_HAS_PRESSURE; + if ((evtype == XI_ButtonPress) || (evtype == XI_ButtonRelease)) + { + WLog_DBG(TAG, "pen button %d", event->detail); + switch (event->detail) + { + case 1: + break; + case 3: + flags |= FREERDP_PEN_BARREL_PRESSED; + break; + default: + return FALSE; + } + } + + switch (evtype) + { + case XI_ButtonPress: + flags |= FREERDP_PEN_PRESS; + if (!freerdp_client_handle_pen(&xfc->common, flags, deviceid, x, y, pressure)) + return FALSE; + break; + case XI_Motion: + flags |= FREERDP_PEN_MOTION; + if (!freerdp_client_handle_pen(&xfc->common, flags, deviceid, x, y, pressure)) + return FALSE; + break; + case XI_ButtonRelease: + flags |= FREERDP_PEN_RELEASE; + if (!freerdp_client_handle_pen(&xfc->common, flags, deviceid, x, y, pressure)) + return FALSE; + break; + default: + break; + } + return TRUE; +} + +static int xf_input_pens_unhover(xfContext* xfc) +{ + WINPR_ASSERT(xfc); + + freerdp_client_pen_cancel_all(&xfc->common); + return 0; +} + +bool xf_use_rel_mouse(xfContext* xfc) +{ + if (!freerdp_client_use_relative_mouse_events(&xfc->common)) + return false; + if (!xfc->isCursorHidden) + return false; + return true; +} + +int xf_input_event(xfContext* xfc, WINPR_ATTR_UNUSED const XEvent* xevent, XIDeviceEvent* event, + int evtype) +{ + WINPR_ASSERT(xfc); + WINPR_ASSERT(xevent); + WINPR_ASSERT(event); + + xfWindow* window = xfc->window; + if (window) + { + if (xf_floatbar_is_locked(window->floatbar)) + return 0; + } + + xf_input_show_cursor(xfc); + + switch (evtype) + { + case XI_ButtonPress: + case XI_ButtonRelease: + xfc->xi_event = !xfc->common.mouse_grabbed || !xf_use_rel_mouse(xfc); + + if (xfc->xi_event) + { + if (!xfc_is_floatbar_window(xfc, event->event) || (evtype != XI_ButtonPress)) + { + xf_generic_ButtonEvent(xfc, (int)event->event_x, (int)event->event_y, + event->detail, event->event, xfc->remote_app, + evtype == XI_ButtonPress); + } + } + break; + + case XI_Motion: + xfc->xi_event = !xfc->common.mouse_grabbed || !xf_use_rel_mouse(xfc); + + if (xfc->xi_event) + { + xf_generic_MotionNotify(xfc, (int)event->event_x, (int)event->event_y, event->event, + xfc->remote_app); + } + break; + case XI_RawButtonPress: + case XI_RawButtonRelease: + xfc->xi_rawevent = xfc->common.mouse_grabbed && xf_use_rel_mouse(xfc); + + if (xfc->xi_rawevent) + { + const XIRawEvent* ev = (const XIRawEvent*)event; + xf_generic_RawButtonEvent(xfc, ev->detail, xfc->remote_app, + evtype == XI_RawButtonPress); + } + break; + case XI_RawMotion: + xfc->xi_rawevent = xfc->common.mouse_grabbed && xf_use_rel_mouse(xfc); + + if (xfc->xi_rawevent) + { + const XIRawEvent* ev = (const XIRawEvent*)event; + double x = 0.0; + double y = 0.0; + if (XIMaskIsSet(ev->valuators.mask, 0)) + x = ev->raw_values[0]; + if (XIMaskIsSet(ev->valuators.mask, 1)) + y = ev->raw_values[1]; + + xf_generic_RawMotionNotify(xfc, (int)x, (int)y, event->event, xfc->remote_app); + } + break; + case XI_DeviceChanged: + { + const XIDeviceChangedEvent* ev = (const XIDeviceChangedEvent*)event; + if (ev->reason != XIDeviceChange) + break; + + /* + * TODO: + * 1. Register only changed devices. + * 2. Both `XIDeviceChangedEvent` and `XIHierarchyEvent` have no target + * `Window` which is used to register xinput events. So assume + * `xfc->window` created by `xf_CreateDesktopWindow` is the same + * `Window` we registered. + */ + if (xfc->window) + register_input_events(xfc, xfc->window->handle); + } + break; + case XI_HierarchyChanged: + if (xfc->window) + register_input_events(xfc, xfc->window->handle); + break; + + default: + WLog_WARN(TAG, "Unhandled event %d: Event was registered but is not handled!", evtype); + break; + } + + return 0; +} + +static int xf_input_handle_event_remote(xfContext* xfc, const XEvent* event) +{ + union + { + const XGenericEventCookie* cc; + XGenericEventCookie* vc; + } cookie; + cookie.cc = &event->xcookie; + XGetEventData(xfc->display, cookie.vc); + + if ((cookie.cc->type == GenericEvent) && (cookie.cc->extension == xfc->XInputOpcode)) + { + switch (cookie.cc->evtype) + { + case XI_TouchBegin: + xf_input_pens_unhover(xfc); + /* fallthrough */ + WINPR_FALLTHROUGH + case XI_TouchUpdate: + case XI_TouchEnd: + xf_input_touch_remote(xfc, cookie.cc->data, cookie.cc->evtype); + break; + case XI_ButtonPress: + case XI_Motion: + case XI_ButtonRelease: + { + WLog_DBG(TAG, "checking for pen"); + XIDeviceEvent* deviceEvent = (XIDeviceEvent*)cookie.cc->data; + int deviceid = deviceEvent->deviceid; + + if (freerdp_client_is_pen(&xfc->common, deviceid)) + { + if (!xf_input_pen_remote(xfc, cookie.cc->data, cookie.cc->evtype, deviceid)) + { + // XXX: don't show cursor + xf_input_event(xfc, event, cookie.cc->data, cookie.cc->evtype); + } + break; + } + } + /* fallthrough */ + WINPR_FALLTHROUGH + default: + xf_input_pens_unhover(xfc); + xf_input_event(xfc, event, cookie.cc->data, cookie.cc->evtype); + break; + } + } + + XFreeEventData(xfc->display, cookie.vc); + return 0; +} + +#else + +int xf_input_init(xfContext* xfc, Window window) +{ + return 0; +} + +#endif + +int xf_input_handle_event(xfContext* xfc, const XEvent* event) +{ +#ifdef WITH_XI + const rdpSettings* settings = nullptr; + WINPR_ASSERT(xfc); + + settings = xfc->common.context.settings; + WINPR_ASSERT(settings); + + if (freerdp_settings_get_bool(settings, FreeRDP_MultiTouchInput)) + { + return xf_input_handle_event_remote(xfc, event); + } + else if (freerdp_settings_get_bool(settings, FreeRDP_MultiTouchGestures)) + { + return xf_input_handle_event_local(xfc, event); + } + else + { + return xf_input_handle_event_local(xfc, event); + } + +#else + return 0; +#endif +} diff --git a/third_party/FreeRDP/client/X11/xf_input.h b/third_party/FreeRDP/client/X11/xf_input.h new file mode 100644 index 0000000..49a8e19 --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_input.h @@ -0,0 +1,34 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Input + * + * Copyright 2013 Corey Clayton + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_X11_INPUT_H +#define FREERDP_CLIENT_X11_INPUT_H + +#include "xf_client.h" +#include "xfreerdp.h" + +#ifdef WITH_XI +#include +#endif + +int xf_input_init(xfContext* xfc, Window window); +int xf_input_handle_event(xfContext* xfc, const XEvent* event); +bool xf_use_rel_mouse(xfContext* xfc); + +#endif /* FREERDP_CLIENT_X11_INPUT_H */ diff --git a/third_party/FreeRDP/client/X11/xf_keyboard.c b/third_party/FreeRDP/client/X11/xf_keyboard.c new file mode 100644 index 0000000..e88c6ab --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_keyboard.c @@ -0,0 +1,1550 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Keyboard Handling + * + * Copyright 2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include + +#include "xf_event.h" + +#include "xf_keyboard.h" + +#include "xf_utils.h" +#include "keyboard_x11.h" + +#include +#define TAG CLIENT_TAG("x11") + +typedef struct +{ + BOOL Shift; + BOOL LeftShift; + BOOL RightShift; + BOOL Alt; + BOOL LeftAlt; + BOOL RightAlt; + BOOL Ctrl; + BOOL LeftCtrl; + BOOL RightCtrl; + BOOL Super; + BOOL LeftSuper; + BOOL RightSuper; +} XF_MODIFIER_KEYS; + +struct x11_key_scancode_t +{ + const char* name; + DWORD sc; +}; + +static const struct x11_key_scancode_t XKB_KEY_NAME_SCANCODE_TABLE[] = { + { "AB01", RDP_SCANCODE_KEY_Z }, /* 052: AB01 [z] */ + { "AB02", RDP_SCANCODE_KEY_X }, /* 053: AB02 [x] */ + { "AB03", RDP_SCANCODE_KEY_C }, /* 054: AB03 [c] */ + { "AB04", RDP_SCANCODE_KEY_V }, /* 055: AB04 [v] */ + { "AB05", RDP_SCANCODE_KEY_B }, /* 056: AB05 [b] */ + { "AB06", RDP_SCANCODE_KEY_N }, /* 057: AB06 [n] */ + { "AB07", RDP_SCANCODE_KEY_M }, /* 058: AB07 [m] */ + { "AB08", RDP_SCANCODE_OEM_COMMA }, /* 059: AB08 [comma] */ + { "AB09", RDP_SCANCODE_OEM_PERIOD }, /* 060: AB09 [period] */ + { "AB10", RDP_SCANCODE_OEM_2 }, /* 061: AB10 [slash] */ + { "AB11", RDP_SCANCODE_ABNT_C1 }, /* 097: AB11 [(null)] */ + { "AC01", RDP_SCANCODE_KEY_A }, /* 038: AC01 [a] */ + { "AC02", RDP_SCANCODE_KEY_S }, /* 039: AC02 [s] */ + { "AC03", RDP_SCANCODE_KEY_D }, /* 040: AC03 [d] */ + { "AC04", RDP_SCANCODE_KEY_F }, /* 041: AC04 [f] */ + { "AC05", RDP_SCANCODE_KEY_G }, /* 042: AC05 [g] */ + { "AC06", RDP_SCANCODE_KEY_H }, /* 043: AC06 [h] */ + { "AC07", RDP_SCANCODE_KEY_J }, /* 044: AC07 [j] */ + { "AC08", RDP_SCANCODE_KEY_K }, /* 045: AC08 [k] */ + { "AC09", RDP_SCANCODE_KEY_L }, /* 046: AC09 [l] */ + { "AC10", RDP_SCANCODE_OEM_1 }, /* 047: AC10 [semicolon] */ + { "AC11", RDP_SCANCODE_OEM_7 }, /* 048: AC11 [dead_acute] */ + { "AD01", RDP_SCANCODE_KEY_Q }, /* 024: AD01 [q] */ + { "AD02", RDP_SCANCODE_KEY_W }, /* 025: AD02 [w] */ + { "AD03", RDP_SCANCODE_KEY_E }, /* 026: AD03 [e] */ + { "AD04", RDP_SCANCODE_KEY_R }, /* 027: AD04 [r] */ + { "AD05", RDP_SCANCODE_KEY_T }, /* 028: AD05 [t] */ + { "AD06", RDP_SCANCODE_KEY_Y }, /* 029: AD06 [y] */ + { "AD07", RDP_SCANCODE_KEY_U }, /* 030: AD07 [u] */ + { "AD08", RDP_SCANCODE_KEY_I }, /* 031: AD08 [i] */ + { "AD09", RDP_SCANCODE_KEY_O }, /* 032: AD09 [o] */ + { "AD10", RDP_SCANCODE_KEY_P }, /* 033: AD10 [p] */ + { "AD11", RDP_SCANCODE_OEM_4 }, /* 034: AD11 [bracketleft] */ + { "AD12", RDP_SCANCODE_OEM_6 }, /* 035: AD12 [bracketright] */ + { "AE01", RDP_SCANCODE_KEY_1 }, /* 010: AE01 [1] */ + { "AE02", RDP_SCANCODE_KEY_2 }, /* 011: AE02 [2] */ + { "AE03", RDP_SCANCODE_KEY_3 }, /* 012: AE03 [3] */ + { "AE04", RDP_SCANCODE_KEY_4 }, /* 013: AE04 [4] */ + { "AE05", RDP_SCANCODE_KEY_5 }, /* 014: AE05 [5] */ + { "AE06", RDP_SCANCODE_KEY_6 }, /* 015: AE06 [6] */ + { "AE07", RDP_SCANCODE_KEY_7 }, /* 016: AE07 [7] */ + { "AE08", RDP_SCANCODE_KEY_8 }, /* 017: AE08 [8] */ + { "AE09", RDP_SCANCODE_KEY_9 }, /* 018: AE09 [9] */ + { "AE10", RDP_SCANCODE_KEY_0 }, /* 019: AE10 [0] */ + { "AE11", RDP_SCANCODE_OEM_MINUS }, /* 020: AE11 [minus] */ + { "AE12", RDP_SCANCODE_OEM_PLUS }, /* 021: AE12 [equal] */ + { "AE13", RDP_SCANCODE_BACKSLASH_JP }, /* 132: AE13 [(null)] */ + { "AGAI" /* codespell:ignore */, RDP_SCANCODE_UNKNOWN }, + { "ALT", RDP_SCANCODE_LMENU }, /* 204: ALT [(null)] */ + { "BKSL", RDP_SCANCODE_OEM_5 }, /* 051: BKSL [backslash] */ + { "BKSP", RDP_SCANCODE_BACKSPACE }, /* 022: BKSP [BackSpace] */ + { "CAPS", RDP_SCANCODE_CAPSLOCK }, /* 066: CAPS [Caps_Lock] */ + { "COMP", RDP_SCANCODE_APPS }, /* 135: COMP [Menu] */ + { "COPY", RDP_SCANCODE_UNKNOWN }, /* 141: COPY [XF86Copy] */ + { "CUT", RDP_SCANCODE_UNKNOWN }, /* 145: CUT [XF86Cut] */ + { "DELE", RDP_SCANCODE_DELETE }, /* 119: DELE [Delete] */ + { "DOWN", RDP_SCANCODE_DOWN }, /* 116: DOWN [Down] */ + { "END", RDP_SCANCODE_END }, /* 115: END [End] */ + { "ESC", RDP_SCANCODE_ESCAPE }, /* 009: ESC [Escape] */ + { "FIND", RDP_SCANCODE_UNKNOWN }, /* 144: FIND [Find] */ + { "FK01", RDP_SCANCODE_F1 }, /* 067: FK01 [F1] */ + { "FK02", RDP_SCANCODE_F2 }, /* 068: FK02 [F2] */ + { "FK03", RDP_SCANCODE_F3 }, /* 069: FK03 [F3] */ + { "FK04", RDP_SCANCODE_F4 }, /* 070: FK04 [F4] */ + { "FK05", RDP_SCANCODE_F5 }, /* 071: FK05 [F5] */ + { "FK06", RDP_SCANCODE_F6 }, /* 072: FK06 [F6] */ + { "FK07", RDP_SCANCODE_F7 }, /* 073: FK07 [F7] */ + { "FK08", RDP_SCANCODE_F8 }, /* 074: FK08 [F8] */ + { "FK09", RDP_SCANCODE_F9 }, /* 075: FK09 [F9] */ + { "FK10", RDP_SCANCODE_F10 }, /* 076: FK10 [F10] */ + { "FK11", RDP_SCANCODE_F11 }, /* 095: FK11 [F11] */ + { "FK12", RDP_SCANCODE_F12 }, /* 096: FK12 [F12] */ + { "FK13", RDP_SCANCODE_F13 }, /* 191: FK13 [XF86Tools] */ + { "FK14", RDP_SCANCODE_F14 }, /* 192: FK14 [XF86Launch5] */ + { "FK15", RDP_SCANCODE_F15 }, /* 193: FK15 [XF86Launch6] */ + { "FK16", RDP_SCANCODE_F16 }, /* 194: FK16 [XF86Launch7] */ + { "FK17", RDP_SCANCODE_F17 }, /* 195: FK17 [XF86Launch8] */ + { "FK18", RDP_SCANCODE_F18 }, /* 196: FK18 [XF86Launch9] */ + { "FK19", RDP_SCANCODE_F19 }, /* 197: FK19 [(null)] */ + { "FK20", RDP_SCANCODE_F20 }, /* 198: FK20 [XF86AudioMicMute] */ + { "FK21", RDP_SCANCODE_F21 }, /* 199: FK21 [XF86TouchpadToggle] */ + { "FK22", RDP_SCANCODE_F22 }, /* 200: FK22 [XF86TouchpadOn] */ + { "FK23", RDP_SCANCODE_F23 }, /* 201: FK23 [XF86TouchpadOff] */ + { "FK24", RDP_SCANCODE_F24 }, /* 202: FK24 [(null)] */ + { "FRNT", RDP_SCANCODE_UNKNOWN }, /* 140: FRNT [SunFront] */ + { "HELP", RDP_SCANCODE_HELP }, /* 146: HELP [Help] */ + { "HENK", RDP_SCANCODE_CONVERT_JP }, /* 100: HENK [Henkan_Mode] */ + { "HIRA", RDP_SCANCODE_HIRAGANA }, /* 099: HIRA [Hiragana] */ + { "HJCV", RDP_SCANCODE_HANJA }, /* 131: HJCV [Hangul_Hanja] */ + { "HKTG", RDP_SCANCODE_HIRAGANA }, /* 101: HKTG [Hiragana_Katakana] */ + { "HNGL", RDP_SCANCODE_HANGUL }, /* 130: HNGL [Hangul] */ + { "HOME", RDP_SCANCODE_HOME }, /* 110: HOME [Home] */ + { "HYPR", RDP_SCANCODE_LWIN }, /* 207: HYPR [(null)] */ + { "I120", RDP_SCANCODE_UNKNOWN }, /* 120: I120 [(null)] */ + { "I126", RDP_SCANCODE_UNKNOWN }, /* 126: I126 [plusminus] */ + { "I128", RDP_SCANCODE_LAUNCH_MEDIA_SELECT }, /* 128: I128 [XF86LaunchA] */ + { "I129", RDP_SCANCODE_ABNT_C2 }, /* 129: I129 [KP_Decimal] */ + { "I147", RDP_SCANCODE_UNKNOWN }, /* 147: I147 [XF86MenuKB] */ + { "I148", RDP_SCANCODE_UNKNOWN }, /* 148: I148 [XF86Calculator] */ + { "I149", RDP_SCANCODE_UNKNOWN }, /* 149: I149 [(null)] */ + { "I150", RDP_SCANCODE_SLEEP }, /* 150: I150 [XF86Sleep] */ + { "I151", RDP_SCANCODE_UNKNOWN }, /* 151: I151 [XF86WakeUp] */ + { "I152", RDP_SCANCODE_UNKNOWN }, /* 152: I152 [XF86Explorer] */ + { "I153", RDP_SCANCODE_UNKNOWN }, /* 153: I153 [XF86Send] */ + { "I154", RDP_SCANCODE_UNKNOWN }, /* 154: I154 [(null)] */ + { "I155", RDP_SCANCODE_UNKNOWN }, /* 155: I155 [XF86Xfer] */ + { "I156", RDP_SCANCODE_LAUNCH_APP1 }, /* 156: I156 [XF86Launch1] */ + { "I157", RDP_SCANCODE_LAUNCH_APP2 }, /* 157: I157 [XF86Launch2] */ + { "I158", RDP_SCANCODE_BROWSER_HOME }, /* 158: I158 [XF86WWW] */ + { "I159", RDP_SCANCODE_UNKNOWN }, /* 159: I159 [XF86DOS] */ + { "I160", RDP_SCANCODE_UNKNOWN }, /* 160: I160 [XF86ScreenSaver] */ + { "I161", RDP_SCANCODE_UNKNOWN }, /* 161: I161 [XF86RotateWindows] */ + { "I162", RDP_SCANCODE_UNKNOWN }, /* 162: I162 [XF86TaskPane] */ + { "I163", RDP_SCANCODE_LAUNCH_MAIL }, /* 163: I163 [XF86Mail] */ + { "I164", RDP_SCANCODE_BROWSER_FAVORITES }, /* 164: I164 [XF86Favorites] */ + { "I165", RDP_SCANCODE_UNKNOWN }, /* 165: I165 [XF86MyComputer] */ + { "I166", RDP_SCANCODE_BROWSER_BACK }, /* 166: I166 [XF86Back] */ + { "I167", RDP_SCANCODE_BROWSER_FORWARD }, /* 167: I167 [XF86Forward] */ + { "I168", RDP_SCANCODE_UNKNOWN }, /* 168: I168 [(null)] */ + { "I169", RDP_SCANCODE_UNKNOWN }, /* 169: I169 [XF86Eject] */ + { "I170", RDP_SCANCODE_UNKNOWN }, /* 170: I170 [XF86Eject] */ + { "I171", RDP_SCANCODE_MEDIA_NEXT_TRACK }, /* 171: I171 [XF86AudioNext] */ + { "I172", RDP_SCANCODE_MEDIA_PLAY_PAUSE }, /* 172: I172 [XF86AudioPlay] */ + { "I173", RDP_SCANCODE_MEDIA_PREV_TRACK }, /* 173: I173 [XF86AudioPrev] */ + { "I174", RDP_SCANCODE_MEDIA_STOP }, /* 174: I174 [XF86AudioStop] */ + { "I175", RDP_SCANCODE_UNKNOWN }, /* 175: I175 [XF86AudioRecord] */ + { "I176", RDP_SCANCODE_UNKNOWN }, /* 176: I176 [XF86AudioRewind] */ + { "I177", RDP_SCANCODE_UNKNOWN }, /* 177: I177 [XF86Phone] */ + { "I178", RDP_SCANCODE_UNKNOWN }, /* 178: I178 [(null)] */ + { "I179", RDP_SCANCODE_UNKNOWN }, /* 179: I179 [XF86Tools] */ + { "I180", RDP_SCANCODE_BROWSER_HOME }, /* 180: I180 [XF86HomePage] */ + { "I181", RDP_SCANCODE_BROWSER_REFRESH }, /* 181: I181 [XF86Reload] */ + { "I182", RDP_SCANCODE_UNKNOWN }, /* 182: I182 [XF86Close] */ + { "I183", RDP_SCANCODE_UNKNOWN }, /* 183: I183 [(null)] */ + { "I184", RDP_SCANCODE_UNKNOWN }, /* 184: I184 [(null)] */ + { "I185", RDP_SCANCODE_UNKNOWN }, /* 185: I185 [XF86ScrollUp] */ + { "I186", RDP_SCANCODE_UNKNOWN }, /* 186: I186 [XF86ScrollDown] */ + { "I187", RDP_SCANCODE_UNKNOWN }, /* 187: I187 [parenleft] */ + { "I188", RDP_SCANCODE_UNKNOWN }, /* 188: I188 [parenright] */ + { "I189", RDP_SCANCODE_UNKNOWN }, /* 189: I189 [XF86New] */ + { "I190", RDP_SCANCODE_UNKNOWN }, /* 190: I190 [Redo] */ + { "I208", RDP_SCANCODE_MEDIA_PLAY_PAUSE }, /* 208: I208 [XF86AudioPlay] */ + { "I209", RDP_SCANCODE_MEDIA_PLAY_PAUSE }, /* 209: I209 [XF86AudioPause] */ + { "I210", RDP_SCANCODE_UNKNOWN }, /* 210: I210 [XF86Launch3] */ + { "I211", RDP_SCANCODE_UNKNOWN }, /* 211: I211 [XF86Launch4] */ + { "I212", RDP_SCANCODE_UNKNOWN }, /* 212: I212 [XF86LaunchB] */ + { "I213", RDP_SCANCODE_UNKNOWN }, /* 213: I213 [XF86Suspend] */ + { "I214", RDP_SCANCODE_UNKNOWN }, /* 214: I214 [XF86Close] */ + { "I215", RDP_SCANCODE_MEDIA_PLAY_PAUSE }, /* 215: I215 [XF86AudioPlay] */ + { "I216", RDP_SCANCODE_MEDIA_NEXT_TRACK }, /* 216: I216 [XF86AudioForward] */ + { "I217", RDP_SCANCODE_UNKNOWN }, /* 217: I217 [(null)] */ + { "I218", RDP_SCANCODE_UNKNOWN }, /* 218: I218 [Print] */ + { "I219", RDP_SCANCODE_UNKNOWN }, /* 219: I219 [(null)] */ + { "I220", RDP_SCANCODE_UNKNOWN }, /* 220: I220 [XF86WebCam] */ + { "I221", RDP_SCANCODE_UNKNOWN }, /* 221: I221 [XF86AudioPreset] */ + { "I222", RDP_SCANCODE_UNKNOWN }, /* 222: I222 [(null)] */ + { "I223", RDP_SCANCODE_LAUNCH_MAIL }, /* 223: I223 [XF86Mail] */ + { "I224", RDP_SCANCODE_UNKNOWN }, /* 224: I224 [XF86Messenger] */ + { "I225", RDP_SCANCODE_BROWSER_SEARCH }, /* 225: I225 [XF86Search] */ + { "I226", RDP_SCANCODE_UNKNOWN }, /* 226: I226 [XF86Go] */ + { "I227", RDP_SCANCODE_UNKNOWN }, /* 227: I227 [XF86Finance] */ + { "I228", RDP_SCANCODE_UNKNOWN }, /* 228: I228 [XF86Game] */ + { "I229", RDP_SCANCODE_UNKNOWN }, /* 229: I229 [XF86Shop] */ + { "I230", RDP_SCANCODE_UNKNOWN }, /* 230: I230 [(null)] */ + { "I231", RDP_SCANCODE_UNKNOWN }, /* 231: I231 [Cancel] */ + { "I232", RDP_SCANCODE_UNKNOWN }, /* 232: I232 [XF86MonBrightnessDown] */ + { "I233", RDP_SCANCODE_UNKNOWN }, /* 233: I233 [XF86MonBrightnessUp] */ + { "I234", RDP_SCANCODE_LAUNCH_MEDIA_SELECT }, /* 234: I234 [XF86AudioMedia] */ + { "I235", RDP_SCANCODE_UNKNOWN }, /* 235: I235 [XF86Display] */ + { "I236", RDP_SCANCODE_UNKNOWN }, /* 236: I236 [XF86KbdLightOnOff] */ + { "I237", RDP_SCANCODE_UNKNOWN }, /* 237: I237 [XF86KbdBrightnessDown] */ + { "I238", RDP_SCANCODE_UNKNOWN }, /* 238: I238 [XF86KbdBrightnessUp] */ + { "I239", RDP_SCANCODE_UNKNOWN }, /* 239: I239 [XF86Send] */ + { "I240", RDP_SCANCODE_UNKNOWN }, /* 240: I240 [XF86Reply] */ + { "I241", RDP_SCANCODE_UNKNOWN }, /* 241: I241 [XF86MailForward] */ + { "I242", RDP_SCANCODE_UNKNOWN }, /* 242: I242 [XF86Save] */ + { "I243", RDP_SCANCODE_UNKNOWN }, /* 243: I243 [XF86Documents] */ + { "I244", RDP_SCANCODE_UNKNOWN }, /* 244: I244 [XF86Battery] */ + { "I245", RDP_SCANCODE_UNKNOWN }, /* 245: I245 [XF86Bluetooth] */ + { "I246", RDP_SCANCODE_UNKNOWN }, /* 246: I246 [XF86WLAN] */ + { "I247", RDP_SCANCODE_UNKNOWN }, /* 247: I247 [XF86UWB] */ + { "I248", RDP_SCANCODE_UNKNOWN }, /* 248: I248 [(null)] */ + { "I249", RDP_SCANCODE_UNKNOWN }, /* 249: I249 [XF86Next_VMode] */ + { "I250", RDP_SCANCODE_UNKNOWN }, /* 250: I250 [XF86Prev_VMode] */ + { "I251", RDP_SCANCODE_UNKNOWN }, /* 251: I251 [XF86MonBrightnessCycle] */ + { "I252", RDP_SCANCODE_UNKNOWN }, /* 252: I252 [XF86BrightnessAuto] */ + { "I253", RDP_SCANCODE_UNKNOWN }, /* 253: I253 [XF86DisplayOff] */ + { "I254", RDP_SCANCODE_UNKNOWN }, /* 254: I254 [XF86WWAN] */ + { "I255", RDP_SCANCODE_UNKNOWN }, /* 255: I255 [XF86RFKill] */ + { "INS", RDP_SCANCODE_INSERT }, /* 118: INS [Insert] */ + { "JPCM", RDP_SCANCODE_UNKNOWN }, /* 103: JPCM [(null)] */ + { "KATA", RDP_SCANCODE_KANA_HANGUL }, /* 098: KATA [Katakana] */ + { "KP0", RDP_SCANCODE_NUMPAD0 }, /* 090: KP0 [KP_Insert] */ + { "KP1", RDP_SCANCODE_NUMPAD1 }, /* 087: KP1 [KP_End] */ + { "KP2", RDP_SCANCODE_NUMPAD2 }, /* 088: KP2 [KP_Down] */ + { "KP3", RDP_SCANCODE_NUMPAD3 }, /* 089: KP3 [KP_Next] */ + { "KP4", RDP_SCANCODE_NUMPAD4 }, /* 083: KP4 [KP_Left] */ + { "KP5", RDP_SCANCODE_NUMPAD5 }, /* 084: KP5 [KP_Begin] */ + { "KP6", RDP_SCANCODE_NUMPAD6 }, /* 085: KP6 [KP_Right] */ + { "KP7", RDP_SCANCODE_NUMPAD7 }, /* 079: KP7 [KP_Home] */ + { "KP8", RDP_SCANCODE_NUMPAD8 }, /* 080: KP8 [KP_Up] */ + { "KP9", RDP_SCANCODE_NUMPAD9 }, /* 081: KP9 [KP_Prior] */ + { "KPAD", RDP_SCANCODE_ADD }, /* 086: KPAD [KP_Add] */ + { "KPDL", RDP_SCANCODE_DECIMAL }, /* 091: KPDL [KP_Delete] */ + { "KPDV", RDP_SCANCODE_DIVIDE }, /* 106: KPDV [KP_Divide] */ + { "KPEN", RDP_SCANCODE_RETURN_KP }, /* 104: KPEN [KP_Enter] */ + { "KPEQ", RDP_SCANCODE_UNKNOWN }, /* 125: KPEQ [KP_Equal] */ + { "KPMU", RDP_SCANCODE_MULTIPLY }, /* 063: KPMU [KP_Multiply] */ + { "KPSU", RDP_SCANCODE_SUBTRACT }, /* 082: KPSU [KP_Subtract] */ + { "LALT", RDP_SCANCODE_LMENU }, /* 064: LALT [Alt_L] */ + { "LCTL", RDP_SCANCODE_LCONTROL }, /* 037: LCTL [Control_L] */ + { "LEFT", RDP_SCANCODE_LEFT }, /* 113: LEFT [Left] */ + { "LFSH", RDP_SCANCODE_LSHIFT }, /* 050: LFSH [Shift_L] */ + { "LNFD", RDP_SCANCODE_UNKNOWN }, /* 109: LNFD [Linefeed] */ + { "LSGT", RDP_SCANCODE_OEM_102 }, /* 094: LSGT [backslash] */ + { "LVL3", RDP_SCANCODE_RMENU }, /* 092: LVL3 [ISO_Level3_Shift] */ + { "LVL5", RDP_SCANCODE_UNKNOWN }, /* 203: LVL5 [ISO_Level5_Shift] */ + { "LWIN", RDP_SCANCODE_LWIN }, /* 133: LWIN [Super_L] */ + { "META", RDP_SCANCODE_LMENU }, /* 205: META [(null)] */ + { "MUHE", RDP_SCANCODE_NONCONVERT_JP }, /* 102: MUHE [Muhenkan] */ + { "MUTE", RDP_SCANCODE_VOLUME_MUTE }, /* 121: MUTE [XF86AudioMute] */ + { "NMLK", RDP_SCANCODE_NUMLOCK }, /* 077: NMLK [Num_Lock] */ + { "OPEN", RDP_SCANCODE_UNKNOWN }, /* 142: OPEN [XF86Open] */ + { "PAST", RDP_SCANCODE_UNKNOWN }, /* 143: PAST [XF86Paste] */ + { "PAUS", RDP_SCANCODE_PAUSE }, /* 127: PAUS [Pause] */ + { "PGDN", RDP_SCANCODE_NEXT }, /* 117: PGDN [Next] */ + { "PGUP", RDP_SCANCODE_PRIOR }, /* 112: PGUP [Prior] */ + { "POWR", RDP_SCANCODE_UNKNOWN }, /* 124: POWR [XF86PowerOff] */ + { "PROP", RDP_SCANCODE_UNKNOWN }, /* 138: PROP [SunProps] */ + { "PRSC", RDP_SCANCODE_PRINTSCREEN }, /* 107: PRSC [Print] */ + { "RALT", RDP_SCANCODE_RMENU }, /* 108: RALT [ISO_Level3_Shift] */ + { "RCTL", RDP_SCANCODE_RCONTROL }, /* 105: RCTL [Control_R] */ + { "RGHT", RDP_SCANCODE_RIGHT }, /* 114: RGHT [Right] */ + { "RTRN", RDP_SCANCODE_RETURN }, /* 036: RTRN [Return] */ + { "RTSH", RDP_SCANCODE_RSHIFT }, /* 062: RTSH [Shift_R] */ + { "RWIN", RDP_SCANCODE_RWIN }, /* 134: RWIN [Super_R] */ + { "SCLK", RDP_SCANCODE_SCROLLLOCK }, /* 078: SCLK [Multi_key] */ + { "SPCE", RDP_SCANCODE_SPACE }, /* 065: SPCE [space] */ + { "STOP", RDP_SCANCODE_BROWSER_STOP }, /* 136: STOP [Cancel] */ + { "SUPR", RDP_SCANCODE_LWIN }, /* 206: SUPR [(null)] */ + { "TAB", RDP_SCANCODE_TAB }, /* 023: TAB [Tab] */ + { "TLDE", RDP_SCANCODE_OEM_3 }, /* 049: TLDE [dead_grave] */ + { "UNDO", RDP_SCANCODE_UNKNOWN }, /* 139: UNDO [Undo] */ + { "UP", RDP_SCANCODE_UP }, /* 111: UP [Up] */ + { "VOL+", RDP_SCANCODE_VOLUME_UP }, /* 123: VOL+ [XF86AudioRaiseVolume] */ + { "VOL-", RDP_SCANCODE_VOLUME_DOWN } /* 122: VOL- [XF86AudioLowerVolume] */ +}; + +typedef struct +{ + KeySym keysym; + DWORD sc; +} x11_keysym_scancode_t; + +static const x11_keysym_scancode_t KEYSYM_SCANCODE_TABLE[] = { + { XK_space, RDP_SCANCODE_SPACE }, + { XK_apostrophe, RDP_SCANCODE_OEM_7 }, + { XK_comma, RDP_SCANCODE_OEM_COMMA }, + { XK_minus, RDP_SCANCODE_OEM_MINUS }, + { XK_period, RDP_SCANCODE_OEM_PERIOD }, + { XK_slash, RDP_SCANCODE_OEM_2 }, + { XK_0, RDP_SCANCODE_KEY_0 }, + { XK_1, RDP_SCANCODE_KEY_1 }, + { XK_2, RDP_SCANCODE_KEY_2 }, + { XK_3, RDP_SCANCODE_KEY_3 }, + { XK_4, RDP_SCANCODE_KEY_4 }, + { XK_5, RDP_SCANCODE_KEY_5 }, + { XK_6, RDP_SCANCODE_KEY_6 }, + { XK_7, RDP_SCANCODE_KEY_7 }, + { XK_8, RDP_SCANCODE_KEY_8 }, + { XK_9, RDP_SCANCODE_KEY_9 }, + { XK_semicolon, RDP_SCANCODE_OEM_1 }, + { XK_less, RDP_SCANCODE_OEM_102 }, + { XK_equal, RDP_SCANCODE_OEM_PLUS }, + { XK_A, RDP_SCANCODE_KEY_A }, + { XK_B, RDP_SCANCODE_KEY_B }, + { XK_C, RDP_SCANCODE_KEY_C }, + { XK_D, RDP_SCANCODE_KEY_D }, + { XK_E, RDP_SCANCODE_KEY_E }, + { XK_F, RDP_SCANCODE_KEY_F }, + { XK_G, RDP_SCANCODE_KEY_G }, + { XK_H, RDP_SCANCODE_KEY_H }, + { XK_I, RDP_SCANCODE_KEY_I }, + { XK_J, RDP_SCANCODE_KEY_J }, + { XK_K, RDP_SCANCODE_KEY_K }, + { XK_L, RDP_SCANCODE_KEY_L }, + { XK_M, RDP_SCANCODE_KEY_M }, + { XK_N, RDP_SCANCODE_KEY_N }, + { XK_O, RDP_SCANCODE_KEY_O }, + { XK_P, RDP_SCANCODE_KEY_P }, + { XK_Q, RDP_SCANCODE_KEY_Q }, + { XK_R, RDP_SCANCODE_KEY_R }, + { XK_S, RDP_SCANCODE_KEY_S }, + { XK_T, RDP_SCANCODE_KEY_T }, + { XK_U, RDP_SCANCODE_KEY_U }, + { XK_V, RDP_SCANCODE_KEY_V }, + { XK_W, RDP_SCANCODE_KEY_W }, + { XK_X, RDP_SCANCODE_KEY_X }, + { XK_Y, RDP_SCANCODE_KEY_Y }, + { XK_Z, RDP_SCANCODE_KEY_Z }, + { XK_bracketleft, RDP_SCANCODE_OEM_4 }, + { XK_backslash, RDP_SCANCODE_OEM_5 }, + { XK_bracketright, RDP_SCANCODE_OEM_6 }, + { XK_grave, RDP_SCANCODE_OEM_3 }, + { XK_a, RDP_SCANCODE_KEY_A }, + { XK_b, RDP_SCANCODE_KEY_B }, + { XK_c, RDP_SCANCODE_KEY_C }, + { XK_d, RDP_SCANCODE_KEY_D }, + { XK_e, RDP_SCANCODE_KEY_E }, + { XK_f, RDP_SCANCODE_KEY_F }, + { XK_g, RDP_SCANCODE_KEY_G }, + { XK_h, RDP_SCANCODE_KEY_H }, + { XK_i, RDP_SCANCODE_KEY_I }, + { XK_j, RDP_SCANCODE_KEY_J }, + { XK_k, RDP_SCANCODE_KEY_K }, + { XK_l, RDP_SCANCODE_KEY_L }, + { XK_m, RDP_SCANCODE_KEY_M }, + { XK_n, RDP_SCANCODE_KEY_N }, + { XK_o, RDP_SCANCODE_KEY_O }, + { XK_p, RDP_SCANCODE_KEY_P }, + { XK_q, RDP_SCANCODE_KEY_Q }, + { XK_r, RDP_SCANCODE_KEY_R }, + { XK_s, RDP_SCANCODE_KEY_S }, + { XK_t, RDP_SCANCODE_KEY_T }, + { XK_u, RDP_SCANCODE_KEY_U }, + { XK_v, RDP_SCANCODE_KEY_V }, + { XK_w, RDP_SCANCODE_KEY_W }, + { XK_x, RDP_SCANCODE_KEY_X }, + { XK_y, RDP_SCANCODE_KEY_Y }, + { XK_z, RDP_SCANCODE_KEY_Z }, + { XK_ISO_Level3_Shift, RDP_SCANCODE_RMENU }, + { XK_ISO_Left_Tab, RDP_SCANCODE_TAB }, + { XK_BackSpace, RDP_SCANCODE_BACKSPACE }, + { XK_Tab, RDP_SCANCODE_TAB }, + { XK_Return, RDP_SCANCODE_RETURN }, + { XK_Pause, RDP_SCANCODE_PAUSE }, + { XK_Scroll_Lock, RDP_SCANCODE_SCROLLLOCK }, + { XK_Escape, RDP_SCANCODE_ESCAPE }, + { XK_Home, RDP_SCANCODE_HOME }, + { XK_Left, RDP_SCANCODE_LEFT }, + { XK_Up, RDP_SCANCODE_UP }, + { XK_Right, RDP_SCANCODE_RIGHT }, + { XK_Down, RDP_SCANCODE_DOWN }, + { XK_Prior, RDP_SCANCODE_PRIOR }, + { XK_Next, RDP_SCANCODE_NEXT }, + { XK_End, RDP_SCANCODE_END }, + { XK_Print, RDP_SCANCODE_PRINTSCREEN }, + { XK_Insert, RDP_SCANCODE_INSERT }, + { XK_Menu, RDP_SCANCODE_APPS }, + { XK_Help, RDP_SCANCODE_HELP }, + { XK_Mode_switch, RDP_SCANCODE_RMENU }, + { XK_Num_Lock, RDP_SCANCODE_NUMLOCK }, + { XK_KP_Enter, RDP_SCANCODE_RETURN_KP }, + { XK_KP_Home, RDP_SCANCODE_NUMPAD7 }, + { XK_KP_Left, RDP_SCANCODE_NUMPAD4 }, + { XK_KP_Up, RDP_SCANCODE_NUMPAD8 }, + { XK_KP_Right, RDP_SCANCODE_NUMPAD6 }, + { XK_KP_Down, RDP_SCANCODE_NUMPAD2 }, + { XK_KP_Prior, RDP_SCANCODE_NUMPAD9 }, + { XK_KP_Next, RDP_SCANCODE_NUMPAD3 }, + { XK_KP_End, RDP_SCANCODE_NUMPAD1 }, + { XK_KP_Begin, RDP_SCANCODE_NUMPAD5 }, + { XK_KP_Insert, RDP_SCANCODE_NUMPAD0 }, + { XK_KP_Delete, RDP_SCANCODE_DECIMAL }, + { XK_KP_Multiply, RDP_SCANCODE_MULTIPLY }, + { XK_KP_Add, RDP_SCANCODE_ADD }, + { XK_KP_Separator, RDP_SCANCODE_DECIMAL }, + { XK_KP_Subtract, RDP_SCANCODE_SUBTRACT }, + { XK_KP_Decimal, RDP_SCANCODE_DECIMAL }, + { XK_KP_Divide, RDP_SCANCODE_DIVIDE }, + { XK_KP_0, RDP_SCANCODE_NUMPAD0 }, + { XK_KP_1, RDP_SCANCODE_NUMPAD1 }, + { XK_KP_2, RDP_SCANCODE_NUMPAD2 }, + { XK_KP_3, RDP_SCANCODE_NUMPAD3 }, + { XK_KP_4, RDP_SCANCODE_NUMPAD4 }, + { XK_KP_5, RDP_SCANCODE_NUMPAD5 }, + { XK_KP_6, RDP_SCANCODE_NUMPAD6 }, + { XK_KP_7, RDP_SCANCODE_NUMPAD7 }, + { XK_KP_8, RDP_SCANCODE_NUMPAD8 }, + { XK_KP_9, RDP_SCANCODE_NUMPAD9 }, + { XK_F1, RDP_SCANCODE_F1 }, + { XK_F2, RDP_SCANCODE_F2 }, + { XK_F3, RDP_SCANCODE_F3 }, + { XK_F4, RDP_SCANCODE_F4 }, + { XK_F5, RDP_SCANCODE_F5 }, + { XK_F6, RDP_SCANCODE_F6 }, + { XK_F7, RDP_SCANCODE_F7 }, + { XK_F8, RDP_SCANCODE_F8 }, + { XK_F9, RDP_SCANCODE_F9 }, + { XK_F10, RDP_SCANCODE_F10 }, + { XK_F11, RDP_SCANCODE_F11 }, + { XK_F12, RDP_SCANCODE_F12 }, + { XK_F13, RDP_SCANCODE_F13 }, + { XK_F14, RDP_SCANCODE_F14 }, + { XK_F15, RDP_SCANCODE_F15 }, + { XK_F16, RDP_SCANCODE_F16 }, + { XK_F17, RDP_SCANCODE_F17 }, + { XK_F18, RDP_SCANCODE_F18 }, + { XK_F19, RDP_SCANCODE_F19 }, + { XK_F20, RDP_SCANCODE_F20 }, + { XK_F21, RDP_SCANCODE_F21 }, + { XK_F22, RDP_SCANCODE_F22 }, + { XK_F23, RDP_SCANCODE_F23 }, + { XK_F24, RDP_SCANCODE_F24 }, + { XK_Shift_L, RDP_SCANCODE_LSHIFT }, + { XK_Shift_R, RDP_SCANCODE_RSHIFT }, + { XK_Control_L, RDP_SCANCODE_LCONTROL }, + { XK_Control_R, RDP_SCANCODE_RCONTROL }, + { XK_Caps_Lock, RDP_SCANCODE_CAPSLOCK }, + { XK_Meta_L, RDP_SCANCODE_LWIN }, + { XK_Meta_R, RDP_SCANCODE_RWIN }, + { XK_Alt_L, RDP_SCANCODE_LMENU }, + { XK_Alt_R, RDP_SCANCODE_RMENU }, + { XK_Super_L, RDP_SCANCODE_LWIN }, + { XK_Super_R, RDP_SCANCODE_RWIN }, + { XK_Hyper_L, RDP_SCANCODE_LWIN }, + { XK_Hyper_R, RDP_SCANCODE_RWIN }, + { XK_Delete, RDP_SCANCODE_DELETE } +}; + +static UINT32 xf_keyboard_get_toggle_keys_state(xfContext* xfc); +static BOOL xf_keyboard_handle_special_keys(xfContext* xfc, KeySym keysym); +static void xf_keyboard_handle_special_keys_release(xfContext* xfc, KeySym keysym); + +static void xf_keyboard_modifier_map_free(xfContext* xfc) +{ + WINPR_ASSERT(xfc); + if (xfc->modifierMap) + { + XFreeModifiermap(xfc->modifierMap); + xfc->modifierMap = nullptr; + } +} + +BOOL xf_keyboard_update_modifier_map(xfContext* xfc) +{ + WINPR_ASSERT(xfc); + xf_keyboard_modifier_map_free(xfc); + xfc->modifierMap = XGetModifierMapping(xfc->display); + return xfc->modifierMap != nullptr; +} + +static void xf_keyboard_send_key(xfContext* xfc, BOOL down, BOOL repeat, const XKeyEvent* ev); + +static BOOL xf_sync_kbd_state(xfContext* xfc) +{ + const UINT32 syncFlags = xf_keyboard_get_toggle_keys_state(xfc); + + WINPR_ASSERT(xfc); + return freerdp_input_send_synchronize_event(xfc->common.context.input, syncFlags); +} + +static void xf_keyboard_clear(xfContext* xfc) +{ + WINPR_ASSERT(xfc); + ZeroMemory(xfc->KeyboardState, sizeof(xfc->KeyboardState)); +} + +static BOOL xf_action_script_append(xfContext* xfc, const char* buffer, size_t size, + WINPR_ATTR_UNUSED void* user, const char* what, const char* arg) +{ + WINPR_ASSERT(xfc); + WINPR_UNUSED(what); + WINPR_UNUSED(arg); + + if (!buffer || (size == 0)) + return TRUE; + return ArrayList_Append(xfc->keyCombinations, buffer); +} + +static void xf_keyboard_action_script_free(xfContext* xfc) +{ + xf_event_action_script_free(xfc); + + if (xfc->keyCombinations) + { + ArrayList_Free(xfc->keyCombinations); + xfc->keyCombinations = nullptr; + xfc->actionScriptExists = FALSE; + } +} + +BOOL xf_keyboard_action_script_init(xfContext* xfc) +{ + WINPR_ASSERT(xfc); + + xf_keyboard_action_script_free(xfc); + xfc->keyCombinations = ArrayList_New(TRUE); + + if (!xfc->keyCombinations) + return FALSE; + + wObject* obj = ArrayList_Object(xfc->keyCombinations); + WINPR_ASSERT(obj); + obj->fnObjectNew = winpr_ObjectStringClone; + obj->fnObjectFree = winpr_ObjectStringFree; + + if (!run_action_script(xfc, "key", nullptr, xf_action_script_append, nullptr)) + return FALSE; + + return xf_event_action_script_init(xfc); +} + +static int xkb_cmp(const void* pva, const void* pvb) +{ + const struct x11_key_scancode_t* a = pva; + const struct x11_key_scancode_t* b = pvb; + + if (!a && !b) + return 0; + if (!a) + return 1; + if (!b) + return -1; + return strcmp(a->name, b->name); +} + +static int keysym_cmp(const void* pva, const void* pvb) +{ + const x11_keysym_scancode_t* a = pva; + const x11_keysym_scancode_t* b = pvb; + + if (!a && !b) + return 0; + if (!a) + return 1; + if (!b) + return -1; + if (a->keysym < b->keysym) + return -1; + if (a->keysym > b->keysym) + return 1; + return 0; +} + +static BOOL try_add(xfContext* xfc, size_t offset, const char* xkb_keyname) +{ + WINPR_ASSERT(xfc); + + struct x11_key_scancode_t key = { .name = xkb_keyname, + .sc = WINPR_ASSERTING_INT_CAST(uint32_t, offset) }; + + struct x11_key_scancode_t* found = + bsearch(&key, XKB_KEY_NAME_SCANCODE_TABLE, ARRAYSIZE(XKB_KEY_NAME_SCANCODE_TABLE), + sizeof(struct x11_key_scancode_t), xkb_cmp); + if (found) + { + WLog_Print(xfc->log, WLOG_DEBUG, + "%4s: keycode: 0x%02" PRIuz " -> rdp scancode: 0x%08" PRIx32 "", xkb_keyname, + offset, found->sc); + xfc->X11_KEYCODE_TO_VIRTUAL_SCANCODE[offset] = found->sc; + return TRUE; + } + + return FALSE; +} + +/* Keysym fallback for X11 forwarding where XKB key names don't match evdev */ +static DWORD xf_keysym_to_rdp_scancode(KeySym keysym) +{ + x11_keysym_scancode_t key = { .keysym = keysym, .sc = 0 }; + x11_keysym_scancode_t* found = + bsearch(&key, KEYSYM_SCANCODE_TABLE, ARRAYSIZE(KEYSYM_SCANCODE_TABLE), + sizeof(x11_keysym_scancode_t), keysym_cmp); + if (found) + return found->sc; + + return RDP_SCANCODE_UNKNOWN; +} + +static int load_map_from_xkbfile(xfContext* xfc) +{ + WINPR_ASSERT(xfc); + int status = -1; + + if (!xfc->display) + return -2; + + XkbDescPtr xkb = XkbGetMap(xfc->display, 0, XkbUseCoreKbd); + if (!xkb) + { + WLog_Print(xfc->log, WLOG_WARN, "XkbGetMap() == nullptr"); + return -3; + } + + const int rc = XkbGetNames(xfc->display, XkbKeyNamesMask, xkb); + if (rc != Success) + { + char buffer[64] = WINPR_C_ARRAY_INIT; + WLog_Print(xfc->log, WLOG_WARN, "XkbGetNames() != Success: [%s]", + x11_error_to_string(xfc, rc, buffer, sizeof(buffer))); + } + else + { + char xkb_keyname[XkbKeyNameLength + 1] = { 42, 42, 42, 42, + 0 }; /* end-of-string at index 5 */ + + WLog_Print(xfc->log, WLOG_TRACE, "XkbGetNames() == Success, min=%" PRIu8 ", max=%" PRIu8, + xkb->min_key_code, xkb->max_key_code); + for (size_t i = xkb->min_key_code; i < xkb->max_key_code; i++) + { + BOOL found = FALSE; + strncpy(xkb_keyname, xkb->names->keys[i].name, XkbKeyNameLength); + + WLog_Print(xfc->log, WLOG_TRACE, "KeyCode %" PRIuz " -> %s", i, xkb_keyname); + if (strnlen(xkb_keyname, ARRAYSIZE(xkb_keyname)) >= 1) + found = try_add(xfc, i, xkb_keyname); + + if (!found) + { +#if defined(__APPLE__) + const DWORD vkcode = + GetVirtualKeyCodeFromKeycode((UINT32)i - 8u, WINPR_KEYCODE_TYPE_APPLE); + xfc->X11_KEYCODE_TO_VIRTUAL_SCANCODE[i] = + GetVirtualScanCodeFromVirtualKeyCode(vkcode, WINPR_KBD_TYPE_IBM_ENHANCED); + found = TRUE; +#endif + } + if (!found) + { + WLog_Print(xfc->log, WLOG_WARN, + "%4s: keycode: 0x%02" PRIx32 " -> no RDP scancode found", xkb_keyname, + WINPR_ASSERTING_INT_CAST(UINT32, i)); + } + else + status = 0; + } + } + + XkbFreeKeyboard(xkb, 0, 1); + + return status; +} + +/* Keysym-based fallback for unmapped keycodes (e.g. X11 forwarding) */ +static BOOL load_map_from_keysym(xfContext* xfc) +{ + WINPR_ASSERT(xfc); + + if (!xfc->display) + return FALSE; + + BOOL mapped = FALSE; + int min_kc = 0; + int max_kc = 0; + XDisplayKeycodes(xfc->display, &min_kc, &max_kc); + + for (int i = min_kc; i <= max_kc; i++) + { + if ((i < 0) || ((size_t)i >= ARRAYSIZE(xfc->X11_KEYCODE_TO_VIRTUAL_SCANCODE))) + continue; + + if (xfc->X11_KEYCODE_TO_VIRTUAL_SCANCODE[i] != RDP_SCANCODE_UNKNOWN) + continue; + + const KeySym ks = XkbKeycodeToKeysym(xfc->display, (KeyCode)i, 0, 0); + if (ks == NoSymbol) + continue; + + const DWORD sc = xf_keysym_to_rdp_scancode(ks); + if (sc != RDP_SCANCODE_UNKNOWN) + { + xfc->X11_KEYCODE_TO_VIRTUAL_SCANCODE[i] = sc; + mapped = TRUE; + WLog_Print(xfc->log, WLOG_DEBUG, + "keycode: 0x%02x -> keysym: 0x%04lx -> rdp scancode: 0x%08" PRIx32 + " (keysym fallback)", + (unsigned)i, (unsigned long)ks, sc); + } + } + + return mapped; +} + +#if defined(WITH_VERBOSE_WINPR_ASSERT) +static BOOL compareKeySym(const x11_keysym_scancode_t* a, size_t counta, + const x11_keysym_scancode_t* b, size_t countb) +{ + WINPR_ASSERT(a || (counta == 0)); + WINPR_ASSERT(b || (countb == 0)); + + if (counta != countb) + return FALSE; + + for (size_t x = 0; x < counta; x++) + { + const x11_keysym_scancode_t* ca = &a[x]; + const x11_keysym_scancode_t* cb = &b[x]; + if (keysym_cmp(ca, cb) != 0) + { + WLog_ERR(TAG, "%" PRIuz "\ta=%lx, should be %lx", x, ca->keysym, cb->keysym); + WLog_ERR(TAG, "KEYSYM_SCANCODE_TABLE is not properly sorted!"); + return FALSE; + } + } + return TRUE; +} + +static BOOL compareKey(const struct x11_key_scancode_t* a, size_t counta, + const struct x11_key_scancode_t* b, size_t countb) +{ + WINPR_ASSERT(a || (counta == 0)); + WINPR_ASSERT(b || (countb == 0)); + + if (counta != countb) + return FALSE; + + for (size_t x = 0; x < counta; x++) + { + const struct x11_key_scancode_t* ca = &a[x]; + const struct x11_key_scancode_t* cb = &b[x]; + if (xkb_cmp(ca, cb) != 0) + { + WLog_ERR(TAG, "%" PRIuz "\ta=%s [%" PRIu32 "], should be %s [%" PRIu32 "]", x, a->name, + a->sc, b->name, b->sc); + WLog_ERR(TAG, "XKB_KEY_NAME_SCANCODE_TABLE is not properly sorted!"); + return FALSE; + } + } + return TRUE; +} +#endif + +BOOL xf_keyboard_init(xfContext* xfc) +{ + rdpSettings* settings = nullptr; + + WINPR_ASSERT(xfc); + +/* When assertions are enabled assert the lists are sorted. */ +#if defined(WITH_VERBOSE_WINPR_ASSERT) + { + x11_keysym_scancode_t copy[ARRAYSIZE(KEYSYM_SCANCODE_TABLE)] = WINPR_C_ARRAY_INIT; + memcpy(copy, KEYSYM_SCANCODE_TABLE, sizeof(copy)); + qsort(copy, ARRAYSIZE(copy), sizeof(x11_keysym_scancode_t), keysym_cmp); + + if (!compareKeySym(KEYSYM_SCANCODE_TABLE, ARRAYSIZE(KEYSYM_SCANCODE_TABLE), copy, + ARRAYSIZE(copy))) + return FALSE; + } + { + struct x11_key_scancode_t copy[ARRAYSIZE(XKB_KEY_NAME_SCANCODE_TABLE)] = WINPR_C_ARRAY_INIT; + memcpy(copy, XKB_KEY_NAME_SCANCODE_TABLE, sizeof(copy)); + qsort(copy, ARRAYSIZE(copy), sizeof(struct x11_key_scancode_t), xkb_cmp); + + if (!compareKey(XKB_KEY_NAME_SCANCODE_TABLE, ARRAYSIZE(XKB_KEY_NAME_SCANCODE_TABLE), copy, + ARRAYSIZE(copy))) + return FALSE; + } +#endif + + settings = xfc->common.context.settings; + WINPR_ASSERT(settings); + + xf_keyboard_clear(xfc); + + /* Layout detection algorithm: + * + * 1. If one was supplied, use that one + * 2. Try to determine current X11 keyboard layout + * 3. Try to determine default layout for current system language + * 4. Fall back to ENGLISH_UNITED_STATES + */ + UINT32 KeyboardLayout = freerdp_settings_get_uint32(settings, FreeRDP_KeyboardLayout); + if (KeyboardLayout == 0) + { + xf_detect_keyboard_layout_from_xkb(xfc->log, &KeyboardLayout); + if (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; + } + + const int rc = load_map_from_xkbfile(xfc); + + const BOOL keysym_mapped = load_map_from_keysym(xfc); + + if (rc != 0 && !keysym_mapped) + return FALSE; + + return xf_keyboard_update_modifier_map(xfc); +} + +void xf_keyboard_free(xfContext* xfc) +{ + xf_keyboard_modifier_map_free(xfc); + xf_keyboard_action_script_free(xfc); +} + +void xf_keyboard_key_press(xfContext* xfc, const XKeyEvent* event, KeySym keysym) +{ + BOOL last = 0; + + WINPR_ASSERT(xfc); + WINPR_ASSERT(event); + WINPR_ASSERT(event->keycode < ARRAYSIZE(xfc->KeyboardState)); + + last = xfc->KeyboardState[event->keycode]; + xfc->KeyboardState[event->keycode] = TRUE; + + if (xf_keyboard_handle_special_keys(xfc, keysym)) + return; + + xf_keyboard_send_key(xfc, TRUE, last, event); +} + +void xf_keyboard_key_release(xfContext* xfc, const XKeyEvent* event, KeySym keysym) +{ + WINPR_ASSERT(xfc); + WINPR_ASSERT(event); + WINPR_ASSERT(event->keycode < ARRAYSIZE(xfc->KeyboardState)); + + BOOL last = xfc->KeyboardState[event->keycode]; + xfc->KeyboardState[event->keycode] = FALSE; + xf_keyboard_handle_special_keys_release(xfc, keysym); + xf_keyboard_send_key(xfc, FALSE, last, event); +} + +static DWORD get_rdp_scancode_from_x11_keycode(xfContext* xfc, DWORD keycode) +{ + WINPR_ASSERT(xfc); + if (keycode >= ARRAYSIZE(xfc->X11_KEYCODE_TO_VIRTUAL_SCANCODE)) + { + WLog_ERR(TAG, "KeyCode %" PRIu32 " exceeds allowed value range [0,%" PRIuz "]", keycode, + ARRAYSIZE(xfc->X11_KEYCODE_TO_VIRTUAL_SCANCODE)); + return 0; + } + + const DWORD scancode = xfc->X11_KEYCODE_TO_VIRTUAL_SCANCODE[keycode]; + const DWORD remapped = freerdp_keyboard_remap_key(xfc->remap_table, scancode); + +#if defined(WITH_DEBUG_KBD) + { + const BOOL ex = RDP_SCANCODE_EXTENDED(scancode); + const DWORD sc = RDP_SCANCODE_CODE(scancode); + WLog_DBG(TAG, "x11 keycode: %02" PRIX32 " -> rdp code: [%04" PRIx16 "] %02" PRIX8 "%s", + keycode, scancode, sc, ex ? " extended" : ""); + } +#endif + + if (remapped != 0) + { +#if defined(WITH_DEBUG_KBD) + { + const BOOL ex = RDP_SCANCODE_EXTENDED(remapped); + const DWORD sc = RDP_SCANCODE_CODE(remapped); + WLog_DBG(TAG, + "x11 keycode: %02" PRIX32 " -> remapped rdp code: [%04" PRIx16 "] %02" PRIX8 + "%s", + keycode, remapped, sc, ex ? " extended" : ""); + } +#endif + return remapped; + } + + return scancode; +} + +void xf_keyboard_release_all_keypress(xfContext* xfc) +{ + WINPR_ASSERT(xfc); + + WINPR_STATIC_ASSERT(ARRAYSIZE(xfc->KeyboardState) <= UINT32_MAX); + for (size_t keycode = 0; keycode < ARRAYSIZE(xfc->KeyboardState); keycode++) + { + if (xfc->KeyboardState[keycode]) + { + const DWORD rdp_scancode = + get_rdp_scancode_from_x11_keycode(xfc, WINPR_ASSERTING_INT_CAST(UINT32, keycode)); + + // release tab before releasing the windows key. + // this stops the start menu from opening on unfocus event. + if (rdp_scancode == RDP_SCANCODE_LWIN) + freerdp_input_send_keyboard_event_ex(xfc->common.context.input, FALSE, FALSE, + RDP_SCANCODE_TAB); + + freerdp_input_send_keyboard_event_ex(xfc->common.context.input, FALSE, FALSE, + rdp_scancode); + xfc->KeyboardState[keycode] = FALSE; + } + } + xf_sync_kbd_state(xfc); +} + +static BOOL xf_keyboard_key_pressed(xfContext* xfc, KeySym keysym) +{ + KeyCode keycode = XKeysymToKeycode(xfc->display, keysym); + WINPR_ASSERT(keycode < ARRAYSIZE(xfc->KeyboardState)); + return xfc->KeyboardState[keycode]; +} + +void xf_keyboard_send_key(xfContext* xfc, BOOL down, BOOL repeat, const XKeyEvent* event) +{ + WINPR_ASSERT(xfc); + WINPR_ASSERT(event); + + rdpInput* input = xfc->common.context.input; + WINPR_ASSERT(input); + + const DWORD rdp_scancode = get_rdp_scancode_from_x11_keycode(xfc, event->keycode); + if (rdp_scancode == RDP_SCANCODE_PAUSE && !xf_keyboard_key_pressed(xfc, XK_Control_L) && + !xf_keyboard_key_pressed(xfc, XK_Control_R)) + { + /* Pause without Ctrl has to be sent as a series of keycodes + * in a single input PDU. Pause only happens on "press"; + * no code is sent on "release". + */ + if (down) + { + freerdp_input_send_keyboard_pause_event(input); + } + } + else + { + if (freerdp_settings_get_bool(xfc->common.context.settings, FreeRDP_UnicodeInput)) + { + wchar_t buffer[32] = WINPR_C_ARRAY_INIT; + int xwc = -1; + + switch (rdp_scancode) + { + case RDP_SCANCODE_RETURN: + break; + default: + { + XIM xim = XOpenIM(xfc->display, nullptr, nullptr, nullptr); + if (!xim) + { + WLog_WARN(TAG, "Failed to XOpenIM"); + } + else + { + XIC xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, + nullptr); + if (!xic) + { + WLog_WARN(TAG, "XCreateIC failed"); + } + else + { + KeySym ignore = WINPR_C_ARRAY_INIT; + Status return_status = 0; + XKeyEvent ev = *event; + ev.type = KeyPress; + xwc = XwcLookupString(xic, &ev, buffer, ARRAYSIZE(buffer), &ignore, + &return_status); + XDestroyIC(xic); + } + XCloseIM(xim); + } + } + break; + } + + if (xwc < 1) + { + if (rdp_scancode == RDP_SCANCODE_UNKNOWN) + WLog_ERR(TAG, "Unknown key with X keycode 0x%02" PRIx8 "", event->keycode); + else + freerdp_input_send_keyboard_event_ex(input, down, repeat, rdp_scancode); + } + else + { + char str[3 * ARRAYSIZE(buffer)] = WINPR_C_ARRAY_INIT; + // NOLINTNEXTLINE(concurrency-mt-unsafe) + const size_t rc = wcstombs(str, buffer, ARRAYSIZE(buffer)); + + WCHAR wbuffer[ARRAYSIZE(buffer)] = WINPR_C_ARRAY_INIT; + (void)ConvertUtf8ToWChar(str, wbuffer, rc); + freerdp_input_send_unicode_keyboard_event(input, down ? 0 : KBD_FLAGS_RELEASE, + wbuffer[0]); + } + } + else if (rdp_scancode == RDP_SCANCODE_UNKNOWN) + WLog_ERR(TAG, "Unknown key with X keycode 0x%02" PRIx8 "", event->keycode); + else + freerdp_input_send_keyboard_event_ex(input, down, repeat, rdp_scancode); + } +} + +static int xf_keyboard_read_keyboard_state(xfContext* xfc) +{ + int dummy = 0; + Window wdummy = 0; + UINT32 state = 0; + + if (!xfc->remote_app && xfc->window) + { + XQueryPointer(xfc->display, xfc->window->handle, &wdummy, &wdummy, &dummy, &dummy, &dummy, + &dummy, &state); + } + else + { + XQueryPointer(xfc->display, DefaultRootWindow(xfc->display), &wdummy, &wdummy, &dummy, + &dummy, &dummy, &dummy, &state); + } + + return WINPR_ASSERTING_INT_CAST(int, state); +} + +static int xf_keyboard_get_keymask(xfContext* xfc, KeySym keysym) +{ + int keysymMask = 0; + KeyCode keycode = XKeysymToKeycode(xfc->display, keysym); + + if (keycode == NoSymbol) + return 0; + + WINPR_ASSERT(xfc->modifierMap); + for (int modifierpos = 0; modifierpos < 8; modifierpos++) + { + int offset = xfc->modifierMap->max_keypermod * modifierpos; + + for (int key = 0; key < xfc->modifierMap->max_keypermod; key++) + { + if (xfc->modifierMap->modifiermap[offset + key] == keycode) + { + keysymMask |= 1 << modifierpos; + } + } + } + + return keysymMask; +} + +static BOOL xf_keyboard_get_key_state(xfContext* xfc, int state, KeySym keysym) +{ + int keysymMask = xf_keyboard_get_keymask(xfc, keysym); + + if (!keysymMask) + return FALSE; + + return (state & keysymMask) != 0; +} + +static BOOL xf_keyboard_set_key_state(xfContext* xfc, BOOL on, KeySym keysym) +{ + if (!xfc->xkbAvailable) + return FALSE; + + const int keysymMask = xf_keyboard_get_keymask(xfc, keysym); + + if (!keysymMask) + { + return FALSE; + } + + return XkbLockModifiers(xfc->display, XkbUseCoreKbd, + WINPR_ASSERTING_INT_CAST(uint32_t, keysymMask), + on ? WINPR_ASSERTING_INT_CAST(uint32_t, keysymMask) : 0); +} + +UINT32 xf_keyboard_get_toggle_keys_state(xfContext* xfc) +{ + UINT32 toggleKeysState = 0; + const int state = xf_keyboard_read_keyboard_state(xfc); + + if (xf_keyboard_get_key_state(xfc, state, XK_Scroll_Lock)) + toggleKeysState |= KBD_SYNC_SCROLL_LOCK; + + if (xf_keyboard_get_key_state(xfc, state, XK_Num_Lock)) + toggleKeysState |= KBD_SYNC_NUM_LOCK; + + if (xf_keyboard_get_key_state(xfc, state, XK_Caps_Lock)) + toggleKeysState |= KBD_SYNC_CAPS_LOCK; + + if (xf_keyboard_get_key_state(xfc, state, XK_Kana_Lock)) + toggleKeysState |= KBD_SYNC_KANA_LOCK; + + return toggleKeysState; +} + +static void xk_keyboard_update_modifier_keys(xfContext* xfc) +{ + const KeySym keysyms[] = { XK_Shift_L, XK_Shift_R, XK_Alt_L, XK_Alt_R, + XK_Control_L, XK_Control_R, XK_Super_L, XK_Super_R }; + + xf_keyboard_clear(xfc); + + const int state = xf_keyboard_read_keyboard_state(xfc); + + for (size_t i = 0; i < ARRAYSIZE(keysyms); i++) + { + if (xf_keyboard_get_key_state(xfc, state, keysyms[i])) + { + const KeyCode keycode = XKeysymToKeycode(xfc->display, keysyms[i]); + WINPR_ASSERT(keycode < ARRAYSIZE(xfc->KeyboardState)); + xfc->KeyboardState[keycode] = TRUE; + } + } +} + +void xf_keyboard_focus_in(xfContext* xfc) +{ + UINT32 state = 0; + Window w = None; + int d = 0; + int x = 0; + int y = 0; + + WINPR_ASSERT(xfc); + if (!xfc->display || !xfc->window) + return; + + rdpInput* input = xfc->common.context.input; + WINPR_ASSERT(input); + + const UINT32 syncFlags = xf_keyboard_get_toggle_keys_state(xfc); + freerdp_input_send_focus_in_event(input, WINPR_ASSERTING_INT_CAST(UINT16, syncFlags)); + xk_keyboard_update_modifier_keys(xfc); + + /* finish with a mouse pointer position like mstsc.exe if required */ + + if (xfc->remote_app || !xfc->window) + return; + + if (XQueryPointer(xfc->display, xfc->window->handle, &w, &w, &d, &d, &x, &y, &state)) + { + if ((x >= 0) && (x < xfc->window->width) && (y >= 0) && (y < xfc->window->height)) + { + xf_event_adjust_coordinates(xfc, &x, &y); + freerdp_client_send_button_event(&xfc->common, FALSE, PTR_FLAGS_MOVE, x, y); + } + } +} + +static BOOL action_script_run(xfContext* xfc, const char* buffer, size_t size, void* user, + const char* what, const char* arg) +{ + WINPR_UNUSED(xfc); + WINPR_UNUSED(what); + WINPR_UNUSED(arg); + WINPR_ASSERT(user); + int* pstatus = user; + + if (size == 0) + { + WLog_WARN(TAG, "ActionScript key: script did not return data"); + return FALSE; + } + + if (strcmp(buffer, "key-local") == 0) + *pstatus = 0; + else if (winpr_PathFileExists(buffer)) + { + FILE* fp = popen(buffer, "w"); + if (!fp) + { + WLog_ERR(TAG, "Failed to execute '%s'", buffer); + return FALSE; + } + + *pstatus = pclose(fp); + if (*pstatus < 0) + { + WLog_ERR(TAG, "Command '%s' returned %d", buffer, *pstatus); + return FALSE; + } + } + else + { + WLog_WARN(TAG, "ActionScript key: no such file '%s'", buffer); + return FALSE; + } + return TRUE; +} + +static int xf_keyboard_execute_action_script(xfContext* xfc, XF_MODIFIER_KEYS* mod, KeySym keysym) +{ + int status = 1; + BOOL match = FALSE; + char command[2048] = WINPR_C_ARRAY_INIT; + char combination[1024] = WINPR_C_ARRAY_INIT; + + if (!xfc->actionScriptExists) + return 1; + + if ((keysym == XK_Shift_L) || (keysym == XK_Shift_R) || (keysym == XK_Alt_L) || + (keysym == XK_Alt_R) || (keysym == XK_Control_L) || (keysym == XK_Control_R)) + { + return 1; + } + + const char* keyStr = XKeysymToString(keysym); + + if (keyStr == nullptr) + { + return 1; + } + + if (mod->Shift) + winpr_str_append("Shift", combination, sizeof(combination), "+"); + + if (mod->Ctrl) + winpr_str_append("Ctrl", combination, sizeof(combination), "+"); + + if (mod->Alt) + winpr_str_append("Alt", combination, sizeof(combination), "+"); + + if (mod->Super) + winpr_str_append("Super", combination, sizeof(combination), "+"); + + winpr_str_append(keyStr, combination, sizeof(combination), "+"); + + for (size_t i = 0; i < strnlen(combination, sizeof(combination)); i++) + combination[i] = WINPR_ASSERTING_INT_CAST(char, tolower(combination[i])); + + const size_t count = ArrayList_Count(xfc->keyCombinations); + + for (size_t index = 0; index < count; index++) + { + const char* keyCombination = (const char*)ArrayList_GetItem(xfc->keyCombinations, index); + + if (_stricmp(keyCombination, combination) == 0) + { + match = TRUE; + break; + } + } + + if (!match) + return 1; + + (void)sprintf_s(command, sizeof(command), "key %s", combination); + if (!run_action_script(xfc, command, nullptr, action_script_run, &status)) + return -1; + + return status; +} + +static int xk_keyboard_get_modifier_keys(xfContext* xfc, XF_MODIFIER_KEYS* mod) +{ + mod->LeftShift = xf_keyboard_key_pressed(xfc, XK_Shift_L); + mod->RightShift = xf_keyboard_key_pressed(xfc, XK_Shift_R); + mod->Shift = mod->LeftShift || mod->RightShift; + mod->LeftAlt = xf_keyboard_key_pressed(xfc, XK_Alt_L); + mod->RightAlt = xf_keyboard_key_pressed(xfc, XK_Alt_R); + mod->Alt = mod->LeftAlt || mod->RightAlt; + mod->LeftCtrl = xf_keyboard_key_pressed(xfc, XK_Control_L); + mod->RightCtrl = xf_keyboard_key_pressed(xfc, XK_Control_R); + mod->Ctrl = mod->LeftCtrl || mod->RightCtrl; + mod->LeftSuper = xf_keyboard_key_pressed(xfc, XK_Super_L); + mod->RightSuper = xf_keyboard_key_pressed(xfc, XK_Super_R); + mod->Super = mod->LeftSuper || mod->RightSuper; + return 0; +} + +BOOL xf_keyboard_handle_special_keys(xfContext* xfc, KeySym keysym) +{ + XF_MODIFIER_KEYS mod = WINPR_C_ARRAY_INIT; + xk_keyboard_get_modifier_keys(xfc, &mod); + + // remember state of RightCtrl to ungrab keyboard if next action is release of RightCtrl + // do not return anything such that the key could be used by client if ungrab is not the goal + if (keysym == XK_Control_R) + { + if (mod.RightCtrl && !xfc->wasRightCtrlAlreadyPressed) + { + // Right Ctrl is pressed, getting ready to ungrab + xfc->ungrabKeyboardWithRightCtrl = TRUE; + xfc->wasRightCtrlAlreadyPressed = TRUE; + } + } + else + { + // some other key has been pressed, abort ungrabbing + if (xfc->ungrabKeyboardWithRightCtrl) + xfc->ungrabKeyboardWithRightCtrl = FALSE; + } + + const int rc = xf_keyboard_execute_action_script(xfc, &mod, keysym); + if (rc < 0) + return FALSE; + if (rc == 0) + return TRUE; + + if (!xfc->remote_app && xfc->fullscreen_toggle) + { + switch (keysym) + { + case XK_Return: + if (mod.Ctrl && mod.Alt) + { + /* Ctrl-Alt-Enter: toggle full screen */ + WLog_INFO(TAG, "++ pressed, toggling fullscreen state..."); + xf_sync_kbd_state(xfc); + xf_toggle_fullscreen(xfc); + return TRUE; + } + break; + default: + break; + } + } + + if (mod.Ctrl && mod.Alt) + { + switch (keysym) + { + case XK_m: + case XK_M: + WLog_INFO(TAG, "++m pressed, minimizing RDP session..."); + xf_sync_kbd_state(xfc); + xf_minimize(xfc); + return TRUE; + case XK_c: + case XK_C: + /* Ctrl-Alt-C: toggle control */ + WLog_INFO(TAG, "++c pressed, toggle encomps control..."); + if (freerdp_client_encomsp_toggle_control(xfc->common.encomsp)) + return TRUE; + break; + case XK_d: + case XK_D: + /* ++d: disconnect session */ + WLog_INFO(TAG, "++d pressed, terminating RDP session..."); + xf_sync_kbd_state(xfc); + return freerdp_abort_connect_context(&xfc->common.context); + + default: + break; + } + } + +#if 0 /* set to 1 to enable multi touch gesture simulation via keyboard */ +#ifdef WITH_XRENDER + + if (!xfc->remote_app && freerdp_settings_get_bool(xfc->common.context.settings, FreeRDP_MultiTouchGestures)) + { + rdpContext* ctx = &xfc->common.context; + + if (mod.Ctrl && mod.Alt) + { + int pdx = 0; + int pdy = 0; + int zdx = 0; + int zdy = 0; + + switch (keysym) + { + case XK_0: /* Ctrl-Alt-0: Reset scaling and panning */{ + const UINT32 sessionWidth = freerdp_settings_get_uint32(xfc->common.context.settings, FreeRDP_DesktopWidth); + const UINT32 sessionHeight = freerdp_settings_get_uint32(xfc->common.context.settings, FreeRDP_DesktopHeight); + + xfc->scaledWidth = sessionWidth; + xfc->scaledHeight = sessionHeight; + xfc->offset_x = 0; + xfc->offset_y = 0; + + if (!xfc->fullscreen && (sessionWidth != xfc->window->width || + sessionHeight != xfc->window->height)) + { + xf_ResizeDesktopWindow(xfc, xfc->window, sessionWidth, sessionHeight); + } + + xf_draw_screen(xfc, 0, 0, sessionWidth, sessionHeight); + return TRUE; +} + + case XK_1: /* Ctrl-Alt-1: Zoom in */ + zdx = zdy = 10; + break; + + case XK_2: /* Ctrl-Alt-2: Zoom out */ + zdx = zdy = -10; + break; + + case XK_3: /* Ctrl-Alt-3: Pan left */ + pdx = -10; + break; + + case XK_4: /* Ctrl-Alt-4: Pan right */ + pdx = 10; + break; + + case XK_5: /* Ctrl-Alt-5: Pan up */ + pdy = -10; + break; + + case XK_6: /* Ctrl-Alt-6: Pan up */ + pdy = 10; + break; + } + + if (pdx != 0 || pdy != 0) + { + PanningChangeEventArgs e; + EventArgsInit(&e, "xfreerdp"); + e.dx = pdx; + e.dy = pdy; + PubSub_OnPanningChange(ctx->pubSub, xfc, &e); + return TRUE; + } + + if (zdx != 0 || zdy != 0) + { + ZoomingChangeEventArgs e; + EventArgsInit(&e, "xfreerdp"); + e.dx = zdx; + e.dy = zdy; + PubSub_OnZoomingChange(ctx->pubSub, xfc, &e); + return TRUE; + } + } + } + +#endif /* WITH_XRENDER defined */ +#endif /* pinch/zoom/pan simulation */ + return FALSE; +} + +void xf_keyboard_handle_special_keys_release(xfContext* xfc, KeySym keysym) +{ + if (keysym != XK_Control_R) + return; + + xfc->wasRightCtrlAlreadyPressed = FALSE; + + if (!xfc->ungrabKeyboardWithRightCtrl) + return; + + // all requirements for ungrab are fulfilled, ungrabbing now + XF_MODIFIER_KEYS mod = WINPR_C_ARRAY_INIT; + xk_keyboard_get_modifier_keys(xfc, &mod); + + if (!mod.RightCtrl) + { + if (!xfc->fullscreen) + { + xf_sync_kbd_state(xfc); + freerdp_client_encomsp_toggle_control(xfc->common.encomsp); + } + + xfc->mouse_active = FALSE; + xf_ungrab(xfc); + } + + // ungrabbed + xfc->ungrabKeyboardWithRightCtrl = FALSE; +} + +BOOL xf_keyboard_set_indicators(rdpContext* context, UINT16 led_flags) +{ + xfContext* xfc = (xfContext*)context; + xf_keyboard_set_key_state(xfc, led_flags & KBD_SYNC_SCROLL_LOCK, XK_Scroll_Lock); + xf_keyboard_set_key_state(xfc, led_flags & KBD_SYNC_NUM_LOCK, XK_Num_Lock); + xf_keyboard_set_key_state(xfc, led_flags & KBD_SYNC_CAPS_LOCK, XK_Caps_Lock); + xf_keyboard_set_key_state(xfc, led_flags & KBD_SYNC_KANA_LOCK, XK_Kana_Lock); + return TRUE; +} + +BOOL xf_keyboard_set_ime_status(rdpContext* context, UINT16 imeId, UINT32 imeState, + UINT32 imeConvMode) +{ + if (!context) + return FALSE; + + WLog_WARN(TAG, + "KeyboardSetImeStatus(unitId=%04" PRIx16 ", imeState=%08" PRIx32 + ", imeConvMode=%08" PRIx32 ") ignored", + imeId, imeState, imeConvMode); + return TRUE; +} + +BOOL xf_ungrab(xfContext* xfc) +{ + WINPR_ASSERT(xfc); + XUngrabKeyboard(xfc->display, CurrentTime); + XUngrabPointer(xfc->display, CurrentTime); + xfc->common.mouse_grabbed = FALSE; + return TRUE; +} diff --git a/third_party/FreeRDP/client/X11/xf_keyboard.h b/third_party/FreeRDP/client/X11/xf_keyboard.h new file mode 100644 index 0000000..a1954b5 --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_keyboard.h @@ -0,0 +1,47 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Keyboard Handling + * + * Copyright 2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_X11_XF_KEYBOARD_H +#define FREERDP_CLIENT_X11_XF_KEYBOARD_H + +#include + +#include "xf_client.h" +#include "xfreerdp.h" + +BOOL xf_keyboard_init(xfContext* xfc); +void xf_keyboard_free(xfContext* xfc); + +BOOL xf_keyboard_action_script_init(xfContext* xfc); + +void xf_keyboard_key_press(xfContext* xfc, const XKeyEvent* event, KeySym keysym); +void xf_keyboard_key_release(xfContext* xfc, const XKeyEvent* event, KeySym keysym); + +void xf_keyboard_release_all_keypress(xfContext* xfc); + +void xf_keyboard_focus_in(xfContext* xfc); +BOOL xf_keyboard_set_indicators(rdpContext* context, UINT16 led_flags); +BOOL xf_keyboard_set_ime_status(rdpContext* context, UINT16 imeId, UINT32 imeState, + UINT32 imeConvMode); + +BOOL xf_ungrab(xfContext* xfc); + +void xf_button_map_init(xfContext* xfc); + +#endif /* FREERDP_CLIENT_X11_XF_KEYBOARD_H */ diff --git a/third_party/FreeRDP/client/X11/xf_monitor.c b/third_party/FreeRDP/client/X11/xf_monitor.c new file mode 100644 index 0000000..8dac708 --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_monitor.c @@ -0,0 +1,672 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Monitor Handling + * + * Copyright 2011 Marc-Andre Moreau + * Copyright 2017 David Fort + * Copyright 2018 Kai Harms + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#define TAG CLIENT_TAG("x11") + +#ifdef WITH_XINERAMA +#include +#endif + +#ifdef WITH_XRANDR +#include +#include + +#if (RANDR_MAJOR * 100 + RANDR_MINOR) >= 105 +#define USABLE_XRANDR +#endif + +#endif + +#include "xf_monitor.h" +#include "xf_utils.h" + +/* See MSDN Section on Multiple Display Monitors: http://msdn.microsoft.com/en-us/library/dd145071 + */ + +int xf_list_monitors(xfContext* xfc) +{ + WINPR_UNUSED(xfc); + + int major = 0; + int minor = 0; + int nmonitors = 0; + Display* display = XOpenDisplay(nullptr); + + if (!display) + { + WLog_ERR(TAG, "failed to open X display"); + return -1; + } + +#if defined(USABLE_XRANDR) + + if (XRRQueryExtension(display, &major, &minor) && + (XRRQueryVersion(display, &major, &minor) == True) && (major * 100 + minor >= 105)) + { + XRRMonitorInfo* monitors = + XRRGetMonitors(display, DefaultRootWindow(display), 1, &nmonitors); + + for (int i = 0; i < nmonitors; i++) + { + printf(" %s [%d] %dx%d\t+%d+%d\n", monitors[i].primary ? "*" : " ", i, + monitors[i].width, monitors[i].height, monitors[i].x, monitors[i].y); + } + + XRRFreeMonitors(monitors); + } + else +#endif +#ifdef WITH_XINERAMA + if (XineramaQueryExtension(display, &major, &minor)) + { + if (XineramaIsActive(display)) + { + XineramaScreenInfo* screen = XineramaQueryScreens(display, &nmonitors); + + for (int i = 0; i < nmonitors; i++) + { + printf(" %s [%d] %hdx%hd\t+%hd+%hd\n", (i == 0) ? "*" : " ", i, + screen[i].width, screen[i].height, screen[i].x_org, screen[i].y_org); + } + + XFree(screen); + } + } + else +#else + { + Screen* screen = ScreenOfDisplay(display, DefaultScreen(display)); + printf(" * [0] %dx%d\t+0+0\n", WidthOfScreen(screen), HeightOfScreen(screen)); + } + +#endif + LogDynAndXCloseDisplay(xfc->log, display); + return 0; +} + +static BOOL xf_is_monitor_id_active(xfContext* xfc, UINT32 id) +{ + const rdpSettings* settings = nullptr; + + WINPR_ASSERT(xfc); + + settings = xfc->common.context.settings; + WINPR_ASSERT(settings); + + const UINT32 NumMonitorIds = freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds); + if (NumMonitorIds == 0) + return TRUE; + + for (UINT32 index = 0; index < NumMonitorIds; index++) + { + const UINT32* cur = freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorIds, index); + if (cur && (*cur == id)) + return TRUE; + } + + return FALSE; +} + +BOOL xf_detect_monitors(xfContext* xfc, UINT32* pMaxWidth, UINT32* pMaxHeight) +{ + BOOL rc = FALSE; + UINT32 monitor_index = 0; + BOOL primaryMonitorFound = FALSE; + int mouse_x = 0; + int mouse_y = 0; + int _dummy_i = 0; + Window _dummy_w = 0; + UINT32 current_monitor = 0; + Screen* screen = nullptr; +#if defined WITH_XINERAMA || defined WITH_XRANDR + int major = 0; + int minor = 0; +#endif +#if defined(USABLE_XRANDR) + XRRMonitorInfo* rrmonitors = nullptr; + BOOL useXRandr = FALSE; +#endif + + if (!xfc || !pMaxWidth || !pMaxHeight || !xfc->common.context.settings) + return FALSE; + + rdpSettings* settings = xfc->common.context.settings; + VIRTUAL_SCREEN* vscreen = &xfc->vscreen; + + *pMaxWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + *pMaxHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + + if (freerdp_settings_get_uint64(settings, FreeRDP_ParentWindowId) > 0) + { + xfc->workArea.x = 0; + xfc->workArea.y = 0; + xfc->workArea.width = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth); + xfc->workArea.height = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight); + return TRUE; + } + + /* get mouse location */ + if (!XQueryPointer(xfc->display, DefaultRootWindow(xfc->display), &_dummy_w, &_dummy_w, + &mouse_x, &mouse_y, &_dummy_i, &_dummy_i, (void*)&_dummy_i)) + mouse_x = mouse_y = 0; + +#if defined(USABLE_XRANDR) + + if (XRRQueryExtension(xfc->display, &major, &minor) && + (XRRQueryVersion(xfc->display, &major, &minor) == True) && (major * 100 + minor >= 105)) + { + int nmonitors = 0; + rrmonitors = XRRGetMonitors(xfc->display, DefaultRootWindow(xfc->display), 1, &nmonitors); + + if ((nmonitors < 0) || (nmonitors > 16)) + vscreen->nmonitors = 0; + else + vscreen->nmonitors = (UINT32)nmonitors; + + if (vscreen->nmonitors) + { + for (UINT32 i = 0; i < vscreen->nmonitors; i++) + { + MONITOR_INFO* cur_vscreen = &vscreen->monitors[i]; + const XRRMonitorInfo* cur_monitor = &rrmonitors[i]; + + cur_vscreen->area.left = WINPR_ASSERTING_INT_CAST(UINT16, cur_monitor->x); + cur_vscreen->area.top = WINPR_ASSERTING_INT_CAST(UINT16, cur_monitor->y); + cur_vscreen->area.right = + WINPR_ASSERTING_INT_CAST(UINT16, cur_monitor->x + cur_monitor->width - 1); + cur_vscreen->area.bottom = + WINPR_ASSERTING_INT_CAST(UINT16, cur_monitor->y + cur_monitor->height - 1); + cur_vscreen->primary = cur_monitor->primary > 0; + } + } + + useXRandr = TRUE; + } + else +#endif +#ifdef WITH_XINERAMA + if (XineramaQueryExtension(xfc->display, &major, &minor) && XineramaIsActive(xfc->display)) + { + int nmonitors = 0; + XineramaScreenInfo* screenInfo = XineramaQueryScreens(xfc->display, &nmonitors); + + if ((nmonitors < 0) || (nmonitors > 16)) + vscreen->nmonitors = 0; + else + vscreen->nmonitors = (UINT32)nmonitors; + + if (vscreen->nmonitors) + { + for (UINT32 i = 0; i < vscreen->nmonitors; i++) + { + MONITOR_INFO* monitor = &vscreen->monitors[i]; + monitor->area.left = WINPR_ASSERTING_INT_CAST(uint16_t, screenInfo[i].x_org); + monitor->area.top = WINPR_ASSERTING_INT_CAST(uint16_t, screenInfo[i].y_org); + monitor->area.right = WINPR_ASSERTING_INT_CAST( + uint16_t, screenInfo[i].x_org + screenInfo[i].width - 1); + monitor->area.bottom = WINPR_ASSERTING_INT_CAST( + uint16_t, screenInfo[i].y_org + screenInfo[i].height - 1); + } + } + + XFree(screenInfo); + } + else +#endif + { + /* Both XRandR and Xinerama are either not compiled in or are not working, do nothing. + */ + } + + rdpMonitor* rdpmonitors = calloc(vscreen->nmonitors + 1, sizeof(rdpMonitor)); + if (!rdpmonitors) + goto fail; + + xfc->fullscreenMonitors.top = 0; + xfc->fullscreenMonitors.bottom = 0; + xfc->fullscreenMonitors.left = 0; + xfc->fullscreenMonitors.right = 0; + + /* Determine which monitor that the mouse cursor is on */ + if (vscreen->monitors) + { + for (UINT32 i = 0; i < vscreen->nmonitors; i++) + { + const MONITOR_INFO* monitor = &vscreen->monitors[i]; + + if ((mouse_x >= monitor->area.left) && (mouse_x <= monitor->area.right) && + (mouse_y >= monitor->area.top) && (mouse_y <= monitor->area.bottom)) + { + current_monitor = i; + break; + } + } + } + + /* + Even for a single monitor, we need to calculate the virtual screen to support + window managers that do not implement all X window state hints. + + If the user did not request multiple monitor or is using workarea + without remote app, we force the number of monitors be 1 so later + the rest of the client don't end up using more monitors than the user desires. + */ + 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) + { + UINT32 id = current_monitor; + if (!freerdp_settings_set_pointer_len(settings, FreeRDP_MonitorIds, &id, 1)) + goto fail; + } + + /* Always sets number of monitors from command-line to just 1. + * If the monitor is invalid then we will default back to current monitor + * later as a fallback. So, there is no need to validate command-line entry here. + */ + if (!freerdp_settings_set_uint32(settings, FreeRDP_NumMonitorIds, 1)) + goto fail; + } + + /* WORKAROUND: With Remote Application Mode - using NET_WM_WORKAREA + * causes issues with the ability to fully size the window vertically + * (the bottom of the window area is never updated). So, we just set + * the workArea to match the full Screen width/height. + */ + if (freerdp_settings_get_bool(settings, FreeRDP_RemoteApplicationMode) || !xf_GetWorkArea(xfc)) + { + /* + if only 1 monitor is enabled, use monitor area + this is required in case of a screen composed of more than one monitor + but user did not enable multimonitor + */ + if ((freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds) == 1) && + (vscreen->nmonitors > current_monitor)) + { + MONITOR_INFO* monitor = vscreen->monitors + current_monitor; + + if (!monitor) + goto fail; + + xfc->workArea.x = monitor->area.left; + xfc->workArea.y = monitor->area.top; + xfc->workArea.width = monitor->area.right - monitor->area.left + 1; + xfc->workArea.height = monitor->area.bottom - monitor->area.top + 1; + } + else + { + xfc->workArea.x = 0; + xfc->workArea.y = 0; + xfc->workArea.width = WINPR_ASSERTING_INT_CAST(uint32_t, WidthOfScreen(xfc->screen)); + xfc->workArea.height = WINPR_ASSERTING_INT_CAST(uint32_t, HeightOfScreen(xfc->screen)); + } + } + + if (freerdp_settings_get_bool(settings, FreeRDP_Fullscreen)) + { + *pMaxWidth = WINPR_ASSERTING_INT_CAST(uint32_t, WidthOfScreen(xfc->screen)); + *pMaxHeight = WINPR_ASSERTING_INT_CAST(uint32_t, HeightOfScreen(xfc->screen)); + } + else if (freerdp_settings_get_bool(settings, FreeRDP_Workarea)) + { + *pMaxWidth = xfc->workArea.width; + *pMaxHeight = xfc->workArea.height; + } + else if (freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)) + { + /* If we have specific monitor information then limit the PercentScreen value + * to only affect the current monitor vs. the entire desktop + */ + if (vscreen->nmonitors > 0) + { + if (!vscreen->monitors) + goto fail; + + const MONITOR_INFO* vmonitor = &vscreen->monitors[current_monitor]; + const RECTANGLE_16* area = &vmonitor->area; + + *pMaxWidth = area->right - area->left + 1; + *pMaxHeight = area->bottom - area->top + 1; + + if (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseWidth)) + *pMaxWidth = ((area->right - area->left + 1) * + freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)) / + 100; + + if (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseHeight)) + *pMaxHeight = ((area->bottom - area->top + 1) * + freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)) / + 100; + } + else + { + *pMaxWidth = xfc->workArea.width; + *pMaxHeight = xfc->workArea.height; + + if (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseWidth)) + *pMaxWidth = (xfc->workArea.width * + freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen)) / + 100; + + if (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseHeight)) + *pMaxHeight = (xfc->workArea.height * + 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); + } + + /* Create array of all active monitors by taking into account monitors requested on the + * command-line */ + { + size_t nmonitors = 0; + { + UINT32 nr = 0; + + { + const UINT32* ids = freerdp_settings_get_pointer(settings, FreeRDP_MonitorIds); + if (ids) + nr = *ids; + } + for (UINT32 i = 0; i < vscreen->nmonitors; i++) + { + MONITOR_ATTRIBUTES* attrs = nullptr; + + if (!xf_is_monitor_id_active(xfc, i)) + continue; + + if (!vscreen->monitors) + goto fail; + + rdpMonitor* monitor = &rdpmonitors[nmonitors]; + const RECTANGLE_16* area = &vscreen->monitors[i].area; + monitor->x = + WINPR_ASSERTING_INT_CAST( + int32_t, + area->left*( + freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseWidth) + ? freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen) + : 100)) / + 100; + monitor->y = + WINPR_ASSERTING_INT_CAST( + int32_t, + area->top*( + freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseHeight) + ? freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen) + : 100)) / + 100; + monitor->width = + WINPR_ASSERTING_INT_CAST( + int32_t, + (area->right - area->left + 1) * + (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseWidth) + ? freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen) + : 100)) / + 100; + monitor->height = + WINPR_ASSERTING_INT_CAST( + int32_t, + (area->bottom - area->top + 1) * + (freerdp_settings_get_bool(settings, FreeRDP_PercentScreenUseHeight) + ? freerdp_settings_get_uint32(settings, FreeRDP_PercentScreen) + : 100)) / + 100; + monitor->orig_screen = i; +#ifdef USABLE_XRANDR + + if (useXRandr && rrmonitors) + { + Rotation rot = 0; + Rotation ret = 0; + attrs = &monitor->attributes; + attrs->physicalWidth = WINPR_ASSERTING_INT_CAST(uint32_t, rrmonitors[i].mwidth); + attrs->physicalHeight = + WINPR_ASSERTING_INT_CAST(uint32_t, rrmonitors[i].mheight); + ret = XRRRotations(xfc->display, WINPR_ASSERTING_INT_CAST(int, i), &rot); + switch (rot & ret) + { + + case RR_Rotate_90: + attrs->orientation = ORIENTATION_PORTRAIT; + break; + case RR_Rotate_180: + attrs->orientation = ORIENTATION_LANDSCAPE_FLIPPED; + break; + case RR_Rotate_270: + attrs->orientation = ORIENTATION_PORTRAIT_FLIPPED; + break; + case RR_Rotate_0: + default: + attrs->orientation = ORIENTATION_LANDSCAPE; + break; + } + } + +#endif + + if (i == nr) + { + monitor->is_primary = TRUE; + primaryMonitorFound = TRUE; + } + + nmonitors++; + } + } + + /* If no monitor is active(bogus command-line monitor specification) - then lets try to + * fallback to go fullscreen on the current monitor only */ + if ((nmonitors == 0) && (vscreen->nmonitors > 0)) + { + if (!vscreen->monitors) + goto fail; + + const MONITOR_INFO* vmonitor = &vscreen->monitors[current_monitor]; + const RECTANGLE_16* area = &vmonitor->area; + + const INT32 width = area->right - area->left + 1; + const INT32 height = area->bottom - area->top + 1; + const INT32 maxw = + ((width < 0) || ((UINT32)width < *pMaxWidth)) ? width : (INT32)*pMaxWidth; + const INT32 maxh = + ((height < 0) || ((UINT32)height < *pMaxHeight)) ? width : (INT32)*pMaxHeight; + + rdpMonitor* monitor = &rdpmonitors[0]; + if (!monitor) + goto fail; + + monitor->x = area->left; + monitor->y = area->top; + monitor->width = maxw; + monitor->height = maxh; + monitor->orig_screen = current_monitor; + nmonitors = 1; + } + + if (!freerdp_settings_set_uint32(settings, FreeRDP_MonitorCount, + WINPR_ASSERTING_INT_CAST(uint32_t, nmonitors))) + goto fail; + + /* If we have specific monitor information */ + if (freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount) > 0) + { + const rdpMonitor* cmonitor = &rdpmonitors[0]; + if (!cmonitor) + goto fail; + + /* Initialize bounding rectangle for all monitors */ + int vX = cmonitor->x; + int vY = cmonitor->y; + int vR = vX + cmonitor->width; + int vB = vY + cmonitor->height; + const int32_t corig = WINPR_ASSERTING_INT_CAST(int32_t, cmonitor->orig_screen); + xfc->fullscreenMonitors.top = corig; + xfc->fullscreenMonitors.bottom = corig; + xfc->fullscreenMonitors.left = corig; + xfc->fullscreenMonitors.right = corig; + + /* Calculate bounding rectangle around all monitors to be used AND + * also set the Xinerama indices which define left/top/right/bottom monitors. + */ + for (UINT32 i = 0; i < freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount); i++) + { + rdpMonitor* monitor = &rdpmonitors[i]; + + /* does the same as gdk_rectangle_union */ + const int destX = MIN(vX, monitor->x); + const int destY = MIN(vY, monitor->y); + const int destR = MAX(vR, monitor->x + monitor->width); + const int destB = MAX(vB, monitor->y + monitor->height); + const int32_t orig = WINPR_ASSERTING_INT_CAST(int32_t, monitor->orig_screen); + + if (vX != destX) + xfc->fullscreenMonitors.left = orig; + + if (vY != destY) + xfc->fullscreenMonitors.top = orig; + + if (vR != destR) + xfc->fullscreenMonitors.right = orig; + + if (vB != destB) + xfc->fullscreenMonitors.bottom = orig; + + vX = destX; + vY = destY; + vR = destR; + vB = destB; + } + + vscreen->area.left = 0; + const int r = vR - vX - 1; + vscreen->area.right = WINPR_ASSERTING_INT_CAST(UINT16, r); + vscreen->area.top = 0; + const int b = vB - vY - 1; + vscreen->area.bottom = WINPR_ASSERTING_INT_CAST(UINT16, b); + + if (freerdp_settings_get_bool(settings, FreeRDP_Workarea)) + { + INT64 bottom = 1LL * xfc->workArea.height + xfc->workArea.y - 1LL; + vscreen->area.top = WINPR_ASSERTING_INT_CAST(UINT16, xfc->workArea.y); + vscreen->area.bottom = WINPR_ASSERTING_INT_CAST(UINT16, bottom); + } + + if (!primaryMonitorFound) + { + /* If we have a command line setting we should use it */ + if (freerdp_settings_get_uint32(settings, FreeRDP_NumMonitorIds) > 0) + { + /* The first monitor is the first in the setting which should be used */ + UINT32* ids = freerdp_settings_get_pointer_array_writable( + settings, FreeRDP_MonitorIds, 0); + if (ids) + monitor_index = *ids; + } + else + { + /* This is the same as when we would trust the Xinerama results.. + and set the monitor index to zero. + The monitor listed with /list:monitor on index zero is always the primary + */ + screen = DefaultScreenOfDisplay(xfc->display); + monitor_index = + WINPR_ASSERTING_INT_CAST(uint32_t, XScreenNumberOfScreen(screen)); + } + + UINT32 j = monitor_index; + rdpMonitor* pmonitor = &rdpmonitors[j]; + + /* If the "default" monitor is not 0,0 use it */ + if ((pmonitor->x != 0) || (pmonitor->y != 0)) + { + pmonitor->is_primary = TRUE; + } + else + { + /* Lets try to see if there is a monitor with a 0,0 coordinate and use it as a + * fallback*/ + for (UINT32 i = 0; + i < freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount); i++) + { + rdpMonitor* monitor = &rdpmonitors[i]; + if (!primaryMonitorFound && monitor->x == 0 && monitor->y == 0) + { + monitor->is_primary = TRUE; + primaryMonitorFound = TRUE; + } + } + } + } + + /* Set the desktop width and height according to the bounding rectangle around the + * active monitors */ + *pMaxWidth = MIN(*pMaxWidth, (UINT32)vscreen->area.right - vscreen->area.left + 1); + *pMaxHeight = MIN(*pMaxHeight, (UINT32)vscreen->area.bottom - vscreen->area.top + 1); + } + + /* some 2008 server freeze at logon if we announce support for monitor layout PDU with + * #monitors < 2. So let's announce it only if we have more than 1 monitor. + */ + nmonitors = freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount); + if (nmonitors > 1) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_SupportMonitorLayoutPdu, TRUE)) + goto fail; + } + + rc = freerdp_settings_set_monitor_def_array_sorted(settings, rdpmonitors, nmonitors); + } + +fail: +#ifdef USABLE_XRANDR + + if (rrmonitors) + XRRFreeMonitors(rrmonitors); + +#endif + free(rdpmonitors); + return rc; +} diff --git a/third_party/FreeRDP/client/X11/xf_monitor.h b/third_party/FreeRDP/client/X11/xf_monitor.h new file mode 100644 index 0000000..5eea263 --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_monitor.h @@ -0,0 +1,33 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Monitor Handling + * + * Copyright 2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_X11_MONITOR_H +#define FREERDP_CLIENT_X11_MONITOR_H + +#include +#include +#include + +#include "xf_types.h" + +FREERDP_API int xf_list_monitors(xfContext* xfc); +FREERDP_API BOOL xf_detect_monitors(xfContext* xfc, UINT32* pMaxWidth, UINT32* pMaxHeight); +FREERDP_API void xf_monitors_free(xfContext* xfc); + +#endif /* FREERDP_CLIENT_X11_MONITOR_H */ diff --git a/third_party/FreeRDP/client/X11/xf_rail.c b/third_party/FreeRDP/client/X11/xf_rail.c new file mode 100644 index 0000000..bc6ee6f --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_rail.c @@ -0,0 +1,1420 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 RAIL + * + * Copyright 2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "xf_window.h" +#include "xf_rail.h" +#include "xf_utils.h" + +#include +#define TAG CLIENT_TAG("x11") + +static const char* error_code2str(UINT32 code) +{ +#define EVCASE(x) \ + case x: \ + return #x + switch (code) + { + EVCASE(RAIL_EXEC_S_OK); + EVCASE(RAIL_EXEC_E_HOOK_NOT_LOADED); + EVCASE(RAIL_EXEC_E_DECODE_FAILED); + EVCASE(RAIL_EXEC_E_NOT_IN_ALLOWLIST); + EVCASE(RAIL_EXEC_E_FILE_NOT_FOUND); + EVCASE(RAIL_EXEC_E_FAIL); + EVCASE(RAIL_EXEC_E_SESSION_LOCKED); + default: + return "RAIL_EXEC_E_UNKNOWN"; + } +#undef EVCASE +} + +static const char* movetype2str(UINT32 code) +{ +#define EVCASE(x) \ + case x: \ + return #x + + switch (code) + { + + EVCASE(RAIL_WMSZ_LEFT); + EVCASE(RAIL_WMSZ_RIGHT); + EVCASE(RAIL_WMSZ_TOP); + EVCASE(RAIL_WMSZ_TOPLEFT); + EVCASE(RAIL_WMSZ_TOPRIGHT); + EVCASE(RAIL_WMSZ_BOTTOM); + EVCASE(RAIL_WMSZ_BOTTOMLEFT); + EVCASE(RAIL_WMSZ_BOTTOMRIGHT); + EVCASE(RAIL_WMSZ_MOVE); + EVCASE(RAIL_WMSZ_KEYMOVE); + EVCASE(RAIL_WMSZ_KEYSIZE); + default: + return "RAIL_WMSZ_INVALID"; + } +#undef EVCASE +} + +struct xf_rail_icon +{ + long* data; + int length; +}; +typedef struct xf_rail_icon xfRailIcon; + +struct xf_rail_icon_cache +{ + xfRailIcon* entries; + UINT32 numCaches; + UINT32 numCacheEntries; + xfRailIcon scratch; +}; + +typedef struct +{ + xfContext* xfc; + const RECTANGLE_16* rect; +} rail_paint_fn_arg_t; + +BOOL xf_rail_enable_remoteapp_mode(xfContext* xfc) +{ + WINPR_ASSERT(xfc); + if (!xfc->remote_app) + { + rdpGdi* gdi = xfc->common.context.gdi; + WINPR_ASSERT(gdi); + + const BOOL old = gdi->suppressOutput; + gdi->suppressOutput = TRUE; + xfc->remote_app = TRUE; + xfc->drawable = xf_CreateDummyWindow(xfc); + xf_DestroyDesktopWindow(xfc, xfc->window); + xfc->window = nullptr; + + gdi->suppressOutput = old; + } + return TRUE; +} + +BOOL xf_rail_disable_remoteapp_mode(xfContext* xfc) +{ + WINPR_ASSERT(xfc); + if (xfc->remote_app) + { + rdpGdi* gdi = xfc->common.context.gdi; + WINPR_ASSERT(gdi); + + const BOOL old = gdi->suppressOutput; + gdi->suppressOutput = TRUE; + + xfc->remote_app = FALSE; + xf_DestroyDummyWindow(xfc, xfc->drawable); + xf_destroy_window(xfc); + xf_create_window(xfc); + xf_create_image(xfc); + + gdi->suppressOutput = old; + } + return TRUE; +} + +BOOL xf_rail_send_activate(xfContext* xfc, Window xwindow, BOOL enabled) +{ + RAIL_ACTIVATE_ORDER activate = WINPR_C_ARRAY_INIT; + xfAppWindow* appWindow = xf_AppWindowFromX11Window(xfc, xwindow); + + if (!appWindow) + return FALSE; + + if (enabled) + xf_SetWindowStyle(xfc, appWindow, appWindow->dwStyle, appWindow->dwExStyle); + + WINPR_ASSERT(appWindow->windowId <= UINT32_MAX); + activate.windowId = (UINT32)appWindow->windowId; + xf_rail_return_window(appWindow, FALSE); + + activate.enabled = enabled; + const UINT rc = xfc->rail->ClientActivate(xfc->rail, &activate); + return rc == CHANNEL_RC_OK; +} + +BOOL xf_rail_send_client_system_command(xfContext* xfc, UINT64 windowId, UINT16 command) +{ + WINPR_ASSERT(xfc); + WINPR_ASSERT(xfc->rail); + WINPR_ASSERT(xfc->rail->ClientSystemCommand); + if (windowId > UINT32_MAX) + return FALSE; + + const RAIL_SYSCOMMAND_ORDER syscommand = { .windowId = (UINT32)windowId, .command = command }; + const UINT rc = xfc->rail->ClientSystemCommand(xfc->rail, &syscommand); + return rc == CHANNEL_RC_OK; +} + +/** + * The position of the X window can become out of sync with the RDP window + * if the X window is moved locally by the window manager. In this event + * send an update to the RDP server informing it of the new window position + * and size. + */ +BOOL xf_rail_adjust_position(xfContext* xfc, xfAppWindow* appWindow) +{ + RAIL_WINDOW_MOVE_ORDER windowMove = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(xfc); + WINPR_ASSERT(appWindow); + if (!appWindow->is_mapped || appWindow->local_move.state != LMS_NOT_ACTIVE) + return FALSE; + + /* If current window position disagrees with RDP window position, send update to RDP server */ + if (appWindow->x != appWindow->windowOffsetX || appWindow->y != appWindow->windowOffsetY || + appWindow->width != (INT64)appWindow->windowWidth || + appWindow->height != (INT64)appWindow->windowHeight) + { + WINPR_ASSERT(appWindow->windowId <= UINT32_MAX); + windowMove.windowId = (UINT32)appWindow->windowId; + /* + * Calculate new size/position for the rail window(new values for + * windowOffsetX/windowOffsetY/windowWidth/windowHeight) on the server + */ + const INT16 left = WINPR_ASSERTING_INT_CAST(INT16, appWindow->resizeMarginLeft); + const INT16 right = WINPR_ASSERTING_INT_CAST(INT16, appWindow->resizeMarginRight); + const INT16 top = WINPR_ASSERTING_INT_CAST(INT16, appWindow->resizeMarginTop); + const INT16 bottom = WINPR_ASSERTING_INT_CAST(INT16, appWindow->resizeMarginBottom); + windowMove.left = WINPR_ASSERTING_INT_CAST(INT16, appWindow->x - left); + windowMove.top = WINPR_ASSERTING_INT_CAST(INT16, appWindow->y - top); + windowMove.right = WINPR_ASSERTING_INT_CAST(INT16, appWindow->x + appWindow->width + right); + windowMove.bottom = + WINPR_ASSERTING_INT_CAST(INT16, appWindow->y + appWindow->height + bottom); + const UINT rc = xfc->rail->ClientWindowMove(xfc->rail, &windowMove); + return rc == CHANNEL_RC_OK; + } + return TRUE; +} + +BOOL xf_rail_end_local_move(xfContext* xfc, xfAppWindow* appWindow) +{ + int x = 0; + int y = 0; + int child_x = 0; + int child_y = 0; + unsigned int mask = 0; + Window root_window = 0; + Window child_window = 0; + + WINPR_ASSERT(xfc); + WINPR_ASSERT(appWindow); + + if ((appWindow->local_move.direction == NET_WM_MOVERESIZE_MOVE_KEYBOARD) || + (appWindow->local_move.direction == NET_WM_MOVERESIZE_SIZE_KEYBOARD)) + { + RAIL_WINDOW_MOVE_ORDER windowMove = WINPR_C_ARRAY_INIT; + + /* + * For keyboard moves send and explicit update to RDP server + */ + WINPR_ASSERT(appWindow->windowId <= UINT32_MAX); + windowMove.windowId = (UINT32)appWindow->windowId; + /* + * Calculate new size/position for the rail window(new values for + * windowOffsetX/windowOffsetY/windowWidth/windowHeight) on the server + * + */ + const INT16 left = WINPR_ASSERTING_INT_CAST(INT16, appWindow->resizeMarginLeft); + const INT16 right = WINPR_ASSERTING_INT_CAST(INT16, appWindow->resizeMarginRight); + const INT16 top = WINPR_ASSERTING_INT_CAST(INT16, appWindow->resizeMarginTop); + const INT16 bottom = WINPR_ASSERTING_INT_CAST(INT16, appWindow->resizeMarginBottom); + const INT16 w = WINPR_ASSERTING_INT_CAST(INT16, appWindow->width + right); + const INT16 h = WINPR_ASSERTING_INT_CAST(INT16, appWindow->height + bottom); + windowMove.left = WINPR_ASSERTING_INT_CAST(INT16, appWindow->x - left); + windowMove.top = WINPR_ASSERTING_INT_CAST(INT16, appWindow->y - top); + windowMove.right = WINPR_ASSERTING_INT_CAST(INT16, appWindow->x + w); /* In the update to + RDP the position is one past the window */ + windowMove.bottom = WINPR_ASSERTING_INT_CAST(INT16, appWindow->y + h); + const UINT rc = xfc->rail->ClientWindowMove(xfc->rail, &windowMove); + if (rc != CHANNEL_RC_OK) + return FALSE; + } + + /* + * Simulate button up at new position to end the local move (per RDP spec) + */ + XQueryPointer(xfc->display, appWindow->handle, &root_window, &child_window, &x, &y, &child_x, + &child_y, &mask); + + /* only send the mouse coordinates if not a keyboard move or size */ + if ((appWindow->local_move.direction != NET_WM_MOVERESIZE_MOVE_KEYBOARD) && + (appWindow->local_move.direction != NET_WM_MOVERESIZE_SIZE_KEYBOARD)) + { + if (!freerdp_client_send_button_event(&xfc->common, FALSE, PTR_FLAGS_BUTTON1, x, y)) + return FALSE; + } + + /* + * Proactively update the RAIL window dimensions. There is a race condition where + * we can start to receive GDI orders for the new window dimensions before we + * receive the RAIL ORDER for the new window size. This avoids that race condition. + */ + appWindow->windowOffsetX = appWindow->x; + appWindow->windowOffsetY = appWindow->y; + appWindow->windowWidth = WINPR_ASSERTING_INT_CAST(uint32_t, appWindow->width); + appWindow->windowHeight = WINPR_ASSERTING_INT_CAST(uint32_t, appWindow->height); + appWindow->local_move.state = LMS_TERMINATING; + return TRUE; +} + +BOOL xf_rail_paint_surface(xfContext* xfc, UINT64 windowId, const RECTANGLE_16* rect) +{ + xfAppWindow* appWindow = xf_rail_get_window(xfc, windowId, FALSE); + + WINPR_ASSERT(rect); + + if (!appWindow) + return FALSE; + + const RECTANGLE_16 windowRect = { + .left = WINPR_ASSERTING_INT_CAST(UINT16, MAX(appWindow->x, 0)), + .top = WINPR_ASSERTING_INT_CAST(UINT16, MAX(appWindow->y, 0)), + .right = WINPR_ASSERTING_INT_CAST(UINT16, MAX(appWindow->x + appWindow->width, 0)), + .bottom = WINPR_ASSERTING_INT_CAST(UINT16, MAX(appWindow->y + appWindow->height, 0)) + }; + + REGION16 windowInvalidRegion = WINPR_C_ARRAY_INIT; + region16_init(&windowInvalidRegion); + if (!region16_union_rect(&windowInvalidRegion, &windowInvalidRegion, &windowRect)) + return FALSE; + if (!region16_intersect_rect(&windowInvalidRegion, &windowInvalidRegion, rect)) + return FALSE; + + if (!region16_is_empty(&windowInvalidRegion)) + { + const RECTANGLE_16* extents = region16_extents(&windowInvalidRegion); + + const RECTANGLE_16 updateRect = { + .left = WINPR_ASSERTING_INT_CAST(UINT16, extents->left - appWindow->x), + .top = WINPR_ASSERTING_INT_CAST(UINT16, extents->top - appWindow->y), + .right = WINPR_ASSERTING_INT_CAST(UINT16, extents->right - appWindow->x), + .bottom = WINPR_ASSERTING_INT_CAST(UINT16, extents->bottom - appWindow->y) + }; + + xf_UpdateWindowArea(xfc, appWindow, updateRect.left, updateRect.top, + updateRect.right - updateRect.left, updateRect.bottom - updateRect.top); + } + region16_uninit(&windowInvalidRegion); + xf_rail_return_window(appWindow, FALSE); + return TRUE; +} + +static BOOL rail_paint_fn(const void* pvkey, WINPR_ATTR_UNUSED void* value, void* pvarg) +{ + rail_paint_fn_arg_t* arg = pvarg; + WINPR_ASSERT(pvkey); + WINPR_ASSERT(arg); + + const UINT64 key = *(const UINT64*)pvkey; + return xf_rail_paint_surface(arg->xfc, key, arg->rect); +} + +BOOL xf_rail_paint(xfContext* xfc, const RECTANGLE_16* rect) +{ + rail_paint_fn_arg_t arg = { .xfc = xfc, .rect = rect }; + + WINPR_ASSERT(xfc); + WINPR_ASSERT(rect); + + if (!xfc->railWindows) + return TRUE; + + return HashTable_Foreach(xfc->railWindows, rail_paint_fn, &arg); +} + +#define window_state_log_style(log, windowState) \ + window_state_log_style_int((log), (windowState), __FILE__, __func__, __LINE__) +static void window_state_log_style_int(wLog* log, const WINDOW_STATE_ORDER* windowState, + const char* file, const char* fkt, size_t line) +{ + const DWORD log_level = WLOG_DEBUG; + + WINPR_ASSERT(log); + WINPR_ASSERT(windowState); + if (WLog_IsLevelActive(log, log_level)) + { + char buffer1[128] = WINPR_C_ARRAY_INIT; + char buffer2[128] = WINPR_C_ARRAY_INIT; + + window_styles_to_string(windowState->style, buffer1, sizeof(buffer1)); + window_styles_ex_to_string(windowState->extendedStyle, buffer2, sizeof(buffer2)); + WLog_PrintTextMessage(log, log_level, line, file, fkt, "windowStyle={%s, %s}", buffer1, + buffer2); + } +} + +/* RemoteApp Core Protocol Extension */ + +static BOOL xf_rail_window_common(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo, + const WINDOW_STATE_ORDER* windowState) +{ + BOOL rc = FALSE; + xfContext* xfc = (xfContext*)context; + + WINPR_ASSERT(xfc); + WINPR_ASSERT(orderInfo); + WINPR_ASSERT(windowState); + + UINT32 fieldFlags = orderInfo->fieldFlags; + BOOL position_or_size_updated = FALSE; + xfAppWindow* appWindow = xf_rail_get_window(xfc, orderInfo->windowId, FALSE); + + if (fieldFlags & WINDOW_ORDER_STATE_NEW) + { + if (!appWindow) + appWindow = xf_rail_add_window(xfc, orderInfo->windowId, windowState->windowOffsetX, + windowState->windowOffsetY, windowState->windowWidth, + windowState->windowHeight, 0xFFFFFFFF); + + if (!appWindow) + goto fail; + + appWindow->dwStyle = windowState->style; + appWindow->dwExStyle = windowState->extendedStyle; + window_state_log_style(xfc->log, windowState); + + /* Ensure window always gets a window title */ + if (fieldFlags & WINDOW_ORDER_FIELD_TITLE) + { + union + { + WCHAR* wc; + BYTE* b; + } cnv; + char* title = nullptr; + + cnv.b = windowState->titleInfo.string; + if (windowState->titleInfo.length == 0) + { + if (!(title = _strdup(""))) + { + WLog_ERR(TAG, "failed to duplicate empty window title string"); + /* error handled below */ + } + } + else if (!(title = ConvertWCharNToUtf8Alloc( + cnv.wc, windowState->titleInfo.length / sizeof(WCHAR), nullptr))) + { + WLog_ERR(TAG, "failed to convert window title"); + /* error handled below */ + } + + appWindow->title = title; + } + else + { + if (!(appWindow->title = _strdup("RdpRailWindow"))) + WLog_ERR(TAG, "failed to duplicate default window title string"); + } + + if (!appWindow->title) + goto fail; + + xf_AppWindowInit(xfc, appWindow); + } + + if (!appWindow) + return FALSE; + + /* Keep track of any position/size update so that we can force a refresh of the window */ + if ((fieldFlags & WINDOW_ORDER_FIELD_WND_OFFSET) || + (fieldFlags & WINDOW_ORDER_FIELD_WND_SIZE) || + (fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_OFFSET) || + (fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_SIZE) || + (fieldFlags & WINDOW_ORDER_FIELD_WND_CLIENT_DELTA) || + (fieldFlags & WINDOW_ORDER_FIELD_VIS_OFFSET) || + (fieldFlags & WINDOW_ORDER_FIELD_VISIBILITY)) + { + position_or_size_updated = TRUE; + } + + /* Update Parameters */ + + if (fieldFlags & WINDOW_ORDER_FIELD_WND_OFFSET) + { + appWindow->windowOffsetX = windowState->windowOffsetX; + appWindow->windowOffsetY = windowState->windowOffsetY; + } + + if (fieldFlags & WINDOW_ORDER_FIELD_WND_SIZE) + { + appWindow->windowWidth = windowState->windowWidth; + appWindow->windowHeight = windowState->windowHeight; + } + + if (fieldFlags & WINDOW_ORDER_FIELD_RESIZE_MARGIN_X) + { + appWindow->resizeMarginLeft = windowState->resizeMarginLeft; + appWindow->resizeMarginRight = windowState->resizeMarginRight; + } + + if (fieldFlags & WINDOW_ORDER_FIELD_RESIZE_MARGIN_Y) + { + appWindow->resizeMarginTop = windowState->resizeMarginTop; + appWindow->resizeMarginBottom = windowState->resizeMarginBottom; + } + + if (fieldFlags & WINDOW_ORDER_FIELD_OWNER) + { + appWindow->ownerWindowId = windowState->ownerWindowId; + } + + if (fieldFlags & WINDOW_ORDER_FIELD_STYLE) + { + appWindow->dwStyle = windowState->style; + appWindow->dwExStyle = windowState->extendedStyle; + window_state_log_style(xfc->log, windowState); + } + + if (fieldFlags & WINDOW_ORDER_FIELD_SHOW) + { + appWindow->showState = windowState->showState; + } + + if (fieldFlags & WINDOW_ORDER_FIELD_TITLE) + { + char* title = nullptr; + union + { + WCHAR* wc; + BYTE* b; + } cnv; + + cnv.b = windowState->titleInfo.string; + if (windowState->titleInfo.length == 0) + { + if (!(title = _strdup(""))) + { + WLog_ERR(TAG, "failed to duplicate empty window title string"); + goto fail; + } + } + else if (!(title = ConvertWCharNToUtf8Alloc( + cnv.wc, windowState->titleInfo.length / sizeof(WCHAR), nullptr))) + { + WLog_ERR(TAG, "failed to convert window title"); + goto fail; + } + + free(appWindow->title); + appWindow->title = title; + } + + if (fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_OFFSET) + { + appWindow->clientOffsetX = windowState->clientOffsetX; + appWindow->clientOffsetY = windowState->clientOffsetY; + } + + if (fieldFlags & WINDOW_ORDER_FIELD_CLIENT_AREA_SIZE) + { + appWindow->clientAreaWidth = windowState->clientAreaWidth; + appWindow->clientAreaHeight = windowState->clientAreaHeight; + } + + if (fieldFlags & WINDOW_ORDER_FIELD_WND_CLIENT_DELTA) + { + appWindow->windowClientDeltaX = windowState->windowClientDeltaX; + appWindow->windowClientDeltaY = windowState->windowClientDeltaY; + } + + if (fieldFlags & WINDOW_ORDER_FIELD_WND_RECTS) + { + if (appWindow->windowRects) + { + free(appWindow->windowRects); + appWindow->windowRects = nullptr; + } + + appWindow->numWindowRects = windowState->numWindowRects; + + if (appWindow->numWindowRects) + { + appWindow->windowRects = + (RECTANGLE_16*)calloc(appWindow->numWindowRects, sizeof(RECTANGLE_16)); + + if (!appWindow->windowRects) + goto fail; + + CopyMemory(appWindow->windowRects, windowState->windowRects, + appWindow->numWindowRects * sizeof(RECTANGLE_16)); + } + } + + if (fieldFlags & WINDOW_ORDER_FIELD_VIS_OFFSET) + { + appWindow->visibleOffsetX = windowState->visibleOffsetX; + appWindow->visibleOffsetY = windowState->visibleOffsetY; + } + + if (fieldFlags & WINDOW_ORDER_FIELD_VISIBILITY) + { + if (appWindow->visibilityRects) + { + free(appWindow->visibilityRects); + appWindow->visibilityRects = nullptr; + } + + appWindow->numVisibilityRects = windowState->numVisibilityRects; + + if (appWindow->numVisibilityRects) + { + appWindow->visibilityRects = + (RECTANGLE_16*)calloc(appWindow->numVisibilityRects, sizeof(RECTANGLE_16)); + + if (!appWindow->visibilityRects) + goto fail; + + CopyMemory(appWindow->visibilityRects, windowState->visibilityRects, + appWindow->numVisibilityRects * sizeof(RECTANGLE_16)); + } + } + + /* Update Window */ + + if (fieldFlags & WINDOW_ORDER_FIELD_STYLE) + { + } + + if (fieldFlags & WINDOW_ORDER_FIELD_SHOW) + { + xf_ShowWindow(xfc, appWindow, WINPR_ASSERTING_INT_CAST(UINT8, appWindow->showState)); + } + + if (fieldFlags & WINDOW_ORDER_FIELD_TITLE) + { + if (appWindow->title) + xf_SetWindowText(xfc, appWindow, appWindow->title); + } + + if (position_or_size_updated) + { + const INT32 visibilityRectsOffsetX = + (appWindow->visibleOffsetX - + (appWindow->clientOffsetX - appWindow->windowClientDeltaX)); + const INT32 visibilityRectsOffsetY = + (appWindow->visibleOffsetY - + (appWindow->clientOffsetY - appWindow->windowClientDeltaY)); + + /* + * The rail server like to set the window to a small size when it is minimized even though + * it is hidden in some cases this can cause the window not to restore back to its original + * size. Therefore we don't update our local window when that rail window state is minimized + */ + if (appWindow->rail_state != WINDOW_SHOW_MINIMIZED) + { + /* Redraw window area if already in the correct position */ + if (appWindow->x == (INT64)appWindow->windowOffsetX && + appWindow->y == (INT64)appWindow->windowOffsetY && + appWindow->width == (INT64)appWindow->windowWidth && + appWindow->height == (INT64)appWindow->windowHeight) + { + xf_UpdateWindowArea(xfc, appWindow, 0, 0, + WINPR_ASSERTING_INT_CAST(int, appWindow->windowWidth), + WINPR_ASSERTING_INT_CAST(int, appWindow->windowHeight)); + } + else + { + xf_MoveWindow(xfc, appWindow, appWindow->windowOffsetX, appWindow->windowOffsetY, + WINPR_ASSERTING_INT_CAST(int, appWindow->windowWidth), + WINPR_ASSERTING_INT_CAST(int, appWindow->windowHeight)); + } + + xf_SetWindowVisibilityRects( + xfc, appWindow, WINPR_ASSERTING_INT_CAST(uint32_t, visibilityRectsOffsetX), + WINPR_ASSERTING_INT_CAST(uint32_t, visibilityRectsOffsetY), + appWindow->visibilityRects, + WINPR_ASSERTING_INT_CAST(int, appWindow->numVisibilityRects)); + } + + if (appWindow->rail_state == WINDOW_SHOW_MAXIMIZED) + { + xf_SendClientEvent(xfc, appWindow->handle, xfc->NET_WM_STATE, 4, NET_WM_STATE_ADD, + xfc->NET_WM_STATE_MAXIMIZED_VERT, xfc->NET_WM_STATE_MAXIMIZED_HORZ, + 0, 0); + } + } + + if (fieldFlags & (WINDOW_ORDER_STATE_NEW | WINDOW_ORDER_FIELD_STYLE)) + xf_SetWindowStyle(xfc, appWindow, appWindow->dwStyle, appWindow->dwExStyle); + + /* We should only be using the visibility rects for shaping the window */ + /*if (fieldFlags & WINDOW_ORDER_FIELD_WND_RECTS) + { + xf_SetWindowRects(xfc, appWindow, appWindow->windowRects, appWindow->numWindowRects); + }*/ + rc = TRUE; +fail: + xf_rail_return_window(appWindow, FALSE); + return rc; +} + +static BOOL xf_rail_window_delete(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo) +{ + xfContext* xfc = (xfContext*)context; + WINPR_ASSERT(xfc); + return xf_rail_del_window(xfc, orderInfo->windowId); +} + +static xfRailIconCache* RailIconCache_New(rdpSettings* settings) +{ + xfRailIconCache* cache = calloc(1, sizeof(xfRailIconCache)); + + if (!cache) + return nullptr; + + cache->numCaches = freerdp_settings_get_uint32(settings, FreeRDP_RemoteAppNumIconCaches); + cache->numCacheEntries = + freerdp_settings_get_uint32(settings, FreeRDP_RemoteAppNumIconCacheEntries); + cache->entries = calloc(1ull * cache->numCaches * cache->numCacheEntries, sizeof(xfRailIcon)); + + if (!cache->entries) + { + WLog_ERR(TAG, "failed to allocate icon cache %" PRIu32 " x %" PRIu32 " entries", + cache->numCaches, cache->numCacheEntries); + free(cache); + return nullptr; + } + + return cache; +} + +static void RailIconCache_Free(xfRailIconCache* cache) +{ + if (!cache) + return; + + for (UINT32 i = 0; i < cache->numCaches * cache->numCacheEntries; i++) + { + xfRailIcon* cur = &cache->entries[i]; + free(cur->data); + } + + free(cache->scratch.data); + free(cache->entries); + free(cache); +} + +static xfRailIcon* RailIconCache_Lookup(xfRailIconCache* cache, UINT8 cacheId, UINT16 cacheEntry) +{ + WINPR_ASSERT(cache); + /* + * MS-RDPERP 2.2.1.2.3 Icon Info (TS_ICON_INFO) + * + * CacheId (1 byte): + * If the value is 0xFFFF, the icon SHOULD NOT be cached. + * + * Yes, the spec says "0xFFFF" in the 2018-03-16 revision, + * but the actual protocol field is 1-byte wide. + */ + if (cacheId == 0xFF) + return &cache->scratch; + + if (cacheId >= cache->numCaches) + return nullptr; + + if (cacheEntry >= cache->numCacheEntries) + return nullptr; + + return &cache->entries[cache->numCacheEntries * cacheId + cacheEntry]; +} + +/* + * _NET_WM_ICON format is defined as "array of CARDINAL" values which for + * Xlib must be represented with an array of C's "long" values. Note that + * "long" != "INT32" on 64-bit systems. Therefore we can't simply cast + * the bitmap data as (unsigned char*), we have to copy all the pixels. + * + * The first two values are width and height followed by actual color data + * in ARGB format (e.g., 0xFFFF0000L is opaque red), pixels are in normal, + * left-to-right top-down order. + */ +static BOOL convert_rail_icon(const ICON_INFO* iconInfo, xfRailIcon* railIcon) +{ + WINPR_ASSERT(iconInfo); + WINPR_ASSERT(railIcon); + + BYTE* nextPixel = nullptr; + long* pixels = nullptr; + BYTE* argbPixels = calloc(1ull * iconInfo->width * iconInfo->height, 4); + + if (!argbPixels) + goto error; + + if (!freerdp_image_copy_from_icon_data( + argbPixels, PIXEL_FORMAT_ARGB32, 0, 0, 0, + WINPR_ASSERTING_INT_CAST(UINT16, iconInfo->width), + WINPR_ASSERTING_INT_CAST(UINT16, iconInfo->height), iconInfo->bitsColor, + WINPR_ASSERTING_INT_CAST(UINT16, iconInfo->cbBitsColor), iconInfo->bitsMask, + WINPR_ASSERTING_INT_CAST(UINT16, iconInfo->cbBitsMask), iconInfo->colorTable, + WINPR_ASSERTING_INT_CAST(UINT16, iconInfo->cbColorTable), iconInfo->bpp)) + goto error; + + { + const UINT32 nelements = 2 + iconInfo->width * iconInfo->height; + pixels = realloc(railIcon->data, nelements * sizeof(long)); + + if (!pixels) + goto error; + + railIcon->data = pixels; + + railIcon->length = WINPR_ASSERTING_INT_CAST(int, nelements); + pixels[0] = iconInfo->width; + pixels[1] = iconInfo->height; + nextPixel = argbPixels; + + for (UINT32 i = 2; i < nelements; i++) + { + pixels[i] = FreeRDPReadColor(nextPixel, PIXEL_FORMAT_BGRA32); + nextPixel += 4; + } + } + + free(argbPixels); + return TRUE; +error: + free(argbPixels); + return FALSE; +} + +static void xf_rail_set_window_icon(xfContext* xfc, xfAppWindow* railWindow, xfRailIcon* icon, + BOOL replace) +{ + WINPR_ASSERT(xfc); + + LogDynAndXChangeProperty(xfc->log, xfc->display, railWindow->handle, xfc->NET_WM_ICON, + XA_CARDINAL, 32, replace ? PropModeReplace : PropModeAppend, + (unsigned char*)icon->data, icon->length); + LogDynAndXFlush(xfc->log, xfc->display); +} + +static BOOL xf_rail_window_icon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo, + const WINDOW_ICON_ORDER* windowIcon) +{ + BOOL rc = FALSE; + xfContext* xfc = (xfContext*)context; + BOOL replaceIcon = 0; + xfAppWindow* railWindow = xf_rail_get_window(xfc, orderInfo->windowId, FALSE); + + if (!railWindow) + return TRUE; + + WINPR_ASSERT(windowIcon); + WINPR_ASSERT(windowIcon->iconInfo); + xfRailIcon* icon = RailIconCache_Lookup( + xfc->railIconCache, WINPR_ASSERTING_INT_CAST(UINT8, windowIcon->iconInfo->cacheId), + WINPR_ASSERTING_INT_CAST(UINT16, windowIcon->iconInfo->cacheEntry)); + + if (!icon) + { + WLog_Print(xfc->log, WLOG_WARN, "failed to get icon from cache %02X:%04X", + windowIcon->iconInfo->cacheId, windowIcon->iconInfo->cacheEntry); + } + else if (!convert_rail_icon(windowIcon->iconInfo, icon)) + { + WLog_Print(xfc->log, WLOG_WARN, "failed to convert icon for window %08X", + orderInfo->windowId); + } + else + { + replaceIcon = !!(orderInfo->fieldFlags & WINDOW_ORDER_STATE_NEW); + xf_rail_set_window_icon(xfc, railWindow, icon, replaceIcon); + rc = TRUE; + } + xf_rail_return_window(railWindow, FALSE); + return rc; +} + +static BOOL xf_rail_window_cached_icon(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo, + const WINDOW_CACHED_ICON_ORDER* windowCachedIcon) +{ + BOOL rc = FALSE; + xfContext* xfc = (xfContext*)context; + WINPR_ASSERT(orderInfo); + + BOOL replaceIcon = 0; + xfAppWindow* railWindow = xf_rail_get_window(xfc, orderInfo->windowId, FALSE); + + if (!railWindow) + return TRUE; + + WINPR_ASSERT(windowCachedIcon); + + xfRailIcon* icon = RailIconCache_Lookup( + xfc->railIconCache, WINPR_ASSERTING_INT_CAST(UINT8, windowCachedIcon->cachedIcon.cacheId), + WINPR_ASSERTING_INT_CAST(UINT16, windowCachedIcon->cachedIcon.cacheEntry)); + + if (!icon) + { + WLog_Print(xfc->log, WLOG_WARN, "failed to get icon from cache %02X:%04X", + windowCachedIcon->cachedIcon.cacheId, windowCachedIcon->cachedIcon.cacheEntry); + } + else + { + replaceIcon = !!(orderInfo->fieldFlags & WINDOW_ORDER_STATE_NEW); + xf_rail_set_window_icon(xfc, railWindow, icon, replaceIcon); + rc = TRUE; + } + xf_rail_return_window(railWindow, FALSE); + return rc; +} + +static BOOL +xf_rail_notify_icon_common(WINPR_ATTR_UNUSED rdpContext* context, + const WINDOW_ORDER_INFO* orderInfo, + WINPR_ATTR_UNUSED const NOTIFY_ICON_STATE_ORDER* notifyIconState) +{ + WLog_ERR("TODO", "TODO: implement"); + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_VERSION) + { + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_TIP) + { + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_INFO_TIP) + { + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_NOTIFY_STATE) + { + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_ICON) + { + } + + if (orderInfo->fieldFlags & WINDOW_ORDER_CACHED_ICON) + { + } + + return TRUE; +} + +static BOOL xf_rail_notify_icon_create(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo, + const NOTIFY_ICON_STATE_ORDER* notifyIconState) +{ + return xf_rail_notify_icon_common(context, orderInfo, notifyIconState); +} + +static BOOL xf_rail_notify_icon_update(rdpContext* context, const WINDOW_ORDER_INFO* orderInfo, + const NOTIFY_ICON_STATE_ORDER* notifyIconState) +{ + return xf_rail_notify_icon_common(context, orderInfo, notifyIconState); +} + +static BOOL xf_rail_notify_icon_delete(WINPR_ATTR_UNUSED rdpContext* context, + WINPR_ATTR_UNUSED const WINDOW_ORDER_INFO* orderInfo) +{ + WLog_ERR("TODO", "TODO: implement"); + return TRUE; +} + +static BOOL +xf_rail_monitored_desktop(WINPR_ATTR_UNUSED rdpContext* context, + WINPR_ATTR_UNUSED const WINDOW_ORDER_INFO* orderInfo, + WINPR_ATTR_UNUSED const MONITORED_DESKTOP_ORDER* monitoredDesktop) +{ + const UINT32 mask = WINDOW_ORDER_TYPE_DESKTOP | WINDOW_ORDER_FIELD_DESKTOP_HOOKED | + WINDOW_ORDER_FIELD_DESKTOP_ARC_BEGAN | + WINDOW_ORDER_FIELD_DESKTOP_ARC_COMPLETED | + WINDOW_ORDER_FIELD_DESKTOP_ACTIVE_WND | WINDOW_ORDER_FIELD_DESKTOP_ZORDER; + xfContext* xfc = (xfContext*)context; + + if (!context || !orderInfo || !monitoredDesktop) + return FALSE; + + if ((orderInfo->fieldFlags & WINDOW_ORDER_TYPE_DESKTOP) == 0) + { + WLog_Print(xfc->log, WLOG_WARN, "WINDOW_ORDER_TYPE_DESKTOP flag missing!"); + return FALSE; + } + + if ((orderInfo->fieldFlags & WINDOW_ORDER_FIELD_DESKTOP_ARC_BEGAN) && + (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_DESKTOP_HOOKED)) + { + // discard all windows/notify icons + WLog_Print(xfc->log, WLOG_WARN, + "TODO: implement WINDOW_ORDER_FIELD_DESKTOP_ARC_BEGAN && " + "WINDOW_ORDER_FIELD_DESKTOP_HOOKED"); + } + else if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_DESKTOP_HOOKED) + { + WLog_Print(xfc->log, WLOG_WARN, "TODO: implement WINDOW_ORDER_FIELD_DESKTOP_HOOKED"); + } + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_DESKTOP_ARC_COMPLETED) + { + WLog_DBG(TAG, "WINDOW_ORDER_FIELD_DESKTOP_ARC_COMPLETED -> switch to RAILS mode"); + xf_rail_enable_remoteapp_mode(xfc); + } + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_DESKTOP_ACTIVE_WND) + { + WLog_Print(xfc->log, WLOG_WARN, "TODO: implement WINDOW_ORDER_FIELD_DESKTOP_ACTIVE_WND"); + } + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_DESKTOP_ZORDER) + { + WLog_Print(xfc->log, WLOG_WARN, "TODO: implement WINDOW_ORDER_FIELD_DESKTOP_ZORDER"); + } + if (orderInfo->fieldFlags & ~mask) + { + WLog_Print(xfc->log, WLOG_WARN, "unknown flags 0x%08" PRIx32 "!", orderInfo->fieldFlags); + } + return TRUE; +} + +static BOOL xf_rail_non_monitored_desktop(rdpContext* context, + WINPR_ATTR_UNUSED const WINDOW_ORDER_INFO* orderInfo) +{ + xfContext* xfc = (xfContext*)context; + const UINT32 mask = WINDOW_ORDER_TYPE_DESKTOP | WINDOW_ORDER_FIELD_DESKTOP_NONE; + + if (!context || !orderInfo) + return FALSE; + + if ((orderInfo->fieldFlags & WINDOW_ORDER_TYPE_DESKTOP) == 0) + { + WLog_Print(xfc->log, WLOG_WARN, "TODO: implement WINDOW_ORDER_TYPE_DESKTOP"); + return FALSE; + } + if (orderInfo->fieldFlags & WINDOW_ORDER_FIELD_DESKTOP_NONE) + { + WLog_Print(xfc->log, WLOG_WARN, "TODO: implement WINDOW_ORDER_FIELD_DESKTOP_NONE"); + } + if (orderInfo->fieldFlags & ~mask) + { + WLog_Print(xfc->log, WLOG_WARN, "unknown flags 0x%08" PRIx32 "!", orderInfo->fieldFlags); + } + + xf_rail_disable_remoteapp_mode(xfc); + return TRUE; +} + +static void xf_rail_register_update_callbacks(rdpUpdate* update) +{ + WINPR_ASSERT(update); + + rdpWindowUpdate* window = update->window; + WINPR_ASSERT(window); + + window->WindowCreate = xf_rail_window_common; + window->WindowUpdate = xf_rail_window_common; + window->WindowDelete = xf_rail_window_delete; + window->WindowIcon = xf_rail_window_icon; + window->WindowCachedIcon = xf_rail_window_cached_icon; + window->NotifyIconCreate = xf_rail_notify_icon_create; + window->NotifyIconUpdate = xf_rail_notify_icon_update; + window->NotifyIconDelete = xf_rail_notify_icon_delete; + window->MonitoredDesktop = xf_rail_monitored_desktop; + window->NonMonitoredDesktop = xf_rail_non_monitored_desktop; +} + +/* RemoteApp Virtual Channel Extension */ + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_rail_server_execute_result(RailClientContext* context, + const RAIL_EXEC_RESULT_ORDER* execResult) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(execResult); + + xfContext* xfc = (xfContext*)context->custom; + WINPR_ASSERT(xfc); + + if (execResult->execResult != RAIL_EXEC_S_OK) + { + WLog_ERR(TAG, "RAIL exec error: execResult=%s [0x%08" PRIx32 "] NtError=0x%X\n", + error_code2str(execResult->execResult), execResult->execResult, + execResult->rawResult); + freerdp_abort_connect_context(&xfc->common.context); + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_rail_server_system_param(WINPR_ATTR_UNUSED RailClientContext* context, + WINPR_ATTR_UNUSED const RAIL_SYSPARAM_ORDER* sysparam) +{ + // TODO: Actually apply param + WLog_ERR("TODO", "TODO: implement"); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_rail_server_handshake(RailClientContext* context, + WINPR_ATTR_UNUSED const RAIL_HANDSHAKE_ORDER* handshake) +{ + return client_rail_server_start_cmd(context); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +xf_rail_server_handshake_ex(RailClientContext* context, + WINPR_ATTR_UNUSED const RAIL_HANDSHAKE_EX_ORDER* handshakeEx) +{ + return client_rail_server_start_cmd(context); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_rail_server_local_move_size(RailClientContext* context, + const RAIL_LOCALMOVESIZE_ORDER* localMoveSize) +{ + int x = 0; + int y = 0; + int direction = 0; + Window child_window = 0; + WINPR_ASSERT(context); + WINPR_ASSERT(localMoveSize); + + xfContext* xfc = (xfContext*)context->custom; + xfAppWindow* appWindow = xf_rail_get_window(xfc, localMoveSize->windowId, FALSE); + + if (!appWindow) + return ERROR_INTERNAL_ERROR; + + WLog_Print(xfc->log, WLOG_TRACE, "%s [0x%08" PRIx32 "]", + movetype2str(localMoveSize->moveSizeType), localMoveSize->moveSizeType); + switch (localMoveSize->moveSizeType) + { + case RAIL_WMSZ_LEFT: + direction = NET_WM_MOVERESIZE_SIZE_LEFT; + x = localMoveSize->posX; + y = localMoveSize->posY; + break; + + case RAIL_WMSZ_RIGHT: + direction = NET_WM_MOVERESIZE_SIZE_RIGHT; + x = localMoveSize->posX; + y = localMoveSize->posY; + break; + + case RAIL_WMSZ_TOP: + direction = NET_WM_MOVERESIZE_SIZE_TOP; + x = localMoveSize->posX; + y = localMoveSize->posY; + break; + + case RAIL_WMSZ_TOPLEFT: + direction = NET_WM_MOVERESIZE_SIZE_TOPLEFT; + x = localMoveSize->posX; + y = localMoveSize->posY; + break; + + case RAIL_WMSZ_TOPRIGHT: + direction = NET_WM_MOVERESIZE_SIZE_TOPRIGHT; + x = localMoveSize->posX; + y = localMoveSize->posY; + break; + + case RAIL_WMSZ_BOTTOM: + direction = NET_WM_MOVERESIZE_SIZE_BOTTOM; + x = localMoveSize->posX; + y = localMoveSize->posY; + break; + + case RAIL_WMSZ_BOTTOMLEFT: + direction = NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT; + x = localMoveSize->posX; + y = localMoveSize->posY; + break; + + case RAIL_WMSZ_BOTTOMRIGHT: + direction = NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT; + x = localMoveSize->posX; + y = localMoveSize->posY; + break; + + case RAIL_WMSZ_MOVE: + direction = NET_WM_MOVERESIZE_MOVE; + XTranslateCoordinates(xfc->display, appWindow->handle, RootWindowOfScreen(xfc->screen), + localMoveSize->posX, localMoveSize->posY, &x, &y, &child_window); + break; + + case RAIL_WMSZ_KEYMOVE: + direction = NET_WM_MOVERESIZE_MOVE_KEYBOARD; + x = localMoveSize->posX; + y = localMoveSize->posY; + /* FIXME: local keyboard moves not working */ + break; + + case RAIL_WMSZ_KEYSIZE: + direction = NET_WM_MOVERESIZE_SIZE_KEYBOARD; + x = localMoveSize->posX; + y = localMoveSize->posY; + /* FIXME: local keyboard moves not working */ + break; + default: + break; + } + + if (localMoveSize->isMoveSizeStart) + xf_StartLocalMoveSize(xfc, appWindow, direction, x, y); + else + xf_EndLocalMoveSize(xfc, appWindow); + + xf_rail_return_window(appWindow, FALSE); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT xf_rail_server_min_max_info(RailClientContext* context, + const RAIL_MINMAXINFO_ORDER* minMaxInfo) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(minMaxInfo); + + xfContext* xfc = (xfContext*)context->custom; + xfAppWindow* appWindow = xf_rail_get_window(xfc, minMaxInfo->windowId, FALSE); + + if (appWindow) + { + xf_SetWindowMinMaxInfo(xfc, appWindow, minMaxInfo->maxWidth, minMaxInfo->maxHeight, + minMaxInfo->maxPosX, minMaxInfo->maxPosY, minMaxInfo->minTrackWidth, + minMaxInfo->minTrackHeight, minMaxInfo->maxTrackWidth, + minMaxInfo->maxTrackHeight); + } + xf_rail_return_window(appWindow, FALSE); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +xf_rail_server_language_bar_info(WINPR_ATTR_UNUSED RailClientContext* context, + WINPR_ATTR_UNUSED const RAIL_LANGBAR_INFO_ORDER* langBarInfo) +{ + WLog_ERR("TODO", "TODO: implement"); + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT +xf_rail_server_get_appid_response(WINPR_ATTR_UNUSED RailClientContext* context, + WINPR_ATTR_UNUSED const RAIL_GET_APPID_RESP_ORDER* getAppIdResp) +{ + WLog_ERR("TODO", "TODO: implement"); + return CHANNEL_RC_OK; +} + +static BOOL rail_window_key_equals(const void* key1, const void* key2) +{ + const UINT64* k1 = (const UINT64*)key1; + const UINT64* k2 = (const UINT64*)key2; + + if (!k1 || !k2) + return FALSE; + + return *k1 == *k2; +} + +static UINT32 rail_window_key_hash(const void* key) +{ + const UINT64* k1 = (const UINT64*)key; + return (UINT32)*k1; +} + +static void rail_window_free(void* value) +{ + xfAppWindow* appWindow = (xfAppWindow*)value; + + if (!appWindow) + return; + + xf_DestroyWindow(appWindow->xfc, appWindow); +} + +int xf_rail_init(xfContext* xfc, RailClientContext* rail) +{ + rdpContext* context = (rdpContext*)xfc; + + if (!xfc || !rail) + return 0; + + xfc->rail = rail; + xf_rail_register_update_callbacks(context->update); + rail->custom = (void*)xfc; + rail->ServerExecuteResult = xf_rail_server_execute_result; + rail->ServerSystemParam = xf_rail_server_system_param; + rail->ServerHandshake = xf_rail_server_handshake; + rail->ServerHandshakeEx = xf_rail_server_handshake_ex; + rail->ServerLocalMoveSize = xf_rail_server_local_move_size; + rail->ServerMinMaxInfo = xf_rail_server_min_max_info; + rail->ServerLanguageBarInfo = xf_rail_server_language_bar_info; + rail->ServerGetAppIdResponse = xf_rail_server_get_appid_response; + xfc->railWindows = HashTable_New(TRUE); + + if (!xfc->railWindows) + return 0; + + if (!HashTable_SetHashFunction(xfc->railWindows, rail_window_key_hash)) + goto fail; + { + wObject* obj = HashTable_KeyObject(xfc->railWindows); + obj->fnObjectEquals = rail_window_key_equals; + } + { + wObject* obj = HashTable_ValueObject(xfc->railWindows); + obj->fnObjectFree = rail_window_free; + } + xfc->railIconCache = RailIconCache_New(xfc->common.context.settings); + + if (!xfc->railIconCache) + { + } + + return 1; +fail: + HashTable_Free(xfc->railWindows); + return 0; +} + +int xf_rail_uninit(xfContext* xfc, RailClientContext* rail) +{ + WINPR_UNUSED(rail); + + if (xfc->rail) + { + xfc->rail->custom = nullptr; + xfc->rail = nullptr; + } + + if (xfc->railWindows) + { + HashTable_Free(xfc->railWindows); + xfc->railWindows = nullptr; + } + + if (xfc->railIconCache) + { + RailIconCache_Free(xfc->railIconCache); + xfc->railIconCache = nullptr; + } + + return 1; +} + +xfAppWindow* xf_rail_add_window(xfContext* xfc, UINT64 id, INT32 x, INT32 y, UINT32 width, + UINT32 height, UINT32 surfaceId) +{ + if (!xfc) + return nullptr; + + xfAppWindow* appWindow = (xfAppWindow*)calloc(1, sizeof(xfAppWindow)); + + if (!appWindow) + return nullptr; + + appWindow->xfc = xfc; + appWindow->windowId = id; + appWindow->surfaceId = surfaceId; + appWindow->x = x; + appWindow->y = y; + appWindow->width = WINPR_ASSERTING_INT_CAST(int, width); + appWindow->height = WINPR_ASSERTING_INT_CAST(int, height); + + xf_AppWindowsLock(xfc); + if (!xf_AppWindowCreate(xfc, appWindow)) + goto fail; + + if (!HashTable_Insert(xfc->railWindows, &appWindow->windowId, (void*)appWindow)) + goto fail; + return appWindow; +fail: + rail_window_free(appWindow); + xf_AppWindowsUnlock(xfc); + return nullptr; +} + +BOOL xf_rail_del_window(xfContext* xfc, UINT64 id) +{ + if (!xfc) + return FALSE; + + if (!xfc->railWindows) + return FALSE; + + xf_lock_x11(xfc); + const BOOL res = HashTable_Remove(xfc->railWindows, &id); + xf_unlock_x11(xfc); + return res; +} + +void xf_rail_return_windowFrom(xfAppWindow* window, BOOL alreadyLocked, const char* file, + const char* fkt, size_t line) +{ + if (!window) + return; + + if (alreadyLocked) + return; + + xfAppWindowsUnlockFrom(window->xfc, file, fkt, line); +} diff --git a/third_party/FreeRDP/client/X11/xf_rail.h b/third_party/FreeRDP/client/X11/xf_rail.h new file mode 100644 index 0000000..ba030f0 --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_rail.h @@ -0,0 +1,145 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 RAIL + * + * Copyright 2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_X11_RAIL_H +#define FREERDP_CLIENT_X11_RAIL_H + +#include + +#include +#include + +#include "xf_types.h" + +enum xf_localmove_state +{ + LMS_NOT_ACTIVE, + LMS_STARTING, + LMS_ACTIVE, + LMS_TERMINATING +}; + +struct xf_localmove +{ + int root_x; + int root_y; + int window_x; + int window_y; + enum xf_localmove_state state; + int direction; +}; +typedef struct xf_localmove xfLocalMove; + +struct xf_app_window +{ + xfContext* xfc; + + int x; + int y; + int width; + int height; + char* title; + + UINT32 surfaceId; + UINT64 windowId; + UINT32 ownerWindowId; + + UINT32 dwStyle; + UINT32 dwExStyle; + UINT32 showState; + + INT32 clientOffsetX; + INT32 clientOffsetY; + UINT32 clientAreaWidth; + UINT32 clientAreaHeight; + + INT32 windowOffsetX; + INT32 windowOffsetY; + INT32 windowClientDeltaX; + INT32 windowClientDeltaY; + UINT32 windowWidth; + UINT32 windowHeight; + UINT32 numWindowRects; + RECTANGLE_16* windowRects; + + INT32 visibleOffsetX; + INT32 visibleOffsetY; + UINT32 numVisibilityRects; + RECTANGLE_16* visibilityRects; + + UINT32 localWindowOffsetCorrX; + UINT32 localWindowOffsetCorrY; + + UINT32 resizeMarginLeft; + UINT32 resizeMarginTop; + UINT32 resizeMarginRight; + UINT32 resizeMarginBottom; + + GC gc; + int shmid; + Window handle; + Window* xfwin; + BOOL fullscreen; + BOOL decorations; + BOOL is_mapped; + BOOL is_transient; + xfLocalMove local_move; + BYTE rail_state; + BOOL maxVert; + BOOL maxHorz; + BOOL minimized; + BOOL rail_ignore_configure; + + Pixmap pixmap; + XImage* image; +}; +typedef struct xf_app_window xfAppWindow; +typedef struct xf_rail_icon_cache xfRailIconCache; + +BOOL xf_rail_paint(xfContext* xfc, const RECTANGLE_16* rect); +BOOL xf_rail_paint_surface(xfContext* xfc, UINT64 windowId, const RECTANGLE_16* rect); + +BOOL xf_rail_send_client_system_command(xfContext* xfc, UINT64 windowId, UINT16 command); +BOOL xf_rail_send_activate(xfContext* xfc, Window xwindow, BOOL enabled); +BOOL xf_rail_adjust_position(xfContext* xfc, xfAppWindow* appWindow); +BOOL xf_rail_end_local_move(xfContext* xfc, xfAppWindow* appWindow); +BOOL xf_rail_enable_remoteapp_mode(xfContext* xfc); +BOOL xf_rail_disable_remoteapp_mode(xfContext* xfc); + +xfAppWindow* xf_rail_add_window(xfContext* xfc, UINT64 id, INT32 x, INT32 y, UINT32 width, + UINT32 height, UINT32 surfaceId); + +#define xf_rail_return_window(window, alreadyLocked) \ + xf_rail_return_windowFrom((window), (alreadyLocked), __FILE__, __func__, __LINE__) +void xf_rail_return_windowFrom(xfAppWindow* window, BOOL alreadyLocked, const char* file, + const char* fkt, size_t line); + +#define xf_rail_get_window(xfc, id, alreadyLocked) \ + xf_rail_get_windowFrom((xfc), (id), (alreadyLocked), __FILE__, __func__, __LINE__) + +WINPR_ATTR_MALLOC(xf_rail_return_windowFrom, 1) +xfAppWindow* xf_rail_get_windowFrom(xfContext* xfc, UINT64 id, BOOL alreadyLocked, const char* file, + const char* fkt, size_t line); + +BOOL xf_rail_del_window(xfContext* xfc, UINT64 id); + +int xf_rail_init(xfContext* xfc, RailClientContext* rail); +int xf_rail_uninit(xfContext* xfc, RailClientContext* rail); + +#endif /* FREERDP_CLIENT_X11_RAIL_H */ diff --git a/third_party/FreeRDP/client/X11/xf_tsmf.c b/third_party/FreeRDP/client/X11/xf_tsmf.c new file mode 100644 index 0000000..a3add01 --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_tsmf.c @@ -0,0 +1,479 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Video Redirection + * + * Copyright 2010-2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "xf_tsmf.h" +#include "xf_utils.h" + +#ifdef WITH_XV + +#include +#include + +static long xv_port = 0; + +struct xf_xv_context +{ + XvPortID xv_port; + Atom xv_colorkey_atom; + int xv_image_size; + int xv_shmid; + char* xv_shmaddr; + UINT32* xv_pixfmts; +}; +typedef struct xf_xv_context xfXvContext; + +#define TAG CLIENT_TAG("x11") + +static BOOL xf_tsmf_is_format_supported(xfXvContext* xv, UINT32 pixfmt) +{ + if (!xv->xv_pixfmts) + return FALSE; + + for (int i = 0; xv->xv_pixfmts[i]; i++) + { + if (xv->xv_pixfmts[i] == pixfmt) + return TRUE; + } + + return FALSE; +} + +static int xf_tsmf_xv_video_frame_event(TsmfClientContext* tsmf, TSMF_VIDEO_FRAME_EVENT* event) +{ + int x = 0; + int y = 0; + UINT32 width = 0; + UINT32 height = 0; + BYTE* data1 = nullptr; + BYTE* data2 = nullptr; + UINT32 pixfmt = 0; + UINT32 xvpixfmt = 0; + XvImage* image = nullptr; + int colorkey = 0; + int numRects = 0; + xfContext* xfc = nullptr; + xfXvContext* xv = nullptr; + XRectangle* xrects = nullptr; + XShmSegmentInfo shminfo; + BOOL converti420yv12 = FALSE; + + if (!tsmf) + return -1; + + xfc = (xfContext*)tsmf->custom; + + if (!xfc) + return -1; + + xv = (xfXvContext*)xfc->xv_context; + + if (!xv) + return -1; + + if (xv->xv_port == 0) + return -1001; + + /* In case the player is minimized */ + if (event->x < -2048 || event->y < -2048 || event->numVisibleRects == 0) + { + return -1002; + } + + xrects = nullptr; + numRects = event->numVisibleRects; + + if (numRects > 0) + { + xrects = (XRectangle*)calloc(numRects, sizeof(XRectangle)); + + if (!xrects) + return -1; + + for (int i = 0; i < numRects; i++) + { + x = event->x + event->visibleRects[i].left; + y = event->y + event->visibleRects[i].top; + width = event->visibleRects[i].right - event->visibleRects[i].left; + height = event->visibleRects[i].bottom - event->visibleRects[i].top; + + xrects[i].x = WINPR_ASSERTING_INT_CAST(short, x); + xrects[i].y = WINPR_ASSERTING_INT_CAST(short, y); + xrects[i].width = width; + xrects[i].height = height; + } + } + + if (xv->xv_colorkey_atom != None) + { + XvGetPortAttribute(xfc->display, xv->xv_port, xv->xv_colorkey_atom, &colorkey); + LogDynAndXSetFunction(xfc->log, xfc->display, xfc->gc, GXcopy); + LogDynAndXSetFillStyle(xfc->log, xfc->display, xfc->gc, FillSolid); + LogDynAndXSetForeground(xfc->log, xfc->display, xfc->gc, colorkey); + + if (event->numVisibleRects < 1) + { + LogDynAndXSetClipMask(xfc->log, xfc->display, xfc->gc, None); + } + else + { + XFillRectangles(xfc->display, xfc->window->handle, xfc->gc, xrects, numRects); + } + } + else + { + LogDynAndXSetFunction(xfc->log, xfc->display, xfc->gc, GXcopy); + LogDynAndXSetFillStyle(xfc->log, xfc->display, xfc->gc, FillSolid); + + if (event->numVisibleRects < 1) + { + LogDynAndXSetClipMask(xfc->log, xfc->display, xfc->gc, None); + } + else + { + XSetClipRectangles(xfc->display, xfc->gc, 0, 0, xrects, numRects, YXBanded); + } + } + + pixfmt = event->framePixFmt; + + if (xf_tsmf_is_format_supported(xv, pixfmt)) + { + xvpixfmt = pixfmt; + } + else if (pixfmt == RDP_PIXFMT_I420 && xf_tsmf_is_format_supported(xv, RDP_PIXFMT_YV12)) + { + xvpixfmt = RDP_PIXFMT_YV12; + converti420yv12 = TRUE; + } + else if (pixfmt == RDP_PIXFMT_YV12 && xf_tsmf_is_format_supported(xv, RDP_PIXFMT_I420)) + { + xvpixfmt = RDP_PIXFMT_I420; + converti420yv12 = TRUE; + } + else + { + WLog_DBG(TAG, "pixel format 0x%" PRIX32 " not supported by hardware.", pixfmt); + free(xrects); + return -1003; + } + + image = XvShmCreateImage(xfc->display, xv->xv_port, WINPR_ASSERTING_INT_CAST(int, xvpixfmt), 0, + event->frameWidth, event->frameHeight, &shminfo); + + if (xv->xv_image_size != image->data_size) + { + if (xv->xv_image_size > 0) + { + shmdt(xv->xv_shmaddr); + shmctl(xv->xv_shmid, IPC_RMID, nullptr); + } + + xv->xv_image_size = image->data_size; + xv->xv_shmid = shmget(IPC_PRIVATE, image->data_size, IPC_CREAT | 0777); + xv->xv_shmaddr = shmat(xv->xv_shmid, 0, 0); + } + + shminfo.shmid = xv->xv_shmid; + shminfo.shmaddr = image->data = xv->xv_shmaddr; + shminfo.readOnly = FALSE; + + if (!XShmAttach(xfc->display, &shminfo)) + { + XFree(image); + free(xrects); + WLog_DBG(TAG, "XShmAttach failed."); + return -1004; + } + + /* The video driver may align each line to a different size + and we need to convert our original image data. */ + switch (pixfmt) + { + case RDP_PIXFMT_I420: + case RDP_PIXFMT_YV12: + /* Y */ + if (image->pitches[0] == event->frameWidth) + { + CopyMemory(image->data + image->offsets[0], event->frameData, + 1ULL * event->frameWidth * event->frameHeight); + } + else + { + for (int i = 0; i < event->frameHeight; i++) + { + CopyMemory(image->data + 1ULL * image->offsets[0] + + 1ULL * i * image->pitches[0], + event->frameData + 1ULL * i * event->frameWidth, event->frameWidth); + } + } + /* UV */ + /* Conversion between I420 and YV12 is to simply swap U and V */ + if (!converti420yv12) + { + data1 = event->frameData + 1ULL * event->frameWidth * event->frameHeight; + data2 = event->frameData + 1ULL * event->frameWidth * event->frameHeight + + 1ULL * event->frameWidth * event->frameHeight / 4; + } + else + { + data2 = event->frameData + 1ULL * event->frameWidth * event->frameHeight; + data1 = event->frameData + 1ULL * event->frameWidth * event->frameHeight + + 1ULL * event->frameWidth * event->frameHeight / 4; + image->id = pixfmt == RDP_PIXFMT_I420 ? RDP_PIXFMT_YV12 : RDP_PIXFMT_I420; + } + + if (image->pitches[1] * 2 == event->frameWidth) + { + CopyMemory(image->data + image->offsets[1], data1, + event->frameWidth * event->frameHeight / 4); + CopyMemory(image->data + image->offsets[2], data2, + event->frameWidth * event->frameHeight / 4); + } + else + { + for (int i = 0; i < event->frameHeight / 2; i++) + { + CopyMemory(image->data + 1ULL * image->offsets[1] + + 1ULL * i * image->pitches[1], + data1 + 1ULL * i * event->frameWidth / 2, event->frameWidth / 2); + CopyMemory(image->data + 1ULL * image->offsets[2] + + 1ULL * i * image->pitches[2], + data2 + 1ULL * i * event->frameWidth / 2, event->frameWidth / 2); + } + } + break; + + default: + if (image->data_size < 0) + { + free(xrects); + return -2000; + } + else + { + const size_t size = ((UINT32)image->data_size <= event->frameSize) + ? (UINT32)image->data_size + : event->frameSize; + CopyMemory(image->data, event->frameData, size); + } + break; + } + + XvShmPutImage(xfc->display, xv->xv_port, xfc->window->handle, xfc->gc, image, 0, 0, + image->width, image->height, event->x, event->y, event->width, event->height, + FALSE); + + if (xv->xv_colorkey_atom == None) + LogDynAndXSetClipMask(xfc->log, xfc->display, xfc->gc, None); + + LogDynAndXSync(xfc->log, xfc->display, FALSE); + + XShmDetach(xfc->display, &shminfo); + XFree(image); + + free(xrects); + + return 1; +} + +static int xf_tsmf_xv_init(xfContext* xfc, TsmfClientContext* tsmf) +{ + int ret = 0; + unsigned int version = 0; + unsigned int release = 0; + unsigned int event_base = 0; + unsigned int error_base = 0; + unsigned int request_base = 0; + unsigned int num_adaptors = 0; + xfXvContext* xv = nullptr; + XvAdaptorInfo* ai = nullptr; + XvAttribute* attr = nullptr; + XvImageFormatValues* fo = nullptr; + + if (xfc->xv_context) + return 1; /* context already created */ + + xv = (xfXvContext*)calloc(1, sizeof(xfXvContext)); + + if (!xv) + return -1; + + xfc->xv_context = xv; + + xv->xv_colorkey_atom = None; + xv->xv_image_size = 0; + xv->xv_port = xv_port; + + if (!XShmQueryExtension(xfc->display)) + { + WLog_DBG(TAG, "no xshm available."); + return -1; + } + + ret = + XvQueryExtension(xfc->display, &version, &release, &request_base, &event_base, &error_base); + + if (ret != Success) + { + WLog_DBG(TAG, "XvQueryExtension failed %d.", ret); + return -1; + } + + WLog_DBG(TAG, "version %u release %u", version, release); + + ret = XvQueryAdaptors(xfc->display, DefaultRootWindow(xfc->display), &num_adaptors, &ai); + + if (ret != Success) + { + WLog_DBG(TAG, "XvQueryAdaptors failed %d.", ret); + return -1; + } + + for (unsigned int i = 0; i < num_adaptors; i++) + { + WLog_DBG(TAG, "adapter port %lu-%lu (%s)", ai[i].base_id, + ai[i].base_id + ai[i].num_ports - 1, ai[i].name); + + if (xv->xv_port == 0 && i == num_adaptors - 1) + xv->xv_port = ai[i].base_id; + } + + if (num_adaptors > 0) + XvFreeAdaptorInfo(ai); + + if (xv->xv_port == 0) + { + WLog_DBG(TAG, "no adapter selected, video frames will not be processed."); + return -1; + } + WLog_DBG(TAG, "selected %ld", xv->xv_port); + + attr = XvQueryPortAttributes(xfc->display, xv->xv_port, &ret); + + unsigned int i = 0; + for (; i < (unsigned int)ret; i++) + { + if (strcmp(attr[i].name, "XV_COLORKEY") == 0) + { + static wLog* log = nullptr; + if (!log) + log = WLog_Get(TAG); + xv->xv_colorkey_atom = Logging_XInternAtom(log, xfc->display, "XV_COLORKEY", FALSE); + XvSetPortAttribute(xfc->display, xv->xv_port, xv->xv_colorkey_atom, + attr[i].min_value + 1); + break; + } + } + XFree(attr); + + WLog_DBG(TAG, "xf_tsmf_init: pixel format "); + + fo = XvListImageFormats(xfc->display, xv->xv_port, &ret); + + if (ret > 0) + { + xv->xv_pixfmts = (UINT32*)calloc((ret + 1), sizeof(UINT32)); + + size_t x = 0; + for (; x < (size_t)ret; x++) + { + xv->xv_pixfmts[x] = fo[x].id; + WLog_DBG(TAG, "%c%c%c%c ", ((char*)(xv->xv_pixfmts + x))[0], + ((char*)(xv->xv_pixfmts + x))[1], ((char*)(xv->xv_pixfmts + x))[2], + ((char*)(xv->xv_pixfmts + x))[3]); + } + xv->xv_pixfmts[x] = 0; + } + XFree(fo); + + if (tsmf) + { + xfc->tsmf = tsmf; + tsmf->custom = (void*)xfc; + + tsmf->FrameEvent = xf_tsmf_xv_video_frame_event; + } + + return 1; +} + +static int xf_tsmf_xv_uninit(xfContext* xfc, TsmfClientContext* tsmf) +{ + xfXvContext* xv = (xfXvContext*)xfc->xv_context; + + WINPR_UNUSED(tsmf); + if (xv) + { + if (xv->xv_image_size > 0) + { + shmdt(xv->xv_shmaddr); + shmctl(xv->xv_shmid, IPC_RMID, nullptr); + } + if (xv->xv_pixfmts) + { + free(xv->xv_pixfmts); + xv->xv_pixfmts = nullptr; + } + free(xv); + xfc->xv_context = nullptr; + } + + if (xfc->tsmf) + { + xfc->tsmf->custom = nullptr; + xfc->tsmf = nullptr; + } + + return 1; +} + +#endif + +int xf_tsmf_init(xfContext* xfc, TsmfClientContext* tsmf) +{ +#ifdef WITH_XV + return xf_tsmf_xv_init(xfc, tsmf); +#else + return 1; +#endif +} + +int xf_tsmf_uninit(xfContext* xfc, TsmfClientContext* tsmf) +{ +#ifdef WITH_XV + return xf_tsmf_xv_uninit(xfc, tsmf); +#else + return 1; +#endif +} diff --git a/third_party/FreeRDP/client/X11/xf_tsmf.h b/third_party/FreeRDP/client/X11/xf_tsmf.h new file mode 100644 index 0000000..63a973a --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_tsmf.h @@ -0,0 +1,29 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Video Redirection + * + * Copyright 2010-2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_X11_TSMF_H +#define FREERDP_CLIENT_X11_TSMF_H + +#include "xf_client.h" +#include "xfreerdp.h" + +int xf_tsmf_init(xfContext* xfc, TsmfClientContext* tsmf); +int xf_tsmf_uninit(xfContext* xfc, TsmfClientContext* tsmf); + +#endif /* FREERDP_CLIENT_X11_TSMF_H */ diff --git a/third_party/FreeRDP/client/X11/xf_types.h b/third_party/FreeRDP/client/X11/xf_types.h new file mode 100644 index 0000000..22072ea --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_types.h @@ -0,0 +1,42 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Client + * + * Copyright 2026 Thincast Technologies GmbH + * Copyright 2026 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +/* Forward declarations */ +typedef struct xf_context xfContext; + +typedef struct +{ + RECTANGLE_16 area; + RECTANGLE_16 workarea; + BOOL primary; +} MONITOR_INFO; + +typedef struct +{ + UINT32 nmonitors; + RECTANGLE_16 area; + RECTANGLE_16 workarea; + MONITOR_INFO* monitors; +} VIRTUAL_SCREEN; diff --git a/third_party/FreeRDP/client/X11/xf_utils.c b/third_party/FreeRDP/client/X11/xf_utils.c new file mode 100644 index 0000000..7f1142f --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_utils.c @@ -0,0 +1,769 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 helper utilities + * + * Copyright 2023 Armin Novak + * Copyringht 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 +#include +#include +#include + +#include "xf_utils.h" +#include "xfreerdp.h" + +#include +#include + +#define TAG CLIENT_TAG("xfreerdp.utils") + +static const DWORD log_level = WLOG_TRACE; + +static const char* error_to_string(wLog* log, Display* display, int error, char* buffer, + size_t size) +{ + WINPR_ASSERT(size <= INT32_MAX); + const int rc = XGetErrorText(display, error, buffer, (int)size); + if (rc != Success) + WLog_Print(log, WLOG_WARN, "XGetErrorText returned %d", rc); + return buffer; +} + +WINPR_ATTR_FORMAT_ARG(6, 7) +static void write_log(wLog* log, DWORD level, const char* fname, const char* fkt, size_t line, + WINPR_FORMAT_ARG const char* fmt, ...) +{ + va_list ap = WINPR_C_ARRAY_INIT; + va_start(ap, fmt); + WLog_PrintTextMessageVA(log, level, line, fname, fkt, fmt, ap); + va_end(ap); +} + +static BOOL ignore_code(int rc, size_t count, va_list ap) +{ + for (size_t x = 0; x < count; x++) + { + const int val = va_arg(ap, int); + if (rc == val) + return TRUE; + } + return FALSE; +} + +/* libx11 return codes are not really well documented, so checked against + * https://gitlab.freedesktop.org/xorg/lib/libx11.git */ +static int write_result_log_va(wLog* log, DWORD level, const char* fname, const char* fkt, + size_t line, Display* display, char* name, int rc, size_t count, + va_list ap) +{ + const BOOL ignore = ignore_code(rc, count, ap); + if (!ignore) + { + char buffer[128] = WINPR_C_ARRAY_INIT; + + if (WLog_IsLevelActive(log, level)) + { + WLog_PrintTextMessage(log, level, line, fname, fkt, "%s returned %s", name, + error_to_string(log, display, rc, buffer, sizeof(buffer))); + } + } + return rc; +} + +static int write_result_log_expect_success(wLog* log, DWORD level, const char* fname, + const char* fkt, size_t line, Display* display, + char* name, int rc) +{ + if (rc != Success) + { + va_list ap = WINPR_C_ARRAY_INIT; + (void)write_result_log_va(log, level, fname, fkt, line, display, name, rc, 0, ap); + } + return rc; +} + +static int write_result_log_expect_one(wLog* log, DWORD level, const char* fname, const char* fkt, + size_t line, Display* display, char* name, int rc) +{ + if (rc != 1) + { + va_list ap = WINPR_C_ARRAY_INIT; + (void)write_result_log_va(log, level, fname, fkt, line, display, name, rc, 0, ap); + } + return rc; +} + +char* Safe_XGetAtomNameEx(wLog* log, Display* display, Atom atom, const char* varname) +{ + WLog_Print(log, log_level, "XGetAtomName(%s, 0x%08lx)", varname, atom); + if (atom == None) + return strdup("Atom_None"); + return XGetAtomName(display, atom); +} + +Atom Logging_XInternAtom(wLog* log, Display* display, _Xconst char* atom_name, Bool only_if_exists) +{ + Atom atom = XInternAtom(display, atom_name, only_if_exists); + if (WLog_IsLevelActive(log, log_level)) + { + WLog_Print(log, log_level, "XInternAtom(%p, %s, %s) -> 0x%08" PRIx32, (void*)display, + atom_name, only_if_exists ? "True" : "False", + WINPR_CXX_COMPAT_CAST(UINT32, atom)); + } + return atom; +} + +const char* x11_error_to_string(xfContext* xfc, int error, char* buffer, size_t size) +{ + WINPR_ASSERT(xfc); + return error_to_string(xfc->log, xfc->display, error, buffer, size); +} + +int LogDynAndXChangeProperty_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window w, Atom property, Atom type, int format, + int mode, const unsigned char* data, int nelements) +{ + if (WLog_IsLevelActive(log, log_level)) + { + char* propstr = Safe_XGetAtomName(log, display, property); + char* typestr = Safe_XGetAtomName(log, display, type); + write_log(log, log_level, file, fkt, line, + "XChangeProperty(%p, %lu, %s [%lu], %s [%lu], %d, %d, %p, %d)", (void*)display, w, + propstr, property, typestr, type, format, mode, (const void*)data, nelements); + XFree(propstr); + XFree(typestr); + } + const int rc = XChangeProperty(display, w, property, type, format, mode, data, nelements); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XChangeProperty", + rc); +} + +int LogDynAndXDeleteProperty_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window w, Atom property) +{ + if (WLog_IsLevelActive(log, log_level)) + { + char* propstr = Safe_XGetAtomName(log, display, property); + write_log(log, log_level, file, fkt, line, "XDeleteProperty(%p, %lu, %s [%lu])", + (void*)display, w, propstr, property); + XFree(propstr); + } + const int rc = XDeleteProperty(display, w, property); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XDeleteProperty", + rc); +} + +int LogDynAndXConvertSelection_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Atom selection, Atom target, Atom property, + Window requestor, Time time) +{ + if (WLog_IsLevelActive(log, log_level)) + { + char* selectstr = Safe_XGetAtomName(log, display, selection); + char* targetstr = Safe_XGetAtomName(log, display, target); + char* propstr = Safe_XGetAtomName(log, display, property); + write_log(log, log_level, file, fkt, line, + "XConvertSelection(%p, %s [%lu], %s [%lu], %s [%lu], %lu, %lu)", (void*)display, + selectstr, selection, targetstr, target, propstr, property, requestor, time); + XFree(propstr); + XFree(targetstr); + XFree(selectstr); + } + const int rc = XConvertSelection(display, selection, target, property, requestor, time); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, + "XConvertSelection", rc); +} + +int LogDynAndXGetWindowProperty_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window w, Atom property, long long_offset, + long long_length, int c_delete, Atom req_type, + Atom* actual_type_return, int* actual_format_return, + unsigned long* nitems_return, unsigned long* bytes_after_return, + unsigned char** prop_return) +{ + if (WLog_IsLevelActive(log, log_level)) + { + char* propstr = Safe_XGetAtomName(log, display, property); + char* req_type_str = Safe_XGetAtomName(log, display, req_type); + write_log( + log, log_level, file, fkt, line, + "XGetWindowProperty(%p, %lu, %s [%lu], %ld, %ld, %d, %s [%lu], %p, %p, %p, %p, %p)", + (void*)display, w, propstr, property, long_offset, long_length, c_delete, req_type_str, + req_type, (void*)actual_type_return, (void*)actual_format_return, (void*)nitems_return, + (void*)bytes_after_return, (void*)prop_return); + XFree(propstr); + XFree(req_type_str); + } + const int rc = XGetWindowProperty(display, w, property, long_offset, long_length, c_delete, + req_type, actual_type_return, actual_format_return, + nitems_return, bytes_after_return, prop_return); + return write_result_log_expect_success(log, WLOG_WARN, file, fkt, line, display, + "XGetWindowProperty", rc); +} + +BOOL IsGnome(void) +{ + // NOLINTNEXTLINE(concurrency-mt-unsafe) + char* env = getenv("DESKTOP_SESSION"); + return (env != nullptr && strcmp(env, "gnome") == 0); +} + +BOOL run_action_script(xfContext* xfc, const char* what, const char* arg, fn_action_script_run fkt, + void* user) +{ + BOOL rc = FALSE; + FILE* keyScript = nullptr; + WINPR_ASSERT(xfc); + + rdpSettings* settings = xfc->common.context.settings; + WINPR_ASSERT(settings); + + const char* ActionScript = freerdp_settings_get_string(settings, FreeRDP_ActionScript); + + xfc->actionScriptExists = winpr_PathFileExists(ActionScript); + + if (!xfc->actionScriptExists) + { + WLog_DBG(TAG, "[ActionScript] no such script '%s'", ActionScript); + goto fail; + } + + { + char command[2048] = WINPR_C_ARRAY_INIT; + (void)sprintf_s(command, sizeof(command), "%s %s", ActionScript, what); + keyScript = popen(command, "r"); + + if (!keyScript) + { + WLog_ERR(TAG, "[ActionScript] Failed to execute '%s'", command); + goto fail; + } + + { + BOOL read_data = FALSE; + char buffer[2048] = WINPR_C_ARRAY_INIT; + while (fgets(buffer, sizeof(buffer), keyScript) != nullptr) + { + char* context = nullptr; + (void)strtok_s(buffer, "\n", &context); + + if (fkt) + { + if (!fkt(xfc, buffer, strnlen(buffer, sizeof(buffer)), user, what, arg)) + goto fail; + } + read_data = TRUE; + } + + rc = read_data; + } + if (!rc) + WLog_ERR(TAG, "[ActionScript] No data returned from command '%s'", command); + } +fail: + if (keyScript) + pclose(keyScript); + const BOOL res = rc || !xfc->actionScriptExists; + if (!rc) + xfc->actionScriptExists = FALSE; + return res; +} + +int LogDynAndXCopyArea_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Pixmap src, Window dest, GC gc, int src_x, int src_y, + unsigned int width, unsigned int height, int dest_x, int dest_y) +{ + if (WLog_IsLevelActive(log, log_level)) + { + XWindowAttributes attr = WINPR_C_ARRAY_INIT; + const Status rc = XGetWindowAttributes(display, dest, &attr); + + write_log(log, log_level, file, fkt, line, + "XCopyArea(%p, src: {%lu}, dest: [%d]{%lu, %lu, %d}, gc: {%p}, src_x: {%d}, " + "src_y: {%d}, " + "width: {%u}, " + "height: {%u}, dest_x: {%d}, dest_y: {%d})", + (void*)display, src, rc, dest, attr.root, attr.depth, (void*)gc, src_x, src_y, + width, height, dest_x, dest_y); + } + + if ((width == 0) || (height == 0)) + { + const DWORD lvl = WLOG_WARN; + if (WLog_IsLevelActive(log, lvl)) + write_log(log, lvl, file, fkt, line, "XCopyArea(width=%u, height=%u) !", width, height); + return Success; + } + + const int rc = XCopyArea(display, src, dest, gc, src_x, src_y, width, height, dest_x, dest_y); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XCopyArea", rc); +} + +int LogDynAndXPutImage_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Drawable d, GC gc, XImage* image, int src_x, int src_y, + int dest_x, int dest_y, unsigned int width, unsigned int height) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, + "XPutImage(%p, d: {%lu}, gc: {%p}, image: [%p]{%d}, src_x: {%d}, src_y: {%d}, " + "dest_x: {%d}, " + "dest_y: {%d}, width: {%u}, " + "height: {%u})", + (void*)display, d, (void*)gc, (void*)image, image ? image->depth : -1, src_x, + src_y, dest_x, dest_y, width, height); + } + + if ((width == 0) || (height == 0)) + { + const DWORD lvl = WLOG_WARN; + if (WLog_IsLevelActive(log, lvl)) + write_log(log, lvl, file, fkt, line, "XPutImage(width=%u, height=%u) !", width, height); + return Success; + } + + const int rc = XPutImage(display, d, gc, image, src_x, src_y, dest_x, dest_y, width, height); + return write_result_log_expect_success(log, WLOG_WARN, file, fkt, line, display, "XPutImage", + rc); +} + +/* be careful here. + * XSendEvent returns Status, but implementation always returns 1 + */ +Status LogDynAndXSendEvent_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window w, int propagate, long event_mask, + XEvent* event_send) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, + "XSendEvent(d: {%p}, w: {%lu}, propagate: {%d}, event_mask: {%ld}, " + "event_send: [%p]{TODO})", + (void*)display, w, propagate, event_mask, (void*)event_send); + } + + const int rc = XSendEvent(display, w, propagate, event_mask, event_send); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XSendEvent", rc); +} + +int LogDynAndXFlush_ex(wLog* log, const char* file, const char* fkt, size_t line, Display* display) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XFlush(%p)", (void*)display); + } + + const int rc = XFlush(display); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XFlush", rc); +} + +Window LogDynAndXGetSelectionOwner_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Atom selection) +{ + if (WLog_IsLevelActive(log, log_level)) + { + char* selectionstr = Safe_XGetAtomName(log, display, selection); + write_log(log, log_level, file, fkt, line, "XGetSelectionOwner(%p, %s)", (void*)display, + selectionstr); + XFree(selectionstr); + } + return XGetSelectionOwner(display, selection); +} + +int LogDynAndXDestroyWindow_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window window) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XDestroyWindow(%p, %lu)", (void*)display, + window); + } + const int rc = XDestroyWindow(display, window); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XDestroyWindow", + rc); +} + +int LogDynAndXSync_ex(wLog* log, const char* file, const char* fkt, size_t line, Display* display, + Bool discard) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XSync(%p, %d)", (void*)display, discard); + } + const int rc = XSync(display, discard); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XSync", rc); +} + +int LogDynAndXChangeWindowAttributes_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window window, unsigned long valuemask, + XSetWindowAttributes* attributes) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XChangeWindowAttributes(%p, %lu, 0x%08lu, %p)", + (void*)display, window, valuemask, (void*)attributes); + } + const int rc = XChangeWindowAttributes(display, window, valuemask, attributes); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, + "XChangeWindowAttributes", rc); +} + +int LogDynAndXSetTransientForHint_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window window, Window prop_window) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XSetTransientForHint(%p, %lu, %lu)", + (void*)display, window, prop_window); + } + const int rc = XSetTransientForHint(display, window, prop_window); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, + "XSetTransientForHint", rc); +} + +int LogDynAndXCloseDisplay_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XCloseDisplay(%p)", (void*)display); + } + const int rc = XCloseDisplay(display); + return write_result_log_expect_success(log, WLOG_WARN, file, fkt, line, display, + "XCloseDisplay", rc); +} + +XImage* LogDynAndXCreateImage_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Visual* visual, unsigned int depth, int format, + int offset, char* data, unsigned int width, unsigned int height, + int bitmap_pad, int bytes_per_line) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XCreateImage(%p)", (void*)display); + } + return XCreateImage(display, visual, depth, format, offset, data, width, height, bitmap_pad, + bytes_per_line); +} + +Window LogDynAndXCreateWindow_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window parent, int x, int y, unsigned int width, + unsigned int height, unsigned int border_width, int depth, + unsigned int c_class, Visual* visual, unsigned long valuemask, + XSetWindowAttributes* attributes) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XCreateWindow(%p)", (void*)display); + } + return XCreateWindow(display, parent, x, y, width, height, border_width, depth, c_class, visual, + valuemask, attributes); +} + +GC LogDynAndXCreateGC_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Drawable d, unsigned long valuemask, XGCValues* values) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XCreateGC(%p)", (void*)display); + } + return XCreateGC(display, d, valuemask, values); +} + +int LogDynAndXFreeGC_ex(wLog* log, const char* file, const char* fkt, size_t line, Display* display, + GC gc) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XFreeGC(%p)", (void*)display); + } + const int rc = XFreeGC(display, gc); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XFreeGC", rc); +} + +Pixmap LogDynAndXCreatePixmap_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Drawable d, unsigned int width, + unsigned int height, unsigned int depth) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XCreatePixmap(%p, 0x%08lu, %u, %u, %u)", + (void*)display, d, width, height, depth); + } + return XCreatePixmap(display, d, width, height, depth); +} + +int LogDynAndXFreePixmap_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Pixmap pixmap) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XFreePixmap(%p)", (void*)display); + } + const int rc = XFreePixmap(display, pixmap); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XFreePixmap", rc); +} + +int LogDynAndXSetSelectionOwner_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Atom selection, Window owner, Time time) +{ + if (WLog_IsLevelActive(log, log_level)) + { + char* selectionstr = Safe_XGetAtomName(log, display, selection); + write_log(log, log_level, file, fkt, line, "XSetSelectionOwner(%p, %s, 0x%08lu, %lu)", + (void*)display, selectionstr, owner, time); + XFree(selectionstr); + } + const int rc = XSetSelectionOwner(display, selection, owner, time); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, + "XSetSelectionOwner", rc); +} + +int LogDynAndXSetForeground_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, GC gc, unsigned long foreground) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XSetForeground(%p, %p, 0x%08lu)", + (void*)display, (void*)gc, foreground); + } + const int rc = XSetForeground(display, gc, foreground); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XSetForeground", + rc); +} + +int LogDynAndXMoveWindow_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window w, int x, int y) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XMoveWindow(%p, 0x%08lu, %d, %d)", + (void*)display, w, x, y); + } + const int rc = XMoveWindow(display, w, x, y); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XMoveWindow", rc); +} + +int LogDynAndXSetFillStyle_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, GC gc, int fill_style) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XSetFillStyle(%p, %p, %d)", (void*)display, + (void*)gc, fill_style); + } + const int rc = XSetFillStyle(display, gc, fill_style); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XSetFillStyle", + rc); +} + +int LogDynAndXSetFunction_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, GC gc, int function) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XSetFunction(%p, %p, %d)", (void*)display, + (void*)gc, function); + } + const int rc = XSetFunction(display, gc, function); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XSetFunction", + rc); +} + +int LogDynAndXRaiseWindow_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window w) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XRaiseWindow(%p, %lu)", (void*)display, w); + } + const int rc = XRaiseWindow(display, w); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XRaiseWindow", + rc); +} + +int LogDynAndXMapWindow_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window w) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XMapWindow(%p, %lu)", (void*)display, w); + } + const int rc = XMapWindow(display, w); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XMapWindow", rc); +} + +int LogDynAndXUnmapWindow_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window w) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XUnmapWindow(%p, %lu)", (void*)display, w); + } + const int rc = XUnmapWindow(display, w); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XUnmapWindow", + rc); +} + +int LogDynAndXMoveResizeWindow_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window w, int x, int y, unsigned int width, + unsigned int height) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XMoveResizeWindow(%p, %lu, %d, %d, %u, %u)", + (void*)display, w, x, y, width, height); + } + const int rc = XMoveResizeWindow(display, w, x, y, width, height); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, + "XMoveResizeWindow", rc); +} + +Status LogDynAndXWithdrawWindow_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window w, int screen_number) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XWithdrawWindow(%p, %lu, %d)", (void*)display, + w, screen_number); + } + const Status rc = XWithdrawWindow(display, w, screen_number); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XWithdrawWindow", + rc); +} + +int LogDynAndXResizeWindow_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window w, unsigned int width, unsigned int height) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XResizeWindow(%p, %lu, %u, %u)", (void*)display, + w, width, height); + } + const int rc = XResizeWindow(display, w, width, height); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XResizeWindow", + rc); +} + +int LogDynAndXClearWindow_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window w) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XClearWindow(%p, %lu)", (void*)display, w); + } + const int rc = XClearWindow(display, w); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XClearWindow", + rc); +} + +int LogDynAndXSetBackground_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, GC gc, unsigned long background) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XSetBackground(%p, %p, %lu)", (void*)display, + (void*)gc, background); + } + const int rc = XSetBackground(display, gc, background); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XSetBackground", + rc); +} + +int LogDynAndXSetClipMask_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, GC gc, Pixmap pixmap) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XSetClipMask(%p, %p, %lu)", (void*)display, + (void*)gc, pixmap); + } + const int rc = XSetClipMask(display, gc, pixmap); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XSetClipMask", + rc); +} + +int LogDynAndXFillRectangle_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window w, GC gc, int x, int y, unsigned int width, + unsigned int height) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XFillRectangle(%p, %lu, %p, %d, %d, %u, %u)", + (void*)display, w, (void*)gc, x, y, width, height); + } + const int rc = XFillRectangle(display, w, gc, x, y, width, height); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XFillRectangle", + rc); +} + +int LogDynAndXSetRegion_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, GC gc, Region r) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XSetRegion(%p, %p, %p)", (void*)display, + (void*)gc, (void*)r); + } + const int rc = XSetRegion(display, gc, r); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XSetRegion", rc); +} + +int LogDynAndXReparentWindow_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window w, Window parent, int x, int y) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XReparentWindow(%p, %lu, %lu, %d, %d)", + (void*)display, w, parent, x, y); + } + const int rc = XReparentWindow(display, w, parent, x, y); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XReparentWindow", + rc); +} + +char* getConfigOption(BOOL system, const char* option) +{ + char* res = nullptr; + WINPR_JSON* file = freerdp_GetJSONConfigFile(system, "xfreerdp.json"); + if (!file) + return nullptr; + + WINPR_JSON* obj = WINPR_JSON_GetObjectItemCaseSensitive(file, option); + if (obj) + { + const char* val = WINPR_JSON_GetStringValue(obj); + if (val) + res = _strdup(val); + } + WINPR_JSON_Delete(file); + + return res; +} + +int LogDynAndXRestackWindows_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window* windows, int nwindows) +{ + if (WLog_IsLevelActive(log, log_level)) + { + write_log(log, log_level, file, fkt, line, "XRestackWindows(%p, %p, %d)", (void*)display, + (const void*)windows, nwindows); + } + const int rc = XRestackWindows(display, windows, nwindows); + return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XRestackWindows", + rc); +} diff --git a/third_party/FreeRDP/client/X11/xf_utils.h b/third_party/FreeRDP/client/X11/xf_utils.h new file mode 100644 index 0000000..0b95574 --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_utils.h @@ -0,0 +1,300 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 helper utilities + * + * Copyright 2023 Armin Novak + * Copyringht 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 +#include + +#include +#include +#include "xfreerdp.h" + +const char* x11_error_to_string(xfContext* xfc, int error, char* buffer, size_t size); + +#define X_GET_ATOM_VAR_NAME(x) #x +#define Safe_XGetAtomName(log, display, atom) \ + Safe_XGetAtomNameEx((log), (display), (atom), X_GET_ATOM_VAR_NAME(atom)) +char* Safe_XGetAtomNameEx(wLog* log, Display* display, Atom atom, const char* varname); +Atom Logging_XInternAtom(wLog* log, Display* display, _Xconst char* atom_name, Bool only_if_exists); + +typedef BOOL (*fn_action_script_run)(xfContext* xfc, const char* buffer, size_t size, void* user, + const char* what, const char* arg); +BOOL run_action_script(xfContext* xfc, const char* what, const char* arg, fn_action_script_run fkt, + void* user); + +#define LogDynAndXCreatePixmap(log, display, d, width, height, depth) \ + LogDynAndXCreatePixmap_ex((log), __FILE__, __func__, __LINE__, (display), (d), (width), \ + (height), (depth)) + +Pixmap LogDynAndXCreatePixmap_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Drawable d, unsigned int width, + unsigned int height, unsigned int depth); + +#define LogDynAndXFreePixmap(log, display, pixmap) \ + LogDynAndXFreePixmap_ex(log, __FILE__, __func__, __LINE__, (display), (pixmap)) + +int LogDynAndXFreePixmap_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Pixmap pixmap); + +#define LogDynAndXCreateWindow(log, display, parent, x, y, width, height, border_width, depth, \ + class, visual, valuemask, attributes) \ + LogDynAndXCreateWindow_ex((log), __FILE__, __func__, __LINE__, (display), (parent), (x), (y), \ + (width), (height), (border_width), (depth), (class), (visual), \ + (valuemask), (attributes)) + +Window LogDynAndXCreateWindow_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window parent, int x, int y, unsigned int width, + unsigned int height, unsigned int border_width, int depth, + unsigned int c_class, Visual* visual, unsigned long valuemask, + XSetWindowAttributes* attributes); + +#define LogDynAndXRaiseWindow(log, display, w) \ + LogDynAndXRaiseWindow_ex((log), __FILE__, __func__, __LINE__, (display), (w)) + +int LogDynAndXRaiseWindow_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window w); + +#define LogDynAndXMapWindow(log, display, w) \ + LogDynAndXMapWindow_ex((log), __FILE__, __func__, __LINE__, (display), (w)) + +int LogDynAndXMapWindow_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window w); + +#define LogDynAndXUnmapWindow(log, display, w) \ + LogDynAndXUnmapWindow_ex((log), __FILE__, __func__, __LINE__, (display), (w)) + +int LogDynAndXUnmapWindow_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window w); + +#define LogDynAndXMoveResizeWindow(log, display, w, x, y, width, height) \ + LogDynAndXMoveResizeWindow_ex((log), __FILE__, __func__, __LINE__, (display), (w), (x), (y), \ + (width), (height)) + +int LogDynAndXMoveResizeWindow_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window w, int x, int y, unsigned int width, + unsigned int height); + +#define LogDynAndXWithdrawWindow(log, display, w, screen_number) \ + LogDynAndXWithdrawWindow_ex((log), __FILE__, __func__, __LINE__, (display), (w), \ + (screen_number)) + +Status LogDynAndXWithdrawWindow_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window w, int screen_number); + +#define LogDynAndXMoveWindow(log, display, w, x, y) \ + LogDynAndXMoveWindow_ex((log), __FILE__, __func__, __LINE__, (display), (w), (x), (y)) + +int LogDynAndXMoveWindow_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window w, int x, int y); + +#define LogDynAndXResizeWindow(log, display, w, width, height) \ + LogDynAndXResizeWindow_ex((log), __FILE__, __func__, __LINE__, (display), (w), (width), \ + (height)) + +int LogDynAndXResizeWindow_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window w, unsigned int width, unsigned int height); + +#define LogDynAndXClearWindow(log, display, w) \ + LogDynAndXClearWindow_ex((log), __FILE__, __func__, __LINE__, (display), (w)) + +int LogDynAndXClearWindow_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window w); + +#define LogDynAndXGetWindowProperty(log, display, w, property, long_offset, long_length, delete, \ + req_type, actual_type_return, actual_format_return, \ + nitems_return, bytes_after_return, prop_return) \ + LogDynAndXGetWindowProperty_ex((log), __FILE__, __func__, __LINE__, (display), (w), \ + (property), (long_offset), (long_length), (delete), (req_type), \ + (actual_type_return), (actual_format_return), (nitems_return), \ + (bytes_after_return), (prop_return)) + +int LogDynAndXGetWindowProperty_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window w, Atom property, long long_offset, + long long_length, Bool c_delete, Atom req_type, + Atom* actual_type_return, int* actual_format_return, + unsigned long* nitems_return, unsigned long* bytes_after_return, + unsigned char** prop_return); + +#define LogDynAndXReparentWindow(log, display, w, parent, x, y) \ + LogDynAndXReparentWindow_ex((log), __FILE__, __func__, __LINE__, (display), (w), (parent), \ + (x), (y)) + +int LogDynAndXReparentWindow_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window w, Window parent, int x, int y); + +#define LogDynAndXChangeProperty(log, display, w, property, type, format, mode, data, nelements) \ + LogDynAndXChangeProperty_ex((log), __FILE__, __func__, __LINE__, (display), (w), (property), \ + (type), (format), (mode), (data), (nelements)) +int LogDynAndXChangeProperty_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window w, Atom property, Atom type, int format, + int mode, _Xconst unsigned char* data, int nelements); + +#define LogDynAndXDeleteProperty(log, display, w, property) \ + LogDynAndXDeleteProperty_ex((log), __FILE__, __func__, __LINE__, (display), (w), (property)) +int LogDynAndXDeleteProperty_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window w, Atom property); + +#define LogDynAndXConvertSelection(log, display, selection, target, property, requestor, time) \ + LogDynAndXConvertSelection_ex((log), __FILE__, __func__, __LINE__, (display), (selection), \ + (target), (property), (requestor), (time)) +int LogDynAndXConvertSelection_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Atom selection, Atom target, Atom property, + Window requestor, Time time); + +#define LogDynAndXCreateGC(log, display, d, valuemask, values) \ + LogDynAndXCreateGC_ex(log, __FILE__, __func__, __LINE__, (display), (d), (valuemask), (values)) + +GC LogDynAndXCreateGC_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Drawable d, unsigned long valuemask, XGCValues* values); + +#define LogDynAndXFreeGC(log, display, gc) \ + LogDynAndXFreeGC_ex(log, __FILE__, __func__, __LINE__, (display), (gc)) + +int LogDynAndXFreeGC_ex(wLog* log, const char* file, const char* fkt, size_t line, Display* display, + GC gc); + +#define LogDynAndXCreateImage(log, display, visual, depth, format, offset, data, width, height, \ + bitmap_pad, bytes_per_line) \ + LogDynAndXCreateImage_ex(log, __FILE__, __func__, __LINE__, (display), (visual), (depth), \ + (format), (offset), (data), (width), (height), (bitmap_pad), \ + (bytes_per_line)) +XImage* LogDynAndXCreateImage_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Visual* visual, unsigned int depth, int format, + int offset, char* data, unsigned int width, unsigned int height, + int bitmap_pad, int bytes_per_line); + +#define LogDynAndXPutImage(log, display, d, gc, image, src_x, src_y, dest_x, dest_y, width, \ + height) \ + LogDynAndXPutImage_ex(log, __FILE__, __func__, __LINE__, (display), (d), (gc), (image), \ + (src_x), (src_y), (dest_x), (dest_y), (width), (height)) +int LogDynAndXPutImage_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Drawable d, GC gc, XImage* image, int src_x, int src_y, + int dest_x, int dest_y, unsigned int width, unsigned int height); + +#define LogDynAndXCopyArea(log, display, src, dest, gc, src_x, src_y, width, height, dest_x, \ + dest_y) \ + LogDynAndXCopyArea_ex(log, __FILE__, __func__, __LINE__, (display), (src), (dest), (gc), \ + (src_x), (src_y), (width), (height), (dest_x), (dest_y)) +extern int LogDynAndXCopyArea_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Pixmap src, Window dest, GC gc, int src_x, + int src_y, unsigned int width, unsigned int height, int dest_x, + int dest_y); + +#define LogDynAndXSendEvent(log, display, w, propagate, event_mask, event_send) \ + LogDynAndXSendEvent_ex(log, __FILE__, __func__, __LINE__, (display), (w), (propagate), \ + (event_mask), (event_send)) +extern Status LogDynAndXSendEvent_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window w, Bool propagate, long event_mask, + XEvent* event_send); + +#define LogDynAndXFlush(log, display) \ + LogDynAndXFlush_ex(log, __FILE__, __func__, __LINE__, (display)) +extern Status LogDynAndXFlush_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display); + +#define LogDynAndXSync(log, display, discard) \ + LogDynAndXSync_ex(log, __FILE__, __func__, __LINE__, (display), (discard)) +extern int LogDynAndXSync_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Bool discard); + +#define LogDynAndXGetSelectionOwner(log, display, selection) \ + LogDynAndXGetSelectionOwner_ex(log, __FILE__, __func__, __LINE__, (display), (selection)) +extern Window LogDynAndXGetSelectionOwner_ex(wLog* log, const char* file, const char* fkt, + size_t line, Display* display, Atom selection); + +#define LogDynAndXSetSelectionOwner(log, display, selection, owner, time) \ + LogDynAndXSetSelectionOwner_ex(log, __FILE__, __func__, __LINE__, (display), (selection), \ + (owner), (time)) +extern int LogDynAndXSetSelectionOwner_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Atom selection, Window owner, + Time time); + +#define LogDynAndXDestroyWindow(log, display, window) \ + LogDynAndXDestroyWindow_ex(log, __FILE__, __func__, __LINE__, (display), (window)) +extern int LogDynAndXDestroyWindow_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window window); + +#define LogDynAndXChangeWindowAttributes(log, display, window, valuemask, attributes) \ + LogDynAndXChangeWindowAttributes_ex(log, __FILE__, __func__, __LINE__, (display), (window), \ + (valuemask), (attributes)) +extern int LogDynAndXChangeWindowAttributes_ex(wLog* log, const char* file, const char* fkt, + size_t line, Display* display, Window window, + unsigned long valuemask, + XSetWindowAttributes* attributes); + +#define LogDynAndXSetTransientForHint(log, display, window, prop_window) \ + LogDynAndXSetTransientForHint_ex(log, __FILE__, __func__, __LINE__, (display), (window), \ + (prop_window)) +extern int LogDynAndXSetTransientForHint_ex(wLog* log, const char* file, const char* fkt, + size_t line, Display* display, Window window, + Window prop_window); + +#define LogDynAndXCloseDisplay(log, display) \ + LogDynAndXCloseDisplay_ex(log, __FILE__, __func__, __LINE__, (display)) +extern int LogDynAndXCloseDisplay_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display); + +#define LogDynAndXSetClipMask(log, display, gc, pixmap) \ + LogDynAndXSetClipMask_ex(log, __FILE__, __func__, __LINE__, (display), (gc), (pixmap)) +extern int LogDynAndXSetClipMask_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, GC gc, Pixmap pixmap); + +#define LogDynAndXSetRegion(log, display, gc, r) \ + LogDynAndXSetRegion_ex(log, __FILE__, __func__, __LINE__, (display), (gc), (r)) +extern int LogDynAndXSetRegion_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, GC gc, Region r); + +#define LogDynAndXSetBackground(log, display, gc, background) \ + LogDynAndXSetBackground_ex(log, __FILE__, __func__, __LINE__, (display), (gc), (background)) +extern int LogDynAndXSetBackground_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, GC gc, unsigned long background); + +#define LogDynAndXSetForeground(log, display, gc, foreground) \ + LogDynAndXSetForeground_ex(log, __FILE__, __func__, __LINE__, (display), (gc), (foreground)) +extern int LogDynAndXSetForeground_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, GC gc, unsigned long foreground); + +#define LogDynAndXSetFillStyle(log, display, gc, fill_style) \ + LogDynAndXSetFillStyle_ex(log, __FILE__, __func__, __LINE__, (display), (gc), (fill_style)) +extern int LogDynAndXSetFillStyle_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, GC gc, int fill_style); + +#define LogDynAndXFillRectangle(log, display, w, gc, x, y, width, height) \ + LogDynAndXFillRectangle_ex(log, __FILE__, __func__, __LINE__, (display), (w), (gc), (x), (y), \ + (width), (height)) +extern int LogDynAndXFillRectangle_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window w, GC gc, int x, int y, + unsigned int width, unsigned int height); + +#define LogDynAndXSetFunction(log, display, gc, function) \ + LogDynAndXSetFunction_ex(log, __FILE__, __func__, __LINE__, (display), (gc), (function)) +extern int LogDynAndXSetFunction_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, GC gc, int function); + +#define LogDynAndXRestackWindows(log, display, windows, count) \ + LogDynAndXRestackWindows_ex(log, __FILE__, __func__, __LINE__, (display), (windows), (count)) +extern int LogDynAndXRestackWindows_ex(wLog* log, const char* file, const char* fkt, size_t line, + Display* display, Window* windows, int nwindows); + +BOOL IsGnome(void); + +char* getConfigOption(BOOL system, const char* option); + +const char* request_code_2_str(int code); diff --git a/third_party/FreeRDP/client/X11/xf_video.c b/third_party/FreeRDP/client/X11/xf_video.c new file mode 100644 index 0000000..bf3a4d8 --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_video.c @@ -0,0 +1,141 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Optimized Remoting Virtual Channel Extension for X11 + * + * Copyright 2017 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include "xf_video.h" +#include "xf_utils.h" + +#include +#define TAG CLIENT_TAG("video") + +typedef struct +{ + VideoSurface base; + XImage* image; +} xfVideoSurface; + +static VideoSurface* xfVideoCreateSurface(VideoClientContext* video, UINT32 x, UINT32 y, + UINT32 width, UINT32 height) +{ + xfContext* xfc = nullptr; + xfVideoSurface* ret = nullptr; + + WINPR_ASSERT(video); + ret = (xfVideoSurface*)VideoClient_CreateCommonContext(sizeof(xfContext), x, y, width, height); + if (!ret) + return nullptr; + + xfc = (xfContext*)video->custom; + WINPR_ASSERT(xfc); + + ret->image = LogDynAndXCreateImage(xfc->log, xfc->display, xfc->visual, + WINPR_ASSERTING_INT_CAST(uint32_t, xfc->depth), ZPixmap, 0, + (char*)ret->base.data, width, height, 8, + WINPR_ASSERTING_INT_CAST(int, ret->base.scanline)); + + if (!ret->image) + { + WLog_ERR(TAG, "unable to create surface image"); + VideoClient_DestroyCommonContext(&ret->base); + return nullptr; + } + + return &ret->base; +} + +static BOOL xfVideoShowSurface(VideoClientContext* video, const VideoSurface* surface, + WINPR_ATTR_UNUSED UINT32 destinationWidth, + WINPR_ATTR_UNUSED UINT32 destinationHeight) +{ + const xfVideoSurface* xfSurface = (const xfVideoSurface*)surface; + xfContext* xfc = nullptr; + const rdpSettings* settings = nullptr; + + WINPR_ASSERT(video); + WINPR_ASSERT(xfSurface); + + xfc = video->custom; + WINPR_ASSERT(xfc); + + settings = xfc->common.context.settings; + WINPR_ASSERT(settings); + +#ifdef WITH_XRENDER + + if (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) || + freerdp_settings_get_bool(settings, FreeRDP_MultiTouchGestures)) + { + LogDynAndXPutImage(xfc->log, xfc->display, xfc->primary, xfc->gc, xfSurface->image, 0, 0, + WINPR_ASSERTING_INT_CAST(int, surface->x), + WINPR_ASSERTING_INT_CAST(int, surface->y), surface->w, surface->h); + xf_draw_screen(xfc, WINPR_ASSERTING_INT_CAST(int32_t, surface->x), + WINPR_ASSERTING_INT_CAST(int32_t, surface->y), + WINPR_ASSERTING_INT_CAST(int32_t, surface->w), + WINPR_ASSERTING_INT_CAST(int32_t, surface->h)); + } + else +#endif + { + LogDynAndXPutImage(xfc->log, xfc->display, xfc->drawable, xfc->gc, xfSurface->image, 0, 0, + WINPR_ASSERTING_INT_CAST(int, surface->x), + WINPR_ASSERTING_INT_CAST(int, surface->y), surface->w, surface->h); + } + + return TRUE; +} + +static BOOL xfVideoDeleteSurface(VideoClientContext* video, VideoSurface* surface) +{ + xfVideoSurface* xfSurface = (xfVideoSurface*)surface; + + WINPR_UNUSED(video); + + if (xfSurface) + XFree(xfSurface->image); + + VideoClient_DestroyCommonContext(surface); + return TRUE; +} + +void xf_video_control_init(xfContext* xfc, VideoClientContext* video) +{ + WINPR_ASSERT(xfc); + WINPR_ASSERT(video); + + gdi_video_control_init(xfc->common.context.gdi, video); + + /* X11 needs to be able to handle 32bpp colors directly. */ + if (xfc->depth >= 24) + { + video->custom = xfc; + video->createSurface = xfVideoCreateSurface; + video->showSurface = xfVideoShowSurface; + video->deleteSurface = xfVideoDeleteSurface; + } +} + +void xf_video_control_uninit(xfContext* xfc, VideoClientContext* video) +{ + WINPR_ASSERT(xfc); + gdi_video_control_uninit(xfc->common.context.gdi, video); +} diff --git a/third_party/FreeRDP/client/X11/xf_video.h b/third_party/FreeRDP/client/X11/xf_video.h new file mode 100644 index 0000000..899cfee --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_video.h @@ -0,0 +1,37 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Optimized Remoting Virtual Channel Extension for X11 + * + * Copyright 2017 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef CLIENT_X11_XF_VIDEO_H_ +#define CLIENT_X11_XF_VIDEO_H_ + +#include + +#include "xf_types.h" + +typedef struct s_xfVideoContext xfVideoContext; + +void xf_video_control_init(xfContext* xfc, VideoClientContext* video); +void xf_video_control_uninit(xfContext* xfc, VideoClientContext* video); + +void xf_video_free(xfVideoContext* context); + +WINPR_ATTR_MALLOC(xf_video_free, 1) +WINPR_ATTR_NODISCARD +xfVideoContext* xf_video_new(xfContext* xfc); + +#endif /* CLIENT_X11_XF_VIDEO_H_ */ diff --git a/third_party/FreeRDP/client/X11/xf_window.c b/third_party/FreeRDP/client/X11/xf_window.c new file mode 100644 index 0000000..9cdec0b --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_window.c @@ -0,0 +1,1647 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Windows + * + * Copyright 2011 Marc-Andre Moreau + * Copyright 2012 HP Development Company, LLC + * Copyright 2016 Thincast Technologies GmbH + * Copyright 2016 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#ifdef WITH_XEXT +#include +#endif + +#ifdef WITH_XI +#include +#endif + +#include "xf_gfx.h" +#include "xf_rail.h" +#include "xf_input.h" +#include "xf_keyboard.h" +#include "xf_utils.h" +#include "xf_debug.h" + +#define TAG CLIENT_TAG("x11") + +#include +#define xf_icon_prop FreeRDP_Icon_256px_prop + +#include "xf_window.h" + +/* Extended Window Manager Hints: http://standards.freedesktop.org/wm-spec/wm-spec-1.3.html */ + +/* bit definitions for MwmHints.flags */ +#define MWM_HINTS_FUNCTIONS (1L << 0) +#define MWM_HINTS_DECORATIONS (1L << 1) +// #define MWM_HINTS_INPUT_MODE (1L << 2) +// #define MWM_HINTS_STATUS (1L << 3) + +/* bit definitions for MwmHints.functions */ +#define MWM_FUNC_ALL (1L << 0) +// #define MWM_FUNC_RESIZE (1L << 1) +// #define MWM_FUNC_MOVE (1L << 2) +// #define MWM_FUNC_MINIMIZE (1L << 3) +// #define MWM_FUNC_MAXIMIZE (1L << 4) +// #define MWM_FUNC_CLOSE (1L << 5) + +/* bit definitions for MwmHints.decorations */ +#define MWM_DECOR_ALL (1L << 0) +// #define MWM_DECOR_BORDER (1L << 1) +// #define MWM_DECOR_RESIZEH (1L << 2) +// #define MWM_DECOR_TITLE (1L << 3) +// #define MWM_DECOR_MENU (1L << 4) +// #define MWM_DECOR_MINIMIZE (1L << 5) +// #define MWM_DECOR_MAXIMIZE (1L << 6) + +#define PROP_MOTIF_WM_HINTS_ELEMENTS 5 + +#define ENTRY(x) \ + case x: \ + return #x + +typedef struct +{ + unsigned long flags; + unsigned long functions; + unsigned long decorations; + long inputMode; + unsigned long status; +} PropMotifWmHints; + +static void xf_XSetTransientForHint(xfContext* xfc, xfAppWindow* window); + +static const char* window_style_to_string(UINT32 style) +{ + switch (style) + { + ENTRY(WS_NONE); + ENTRY(WS_BORDER); + ENTRY(WS_CAPTION); + ENTRY(WS_CHILD); + ENTRY(WS_CLIPCHILDREN); + ENTRY(WS_CLIPSIBLINGS); + ENTRY(WS_DISABLED); + ENTRY(WS_DLGFRAME); + ENTRY(WS_GROUP); + ENTRY(WS_HSCROLL); + ENTRY(WS_MAXIMIZE); + ENTRY(WS_MAXIMIZEBOX); + ENTRY(WS_MINIMIZE); + ENTRY(WS_OVERLAPPEDWINDOW); + ENTRY(WS_POPUP); + ENTRY(WS_POPUPWINDOW); + ENTRY(WS_SIZEBOX); + ENTRY(WS_SYSMENU); + ENTRY(WS_VISIBLE); + ENTRY(WS_VSCROLL); + default: + return nullptr; + } +} + +const char* window_styles_to_string(UINT32 style, char* buffer, size_t length) +{ + (void)_snprintf(buffer, length, "style[0x%08" PRIx32 "] {", style); + const char* sep = ""; + for (size_t x = 0; x < 32; x++) + { + const UINT32 val = 1 << x; + if ((style & val) != 0) + { + const char* str = window_style_to_string(val); + if (str) + { + winpr_str_append(str, buffer, length, sep); + sep = "|"; + } + } + } + winpr_str_append("}", buffer, length, ""); + + return buffer; +} + +static const char* window_style_ex_to_string(UINT32 styleEx) +{ + switch (styleEx) + { + ENTRY(WS_EX_ACCEPTFILES); + ENTRY(WS_EX_APPWINDOW); + ENTRY(WS_EX_CLIENTEDGE); + ENTRY(WS_EX_COMPOSITED); + ENTRY(WS_EX_CONTEXTHELP); + ENTRY(WS_EX_CONTROLPARENT); + ENTRY(WS_EX_DLGMODALFRAME); + ENTRY(WS_EX_LAYERED); + ENTRY(WS_EX_LAYOUTRTL); + ENTRY(WS_EX_LEFTSCROLLBAR); + ENTRY(WS_EX_MDICHILD); + ENTRY(WS_EX_NOACTIVATE); + ENTRY(WS_EX_NOINHERITLAYOUT); + ENTRY(WS_EX_NOPARENTNOTIFY); + ENTRY(WS_EX_OVERLAPPEDWINDOW); + ENTRY(WS_EX_PALETTEWINDOW); + ENTRY(WS_EX_RIGHT); + ENTRY(WS_EX_RIGHTSCROLLBAR); + ENTRY(WS_EX_RTLREADING); + ENTRY(WS_EX_STATICEDGE); + ENTRY(WS_EX_TOOLWINDOW); + ENTRY(WS_EX_TOPMOST); + ENTRY(WS_EX_TRANSPARENT); + ENTRY(WS_EX_WINDOWEDGE); + default: + return nullptr; + } +} + +const char* window_styles_ex_to_string(UINT32 styleEx, char* buffer, size_t length) +{ + (void)_snprintf(buffer, length, "styleEx[0x%08" PRIx32 "] {", styleEx); + const char* sep = ""; + for (size_t x = 0; x < 32; x++) + { + const UINT32 val = (UINT32)(1UL << x); + if ((styleEx & val) != 0) + { + const char* str = window_style_ex_to_string(val); + if (str) + { + winpr_str_append(str, buffer, length, sep); + sep = "|"; + } + } + } + winpr_str_append("}", buffer, length, ""); + + return buffer; +} + +static void xf_SetWindowTitleText(xfContext* xfc, Window window, const char* name) +{ + WINPR_ASSERT(xfc); + WINPR_ASSERT(name); + + const size_t i = strnlen(name, MAX_PATH); + XStoreName(xfc->display, window, name); + Atom wm_Name = xfc->NET_WM_NAME; + Atom utf8Str = xfc->UTF8_STRING; + LogDynAndXChangeProperty(xfc->log, xfc->display, window, wm_Name, utf8Str, 8, PropModeReplace, + (const unsigned char*)name, (int)i); +} + +/** + * Post an event from the client to the X server + */ +void xf_SendClientEvent(xfContext* xfc, Window window, Atom atom, unsigned int numArgs, ...) +{ + XEvent xevent = WINPR_C_ARRAY_INIT; + va_list argp = WINPR_C_ARRAY_INIT; + va_start(argp, numArgs); + + xevent.xclient.type = ClientMessage; + xevent.xclient.serial = 0; + xevent.xclient.send_event = False; + xevent.xclient.display = xfc->display; + xevent.xclient.window = window; + xevent.xclient.message_type = atom; + xevent.xclient.format = 32; + + for (size_t i = 0; i < numArgs; i++) + { + xevent.xclient.data.l[i] = va_arg(argp, int); + } + + DEBUG_X11("Send ClientMessage Event: wnd=0x%04lX", (unsigned long)xevent.xclient.window); + LogDynAndXSendEvent(xfc->log, xfc->display, RootWindowOfScreen(xfc->screen), False, + SubstructureRedirectMask | SubstructureNotifyMask, &xevent); + LogDynAndXSync(xfc->log, xfc->display, False); + va_end(argp); +} + +void xf_SetWindowMinimized(xfContext* xfc, xfWindow* window) +{ + XIconifyWindow(xfc->display, window->handle, xfc->screen_number); +} + +void xf_SetWindowFullscreen(xfContext* xfc, xfWindow* window, BOOL fullscreen) +{ + const rdpSettings* settings = nullptr; + int startX = 0; + int startY = 0; + UINT32 width = WINPR_ASSERTING_INT_CAST(uint32_t, window->width); + UINT32 height = WINPR_ASSERTING_INT_CAST(uint32_t, window->height); + + WINPR_ASSERT(xfc); + + settings = xfc->common.context.settings; + WINPR_ASSERT(settings); + + /* xfc->decorations is set by caller depending on settings and whether it is fullscreen or not + */ + window->decorations = xfc->decorations; + /* show/hide decorations (e.g. title bar) as guided by xfc->decorations */ + xf_SetWindowDecorations(xfc, window->handle, window->decorations); + DEBUG_X11("X window decoration set to %d", (int)window->decorations); + xf_floatbar_toggle_fullscreen(xfc->window->floatbar, fullscreen); + + if (fullscreen) + { + xfc->savedWidth = xfc->window->width; + xfc->savedHeight = xfc->window->height; + xfc->savedPosX = xfc->window->left; + xfc->savedPosY = xfc->window->top; + + startX = (freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosX) != UINT32_MAX) + ? WINPR_ASSERTING_INT_CAST( + int, freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosX)) + : 0; + startY = (freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosY) != UINT32_MAX) + ? WINPR_ASSERTING_INT_CAST( + int, freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosY)) + : 0; + } + else + { + width = WINPR_ASSERTING_INT_CAST(uint32_t, xfc->savedWidth); + height = WINPR_ASSERTING_INT_CAST(uint32_t, xfc->savedHeight); + startX = xfc->savedPosX; + startY = xfc->savedPosY; + } + + /* Determine the x,y starting location for the fullscreen window */ + if (fullscreen) + { + const rdpMonitor* firstMonitor = + freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorDefArray, 0); + /* Initialize startX and startY with reasonable values */ + startX = firstMonitor->x; + startY = firstMonitor->y; + + /* Search all monitors to find the lowest startX and startY values */ + for (size_t i = 0; i < freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount); i++) + { + const rdpMonitor* monitor = + freerdp_settings_get_pointer_array(settings, FreeRDP_MonitorDefArray, i); + startX = MIN(startX, monitor->x); + startY = MIN(startY, monitor->y); + } + + /* Lastly apply any monitor shift(translation from remote to local coordinate system) + * to startX and startY values + */ + startX += freerdp_settings_get_int32(settings, FreeRDP_MonitorLocalShiftX); + startY += freerdp_settings_get_int32(settings, FreeRDP_MonitorLocalShiftY); + } + + /* + It is safe to proceed with simply toggling _NET_WM_STATE_FULLSCREEN window state on the + following conditions: + - The window manager supports multiple monitor full screen + - The user requested to use a single monitor to render the remote desktop + */ + if (xfc->NET_WM_FULLSCREEN_MONITORS != None || + freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount) == 1) + { + xf_ResizeDesktopWindow(xfc, window, WINPR_ASSERTING_INT_CAST(int, width), + WINPR_ASSERTING_INT_CAST(int, height)); + + if (fullscreen) + { + /* enter full screen: move the window before adding NET_WM_STATE_FULLSCREEN */ + LogDynAndXMoveWindow(xfc->log, xfc->display, window->handle, startX, startY); + } + + /* Set the fullscreen state */ + xf_SendClientEvent(xfc, window->handle, xfc->NET_WM_STATE, 4, + fullscreen ? NET_WM_STATE_ADD : NET_WM_STATE_REMOVE, + xfc->NET_WM_STATE_FULLSCREEN, 0, 0); + + if (!fullscreen) + { + /* leave full screen: move the window after removing NET_WM_STATE_FULLSCREEN + * Resize the window again, the previous call to xf_SendClientEvent might have + * changed the window size (borders, ...) + */ + xf_ResizeDesktopWindow(xfc, window, WINPR_ASSERTING_INT_CAST(int, width), + WINPR_ASSERTING_INT_CAST(int, height)); + LogDynAndXMoveWindow(xfc->log, xfc->display, window->handle, startX, startY); + } + + /* Set monitor bounds */ + if (freerdp_settings_get_uint32(settings, FreeRDP_MonitorCount) > 1) + { + xf_SendClientEvent(xfc, window->handle, xfc->NET_WM_FULLSCREEN_MONITORS, 5, + xfc->fullscreenMonitors.top, xfc->fullscreenMonitors.bottom, + xfc->fullscreenMonitors.left, xfc->fullscreenMonitors.right, 1); + } + } + else + { + if (fullscreen) + { + xf_SetWindowDecorations(xfc, window->handle, FALSE); + + if (xfc->fullscreenMonitors.top) + { + xf_SendClientEvent(xfc, window->handle, xfc->NET_WM_STATE, 4, NET_WM_STATE_ADD, + xfc->fullscreenMonitors.top, 0, 0); + } + else + { + XSetWindowAttributes xswa = WINPR_C_ARRAY_INIT; + xswa.override_redirect = True; + LogDynAndXChangeWindowAttributes(xfc->log, xfc->display, window->handle, + CWOverrideRedirect, &xswa); + LogDynAndXRaiseWindow(xfc->log, xfc->display, window->handle); + xswa.override_redirect = False; + LogDynAndXChangeWindowAttributes(xfc->log, xfc->display, window->handle, + CWOverrideRedirect, &xswa); + } + + /* if window is in maximized state, save and remove */ + if (xfc->NET_WM_STATE_MAXIMIZED_VERT != None) + { + BYTE state = 0; + unsigned long nitems = 0; + unsigned long bytes = 0; + BYTE* prop = nullptr; + + if (xf_GetWindowProperty(xfc, window->handle, xfc->NET_WM_STATE, 255, &nitems, + &bytes, &prop)) + { + const Atom* aprop = (const Atom*)prop; + state = 0; + + for (size_t x = 0; x < nitems; x++) + { + if (aprop[x] == xfc->NET_WM_STATE_MAXIMIZED_VERT) + state |= 0x01; + + if (aprop[x] == xfc->NET_WM_STATE_MAXIMIZED_HORZ) + state |= 0x02; + } + + if (state) + { + xf_SendClientEvent(xfc, window->handle, xfc->NET_WM_STATE, 4, + NET_WM_STATE_REMOVE, xfc->NET_WM_STATE_MAXIMIZED_VERT, 0, + 0); + xf_SendClientEvent(xfc, window->handle, xfc->NET_WM_STATE, 4, + NET_WM_STATE_REMOVE, xfc->NET_WM_STATE_MAXIMIZED_HORZ, 0, + 0); + xfc->savedMaximizedState = state; + } + + XFree(prop); + } + } + + width = xfc->vscreen.area.right - xfc->vscreen.area.left + 1; + height = xfc->vscreen.area.bottom - xfc->vscreen.area.top + 1; + DEBUG_X11("X window move and resize %dx%d@%" PRIu32 "x%" PRIu32 "", startX, startY, + width, height); + xf_ResizeDesktopWindow(xfc, window, WINPR_ASSERTING_INT_CAST(int, width), + WINPR_ASSERTING_INT_CAST(int, height)); + LogDynAndXMoveWindow(xfc->log, xfc->display, window->handle, startX, startY); + } + else + { + xf_SetWindowDecorations(xfc, window->handle, window->decorations); + xf_ResizeDesktopWindow(xfc, window, WINPR_ASSERTING_INT_CAST(int, width), + WINPR_ASSERTING_INT_CAST(int, height)); + LogDynAndXMoveWindow(xfc->log, xfc->display, window->handle, startX, startY); + + if (xfc->fullscreenMonitors.top) + { + xf_SendClientEvent(xfc, window->handle, xfc->NET_WM_STATE, 4, NET_WM_STATE_REMOVE, + xfc->fullscreenMonitors.top, 0, 0); + } + + /* restore maximized state, if the window was maximized before setting fullscreen */ + if (xfc->savedMaximizedState & 0x01) + { + xf_SendClientEvent(xfc, window->handle, xfc->NET_WM_STATE, 4, NET_WM_STATE_ADD, + xfc->NET_WM_STATE_MAXIMIZED_VERT, 0, 0); + } + + if (xfc->savedMaximizedState & 0x02) + { + xf_SendClientEvent(xfc, window->handle, xfc->NET_WM_STATE, 4, NET_WM_STATE_ADD, + xfc->NET_WM_STATE_MAXIMIZED_HORZ, 0, 0); + } + + xfc->savedMaximizedState = 0; + } + } +} + +/* http://tronche.com/gui/x/xlib/window-information/XGetWindowProperty.html */ + +BOOL xf_GetWindowProperty(xfContext* xfc, Window window, Atom property, int length, + unsigned long* nitems, unsigned long* bytes, BYTE** prop) +{ + int status = 0; + Atom actual_type = None; + int actual_format = 0; + + WINPR_ASSERT(prop); + + if (property == None) + return FALSE; + + status = LogDynAndXGetWindowProperty(xfc->log, xfc->display, window, property, 0, length, False, + AnyPropertyType, &actual_type, &actual_format, nitems, + bytes, prop); + + if (status != Success) + return FALSE; + + if (actual_type == None) + { + WLog_DBG(TAG, "Property %lu does not exist", (unsigned long)property); + if (*prop) + XFree(*prop); + *prop = nullptr; + return FALSE; + } + + return TRUE; +} + +static BOOL xf_GetNumberOfDesktops(xfContext* xfc, Window root, unsigned* pval) +{ + unsigned long nitems = 0; + unsigned long bytes = 0; + BYTE* bprop = nullptr; + + WINPR_ASSERT(xfc); + WINPR_ASSERT(pval); + + const BOOL rc = + xf_GetWindowProperty(xfc, root, xfc->NET_NUMBER_OF_DESKTOPS, 1, &nitems, &bytes, &bprop); + + long* prop = (long*)bprop; + *pval = 0; + + BOOL res = FALSE; + if (rc) + { + if ((*prop >= 0) && (*prop <= UINT32_MAX)) + { + *pval = (UINT32)*prop; + res = TRUE; + } + } + if (prop) + XFree(prop); + return res; +} + +static BOOL xf_GetCurrentDesktop(xfContext* xfc, Window root) +{ + unsigned long nitems = 0; + unsigned long bytes = 0; + BYTE* bprop = nullptr; + unsigned max = 0; + + if (!xf_GetNumberOfDesktops(xfc, root, &max)) + return FALSE; + if (max < 1) + return FALSE; + + const BOOL rc = + xf_GetWindowProperty(xfc, root, xfc->NET_CURRENT_DESKTOP, 1, &nitems, &bytes, &bprop); + + long* prop = (long*)bprop; + xfc->current_desktop = 0; + if (rc) + xfc->current_desktop = (int)MIN(max - 1, *prop); + if (prop) + XFree(prop); + return rc; +} + +static BOOL xf_GetWorkArea_NET_WORKAREA(xfContext* xfc, Window root) +{ + BOOL rc = FALSE; + unsigned long nitems = 0; + unsigned long bytes = 0; + BYTE* bprop = nullptr; + + const BOOL status = + xf_GetWindowProperty(xfc, root, xfc->NET_WORKAREA, INT_MAX, &nitems, &bytes, &bprop); + long* prop = (long*)bprop; + + if (!status) + goto fail; + + if ((xfc->current_desktop * 4 + 3) >= (INT64)nitems) + goto fail; + + xfc->workArea.x = (INT32)MIN(INT32_MAX, prop[xfc->current_desktop * 4 + 0]); + xfc->workArea.y = (INT32)MIN(INT32_MAX, prop[xfc->current_desktop * 4 + 1]); + xfc->workArea.width = (UINT32)MIN(UINT32_MAX, prop[xfc->current_desktop * 4 + 2]); + xfc->workArea.height = (UINT32)MIN(UINT32_MAX, prop[xfc->current_desktop * 4 + 3]); + + rc = TRUE; +fail: + XFree(prop); + return rc; +} + +BOOL xf_GetWorkArea(xfContext* xfc) +{ + WINPR_ASSERT(xfc); + + Window root = DefaultRootWindow(xfc->display); + (void)xf_GetCurrentDesktop(xfc, root); + return xf_GetWorkArea_NET_WORKAREA(xfc, root); +} + +void xf_SetWindowDecorations(xfContext* xfc, Window window, BOOL show) +{ + PropMotifWmHints hints = { .decorations = (show) ? MWM_DECOR_ALL : 0, + .functions = MWM_FUNC_ALL, + .flags = MWM_HINTS_DECORATIONS | MWM_HINTS_FUNCTIONS, + .inputMode = 0, + .status = 0 }; + WINPR_ASSERT(xfc); + LogDynAndXChangeProperty(xfc->log, xfc->display, window, xfc->MOTIF_WM_HINTS, + xfc->MOTIF_WM_HINTS, 32, PropModeReplace, (BYTE*)&hints, + PROP_MOTIF_WM_HINTS_ELEMENTS); +} + +void xf_SetWindowUnlisted(xfContext* xfc, Window window) +{ + WINPR_ASSERT(xfc); + Atom window_state[] = { xfc->NET_WM_STATE_SKIP_PAGER, xfc->NET_WM_STATE_SKIP_TASKBAR }; + LogDynAndXChangeProperty(xfc->log, xfc->display, window, xfc->NET_WM_STATE, XA_ATOM, 32, + PropModeReplace, (BYTE*)window_state, 2); +} + +static void xf_SetWindowPID(xfContext* xfc, Window window, pid_t pid) +{ + Atom am_wm_pid = 0; + + WINPR_ASSERT(xfc); + if (!pid) + pid = getpid(); + + am_wm_pid = xfc->NET_WM_PID; + LogDynAndXChangeProperty(xfc->log, xfc->display, window, am_wm_pid, XA_CARDINAL, 32, + PropModeReplace, (BYTE*)&pid, 1); +} + +static const char* get_shm_id(void) +{ + static char shm_id[64]; + (void)sprintf_s(shm_id, sizeof(shm_id), "/com.freerdp.xfreerdp.tsmf_%016X", + GetCurrentProcessId()); + return shm_id; +} + +Window xf_CreateDummyWindow(xfContext* xfc) +{ + return LogDynAndXCreateWindow( + xfc->log, xfc->display, RootWindowOfScreen(xfc->screen), + WINPR_ASSERTING_INT_CAST(int, xfc->workArea.x), + WINPR_ASSERTING_INT_CAST(int, xfc->workArea.y), 1, 1, 0, xfc->depth, InputOutput, + xfc->visual, WINPR_ASSERTING_INT_CAST(uint32_t, xfc->attribs_mask), &xfc->attribs); +} + +void xf_DestroyDummyWindow(xfContext* xfc, Window window) +{ + if (window) + LogDynAndXDestroyWindow(xfc->log, xfc->display, window); +} + +xfWindow* xf_CreateDesktopWindow(xfContext* xfc, char* name, int width, int height) +{ + XEvent xevent = WINPR_C_ARRAY_INIT; + int input_mask = 0; + XClassHint* classHints = nullptr; + xfWindow* window = (xfWindow*)calloc(1, sizeof(xfWindow)); + + if (!window) + return nullptr; + + rdpSettings* settings = xfc->common.context.settings; + WINPR_ASSERT(settings); + + Window parentWindow = (Window)freerdp_settings_get_uint64(settings, FreeRDP_ParentWindowId); + window->width = width; + window->height = height; + window->decorations = xfc->decorations; + window->is_mapped = FALSE; + window->is_transient = FALSE; + + WINPR_ASSERT(xfc->depth != 0); + window->handle = LogDynAndXCreateWindow( + xfc->log, xfc->display, RootWindowOfScreen(xfc->screen), + WINPR_ASSERTING_INT_CAST(int, xfc->workArea.x), + WINPR_ASSERTING_INT_CAST(int, xfc->workArea.y), xfc->workArea.width, xfc->workArea.height, + 0, xfc->depth, InputOutput, xfc->visual, + WINPR_ASSERTING_INT_CAST(uint32_t, xfc->attribs_mask), &xfc->attribs); + window->shmid = shm_open(get_shm_id(), (O_CREAT | O_RDWR), (S_IREAD | S_IWRITE)); + + if (window->shmid < 0) + { + DEBUG_X11("xf_CreateDesktopWindow: failed to get access to shared memory - shmget()\n"); + } + else + { + int rc = ftruncate(window->shmid, sizeof(window->handle)); + if (rc != 0) + { +#ifdef WITH_DEBUG_X11 + char ebuffer[256] = WINPR_C_ARRAY_INIT; + DEBUG_X11("ftruncate failed with %s [%d]", winpr_strerror(rc, ebuffer, sizeof(ebuffer)), + rc); +#endif + } + else + { + void* mem = mmap(nullptr, sizeof(window->handle), PROT_READ | PROT_WRITE, MAP_SHARED, + window->shmid, 0); + + if (mem == MAP_FAILED) + { + DEBUG_X11( + "xf_CreateDesktopWindow: failed to assign pointer to the memory address - " + "shmat()\n"); + } + else + { + window->xfwin = mem; + *window->xfwin = window->handle; + } + } + } + + classHints = XAllocClassHint(); + + if (classHints) + { + classHints->res_name = "xfreerdp"; + + char* res_class = nullptr; + const char* WmClass = freerdp_settings_get_string(settings, FreeRDP_WmClass); + if (WmClass) + res_class = _strdup(WmClass); + else + res_class = _strdup("xfreerdp"); + + classHints->res_class = res_class; + XSetClassHint(xfc->display, window->handle, classHints); + XFree(classHints); + free(res_class); + } + + xf_ResizeDesktopWindow(xfc, window, width, height); + xf_SetWindowDecorations(xfc, window->handle, window->decorations); + xf_SetWindowPID(xfc, window->handle, 0); + input_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | + VisibilityChangeMask | FocusChangeMask | StructureNotifyMask | PointerMotionMask | + ExposureMask | PropertyChangeMask; + + if (xfc->grab_keyboard) + input_mask |= EnterWindowMask | LeaveWindowMask; + + LogDynAndXChangeProperty(xfc->log, xfc->display, window->handle, xfc->NET_WM_ICON, XA_CARDINAL, + 32, PropModeReplace, (BYTE*)xf_icon_prop, ARRAYSIZE(xf_icon_prop)); + + if (parentWindow) + LogDynAndXReparentWindow(xfc->log, xfc->display, window->handle, parentWindow, 0, 0); + + XSelectInput(xfc->display, window->handle, input_mask); + LogDynAndXClearWindow(xfc->log, xfc->display, window->handle); + xf_SetWindowTitleText(xfc, window->handle, name); + LogDynAndXMapWindow(xfc->log, xfc->display, window->handle); + xf_input_init(xfc, window->handle); + + /* + * NOTE: This must be done here to handle reparenting the window, + * so that we don't miss the event and hang waiting for the next one + */ + do + { + XMaskEvent(xfc->display, VisibilityChangeMask, &xevent); + } while (xevent.type != VisibilityNotify); + + /* + * The XCreateWindow call will start the window in the upper-left corner of our current + * monitor instead of the upper-left monitor for remote app mode (which uses all monitors). + * This extra call after the window is mapped will position the login window correctly + */ + if (freerdp_settings_get_bool(settings, FreeRDP_RemoteApplicationMode)) + { + LogDynAndXMoveWindow(xfc->log, xfc->display, window->handle, 0, 0); + } + else if ((freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosX) != UINT32_MAX) && + (freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosY) != UINT32_MAX)) + { + LogDynAndXMoveWindow(xfc->log, xfc->display, window->handle, + WINPR_ASSERTING_INT_CAST( + int, freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosX)), + WINPR_ASSERTING_INT_CAST( + int, freerdp_settings_get_uint32(settings, FreeRDP_DesktopPosY))); + } + + window->floatbar = xf_floatbar_new(xfc, window->handle, name, + freerdp_settings_get_uint32(settings, FreeRDP_Floatbar)); + + if (xfc->XWAYLAND_MAY_GRAB_KEYBOARD) + xf_SendClientEvent(xfc, window->handle, xfc->XWAYLAND_MAY_GRAB_KEYBOARD, 1, 1); + + return window; +} + +void xf_ResizeDesktopWindow(xfContext* xfc, xfWindow* window, int width, int height) +{ + XSizeHints* size_hints = nullptr; + rdpSettings* settings = nullptr; + + if (!xfc || !window) + return; + + settings = xfc->common.context.settings; + WINPR_ASSERT(settings); + + if (!(size_hints = XAllocSizeHints())) + return; + + size_hints->flags = PMinSize | PMaxSize | PWinGravity; + size_hints->win_gravity = NorthWestGravity; + size_hints->min_width = size_hints->min_height = 1; + size_hints->max_width = size_hints->max_height = 16384; + LogDynAndXResizeWindow(xfc->log, xfc->display, window->handle, + WINPR_ASSERTING_INT_CAST(uint32_t, width), + WINPR_ASSERTING_INT_CAST(uint32_t, height)); +#ifdef WITH_XRENDER + + if (!freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) && + !freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate)) +#endif + { + if (!xfc->fullscreen) + { + /* min == max is an hint for the WM to indicate that the window should + * not be resizable */ + size_hints->min_width = size_hints->max_width = width; + size_hints->min_height = size_hints->max_height = height; + } + } + + XSetWMNormalHints(xfc->display, window->handle, size_hints); + XFree(size_hints); +} + +void xf_DestroyDesktopWindow(xfContext* xfc, xfWindow* window) +{ + if (!window) + return; + + if (xfc->window == window) + xfc->window = nullptr; + + xf_floatbar_free(window->floatbar); + + if (window->gc) + LogDynAndXFreeGC(xfc->log, xfc->display, window->gc); + + if (window->handle) + { + LogDynAndXUnmapWindow(xfc->log, xfc->display, window->handle); + LogDynAndXDestroyWindow(xfc->log, xfc->display, window->handle); + } + + if (window->xfwin) + munmap(nullptr, sizeof(*window->xfwin)); + + if (window->shmid >= 0) + close(window->shmid); + + shm_unlink(get_shm_id()); + window->xfwin = (Window*)-1; + window->shmid = -1; + free(window); +} + +void xf_SetWindowStyle(xfContext* xfc, xfAppWindow* appWindow, UINT32 style, UINT32 ex_style) +{ + Atom window_type = 0; + BOOL redirect = FALSE; + + window_type = xfc->NET_WM_WINDOW_TYPE_NORMAL; + + if ((ex_style & WS_EX_NOACTIVATE) || (ex_style & WS_EX_TOOLWINDOW)) + { + redirect = TRUE; + appWindow->is_transient = TRUE; + xf_SetWindowUnlisted(xfc, appWindow->handle); + window_type = xfc->NET_WM_WINDOW_TYPE_DROPDOWN_MENU; + } + /* + * TOPMOST window that is not a tool window is treated like a regular window (i.e. task + * manager). Want to do this here, since the window may have type WS_POPUP + */ + else if (ex_style & WS_EX_TOPMOST) + { + window_type = xfc->NET_WM_WINDOW_TYPE_NORMAL; + } + + if (style & WS_POPUP) + { + window_type = xfc->NET_WM_WINDOW_TYPE_DIALOG; + /* this includes dialogs, popups, etc, that need to be full-fledged windows */ + + if (!((ex_style & WS_EX_DLGMODALFRAME) || (ex_style & WS_EX_LAYERED) || + (style & WS_SYSMENU))) + { + appWindow->is_transient = TRUE; + redirect = TRUE; + + xf_SetWindowUnlisted(xfc, appWindow->handle); + } + } + + if (!(style == 0 && ex_style == 0)) + { + xf_SetWindowActions(xfc, appWindow); + } + + { + /* + * Tooltips and menu items should be unmanaged windows + * (called "override redirect" in X windows parlance) + * If they are managed, there are issues with window focus that + * cause the windows to behave improperly. For example, a mouse + * press will dismiss a drop-down menu because the RDP server + * sees that as a focus out event from the window owning the + * dropdown. + */ + XSetWindowAttributes attrs = WINPR_C_ARRAY_INIT; + attrs.override_redirect = redirect ? True : False; + LogDynAndXChangeWindowAttributes(xfc->log, xfc->display, appWindow->handle, + CWOverrideRedirect, &attrs); + } + + LogDynAndXChangeProperty(xfc->log, xfc->display, appWindow->handle, xfc->NET_WM_WINDOW_TYPE, + XA_ATOM, 32, PropModeReplace, (BYTE*)&window_type, 1); + + const BOOL above = (ex_style & WS_EX_TOPMOST) != 0; + const BOOL transient = (style & WS_CHILD) == 0; + + if (transient) + xf_XSetTransientForHint( + xfc, appWindow); // xf_XSetTransientForHint only sets the hint if there is a parent + + xf_SendClientEvent(xfc, appWindow->handle, xfc->NET_WM_STATE, 4, + above ? NET_WM_STATE_ADD : NET_WM_STATE_REMOVE, xfc->NET_WM_STATE_ABOVE, 0, + 0); +} + +void xf_SetWindowActions(xfContext* xfc, xfAppWindow* appWindow) +{ + Atom allowed_actions[] = { + xfc->NET_WM_ACTION_CLOSE, xfc->NET_WM_ACTION_MINIMIZE, + xfc->NET_WM_ACTION_MOVE, xfc->NET_WM_ACTION_RESIZE, + xfc->NET_WM_ACTION_MAXIMIZE_HORZ, xfc->NET_WM_ACTION_MAXIMIZE_VERT, + xfc->NET_WM_ACTION_FULLSCREEN, xfc->NET_WM_ACTION_CHANGE_DESKTOP + }; + + if (!(appWindow->dwStyle & WS_SYSMENU)) + allowed_actions[0] = 0; + + if (!(appWindow->dwStyle & WS_MINIMIZEBOX)) + allowed_actions[1] = 0; + + if (!(appWindow->dwStyle & WS_SIZEBOX)) + allowed_actions[3] = 0; + + if (!(appWindow->dwStyle & WS_MAXIMIZEBOX)) + { + allowed_actions[4] = 0; + allowed_actions[5] = 0; + allowed_actions[6] = 0; + } + + LogDynAndXChangeProperty(xfc->log, xfc->display, appWindow->handle, xfc->NET_WM_ALLOWED_ACTIONS, + XA_ATOM, 32, PropModeReplace, (unsigned char*)&allowed_actions, 8); +} + +void xf_SetWindowText(xfContext* xfc, xfAppWindow* appWindow, const char* name) +{ + xf_SetWindowTitleText(xfc, appWindow->handle, name); +} + +static void xf_FixWindowCoordinates(xfContext* xfc, int* x, int* y, int* width, int* height) +{ + int vscreen_width = 0; + int vscreen_height = 0; + vscreen_width = xfc->vscreen.area.right - xfc->vscreen.area.left + 1; + vscreen_height = xfc->vscreen.area.bottom - xfc->vscreen.area.top + 1; + + if (*x < xfc->vscreen.area.left) + { + *width += *x; + *x = xfc->vscreen.area.left; + } + + if (*y < xfc->vscreen.area.top) + { + *height += *y; + *y = xfc->vscreen.area.top; + } + + if (*width > vscreen_width) + { + *width = vscreen_width; + } + + if (*height > vscreen_height) + { + *height = vscreen_height; + } + + if (*width < 1) + { + *width = 1; + } + + if (*height < 1) + { + *height = 1; + } +} + +int xf_AppWindowInit(xfContext* xfc, xfAppWindow* appWindow) +{ + if (!xfc || !appWindow) + return -1; + + xf_SetWindowDecorations(xfc, appWindow->handle, appWindow->decorations); + xf_SetWindowStyle(xfc, appWindow, appWindow->dwStyle, appWindow->dwExStyle); + xf_SetWindowPID(xfc, appWindow->handle, 0); + xf_ShowWindow(xfc, appWindow, WINDOW_SHOW); + LogDynAndXClearWindow(xfc->log, xfc->display, appWindow->handle); + LogDynAndXMapWindow(xfc->log, xfc->display, appWindow->handle); + /* Move doesn't seem to work until window is mapped. */ + xf_MoveWindow(xfc, appWindow, appWindow->x, appWindow->y, appWindow->width, appWindow->height); + xf_SetWindowText(xfc, appWindow, appWindow->title); + return 1; +} + +BOOL xf_AppWindowCreate(xfContext* xfc, xfAppWindow* appWindow) +{ + XGCValues gcv = WINPR_C_ARRAY_INIT; + int input_mask = 0; + XWMHints* InputModeHint = nullptr; + XClassHint* class_hints = nullptr; + const rdpSettings* settings = nullptr; + + WINPR_ASSERT(xfc); + WINPR_ASSERT(appWindow); + + settings = xfc->common.context.settings; + WINPR_ASSERT(settings); + + xf_FixWindowCoordinates(xfc, &appWindow->x, &appWindow->y, &appWindow->width, + &appWindow->height); + appWindow->shmid = -1; + appWindow->decorations = FALSE; + appWindow->fullscreen = FALSE; + appWindow->local_move.state = LMS_NOT_ACTIVE; + appWindow->is_mapped = FALSE; + appWindow->is_transient = FALSE; + appWindow->rail_state = 0; + appWindow->maxVert = FALSE; + appWindow->maxHorz = FALSE; + appWindow->minimized = FALSE; + appWindow->rail_ignore_configure = FALSE; + + WINPR_ASSERT(xfc->depth != 0); + appWindow->handle = LogDynAndXCreateWindow( + xfc->log, xfc->display, RootWindowOfScreen(xfc->screen), appWindow->x, appWindow->y, + WINPR_ASSERTING_INT_CAST(uint32_t, appWindow->width), + WINPR_ASSERTING_INT_CAST(uint32_t, appWindow->height), 0, xfc->depth, InputOutput, + xfc->visual, WINPR_ASSERTING_INT_CAST(uint32_t, xfc->attribs_mask), &xfc->attribs); + + if (!appWindow->handle) + return FALSE; + + appWindow->gc = + LogDynAndXCreateGC(xfc->log, xfc->display, appWindow->handle, GCGraphicsExposures, &gcv); + + if (!xf_AppWindowResize(xfc, appWindow)) + return FALSE; + + class_hints = XAllocClassHint(); + + if (class_hints) + { + char* strclass = nullptr; + + const char* WmClass = freerdp_settings_get_string(settings, FreeRDP_WmClass); + if (WmClass) + strclass = _strdup(WmClass); + else + { + size_t size = 0; + winpr_asprintf(&strclass, &size, "RAIL:%08" PRIX64 "", appWindow->windowId); + } + class_hints->res_class = strclass; + class_hints->res_name = "RAIL"; + XSetClassHint(xfc->display, appWindow->handle, class_hints); + XFree(class_hints); + free(strclass); + } + + /* Set the input mode hint for the WM */ + InputModeHint = XAllocWMHints(); + InputModeHint->flags = (1L << 0); + InputModeHint->input = True; + XSetWMHints(xfc->display, appWindow->handle, InputModeHint); + XFree(InputModeHint); + XSetWMProtocols(xfc->display, appWindow->handle, &(xfc->WM_DELETE_WINDOW), 1); + input_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | + EnterWindowMask | LeaveWindowMask | PointerMotionMask | Button1MotionMask | + Button2MotionMask | Button3MotionMask | Button4MotionMask | Button5MotionMask | + ButtonMotionMask | KeymapStateMask | ExposureMask | VisibilityChangeMask | + StructureNotifyMask | SubstructureNotifyMask | SubstructureRedirectMask | + FocusChangeMask | PropertyChangeMask | ColormapChangeMask | OwnerGrabButtonMask; + XSelectInput(xfc->display, appWindow->handle, input_mask); + + if (xfc->XWAYLAND_MAY_GRAB_KEYBOARD) + xf_SendClientEvent(xfc, appWindow->handle, xfc->XWAYLAND_MAY_GRAB_KEYBOARD, 1, 1); + + return TRUE; +} + +void xf_SetWindowMinMaxInfo(xfContext* xfc, xfAppWindow* appWindow, WINPR_ATTR_UNUSED int maxWidth, + WINPR_ATTR_UNUSED int maxHeight, WINPR_ATTR_UNUSED int maxPosX, + WINPR_ATTR_UNUSED int maxPosY, int minTrackWidth, int minTrackHeight, + int maxTrackWidth, int maxTrackHeight) +{ + XSizeHints* size_hints = nullptr; + size_hints = XAllocSizeHints(); + + if (size_hints) + { + size_hints->flags = PMinSize | PMaxSize | PResizeInc; + size_hints->min_width = minTrackWidth; + size_hints->min_height = minTrackHeight; + size_hints->max_width = maxTrackWidth; + size_hints->max_height = maxTrackHeight; + /* to speedup window drawing we need to select optimal value for sizing step. */ + size_hints->width_inc = size_hints->height_inc = 1; + XSetWMNormalHints(xfc->display, appWindow->handle, size_hints); + XFree(size_hints); + } +} + +void xf_StartLocalMoveSize(xfContext* xfc, xfAppWindow* appWindow, int direction, int x, int y) +{ + if (appWindow->local_move.state != LMS_NOT_ACTIVE) + return; + + /* + * Save original mouse location relative to root. This will be needed + * to end local move to RDP server and/or X server + */ + appWindow->local_move.root_x = x; + appWindow->local_move.root_y = y; + appWindow->local_move.state = LMS_STARTING; + appWindow->local_move.direction = direction; + + xf_ungrab(xfc); + + xf_SendClientEvent( + xfc, appWindow->handle, + xfc->NET_WM_MOVERESIZE, /* request X window manager to initiate a local move */ + 5, /* 5 arguments to follow */ + x, /* x relative to root window */ + y, /* y relative to root window */ + direction, /* extended ICCM direction flag */ + 1, /* simulated mouse button 1 */ + 1); /* 1 == application request per extended ICCM */ +} + +void xf_EndLocalMoveSize(xfContext* xfc, xfAppWindow* appWindow) +{ + if (appWindow->local_move.state == LMS_NOT_ACTIVE) + return; + + if (appWindow->local_move.state == LMS_STARTING) + { + /* + * The move never was property started. This can happen due to race + * conditions between the mouse button up and the communications to the + * RDP server for local moves. We must cancel the X window manager move. + * Per ICCM, the X client can ask to cancel an active move. + */ + xf_SendClientEvent( + xfc, appWindow->handle, + xfc->NET_WM_MOVERESIZE, /* request X window manager to abort a local move */ + 5, /* 5 arguments to follow */ + appWindow->local_move.root_x, /* x relative to root window */ + appWindow->local_move.root_y, /* y relative to root window */ + NET_WM_MOVERESIZE_CANCEL, /* extended ICCM direction flag */ + 1, /* simulated mouse button 1 */ + 1); /* 1 == application request per extended ICCM */ + } + + appWindow->local_move.state = LMS_NOT_ACTIVE; +} + +void xf_MoveWindow(xfContext* xfc, xfAppWindow* appWindow, int x, int y, int width, int height) +{ + BOOL resize = FALSE; + + if ((width * height) < 1) + return; + + if ((appWindow->width != width) || (appWindow->height != height)) + resize = TRUE; + + if (appWindow->local_move.state == LMS_STARTING || appWindow->local_move.state == LMS_ACTIVE) + return; + + appWindow->x = x; + appWindow->y = y; + appWindow->width = width; + appWindow->height = height; + + if (resize) + { + if (!xf_AppWindowResize(xfc, appWindow)) + return; + + LogDynAndXMoveResizeWindow(xfc->log, xfc->display, appWindow->handle, x, y, + WINPR_ASSERTING_INT_CAST(uint32_t, width), + WINPR_ASSERTING_INT_CAST(uint32_t, height)); + } + else + LogDynAndXMoveWindow(xfc->log, xfc->display, appWindow->handle, x, y); + + xf_UpdateWindowArea(xfc, appWindow, 0, 0, width, height); +} + +void xf_ShowWindow(xfContext* xfc, xfAppWindow* appWindow, BYTE state) +{ + WINPR_ASSERT(xfc); + WINPR_ASSERT(appWindow); + + switch (state) + { + case WINDOW_HIDE: + LogDynAndXWithdrawWindow(xfc->log, xfc->display, appWindow->handle, xfc->screen_number); + break; + + case WINDOW_SHOW_MINIMIZED: + appWindow->minimized = TRUE; + XIconifyWindow(xfc->display, appWindow->handle, xfc->screen_number); + break; + + case WINDOW_SHOW_MAXIMIZED: + /* Set the window as maximized */ + appWindow->maxHorz = TRUE; + appWindow->maxVert = TRUE; + xf_SendClientEvent(xfc, appWindow->handle, xfc->NET_WM_STATE, 4, NET_WM_STATE_ADD, + xfc->NET_WM_STATE_MAXIMIZED_VERT, xfc->NET_WM_STATE_MAXIMIZED_HORZ, + 0); + + /* + * This is a workaround for the case where the window is maximized locally before the + * rail server is told to maximize the window, this appears to be a race condition where + * the local window with incomplete data and once the window is actually maximized on + * the server + * - an update of the new areas may not happen. So, we simply to do a full update of the + * entire window once the rail server notifies us that the window is now maximized. + */ + if (appWindow->rail_state == WINDOW_SHOW_MAXIMIZED) + { + xf_UpdateWindowArea(xfc, appWindow, 0, 0, + WINPR_ASSERTING_INT_CAST(int, appWindow->windowWidth), + WINPR_ASSERTING_INT_CAST(int, appWindow->windowHeight)); + } + + break; + + case WINDOW_SHOW: + /* Ensure the window is not maximized */ + xf_SendClientEvent(xfc, appWindow->handle, xfc->NET_WM_STATE, 4, NET_WM_STATE_REMOVE, + xfc->NET_WM_STATE_MAXIMIZED_VERT, xfc->NET_WM_STATE_MAXIMIZED_HORZ, + 0); + + /* + * Ignore configure requests until both the Maximized properties have been processed + * to prevent condition where WM overrides size of request due to one or both of these + * properties still being set - which causes a position adjustment to be sent back to + * the server thus causing the window to not return to its original size + */ + if (appWindow->rail_state == WINDOW_SHOW_MAXIMIZED) + appWindow->rail_ignore_configure = TRUE; + + if (appWindow->is_transient) + xf_SetWindowUnlisted(xfc, appWindow->handle); + + LogDynAndXMapWindow(xfc->log, xfc->display, appWindow->handle); + break; + default: + break; + } + + /* Save the current rail state of this window */ + appWindow->rail_state = state; + LogDynAndXFlush(xfc->log, xfc->display); +} + +void xf_SetWindowRects(xfContext* xfc, xfAppWindow* appWindow, RECTANGLE_16* rects, int nrects) +{ + XRectangle* xrects = nullptr; + + if (nrects < 1) + return; + +#ifdef WITH_XEXT + xrects = (XRectangle*)calloc(WINPR_ASSERTING_INT_CAST(uint32_t, nrects), sizeof(XRectangle)); + + for (int i = 0; i < nrects; i++) + { + xrects[i].x = WINPR_ASSERTING_INT_CAST(short, rects[i].left); + xrects[i].y = WINPR_ASSERTING_INT_CAST(short, rects[i].top); + xrects[i].width = WINPR_ASSERTING_INT_CAST(unsigned short, rects[i].right - rects[i].left); + xrects[i].height = WINPR_ASSERTING_INT_CAST(unsigned short, rects[i].bottom - rects[i].top); + } + + XShapeCombineRectangles(xfc->display, appWindow->handle, ShapeBounding, 0, 0, xrects, nrects, + ShapeSet, 0); + free(xrects); +#endif +} + +void xf_SetWindowVisibilityRects(xfContext* xfc, xfAppWindow* appWindow, UINT32 rectsOffsetX, + UINT32 rectsOffsetY, RECTANGLE_16* rects, int nrects) +{ + XRectangle* xrects = nullptr; + + if (nrects < 1) + return; + +#ifdef WITH_XEXT + xrects = (XRectangle*)calloc(WINPR_ASSERTING_INT_CAST(uint32_t, nrects), sizeof(XRectangle)); + + for (int i = 0; i < nrects; i++) + { + xrects[i].x = WINPR_ASSERTING_INT_CAST(short, rects[i].left); + xrects[i].y = WINPR_ASSERTING_INT_CAST(short, rects[i].top); + xrects[i].width = WINPR_ASSERTING_INT_CAST(unsigned short, rects[i].right - rects[i].left); + xrects[i].height = WINPR_ASSERTING_INT_CAST(unsigned short, rects[i].bottom - rects[i].top); + } + + XShapeCombineRectangles( + xfc->display, appWindow->handle, ShapeBounding, WINPR_ASSERTING_INT_CAST(int, rectsOffsetX), + WINPR_ASSERTING_INT_CAST(int, rectsOffsetY), xrects, nrects, ShapeSet, 0); + free(xrects); +#endif +} + +void xf_UpdateWindowArea(xfContext* xfc, xfAppWindow* appWindow, int x, int y, int width, + int height) +{ + int ax = 0; + int ay = 0; + const rdpSettings* settings = nullptr; + + WINPR_ASSERT(xfc); + + settings = xfc->common.context.settings; + WINPR_ASSERT(settings); + + if (appWindow == nullptr) + return; + + if (appWindow->surfaceId < UINT16_MAX) + return; + + ax = x + appWindow->windowOffsetX; + ay = y + appWindow->windowOffsetY; + + if (ax + width > appWindow->windowOffsetX + appWindow->width) + width = (appWindow->windowOffsetX + appWindow->width - 1) - ax; + + if (ay + height > appWindow->windowOffsetY + appWindow->height) + height = (appWindow->windowOffsetY + appWindow->height - 1) - ay; + + if (freerdp_settings_get_bool(settings, FreeRDP_SoftwareGdi)) + { + LogDynAndXPutImage(xfc->log, xfc->display, appWindow->pixmap, appWindow->gc, xfc->image, ax, + ay, x, y, WINPR_ASSERTING_INT_CAST(uint32_t, width), + WINPR_ASSERTING_INT_CAST(uint32_t, height)); + } + + LogDynAndXCopyArea(xfc->log, xfc->display, appWindow->pixmap, appWindow->handle, appWindow->gc, + x, y, WINPR_ASSERTING_INT_CAST(uint32_t, width), + WINPR_ASSERTING_INT_CAST(uint32_t, height), x, y); + LogDynAndXFlush(xfc->log, xfc->display); +} + +void xf_AppWindowDestroyImage(xfAppWindow* appWindow) +{ + WINPR_ASSERT(appWindow); + if (appWindow->image) + { + appWindow->image->data = nullptr; + XDestroyImage(appWindow->image); + appWindow->image = nullptr; + } +} + +void xf_DestroyWindow(xfContext* xfc, xfAppWindow* appWindow) +{ + if (!appWindow) + return; + + if (xfc->appWindow == appWindow) + xfc->appWindow = nullptr; + + if (appWindow->gc) + LogDynAndXFreeGC(xfc->log, xfc->display, appWindow->gc); + + if (appWindow->pixmap) + LogDynAndXFreePixmap(xfc->log, xfc->display, appWindow->pixmap); + + xf_AppWindowDestroyImage(appWindow); + + if (appWindow->handle) + { + LogDynAndXUnmapWindow(xfc->log, xfc->display, appWindow->handle); + LogDynAndXDestroyWindow(xfc->log, xfc->display, appWindow->handle); + } + + if (appWindow->xfwin) + munmap(nullptr, sizeof(*appWindow->xfwin)); + + if (appWindow->shmid >= 0) + close(appWindow->shmid); + + shm_unlink(get_shm_id()); + appWindow->xfwin = (Window*)-1; + appWindow->shmid = -1; + free(appWindow->title); + free(appWindow->windowRects); + free(appWindow->visibilityRects); + free(appWindow); +} + +static xfAppWindow* get_windowUnlocked(xfContext* xfc, UINT64 id) +{ + WINPR_ASSERT(xfc); + return HashTable_GetItemValue(xfc->railWindows, &id); +} + +xfAppWindow* xf_rail_get_windowFrom(xfContext* xfc, UINT64 id, BOOL alreadyLocked, const char* file, + const char* fkt, size_t line) +{ + if (!xfc) + return nullptr; + + if (!xfc->railWindows) + return nullptr; + + if (!alreadyLocked) + xfAppWindowsLockFrom(xfc, file, fkt, line); + + xfAppWindow* window = get_windowUnlocked(xfc, id); + + if (!window && !alreadyLocked) + xfAppWindowsUnlockFrom(xfc, file, fkt, line); + + return window; +} + +xfAppWindow* xf_AppWindowFromX11WindowFrom(xfContext* xfc, Window wnd, const char* file, + const char* fkt, size_t line) +{ + ULONG_PTR* pKeys = nullptr; + + WINPR_ASSERT(xfc); + if (!xfc->railWindows) + return nullptr; + + xfAppWindowsLockFrom(xfc, file, fkt, line); + size_t count = HashTable_GetKeys(xfc->railWindows, &pKeys); + + for (size_t index = 0; index < count; index++) + { + xfAppWindow* appWindow = get_windowUnlocked(xfc, *(UINT64*)pKeys[index]); + + if (!appWindow) + { + xfAppWindowsUnlockFrom(xfc, file, fkt, line); + free(pKeys); + return nullptr; + } + + if (appWindow->handle == wnd) + { + free(pKeys); + return appWindow; + } + } + + xfAppWindowsUnlockFrom(xfc, file, fkt, line); + free(pKeys); + return nullptr; +} + +UINT xf_AppUpdateWindowFromSurface(xfContext* xfc, gdiGfxSurface* surface) +{ + XImage* image = nullptr; + UINT rc = ERROR_INTERNAL_ERROR; + + WINPR_ASSERT(xfc); + WINPR_ASSERT(surface); + + xfAppWindow* appWindow = xf_rail_get_window(xfc, surface->windowId, FALSE); + if (!appWindow) + { + WLog_VRB(TAG, "Failed to find a window for id=0x%08" PRIx64, surface->windowId); + return CHANNEL_RC_OK; + } + + const BOOL swGdi = freerdp_settings_get_bool(xfc->common.context.settings, FreeRDP_SoftwareGdi); + UINT32 nrects = 0; + const RECTANGLE_16* rects = region16_rects(&surface->invalidRegion, &nrects); + + if (swGdi) + { + if (appWindow->surfaceId != surface->surfaceId) + { + xf_AppWindowDestroyImage(appWindow); + appWindow->surfaceId = surface->surfaceId; + } + if (appWindow->width != (INT64)surface->width) + xf_AppWindowDestroyImage(appWindow); + if (appWindow->height != (INT64)surface->height) + xf_AppWindowDestroyImage(appWindow); + + if (!appWindow->image) + { + WINPR_ASSERT(xfc->depth != 0); + appWindow->image = LogDynAndXCreateImage( + xfc->log, xfc->display, xfc->visual, WINPR_ASSERTING_INT_CAST(uint32_t, xfc->depth), + ZPixmap, 0, (char*)surface->data, surface->width, surface->height, + xfc->scanline_pad, WINPR_ASSERTING_INT_CAST(int, surface->scanline)); + if (!appWindow->image) + { + WLog_WARN(TAG, + "Failed create a XImage[%" PRIu32 "x%" PRIu32 ", scanline=%" PRIu32 + ", bpp=%" PRId32 "] for window id=0x%08" PRIx64, + surface->width, surface->height, surface->scanline, xfc->depth, + surface->windowId); + goto fail; + } + appWindow->image->byte_order = LSBFirst; + appWindow->image->bitmap_bit_order = LSBFirst; + } + + image = appWindow->image; + } + else + { + xfGfxSurface* xfSurface = (xfGfxSurface*)surface; + image = xfSurface->image; + } + + for (UINT32 x = 0; x < nrects; x++) + { + const RECTANGLE_16* rect = &rects[x]; + const UINT32 width = rect->right - rect->left; + const UINT32 height = rect->bottom - rect->top; + + LogDynAndXPutImage(xfc->log, xfc->display, appWindow->pixmap, appWindow->gc, image, + rect->left, rect->top, rect->left, rect->top, width, height); + + LogDynAndXCopyArea(xfc->log, xfc->display, appWindow->pixmap, appWindow->handle, + appWindow->gc, rect->left, rect->top, width, height, rect->left, + rect->top); + } + + rc = CHANNEL_RC_OK; +fail: + xf_rail_return_window(appWindow, FALSE); + LogDynAndXFlush(xfc->log, xfc->display); + + return rc; +} + +BOOL xf_AppWindowResize(xfContext* xfc, xfAppWindow* appWindow) +{ + WINPR_ASSERT(xfc); + WINPR_ASSERT(appWindow); + + if (appWindow->pixmap != 0) + LogDynAndXFreePixmap(xfc->log, xfc->display, appWindow->pixmap); + + WINPR_ASSERT(xfc->depth != 0); + appWindow->pixmap = LogDynAndXCreatePixmap( + xfc->log, xfc->display, xfc->drawable, WINPR_ASSERTING_INT_CAST(uint32_t, appWindow->width), + WINPR_ASSERTING_INT_CAST(uint32_t, appWindow->height), + WINPR_ASSERTING_INT_CAST(uint32_t, xfc->depth)); + xf_AppWindowDestroyImage(appWindow); + + return appWindow->pixmap != 0; +} + +void xf_XSetTransientForHint(xfContext* xfc, xfAppWindow* window) +{ + WINPR_ASSERT(xfc); + WINPR_ASSERT(window); + + if (window->ownerWindowId == 0) + return; + + xfAppWindow* parent = xf_rail_get_window(xfc, window->ownerWindowId, TRUE); + if (!parent) + return; + + (void)LogDynAndXSetTransientForHint(xfc->log, xfc->display, window->handle, parent->handle); + xf_rail_return_window(parent, TRUE); +} + +void xfAppWindowsLockFrom(xfContext* xfc, WINPR_ATTR_UNUSED const char* file, + WINPR_ATTR_UNUSED const char* fkt, WINPR_ATTR_UNUSED size_t line) +{ + WINPR_ASSERT(xfc); + +#if defined(WITH_VERBOSE_WINPR_ASSERT) + const DWORD level = WLOG_TRACE; + if (WLog_IsLevelActive(xfc->log, level)) + WLog_PrintTextMessage(xfc->log, level, line, file, fkt, "[rails] locking [%s]", fkt); +#endif + + xf_lock_x11(xfc); + HashTable_Lock(xfc->railWindows); + +#if defined(WITH_VERBOSE_WINPR_ASSERT) + WINPR_ASSERT(!xfc->isRailWindowsLocked); + xfc->isRailWindowsLocked = TRUE; +#endif +} + +void xfAppWindowsUnlockFrom(xfContext* xfc, WINPR_ATTR_UNUSED const char* file, + WINPR_ATTR_UNUSED const char* fkt, WINPR_ATTR_UNUSED size_t line) +{ + WINPR_ASSERT(xfc); + +#if defined(WITH_VERBOSE_WINPR_ASSERT) + const DWORD level = WLOG_TRACE; + if (WLog_IsLevelActive(xfc->log, level)) + WLog_PrintTextMessage(xfc->log, level, line, file, fkt, "[rails] unocking [%s]", fkt); + + WINPR_ASSERT(xfc->isRailWindowsLocked); + xfc->isRailWindowsLocked = FALSE; +#endif + + HashTable_Unlock(xfc->railWindows); + xf_unlock_x11(xfc); +} diff --git a/third_party/FreeRDP/client/X11/xf_window.h b/third_party/FreeRDP/client/X11/xf_window.h new file mode 100644 index 0000000..803b239 --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_window.h @@ -0,0 +1,140 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Windows + * + * Copyright 2011 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_X11_WINDOW_H +#define FREERDP_CLIENT_X11_WINDOW_H + +#include + +#include +#include +#include + +#include "xf_types.h" +#include "xf_rail.h" +#include "xf_floatbar.h" + +typedef struct xf_window xfWindow; + +// Extended ICCM flags http://standards.freedesktop.org/wm-spec/wm-spec-latest.html +WINPR_PRAGMA_DIAG_PUSH +WINPR_PRAGMA_DIAG_IGNORED_RESERVED_ID_MACRO + +#define NET_WM_MOVERESIZE_SIZE_TOPLEFT 0 +#define NET_WM_MOVERESIZE_SIZE_TOP 1 +#define NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2 +#define NET_WM_MOVERESIZE_SIZE_RIGHT 3 +#define NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4 +#define NET_WM_MOVERESIZE_SIZE_BOTTOM 5 +#define NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6 +#define NET_WM_MOVERESIZE_SIZE_LEFT 7 +#define NET_WM_MOVERESIZE_MOVE 8 /* movement only */ +#define NET_WM_MOVERESIZE_SIZE_KEYBOARD 9 /* size via keyboard */ +#define NET_WM_MOVERESIZE_MOVE_KEYBOARD 10 /* move via keyboard */ +#define NET_WM_MOVERESIZE_CANCEL 11 /* cancel operation */ + +#define NET_WM_STATE_REMOVE 0 /* remove/unset property */ +#define NET_WM_STATE_ADD 1 /* add/set property */ +#define NET_WM_STATE_TOGGLE 2 /* toggle property */ + +WINPR_PRAGMA_DIAG_POP + +struct xf_window +{ + GC gc; + int left; + int top; + int right; + int bottom; + int width; + int height; + int shmid; + Window handle; + Window* xfwin; + xfFloatbar* floatbar; + BOOL decorations; + BOOL is_mapped; + BOOL is_transient; +}; + +void xf_ewmhints_init(xfContext* xfc); + +BOOL xf_GetWorkArea(xfContext* xfc); + +void xf_SetWindowFullscreen(xfContext* xfc, xfWindow* window, BOOL fullscreen); +void xf_SetWindowMinimized(xfContext* xfc, xfWindow* window); +void xf_SetWindowDecorations(xfContext* xfc, Window window, BOOL show); +void xf_SetWindowUnlisted(xfContext* xfc, Window window); + +void xf_DestroyDesktopWindow(xfContext* xfc, xfWindow* window); + +WINPR_ATTR_MALLOC(xf_DestroyDesktopWindow, 2) +xfWindow* xf_CreateDesktopWindow(xfContext* xfc, char* name, int width, int height); + +void xf_ResizeDesktopWindow(xfContext* xfc, xfWindow* window, int width, int height); + +Window xf_CreateDummyWindow(xfContext* xfc); +void xf_DestroyDummyWindow(xfContext* xfc, Window window); + +BOOL xf_GetWindowProperty(xfContext* xfc, Window window, Atom property, int length, + unsigned long* nitems, unsigned long* bytes, BYTE** prop); +void xf_SendClientEvent(xfContext* xfc, Window window, Atom atom, unsigned int numArgs, ...); + +BOOL xf_AppWindowCreate(xfContext* xfc, xfAppWindow* appWindow); +int xf_AppWindowInit(xfContext* xfc, xfAppWindow* appWindow); + +BOOL xf_AppWindowResize(xfContext* xfc, xfAppWindow* appWindow); + +void xf_SetWindowText(xfContext* xfc, xfAppWindow* appWindow, const char* name); +void xf_MoveWindow(xfContext* xfc, xfAppWindow* appWindow, int x, int y, int width, int height); +void xf_ShowWindow(xfContext* xfc, xfAppWindow* appWindow, BYTE state); +// void xf_SetWindowIcon(xfContext* xfc, xfAppWindow* appWindow, rdpIcon* icon); +void xf_SetWindowRects(xfContext* xfc, xfAppWindow* appWindow, RECTANGLE_16* rects, int nrects); +void xf_SetWindowVisibilityRects(xfContext* xfc, xfAppWindow* appWindow, UINT32 rectsOffsetX, + UINT32 rectsOffsetY, RECTANGLE_16* rects, int nrects); +void xf_SetWindowStyle(xfContext* xfc, xfAppWindow* appWindow, UINT32 style, UINT32 ex_style); +void xf_SetWindowActions(xfContext* xfc, xfAppWindow* appWindow); +void xf_UpdateWindowArea(xfContext* xfc, xfAppWindow* appWindow, int x, int y, int width, + int height); +UINT xf_AppUpdateWindowFromSurface(xfContext* xfc, gdiGfxSurface* surface); + +void xf_AppWindowDestroyImage(xfAppWindow* appWindow); +void xf_DestroyWindow(xfContext* xfc, xfAppWindow* appWindow); +void xf_SetWindowMinMaxInfo(xfContext* xfc, xfAppWindow* appWindow, int maxWidth, int maxHeight, + int maxPosX, int maxPosY, int minTrackWidth, int minTrackHeight, + int maxTrackWidth, int maxTrackHeight); +void xf_StartLocalMoveSize(xfContext* xfc, xfAppWindow* appWindow, int direction, int x, int y); +void xf_EndLocalMoveSize(xfContext* xfc, xfAppWindow* appWindow); + +#define xf_AppWindowFromX11Window(xfc, wnd) \ + xf_AppWindowFromX11WindowFrom((xfc), (wnd), __FILE__, __func__, __LINE__) +WINPR_ATTR_MALLOC(xf_rail_return_windowFrom, 1) +xfAppWindow* xf_AppWindowFromX11WindowFrom(xfContext* xfc, Window wnd, const char* file, + const char* fkt, size_t line); + +#define xf_AppWindowsLock(xfc) xfAppWindowsLockFrom((xfc), __FILE__, __func__, __LINE__) +void xfAppWindowsLockFrom(xfContext* xfc, const char* file, const char* fkt, size_t line); + +#define xf_AppWindowsUnlock(xfc) xfAppWindowsUnlockFrom((xfc), __FILE__, __func__, __LINE__) +void xfAppWindowsUnlockFrom(xfContext* xfc, const char* file, const char* fkt, size_t line); + +const char* window_styles_to_string(UINT32 style, char* buffer, size_t length); +const char* window_styles_ex_to_string(UINT32 styleEx, char* buffer, size_t length); + +#endif /* FREERDP_CLIENT_X11_WINDOW_H */ diff --git a/third_party/FreeRDP/client/X11/xf_x11_utils.c b/third_party/FreeRDP/client/X11/xf_x11_utils.c new file mode 100644 index 0000000..d577856 --- /dev/null +++ b/third_party/FreeRDP/client/X11/xf_x11_utils.c @@ -0,0 +1,157 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 helper utilities + * + * Copyright 2025 Armin Novak + * Copyringht 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. + */ + +// Do not include! X11 has conflicting defines #include "xf_utils.h" +const char* request_code_2_str(int code); + +#include + +const char* request_code_2_str(int code) +{ +#define CASE2STR(x) \ + case x: \ + return #x + switch (code) + { + CASE2STR(X_CreateWindow); + CASE2STR(X_ChangeWindowAttributes); + CASE2STR(X_GetWindowAttributes); + CASE2STR(X_DestroyWindow); + CASE2STR(X_DestroySubwindows); + CASE2STR(X_ChangeSaveSet); + CASE2STR(X_ReparentWindow); + CASE2STR(X_MapWindow); + CASE2STR(X_MapSubwindows); + CASE2STR(X_UnmapWindow); + CASE2STR(X_UnmapSubwindows); + CASE2STR(X_ConfigureWindow); + CASE2STR(X_CirculateWindow); + CASE2STR(X_GetGeometry); + CASE2STR(X_QueryTree); + CASE2STR(X_InternAtom); + CASE2STR(X_GetAtomName); + CASE2STR(X_ChangeProperty); + CASE2STR(X_DeleteProperty); + CASE2STR(X_GetProperty); + CASE2STR(X_ListProperties); + CASE2STR(X_SetSelectionOwner); + CASE2STR(X_GetSelectionOwner); + CASE2STR(X_ConvertSelection); + CASE2STR(X_SendEvent); + CASE2STR(X_GrabPointer); + CASE2STR(X_UngrabPointer); + CASE2STR(X_GrabButton); + CASE2STR(X_UngrabButton); + CASE2STR(X_ChangeActivePointerGrab); + CASE2STR(X_GrabKeyboard); + CASE2STR(X_UngrabKeyboard); + CASE2STR(X_GrabKey); + CASE2STR(X_UngrabKey); + CASE2STR(X_AllowEvents); + CASE2STR(X_GrabServer); + CASE2STR(X_UngrabServer); + CASE2STR(X_QueryPointer); + CASE2STR(X_GetMotionEvents); + CASE2STR(X_TranslateCoords); + CASE2STR(X_WarpPointer); + CASE2STR(X_SetInputFocus); + CASE2STR(X_GetInputFocus); + CASE2STR(X_QueryKeymap); + CASE2STR(X_OpenFont); + CASE2STR(X_CloseFont); + CASE2STR(X_QueryFont); + CASE2STR(X_QueryTextExtents); + CASE2STR(X_ListFonts); + CASE2STR(X_ListFontsWithInfo); + CASE2STR(X_SetFontPath); + CASE2STR(X_GetFontPath); + CASE2STR(X_CreatePixmap); + CASE2STR(X_FreePixmap); + CASE2STR(X_CreateGC); + CASE2STR(X_ChangeGC); + CASE2STR(X_CopyGC); + CASE2STR(X_SetDashes); + CASE2STR(X_SetClipRectangles); + CASE2STR(X_FreeGC); + CASE2STR(X_ClearArea); + CASE2STR(X_CopyArea); + CASE2STR(X_CopyPlane); + CASE2STR(X_PolyPoint); + CASE2STR(X_PolyLine); + CASE2STR(X_PolySegment); + CASE2STR(X_PolyRectangle); + CASE2STR(X_PolyArc); + CASE2STR(X_FillPoly); + CASE2STR(X_PolyFillRectangle); + CASE2STR(X_PolyFillArc); + CASE2STR(X_PutImage); + CASE2STR(X_GetImage); + CASE2STR(X_PolyText8); + CASE2STR(X_PolyText16); + CASE2STR(X_ImageText8); + CASE2STR(X_ImageText16); + CASE2STR(X_CreateColormap); + CASE2STR(X_FreeColormap); + CASE2STR(X_CopyColormapAndFree); + CASE2STR(X_InstallColormap); + CASE2STR(X_UninstallColormap); + CASE2STR(X_ListInstalledColormaps); + CASE2STR(X_AllocColor); + CASE2STR(X_AllocNamedColor); + CASE2STR(X_AllocColorCells); + CASE2STR(X_AllocColorPlanes); + CASE2STR(X_FreeColors); + CASE2STR(X_StoreColors); + CASE2STR(X_StoreNamedColor); + CASE2STR(X_QueryColors); + CASE2STR(X_LookupColor); + CASE2STR(X_CreateCursor); + CASE2STR(X_CreateGlyphCursor); + CASE2STR(X_FreeCursor); + CASE2STR(X_RecolorCursor); + CASE2STR(X_QueryBestSize); + CASE2STR(X_QueryExtension); + CASE2STR(X_ListExtensions); + CASE2STR(X_ChangeKeyboardMapping); + CASE2STR(X_GetKeyboardMapping); + CASE2STR(X_ChangeKeyboardControl); + CASE2STR(X_GetKeyboardControl); + CASE2STR(X_Bell); + CASE2STR(X_ChangePointerControl); + CASE2STR(X_GetPointerControl); + CASE2STR(X_SetScreenSaver); + CASE2STR(X_GetScreenSaver); + CASE2STR(X_ChangeHosts); + CASE2STR(X_ListHosts); + CASE2STR(X_SetAccessControl); + CASE2STR(X_SetCloseDownMode); + CASE2STR(X_KillClient); + CASE2STR(X_RotateProperties); + CASE2STR(X_ForceScreenSaver); + CASE2STR(X_SetPointerMapping); + CASE2STR(X_GetPointerMapping); + CASE2STR(X_SetModifierMapping); + CASE2STR(X_GetModifierMapping); + CASE2STR(X_NoOperation); + + default: + return "UNKNOWN"; + } +} diff --git a/third_party/FreeRDP/client/X11/xfreerdp.h b/third_party/FreeRDP/client/X11/xfreerdp.h new file mode 100644 index 0000000..469ecce --- /dev/null +++ b/third_party/FreeRDP/client/X11/xfreerdp.h @@ -0,0 +1,422 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Client + * + * Copyright 2011 Marc-Andre Moreau + * Copyright 2016 Thincast Technologies GmbH + * Copyright 2016 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CLIENT_X11_FREERDP_H +#define FREERDP_CLIENT_X11_FREERDP_H + +#include + +#include "xf_types.h" +#include "xf_disp.h" +#include "xf_cliprdr.h" +#include "xf_video.h" +#include "xf_rail.h" + +#ifdef WITH_XCURSOR +#include +#endif + +#ifdef WITH_XI +#include +#endif + +#include + +#include "xf_window.h" +#include "xf_monitor.h" +#include "xf_channels.h" + +#if defined(CHANNEL_TSMF_CLIENT) +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if !defined(XcursorUInt) +typedef unsigned int XcursorUInt; +#endif + +#if !defined(XcursorPixel) +typedef XcursorUInt XcursorPixel; +#endif + +struct xf_FullscreenMonitors +{ + INT32 top; + INT32 bottom; + INT32 left; + INT32 right; +}; +typedef struct xf_FullscreenMonitors xfFullscreenMonitors; + +struct xf_WorkArea +{ + INT32 x; + INT32 y; + UINT32 width; + UINT32 height; +}; +typedef struct xf_WorkArea xfWorkArea; + +struct xf_pointer +{ + rdpPointer pointer; + XcursorPixel* cursorPixels; + UINT32 nCursors; + UINT32 mCursors; + UINT32* cursorWidths; + UINT32* cursorHeights; + Cursor* cursors; + Cursor cursor; +}; +typedef struct xf_pointer xfPointer; + +struct xf_bitmap +{ + rdpBitmap bitmap; + Pixmap pixmap; + XImage* image; +}; +typedef struct xf_bitmap xfBitmap; + +struct xf_glyph +{ + rdpGlyph glyph; + Pixmap pixmap; +}; +typedef struct xf_glyph xfGlyph; + +/* Number of buttons that are mapped from X11 to RDP button events. */ +#define NUM_BUTTONS_MAPPED 11 + +typedef struct +{ + UINT32 button; + UINT16 flags; +} button_map; + +#if defined(WITH_XI) +#define MAX_CONTACTS 20 + +typedef struct touch_contact +{ + int id; + int count; + double pos_x; + double pos_y; + double last_x; + double last_y; + +} touchContact; + +#endif + +struct xf_context +{ + rdpClientContext common; + + GC gc; + int xfds; + int depth; + + GC gc_mono; + BOOL invert; + Screen* screen; + XImage* image; + Pixmap primary; + Pixmap drawing; + Visual* visual; + Display* display; + Drawable drawable; + Pixmap bitmap_mono; + Colormap colormap; + int screen_number; + int scanline_pad; + BOOL big_endian; + BOOL fullscreen; + BOOL decorations; + BOOL grab_keyboard; + BOOL unobscured; + BOOL debug; + HANDLE x11event; + xfWindow* window; + xfAppWindow* appWindow; + xfPointer* pointer; + xfWorkArea workArea; + xfFullscreenMonitors fullscreenMonitors; + int current_desktop; + BOOL remote_app; + HANDLE mutex; + BOOL UseXThreads; + BOOL cursorHidden; + + UINT32 bitmap_size; + BYTE* bitmap_buffer; + + BOOL frame_begin; + + int XInputOpcode; + + int savedWidth; + int savedHeight; + int savedPosX; + int savedPosY; + +#ifdef WITH_XRENDER + int scaledWidth; + int scaledHeight; + int offset_x; + int offset_y; +#endif + + BOOL focused; + BOOL mouse_active; + BOOL fullscreen_toggle; + BOOL KeyboardState[256]; + XModifierKeymap* modifierMap; + wArrayList* keyCombinations; + wArrayList* xevents; + BOOL actionScriptExists; + + int attribs_mask; + XSetWindowAttributes attribs; + BOOL complex_regions; + VIRTUAL_SCREEN vscreen; +#if defined(CHANNEL_TSMF_CLIENT) + void* xv_context; +#endif + + Atom* supportedAtoms; + unsigned long supportedAtomCount; + + Atom UTF8_STRING; + + Atom XWAYLAND_MAY_GRAB_KEYBOARD; + + Atom NET_WM_ICON; + Atom MOTIF_WM_HINTS; + Atom NET_NUMBER_OF_DESKTOPS; + Atom NET_CURRENT_DESKTOP; + Atom NET_WORKAREA; + + Atom NET_SUPPORTED; + Atom NET_SUPPORTING_WM_CHECK; + + Atom NET_WM_STATE; + Atom NET_WM_STATE_MODAL; + Atom NET_WM_STATE_STICKY; + Atom NET_WM_STATE_MAXIMIZED_VERT; + Atom NET_WM_STATE_MAXIMIZED_HORZ; + Atom NET_WM_STATE_SHADED; + Atom NET_WM_STATE_SKIP_TASKBAR; + Atom NET_WM_STATE_SKIP_PAGER; + Atom NET_WM_STATE_HIDDEN; + Atom NET_WM_STATE_FULLSCREEN; + Atom NET_WM_STATE_ABOVE; + Atom NET_WM_STATE_BELOW; + Atom NET_WM_STATE_DEMANDS_ATTENTION; + + Atom NET_WM_FULLSCREEN_MONITORS; + + Atom NET_WM_NAME; + Atom NET_WM_PID; + + Atom NET_WM_WINDOW_TYPE; + Atom NET_WM_WINDOW_TYPE_NORMAL; + Atom NET_WM_WINDOW_TYPE_DIALOG; + Atom NET_WM_WINDOW_TYPE_UTILITY; + Atom NET_WM_WINDOW_TYPE_POPUP; + Atom NET_WM_WINDOW_TYPE_POPUP_MENU; + Atom NET_WM_WINDOW_TYPE_DROPDOWN_MENU; + + Atom NET_WM_MOVERESIZE; + Atom NET_MOVERESIZE_WINDOW; + + Atom WM_STATE; + Atom WM_PROTOCOLS; + Atom WM_DELETE_WINDOW; + + /* Allow actions */ + Atom NET_WM_ALLOWED_ACTIONS; + + Atom NET_WM_ACTION_CLOSE; + Atom NET_WM_ACTION_MINIMIZE; + Atom NET_WM_ACTION_MOVE; + Atom NET_WM_ACTION_RESIZE; + Atom NET_WM_ACTION_MAXIMIZE_HORZ; + Atom NET_WM_ACTION_MAXIMIZE_VERT; + Atom NET_WM_ACTION_FULLSCREEN; + Atom NET_WM_ACTION_CHANGE_DESKTOP; + + /* Channels */ +#if defined(CHANNEL_TSMF_CLIENT) + TsmfClientContext* tsmf; +#endif + + xfClipboard* clipboard; + CliprdrClientContext* cliprdr; + xfVideoContext* xfVideo; + xfDispContext* xfDisp; + + RailClientContext* rail; + wHashTable* railWindows; + xfRailIconCache* railIconCache; + +#if defined(WITH_VERBOSE_WINPR_ASSERT) + BOOL isRailWindowsLocked; +#endif + + BOOL xkbAvailable; + BOOL xrenderAvailable; + + /* value to be sent over wire for each logical client mouse button */ + button_map button_map[NUM_BUTTONS_MAPPED]; + BYTE savedMaximizedState; + UINT32 locked; + BOOL wasRightCtrlAlreadyPressed; + BOOL ungrabKeyboardWithRightCtrl; + +#if defined(WITH_XI) + touchContact contacts[MAX_CONTACTS]; + int active_contacts; + int lastEvType; + XIDeviceEvent lastEvent; + double firstDist; + double lastDist; + double z_vector; + double px_vector; + double py_vector; +#endif + BOOL xi_rawevent; + BOOL xi_event; + HANDLE pipethread; + wLog* log; + FREERDP_REMAP_TABLE* remap_table; + DWORD X11_KEYCODE_TO_VIRTUAL_SCANCODE[256]; + bool isCursorHidden; + bool isActionScriptAllowed; +}; + +BOOL xf_create_window(xfContext* xfc); +void xf_destroy_window(xfContext* xfc); + +BOOL xf_create_image(xfContext* xfc); +void xf_toggle_fullscreen(xfContext* xfc); +void xf_minimize(xfContext* xfc); + +enum XF_EXIT_CODE +{ + /* section 0-15: protocol-independent codes */ + XF_EXIT_SUCCESS = 0, + XF_EXIT_DISCONNECT = 1, + XF_EXIT_LOGOFF = 2, + XF_EXIT_IDLE_TIMEOUT = 3, + XF_EXIT_LOGON_TIMEOUT = 4, + XF_EXIT_CONN_REPLACED = 5, + XF_EXIT_OUT_OF_MEMORY = 6, + XF_EXIT_CONN_DENIED = 7, + XF_EXIT_CONN_DENIED_FIPS = 8, + XF_EXIT_USER_PRIVILEGES = 9, + XF_EXIT_FRESH_CREDENTIALS_REQUIRED = 10, + XF_EXIT_DISCONNECT_BY_USER = 11, + + /* section 16-31: license error set */ + XF_EXIT_LICENSE_INTERNAL = 16, + XF_EXIT_LICENSE_NO_LICENSE_SERVER = 17, + XF_EXIT_LICENSE_NO_LICENSE = 18, + XF_EXIT_LICENSE_BAD_CLIENT_MSG = 19, + XF_EXIT_LICENSE_HWID_DOESNT_MATCH = 20, + XF_EXIT_LICENSE_BAD_CLIENT = 21, + XF_EXIT_LICENSE_CANT_FINISH_PROTOCOL = 22, + XF_EXIT_LICENSE_CLIENT_ENDED_PROTOCOL = 23, + XF_EXIT_LICENSE_BAD_CLIENT_ENCRYPTION = 24, + XF_EXIT_LICENSE_CANT_UPGRADE = 25, + XF_EXIT_LICENSE_NO_REMOTE_CONNECTIONS = 26, + + /* section 32-127: RDP protocol error set */ + XF_EXIT_RDP = 32, + + /* section 128-254: xfreerdp specific exit codes */ + XF_EXIT_PARSE_ARGUMENTS = 128, + XF_EXIT_MEMORY = 129, + XF_EXIT_PROTOCOL = 130, + XF_EXIT_CONN_FAILED = 131, + XF_EXIT_AUTH_FAILURE = 132, + XF_EXIT_NEGO_FAILURE = 133, + XF_EXIT_LOGON_FAILURE = 134, + XF_EXIT_ACCOUNT_LOCKED_OUT = 135, + XF_EXIT_PRE_CONNECT_FAILED = 136, + XF_EXIT_CONNECT_UNDEFINED = 137, + XF_EXIT_POST_CONNECT_FAILED = 138, + XF_EXIT_DNS_ERROR = 139, + XF_EXIT_DNS_NAME_NOT_FOUND = 140, + XF_EXIT_CONNECT_FAILED = 141, + XF_EXIT_MCS_CONNECT_INITIAL_ERROR = 142, + XF_EXIT_TLS_CONNECT_FAILED = 143, + XF_EXIT_INSUFFICIENT_PRIVILEGES = 144, + XF_EXIT_CONNECT_CANCELLED = 145, + + XF_EXIT_CONNECT_TRANSPORT_FAILED = 147, + XF_EXIT_CONNECT_PASSWORD_EXPIRED = 148, + XF_EXIT_CONNECT_PASSWORD_MUST_CHANGE = 149, + XF_EXIT_CONNECT_KDC_UNREACHABLE = 150, + XF_EXIT_CONNECT_ACCOUNT_DISABLED = 151, + XF_EXIT_CONNECT_PASSWORD_CERTAINLY_EXPIRED = 152, + XF_EXIT_CONNECT_CLIENT_REVOKED = 153, + XF_EXIT_CONNECT_WRONG_PASSWORD = 154, + XF_EXIT_CONNECT_ACCESS_DENIED = 155, + XF_EXIT_CONNECT_ACCOUNT_RESTRICTION = 156, + XF_EXIT_CONNECT_ACCOUNT_EXPIRED = 157, + XF_EXIT_CONNECT_LOGON_TYPE_NOT_GRANTED = 158, + XF_EXIT_CONNECT_NO_OR_MISSING_CREDENTIALS = 159, + XF_EXIT_CONNECT_TARGET_BOOTING = 160, + XF_EXIT_CODE_LAST = XF_EXIT_CONNECT_TARGET_BOOTING, + XF_EXIT_UNKNOWN = 255, +}; + +#define xf_lock_x11(xfc) xf_lock_x11_(xfc, __func__) +#define xf_unlock_x11(xfc) xf_unlock_x11_(xfc, __func__) + +void xf_lock_x11_(xfContext* xfc, const char* fkt); +void xf_unlock_x11_(xfContext* xfc, const char* fkt); + +BOOL xf_picture_transform_required(xfContext* xfc); + +#define xf_draw_screen(_xfc, _x, _y, _w, _h) \ + xf_draw_screen_((_xfc), (_x), (_y), (_w), (_h), __func__, __FILE__, __LINE__) +void xf_draw_screen_(xfContext* xfc, int x, int y, int w, int h, const char* fkt, const char* file, + int line); + +BOOL xf_keyboard_update_modifier_map(xfContext* xfc); + +int xf_exit_code_from_disconnect_reason(DWORD reason); + +#endif /* FREERDP_CLIENT_X11_FREERDP_H */ diff --git a/third_party/FreeRDP/client/X11/xkb_layout_ids.c b/third_party/FreeRDP/client/X11/xkb_layout_ids.c new file mode 100644 index 0000000..b0a63a5 --- /dev/null +++ b/third_party/FreeRDP/client/X11/xkb_layout_ids.c @@ -0,0 +1,870 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RDP Keyboard layout ID detection from common X11 xkb keyboard layout names + * + * Copyright 2009-2012 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "xkb_layout_ids.h" + +#include + +#include + +#include "xf_debug.h" +#include + +typedef struct +{ + const char* variant; /* XKB Keyboard layout variant */ + INT64 keyboardLayoutID; /* Keyboard Layout ID */ +} XKB_VARIANT; + +typedef struct +{ + const char* layout; /* XKB Keyboard layout */ + INT64 keyboardLayoutID; /* Keyboard Layout ID */ + const XKB_VARIANT* variants; +} XKB_LAYOUT; + +/* Those have been generated automatically and are waiting to be filled by hand */ + +/* USA */ +static const XKB_VARIANT us_variants[] = { + { "chr", 0 }, /* Cherokee */ + { "euro", 0 }, /* With EuroSign on 5 */ + { "intl", KBD_UNITED_STATES_INTERNATIONAL }, /* International (with dead keys) */ + { "alt-intl", + KBD_UNITED_STATES_INTERNATIONAL }, /* Alternative international (former us_intl) */ + { "colemak", 0 }, /* Colemak */ + { "dvorak", KBD_UNITED_STATES_DVORAK }, /* Dvorak */ + { "dvorak-intl", KBD_UNITED_STATES_DVORAK }, /* Dvorak international */ + { "dvorak-l", KBD_UNITED_STATES_DVORAK_FOR_LEFT_HAND }, /* Left handed Dvorak */ + { "dvorak-r", KBD_UNITED_STATES_DVORAK_FOR_RIGHT_HAND }, /* Right handed Dvorak */ + { "dvorak-classic", KBD_UNITED_STATES_DVORAK }, /* Classic Dvorak */ + { "dvp", KBD_UNITED_STATES_DVORAK_PROGRAMMER }, /* Programmer Dvorak */ + { "rus", 0 }, /* Russian phonetic */ + { "mac", KBD_US }, /* Macintosh */ + { "altgr-intl", KBD_UNITED_STATES_INTERNATIONAL }, /* International (AltGr dead keys) */ + { "olpc2", KBD_US }, /* Group toggle on multiply/divide key */ + { "", 0 }, +}; + +/* Afghanistan */ +static const XKB_VARIANT af_variants[] = { + { "ps", KBD_PASHTO }, /* Pashto */ + { "uz", KBD_UZBEK_CYRILLIC }, /* Southern Uzbek */ + { "olpc-ps", KBD_PASHTO }, /* OLPC Pashto */ + { "olpc-fa", 0 }, /* OLPC Dari */ + { "olpc-uz", KBD_UZBEK_CYRILLIC }, /* OLPC Southern Uzbek */ + { "", 0 }, +}; + +/* Arabic */ +static const XKB_VARIANT ara_variants[] = { + { "azerty", KBD_ARABIC_102_AZERTY }, /* azerty */ + { "azerty_digits", KBD_ARABIC_102_AZERTY }, /* azerty/digits */ + { "digits", KBD_ARABIC_102_AZERTY }, /* digits */ + { "qwerty", KBD_ARABIC_101 }, /* qwerty */ + { "qwerty_digits", KBD_ARABIC_101 }, /* qwerty/digits */ + { "buckwalter", KBD_US_ENGLISH_TABLE_FOR_IBM_ARABIC_238_L }, /* Buckwalter */ + { "", 0 }, +}; + +/* Armenia */ +static const XKB_VARIANT am_variants[] = { + { "phonetic", 0 }, /* Phonetic */ + { "phonetic-alt", 0 }, /* Alternative Phonetic */ + { "eastern", KBD_ARMENIAN_EASTERN }, /* Eastern */ + { "western", KBD_ARMENIAN_WESTERN }, /* Western */ + { "eastern-alt", KBD_ARMENIAN_EASTERN }, /* Alternative Eastern */ + { "", 0 }, +}; + +/* Azerbaijan */ +static const XKB_VARIANT az_variants[] = { + { "cyrillic", KBD_AZERI_CYRILLIC }, /* Cyrillic */ + { "", 0 }, +}; + +/* Belarus */ +static const XKB_VARIANT by_variants[] = { + { "winkeys", KBD_BELARUSIAN }, /* Winkeys */ + { "latin", KBD_BELARUSIAN }, /* Latin */ + { "", 0 }, +}; + +/* Belgium */ +static const XKB_VARIANT be_variants[] = { + { "oss", KBD_BELGIAN_FRENCH }, /* Alternative */ + { "oss_latin9", KBD_BELGIAN_FRENCH }, /* Alternative, latin-9 only */ + { "oss_sundeadkeys", KBD_BELGIAN_PERIOD }, /* Alternative, Sun dead keys */ + { "iso-alternate", KBD_BELGIAN_COMMA }, /* ISO Alternate */ + { "nodeadkeys", KBD_BELGIAN_COMMA }, /* Eliminate dead keys */ + { "sundeadkeys", KBD_BELGIAN_PERIOD }, /* Sun dead keys */ + { "wang", KBD_BELGIAN_FRENCH }, /* Wang model 724 azerty */ + { "", 0 }, +}; + +/* Bangladesh */ +static const XKB_VARIANT bd_variants[] = { + { "probhat", KBD_BENGALI_INSCRIPT }, /* Probhat */ + { "", 0 }, +}; + +/* India */ +static const XKB_VARIANT in_variants[] = { + { "ben", KBD_BENGALI }, /* Bengali */ + { "ben_probhat", KBD_BENGALI_INSCRIPT }, /* Bengali Probhat */ + { "guj", KBD_GUJARATI }, /* Gujarati */ + { "guru", 0 }, /* Gurmukhi */ + { "jhelum", 0 }, /* Gurmukhi Jhelum */ + { "kan", KBD_KANNADA }, /* Kannada */ + { "mal", KBD_MALAYALAM }, /* Malayalam */ + { "mal_lalitha", KBD_MALAYALAM }, /* Malayalam Lalitha */ + { "ori", 0 }, /* Oriya */ + { "tam_unicode", KBD_TAMIL }, /* Tamil Unicode */ + { "tam_TAB", KBD_TAMIL }, /* Tamil TAB Typewriter */ + { "tam_TSCII", KBD_TAMIL }, /* Tamil TSCII Typewriter */ + { "tam", KBD_TAMIL }, /* Tamil */ + { "tel", KBD_TELUGU }, /* Telugu */ + { "urd-phonetic", KBD_URDU }, /* Urdu, Phonetic */ + { "urd-phonetic3", KBD_URDU }, /* Urdu, Alternative phonetic */ + { "urd-winkeys", KBD_URDU }, /* Urdu, Winkeys */ + { "bolnagri", KBD_HINDI_TRADITIONAL }, /* Hindi Bolnagri */ + { "hin-wx", KBD_HINDI_TRADITIONAL }, /* Hindi Wx */ + { "", 0 }, +}; + +/* Bosnia and Herzegovina */ +static const XKB_VARIANT ba_variants[] = { + { "alternatequotes", KBD_BOSNIAN }, /* Use guillemets for quotes */ + { "unicode", KBD_BOSNIAN }, /* Use Bosnian digraphs */ + { "unicodeus", KBD_BOSNIAN }, /* US keyboard with Bosnian digraphs */ + { "us", KBD_BOSNIAN_CYRILLIC }, /* US keyboard with Bosnian letters */ + { "", 0 }, +}; + +/* Brazil */ +static const XKB_VARIANT br_variants[] = { + { "nodeadkeys", KBD_PORTUGUESE_BRAZILIAN_ABNT2 }, /* Eliminate dead keys */ + { "dvorak", KBD_UNITED_STATES_DVORAK }, /* Dvorak */ + { "nativo", KBD_PORTUGUESE_BRAZILIAN_ABNT2 }, /* Nativo */ + { "nativo-us", KBD_PORTUGUESE_BRAZILIAN_ABNT2 }, /* Nativo for USA keyboards */ + { "nativo-epo", KBD_PORTUGUESE_BRAZILIAN_ABNT2 }, /* Nativo for Esperanto */ + { "", 0 }, +}; + +/* Bulgaria */ +static const XKB_VARIANT bg_variants[] = { + { "phonetic", KBD_BULGARIAN_LATIN }, /* Traditional Phonetic */ + { "bas_phonetic", KBD_BULGARIAN_LATIN }, /* Standard Phonetic */ + { "", 0 }, +}; + +/* Morocco */ +static const XKB_VARIANT ma_variants[] = { + { "french", KBD_FRENCH }, /* French */ + { "tifinagh", 0 }, /* Tifinagh */ + { "tifinagh-alt", 0 }, /* Tifinagh Alternative */ + { "tifinagh-alt-phonetic", 0 }, /* Tifinagh Alternative Phonetic */ + { "tifinagh-extended", 0 }, /* Tifinagh Extended */ + { "tifinagh-phonetic", 0 }, /* Tifinagh Phonetic */ + { "tifinagh-extended-phonetic", 0 }, /* Tifinagh Extended Phonetic */ + { "", 0 }, +}; + +/* Canada */ +static const XKB_VARIANT ca_variants[] = { + { "fr", KBD_CANADIAN_FRENCH }, /* French Dvorak */ + { "fr-dvorak", KBD_UNITED_STATES_DVORAK }, /* French Dvorak */ + { "fr-legacy", KBD_CANADIAN_FRENCH_LEGACY }, /* French (legacy) */ + { "multix", KBD_CANADIAN_MULTILINGUAL_STANDARD }, /* Multilingual */ + { "multi", KBD_CANADIAN_MULTILINGUAL_STANDARD }, /* Multilingual, first part */ + { "multi-2gr", KBD_CANADIAN_MULTILINGUAL_STANDARD }, /* Multilingual, second part */ + { "ike", KBD_INUKTITUT_LATIN }, /* Inuktitut */ + { "shs" /* codespell:ignore shs */, 0 }, /* Secwepemctsin */ + { "kut", 0 }, /* Ktunaxa */ + { "", 0 }, +}; + +/* China */ +static const XKB_VARIANT cn_variants[] = { + { "tib", 0 }, /* Tibetan */ + { "tib_asciinum", 0 }, /* Tibetan (with ASCII numerals) */ + { "", 0 }, +}; + +/* Croatia */ +static const XKB_VARIANT hr_variants[] = { + { "alternatequotes", KBD_CROATIAN }, /* Use guillemets for quotes */ + { "unicode", KBD_CROATIAN }, /* Use Croatian digraphs */ + { "unicodeus", KBD_CROATIAN }, /* US keyboard with Croatian digraphs */ + { "us", KBD_CROATIAN }, /* US keyboard with Croatian letters */ + { "", 0 }, +}; + +/* Czechia */ +static const XKB_VARIANT cz_variants[] = { + { "bksl", KBD_CZECH_PROGRAMMERS }, /* With <\|> key */ + { "qwerty", KBD_CZECH_QWERTY }, /* qwerty */ + { "qwerty_bksl", KBD_CZECH_QWERTY }, /* qwerty, extended Backslash */ + { "ucw", KBD_CZECH }, /* UCW layout (accented letters only) */ + { "", 0 }, +}; + +/* Denmark */ +static const XKB_VARIANT dk_variants[] = { + { "nodeadkeys", KBD_DANISH }, /* Eliminate dead keys */ + { "mac", KBD_DANISH }, /* Macintosh */ + { "mac_nodeadkeys", KBD_DANISH }, /* Macintosh, eliminate dead keys */ + { "dvorak", KBD_UNITED_STATES_DVORAK }, /* Dvorak */ + { "", 0 }, +}; + +/* Netherlands */ +static const XKB_VARIANT nl_variants[] = { + { "sundeadkeys", KBD_SWISS_FRENCH }, /* Sun dead keys */ + { "mac", KBD_SWISS_FRENCH }, /* Macintosh */ + { "std", KBD_SWISS_FRENCH }, /* Standard */ + { "", 0 }, +}; + +/* Estonia */ +static const XKB_VARIANT ee_variants[] = { + { "nodeadkeys", KBD_US }, /* Eliminate dead keys */ + { "dvorak", KBD_UNITED_STATES_DVORAK }, /* Dvorak */ + { "us", KBD_UNITED_STATES_INTERNATIONAL }, /* US keyboard with Estonian letters */ + { "", 0 }, +}; + +/* Iran */ +static const XKB_VARIANT ir_variants[] = { + { "pro", 0 }, /* Pro */ + { "keypad", 0 }, /* Keypad */ + { "pro_keypad", 0 }, /* Pro Keypad */ + { "ku", 0 }, /* Kurdish, Latin Q */ + { "ku_f", 0 }, /* Kurdish, (F) */ + { "ku_alt", 0 }, /* Kurdish, Latin Alt-Q */ + { "ku_ara", 0 }, /* Kurdish, Arabic-Latin */ + { "", 0 }, +}; + +/* Iraq */ +static const XKB_VARIANT iq_variants[] = { + { "ku", 0 }, /* Kurdish, Latin Q */ + { "ku_f", 0 }, /* Kurdish, (F) */ + { "ku_alt", 0 }, /* Kurdish, Latin Alt-Q */ + { "ku_ara", 0 }, /* Kurdish, Arabic-Latin */ + { "", 0 }, +}; + +/* Faroe Islands */ +static const XKB_VARIANT fo_variants[] = { + { "nodeadkeys", 0 }, /* Eliminate dead keys */ + { "", 0 }, +}; + +/* Finland */ +static const XKB_VARIANT fi_variants[] = { + { "nodeadkeys", 0 }, /* Eliminate dead keys */ + { "smi", 0 }, /* Northern Saami */ + { "classic", 0 }, /* Classic */ + { "mac", 0 }, /* Macintosh */ + { "", 0 }, +}; + +/* France */ +static const XKB_VARIANT fr_variants[] = { + { "nodeadkeys", 0 }, /* Eliminate dead keys */ + { "sundeadkeys", 0 }, /* Sun dead keys */ + { "oss", 0 }, /* Alternative */ + { "oss_latin9", 0 }, /* Alternative, latin-9 only */ + { "oss_nodeadkeys", 0 }, /* Alternative, eliminate dead keys */ + { "oss_sundeadkeys", 0 }, /* Alternative, Sun dead keys */ + { "latin9", 0 }, /* (Legacy) Alternative */ + { "latin9_nodeadkeys", 0 }, /* (Legacy) Alternative, eliminate dead keys */ + { "latin9_sundeadkeys", 0 }, /* (Legacy) Alternative, Sun dead keys */ + { "bepo", KBD_FRENCH_BEPO }, /* Bepo, ergonomic, Dvorak way */ + { "bepo_latin9", 0 }, /* Bepo, ergonomic, Dvorak way, latin-9 only */ + { "dvorak", 0 }, /* Dvorak */ + { "mac", 0 }, /* Macintosh */ + { "bre", 0 }, /* Breton */ + { "oci", 0 }, /* Occitan */ + { "geo", 0 }, /* Georgian AZERTY Tskapo */ + { "", 0 }, +}; + +/* Ghana */ +static const XKB_VARIANT gh_variants[] = { + { "generic", 0 }, /* Multilingual */ + { "akan", 0 }, /* Akan */ + { "ewe", 0 }, /* Ewe */ + { "fula", 0 }, /* Fula */ + { "ga", 0 }, /* Ga */ + { "hausa", 0 }, /* Hausa */ + { "", 0 }, +}; + +/* Georgia */ +static const XKB_VARIANT ge_variants[] = { + { "ergonomic", 0 }, /* Ergonomic */ + { "mess", 0 }, /* MESS */ + { "ru", 0 }, /* Russian */ + { "os", 0 }, /* Ossetian */ + { "", 0 }, +}; + +/* Germany */ +static const XKB_VARIANT de_variants[] = { + { "deadacute", KBD_GERMAN }, /* Dead acute */ + { "deadgraveacute", KBD_GERMAN }, /* Dead grave acute */ + { "nodeadkeys", KBD_GERMAN }, /* Eliminate dead keys */ + { "ro", KBD_GERMAN }, /* Romanian keyboard with German letters */ + { "ro_nodeadkeys", + KBD_GERMAN }, /* Romanian keyboard with German letters, eliminate dead keys */ + { "dvorak", KBD_UNITED_STATES_DVORAK }, /* Dvorak */ + { "sundeadkeys", KBD_GERMAN }, /* Sun dead keys */ + { "neo", KBD_GERMAN_NEO }, /* Neo 2 */ + { "mac", KBD_GERMAN }, /* Macintosh */ + { "mac_nodeadkeys", KBD_GERMAN }, /* Macintosh, eliminate dead keys */ + { "dsb", KBD_GERMAN }, /* Lower Sorbian */ + { "dsb_qwertz", KBD_GERMAN }, /* Lower Sorbian (qwertz) */ + { "qwerty", KBD_GERMAN_IBM }, /* qwerty */ + { "", 0 }, +}; + +/* Greece */ +static const XKB_VARIANT gr_variants[] = { + { "simple", KBD_GREEK_220 }, /* Simple */ + { "extended", KBD_GREEK_319 }, /* Extended */ + { "nodeadkeys", KBD_GREEK_319 }, /* Eliminate dead keys */ + { "polytonic", KBD_GREEK_POLYTONIC }, /* Polytonic */ + { "", 0 }, +}; + +/* Hungary */ +static const XKB_VARIANT hu_variants[] = { + { "standard", KBD_HUNGARIAN_101_KEY }, /* Standard */ + { "nodeadkeys", KBD_HUNGARIAN_101_KEY }, /* Eliminate dead keys */ + { "qwerty", KBD_HUNGARIAN_101_KEY }, /* qwerty */ + { "101_qwertz_comma_dead", KBD_HUNGARIAN_101_KEY }, /* 101/qwertz/comma/Dead keys */ + { "101_qwertz_comma_nodead", KBD_HUNGARIAN_101_KEY }, /* 101/qwertz/comma/Eliminate dead keys */ + { "101_qwertz_dot_dead", KBD_HUNGARIAN_101_KEY }, /* 101/qwertz/dot/Dead keys */ + { "101_qwertz_dot_nodead", KBD_HUNGARIAN_101_KEY }, /* 101/qwertz/dot/Eliminate dead keys */ + { "101_qwerty_comma_dead", KBD_HUNGARIAN_101_KEY }, /* 101/qwerty/comma/Dead keys */ + { "101_qwerty_comma_nodead", KBD_HUNGARIAN_101_KEY }, /* 101/qwerty/comma/Eliminate dead keys */ + { "101_qwerty_dot_dead", KBD_HUNGARIAN_101_KEY }, /* 101/qwerty/dot/Dead keys */ + { "101_qwerty_dot_nodead", KBD_HUNGARIAN_101_KEY }, /* 101/qwerty/dot/Eliminate dead keys */ + { "102_qwertz_comma_dead", KBD_HUNGARIAN_101_KEY }, /* 102/qwertz/comma/Dead keys */ + { "102_qwertz_comma_nodead", KBD_HUNGARIAN_101_KEY }, /* 102/qwertz/comma/Eliminate dead keys */ + { "102_qwertz_dot_dead", KBD_HUNGARIAN_101_KEY }, /* 102/qwertz/dot/Dead keys */ + { "102_qwertz_dot_nodead", KBD_HUNGARIAN_101_KEY }, /* 102/qwertz/dot/Eliminate dead keys */ + { "102_qwerty_comma_dead", KBD_HUNGARIAN_101_KEY }, /* 102/qwerty/comma/Dead keys */ + { "102_qwerty_comma_nodead", KBD_HUNGARIAN_101_KEY }, /* 102/qwerty/comma/Eliminate dead keys */ + { "102_qwerty_dot_dead", KBD_HUNGARIAN_101_KEY }, /* 102/qwerty/dot/Dead keys */ + { "102_qwerty_dot_nodead", KBD_HUNGARIAN_101_KEY }, /* 102/qwerty/dot/Eliminate dead keys */ + { "", 0 }, +}; + +/* Iceland */ +static const XKB_VARIANT is_variants[] = { + { "Sundeadkeys", KBD_ICELANDIC }, /* Sun dead keys */ + { "nodeadkeys", KBD_ICELANDIC }, /* Eliminate dead keys */ + { "mac", KBD_ICELANDIC }, /* Macintosh */ + { "dvorak", KBD_UNITED_STATES_DVORAK }, /* Dvorak */ + { "", 0 }, +}; + +/* Israel */ +static const XKB_VARIANT il_variants[] = { + { "lyx", KBD_HEBREW }, /* lyx */ + { "phonetic", KBD_HEBREW }, /* Phonetic */ + { "biblical", KBD_HEBREW }, /* Biblical Hebrew (Tiro) */ + { "", 0 }, +}; + +/* Italy */ +static const XKB_VARIANT it_variants[] = { + { "nodeadkeys", KBD_ITALIAN_142 }, /* Eliminate dead keys */ + { "mac", KBD_ITALIAN }, /* Macintosh */ + { "geo", KBD_GEORGIAN }, /* Georgian */ + { "", 0 }, +}; + +/* Japan */ +static const XKB_VARIANT jp_variants[] = { + { "kana", KBD_JAPANESE_INPUT_SYSTEM_MS_IME2002 }, /* Kana */ + { "OADG109A", KBD_JAPANESE_INPUT_SYSTEM_MS_IME2002 }, /* OADG 109A */ + { "", 0 }, +}; + +/* Kyrgyzstan */ +static const XKB_VARIANT kg_variants[] = { + { "phonetic", KBD_KYRGYZ_CYRILLIC }, /* Phonetic */ + { "", 0 }, +}; + +/* Kazakhstan */ +static const XKB_VARIANT kz_variants[] = { + { "ruskaz", KBD_KAZAKH }, /* Russian with Kazakh */ + { "kazrus", KBD_KAZAKH }, /* Kazakh with Russian */ + { "", 0 }, +}; + +/* Latin America */ +static const XKB_VARIANT latam_variants[] = { + { "nodeadkeys", KBD_LATIN_AMERICAN }, /* Eliminate dead keys */ + { "deadtilde", KBD_LATIN_AMERICAN }, /* Include dead tilde */ + { "sundeadkeys", KBD_LATIN_AMERICAN }, /* Sun dead keys */ + { "", 0 }, +}; + +/* Lithuania */ +static const XKB_VARIANT lt_variants[] = { + { "std", KBD_LITHUANIAN }, /* Standard */ + { "us", KBD_LITHUANIAN_IBM }, /* US keyboard with Lithuanian letters */ + { "ibm", KBD_LITHUANIAN_IBM }, /* IBM (LST 1205-92) */ + { "lekp", KBD_LITHUANIAN }, /* LEKP */ + { "lekpa", KBD_LITHUANIAN }, /* LEKPa */ + { "balticplus", KBD_LITHUANIAN }, /* Baltic+ */ + { "", 0 }, +}; + +/* Latvia */ +static const XKB_VARIANT lv_variants[] = { + { "apostrophe", KBD_LATVIAN }, /* Apostrophe (') variant */ + { "tilde", KBD_LATVIAN }, /* Tilde (~) variant */ + { "fkey", KBD_LATVIAN }, /* F-letter (F) variant */ + { "", 0 }, +}; + +/* Montenegro */ +static const XKB_VARIANT me_variants[] = { + { "cyrillic", 0 }, /* Cyrillic */ + { "cyrillicyz", 0 }, /* Cyrillic, Z and ZHE swapped */ + { "latinunicode", 0 }, /* Latin unicode */ + { "latinyz", 0 }, /* Latin qwerty */ + { "latinunicodeyz", 0 }, /* Latin unicode qwerty */ + { "cyrillicalternatequotes", 0 }, /* Cyrillic with guillemets */ + { "latinalternatequotes", 0 }, /* Latin with guillemets */ + { "", 0 }, +}; + +/* Macedonia */ +static const XKB_VARIANT mk_variants[] = { + { "nodeadkeys", KBD_FYRO_MACEDONIAN }, /* Eliminate dead keys */ + { "", 0 }, +}; + +/* Malta */ +static const XKB_VARIANT mt_variants[] = { + { "us", KBD_MALTESE_48_KEY }, /* Maltese keyboard with US layout */ + { "", 0 }, +}; + +/* Norway */ +static const XKB_VARIANT no_variants[] = { + { "nodeadkeys", KBD_NORWEGIAN }, /* Eliminate dead keys */ + { "dvorak", KBD_UNITED_STATES_DVORAK }, /* Dvorak */ + { "smi", KBD_NORWEGIAN_WITH_SAMI }, /* Northern Saami */ + { "smi_nodeadkeys", KBD_SAMI_EXTENDED_NORWAY }, /* Northern Saami, eliminate dead keys */ + { "mac", KBD_NORWEGIAN }, /* Macintosh */ + { "mac_nodeadkeys", KBD_SAMI_EXTENDED_NORWAY }, /* Macintosh, eliminate dead keys */ + { "", 0 }, +}; + +/* Poland */ +static const XKB_VARIANT pl_variants[] = { + { "qwertz", KBD_POLISH_214 }, /* qwertz */ + { "dvorak", KBD_UNITED_STATES_DVORAK }, /* Dvorak */ + { "dvorak_quotes", KBD_UNITED_STATES_DVORAK }, /* Dvorak, Polish quotes on quotemark key */ + { "dvorak_altquotes", KBD_UNITED_STATES_DVORAK }, /* Dvorak, Polish quotes on key 1 */ + { "csb", 0 }, /* Kashubian */ + { "ru_phonetic_dvorak", KBD_UNITED_STATES_DVORAK }, /* Russian phonetic Dvorak */ + { "", 0 }, +}; + +/* Portugal */ +static const XKB_VARIANT pt_variants[] = { + { "nodeadkeys", KBD_PORTUGUESE }, /* Eliminate dead keys */ + { "sundeadkeys", KBD_PORTUGUESE }, /* Sun dead keys */ + { "mac", KBD_PORTUGUESE }, /* Macintosh */ + { "mac_nodeadkeys", KBD_PORTUGUESE }, /* Macintosh, eliminate dead keys */ + { "mac_sundeadkeys", KBD_PORTUGUESE }, /* Macintosh, Sun dead keys */ + { "nativo", KBD_PORTUGUESE }, /* Nativo */ + { "nativo-us", KBD_PORTUGUESE }, /* Nativo for USA keyboards */ + { "nativo-epo", KBD_PORTUGUESE }, /* Nativo for Esperanto */ + { "", 0 }, +}; + +/* Romania */ +static const XKB_VARIANT ro_variants[] = { + { "cedilla", KBD_ROMANIAN }, /* Cedilla */ + { "std", KBD_ROMANIAN }, /* Standard */ + { "std_cedilla", KBD_ROMANIAN }, /* Standard (Cedilla) */ + { "winkeys", KBD_ROMANIAN }, /* Winkeys */ + { "crh_f", KBD_TURKISH_F }, /* Crimean Tatar (Turkish F) */ + { "crh_alt", KBD_TURKISH_Q }, /* Crimean Tatar (Turkish Alt-Q) */ + { "crh_dobruca1", KBD_TATAR }, /* Crimean Tatar (Dobruca-1 Q) */ + { "crh_dobruca2", KBD_TATAR }, /* Crimean Tatar (Dobruca-2 Q) */ + { "", 0 }, +}; + +/* Russia */ +static const XKB_VARIANT ru_variants[] = { + { "phonetic", KBD_RUSSIAN }, /* Phonetic */ + { "phonetic_winkeys", KBD_RUSSIAN }, /* Phonetic Winkeys */ + { "typewriter", KBD_RUSSIAN_TYPEWRITER }, /* Typewriter */ + { "legacy", KBD_RUSSIAN }, /* Legacy */ + { "tt", KBD_TATAR }, /* Tatar */ + { "os_legacy", 0 }, /* Ossetian, legacy */ + { "os_winkeys", 0 }, /* Ossetian, Winkeys */ + { "cv", 0 }, /* Chuvash */ + { "cv_latin", 0 }, /* Chuvash Latin */ + { "udm", 0 }, /* Udmurt */ + { "kom", 0 }, /* Komi */ + { "sah", 0 }, /* Yakut */ + { "xal", 0 }, /* Kalmyk */ + { "dos", 0 }, /* DOS */ + { "", 0 }, +}; + +/* Serbia */ +static const XKB_VARIANT rs_variants[] = { + { "yz", KBD_SERBIAN_CYRILLIC }, /* Z and ZHE swapped */ + { "latin", KBD_SERBIAN_LATIN }, /* Latin */ + { "latinunicode", KBD_SERBIAN_LATIN }, /* Latin Unicode */ + { "latinyz", KBD_SERBIAN_LATIN }, /* Latin qwerty */ + { "latinunicodeyz", KBD_SERBIAN_LATIN }, /* Latin Unicode qwerty */ + { "alternatequotes", KBD_SERBIAN_CYRILLIC }, /* With guillemets */ + { "latinalternatequotes", KBD_SERBIAN_LATIN }, /* Latin with guillemets */ + { "", 0 }, +}; + +/* Slovenia */ +static const XKB_VARIANT si_variants[] = { + { "alternatequotes", KBD_SLOVENIAN }, /* Use guillemets for quotes */ + { "us", KBD_UNITED_STATES_INTERNATIONAL }, /* US keyboard with Slovenian letters */ + { "", 0 }, +}; + +/* Slovakia */ +static const XKB_VARIANT sk_variants[] = { + { "bksl", KBD_SLOVAK }, /* Extended Backslash */ + { "qwerty", KBD_SLOVAK_QWERTY }, /* qwerty */ + { "qwerty_bksl", KBD_SLOVAK_QWERTY }, /* qwerty, extended Backslash */ + { "", 0 }, +}; + +/* Spain */ +static const XKB_VARIANT es_variants[] = { + { "nodeadkeys", KBD_SPANISH_VARIATION }, /* Eliminate dead keys */ + { "deadtilde", KBD_SPANISH_VARIATION }, /* Include dead tilde */ + { "sundeadkeys", KBD_SPANISH }, /* Sun dead keys */ + { "dvorak", KBD_UNITED_STATES_DVORAK }, /* Dvorak */ + { "ast", KBD_SPANISH_VARIATION }, /* Asturian variant with bottom-dot H and bottom-dot L */ + { "cat", KBD_SPANISH_VARIATION }, /* Catalan variant with middle-dot L */ + { "mac", KBD_SPANISH }, /* Macintosh */ + { "", 0 }, +}; + +/* Sweden */ +static const XKB_VARIANT se_variants[] = { + { "nodeadkeys", KBD_SWEDISH }, /* Eliminate dead keys */ + { "dvorak", KBD_UNITED_STATES_DVORAK }, /* Dvorak */ + { "rus", KBD_RUSSIAN }, /* Russian phonetic */ + { "rus_nodeadkeys", KBD_RUSSIAN }, /* Russian phonetic, eliminate dead keys */ + { "smi", KBD_SWEDISH_WITH_SAMI }, /* Northern Saami */ + { "mac", KBD_SWEDISH }, /* Macintosh */ + { "svdvorak", KBD_UNITED_STATES_DVORAK }, /* Svdvorak */ + { "", 0 }, +}; + +/* Switzerland */ +static const XKB_VARIANT ch_variants[] = { + { "de_nodeadkeys", KBD_SWISS_GERMAN }, /* German, eliminate dead keys */ + { "de_sundeadkeys", KBD_SWISS_GERMAN }, /* German, Sun dead keys */ + { "fr", KBD_SWISS_FRENCH }, /* French */ + { "fr_nodeadkeys", KBD_SWISS_FRENCH }, /* French, eliminate dead keys */ + { "fr_sundeadkeys", KBD_SWISS_FRENCH }, /* French, Sun dead keys */ + { "fr_mac", KBD_SWISS_FRENCH }, /* French (Macintosh) */ + { "de_mac", KBD_SWISS_GERMAN }, /* German (Macintosh) */ + { "", 0 }, +}; + +/* Syria */ +static const XKB_VARIANT sy_variants[] = { + { "syc", KBD_SYRIAC }, /* Syriac */ + { "syc_phonetic", KBD_SYRIAC_PHONETIC }, /* Syriac phonetic */ + { "ku", 0 }, /* Kurdish, Latin Q */ + { "ku_f", 0 }, /* Kurdish, (F) */ + { "ku_alt", 0 }, /* Kurdish, Latin Alt-Q */ + { "", 0 }, +}; + +/* Tajikistan */ +static const XKB_VARIANT tj_variants[] = { + { "legacy", 0 }, /* Legacy */ + { "", 0 }, +}; + +/* Sri Lanka */ +static const XKB_VARIANT lk_variants[] = { + { "tam_unicode", KBD_TAMIL }, /* Tamil Unicode */ + { "tam_TAB", KBD_TAMIL }, /* Tamil TAB Typewriter */ + { "", 0 }, +}; + +/* Thailand */ +static const XKB_VARIANT th_variants[] = { + { "tis", KBD_THAI_KEDMANEE_NON_SHIFTLOCK }, /* TIS-820.2538 */ + { "pat", KBD_THAI_PATTACHOTE }, /* Pattachote */ + { "", 0 }, +}; + +/* Turkey */ +static const XKB_VARIANT tr_variants[] = { + { "f", KBD_TURKISH_F }, /* (F) */ + { "alt", KBD_TURKISH_Q }, /* Alt-Q */ + { "sundeadkeys", KBD_TURKISH_F }, /* Sun dead keys */ + { "ku", 0 }, /* Kurdish, Latin Q */ + { "ku_f", 0 }, /* Kurdish, (F) */ + { "ku_alt", 0 }, /* Kurdish, Latin Alt-Q */ + { "intl", KBD_TURKISH_F }, /* International (with dead keys) */ + { "crh", KBD_TATAR }, /* Crimean Tatar (Turkish Q) */ + { "crh_f", KBD_TURKISH_F }, /* Crimean Tatar (Turkish F) */ + { "crh_alt", KBD_TURKISH_Q }, /* Crimean Tatar (Turkish Alt-Q) */ + { "", 0 }, +}; + +/* Ukraine */ +static const XKB_VARIANT ua_variants[] = { + { "phonetic", KBD_UKRAINIAN }, /* Phonetic */ + { "typewriter", KBD_UKRAINIAN }, /* Typewriter */ + { "winkeys", KBD_UKRAINIAN }, /* Winkeys */ + { "legacy", KBD_UKRAINIAN }, /* Legacy */ + { "rstu", KBD_UKRAINIAN }, /* Standard RSTU */ + { "rstu_ru", KBD_UKRAINIAN }, /* Standard RSTU on Russian layout */ + { "homophonic", KBD_UKRAINIAN }, /* Homophonic */ + { "crh", KBD_TATAR }, /* Crimean Tatar (Turkish Q) */ + { "crh_f", KBD_TURKISH_F }, /* Crimean Tatar (Turkish F) */ + { "crh_alt", KBD_TURKISH_Q }, /* Crimean Tatar (Turkish Alt-Q) */ + { "", 0 }, +}; + +/* United Kingdom */ +static const XKB_VARIANT gb_variants[] = { + { "extd", KBD_UNITED_KINGDOM_EXTENDED }, /* Extended - Winkeys */ + { "intl", KBD_UNITED_KINGDOM_EXTENDED }, /* International (with dead keys) */ + { "dvorak", KBD_UNITED_STATES_DVORAK }, /* Dvorak */ + { "dvorakukp", KBD_UNITED_STATES_DVORAK }, /* Dvorak (UK Punctuation) */ + { "mac", KBD_UNITED_KINGDOM }, /* Macintosh */ + { "colemak", 0 }, /* Colemak */ + { "", 0 }, +}; + +/* Uzbekistan */ +static const XKB_VARIANT uz_variants[] = { + { "latin", 0 }, /* Latin */ + { "crh", KBD_TATAR }, /* Crimean Tatar (Turkish Q) */ + { "crh_f", KBD_TURKISH_F }, /* Crimean Tatar (Turkish F) */ + { "crh_alt", KBD_TURKISH_Q }, /* Crimean Tatar (Turkish Alt-Q) */ + { "", 0 }, +}; + +/* Korea, Republic of */ +static const XKB_VARIANT kr_variants[] = { + { "kr104", KBD_KOREAN_INPUT_SYSTEM_IME_2000 }, /* 101/104 key Compatible */ + { "", 0 }, +}; + +/* Ireland */ +static const XKB_VARIANT ie_variants[] = { + { "CloGaelach", KBD_GAELIC }, /* CloGaelach */ + { "UnicodeExpert", KBD_GAELIC }, /* UnicodeExpert */ + { "ogam", KBD_GAELIC }, /* Ogham */ + { "ogam_is434", KBD_GAELIC }, /* Ogham IS434 */ + { "", 0 }, +}; + +/* Pakistan */ +static const XKB_VARIANT pk_variants[] = { + { "urd-crulp", 0 }, /* CRULP */ + { "urd-nla", 0 }, /* NLA */ + { "ara", KBD_ARABIC_101 }, /* Arabic */ + { "", 0 }, +}; + +/* Esperanto */ +static const XKB_VARIANT epo_variants[] = { + { "legacy", 0 }, /* displaced semicolon and quote (obsolete) */ + { "", 0 }, +}; + +/* Nigeria */ +static const XKB_VARIANT ng_variants[] = { + { "igbo", 0 }, /* Igbo */ + { "yoruba", 0 }, /* Yoruba */ + { "hausa", 0 }, /* Hausa */ + { "", 0 }, +}; + +/* Braille */ +static const XKB_VARIANT brai_variants[] = { + { "left_hand", 0 }, /* Left hand */ + { "right_hand", 0 }, /* Right hand */ + { "", 0 }, +}; + +/* Turkmenistan */ +static const XKB_VARIANT tm_variants[] = { + { "alt", KBD_TURKISH_Q }, /* Alt-Q */ + { "", 0 }, +}; + +static const XKB_LAYOUT xkbLayouts[] = { + { "us", KBD_US, us_variants }, /* USA */ + { "ad", 0, nullptr }, /* Andorra */ + { "af", KBD_FARSI, af_variants }, /* Afghanistan */ + { "ara", KBD_ARABIC_101, ara_variants }, /* Arabic */ + { "al", 0, nullptr }, /* Albania */ + { "am", KBD_ARMENIAN_EASTERN, am_variants }, /* Armenia */ + { "az", KBD_AZERI_CYRILLIC, az_variants }, /* Azerbaijan */ + { "by", KBD_BELARUSIAN, by_variants }, /* Belarus */ + { "be", KBD_BELGIAN_FRENCH, be_variants }, /* Belgium */ + { "bd", KBD_BENGALI, bd_variants }, /* Bangladesh */ + { "in", KBD_HINDI_TRADITIONAL, in_variants }, /* India */ + { "ba", KBD_CROATIAN, ba_variants }, /* Bosnia and Herzegovina */ + { "br", KBD_PORTUGUESE_BRAZILIAN_ABNT, br_variants }, /* Brazil */ + { "bg", KBD_BULGARIAN_LATIN, bg_variants }, /* Bulgaria */ + { "ma", KBD_FRENCH, ma_variants }, /* Morocco */ + { "mm", 0, nullptr }, /* Myanmar */ + { "ca", KBD_US, ca_variants }, /* Canada */ + { "cd", 0, nullptr }, /* Congo, Democratic Republic of the */ + { "cn", KBD_CHINESE_TRADITIONAL_PHONETIC, cn_variants }, /* China */ + { "hr", KBD_CROATIAN, hr_variants }, /* Croatia */ + { "cz", KBD_CZECH, cz_variants }, /* Czechia */ + { "dk", KBD_DANISH, dk_variants }, /* Denmark */ + { "nl", KBD_DUTCH, nl_variants }, /* Netherlands */ + { "bt", 0, nullptr }, /* Bhutan */ + { "ee", KBD_ESTONIAN, ee_variants }, /* Estonia */ + { "ir", 0, ir_variants }, /* Iran */ + { "iq", 0, iq_variants }, /* Iraq */ + { "fo", 0, fo_variants }, /* Faroe Islands */ + { "fi", KBD_FINNISH, fi_variants }, /* Finland */ + { "fr", KBD_FRENCH, fr_variants }, /* France */ + { "gh", 0, gh_variants }, /* Ghana */ + { "gn", 0, nullptr }, /* Guinea */ + { "ge", KBD_GEORGIAN, ge_variants }, /* Georgia */ + { "at", KBD_GERMAN, de_variants }, /* Austria */ + { "de", KBD_GERMAN, de_variants }, /* Germany */ + { "gr", KBD_GREEK, gr_variants }, /* Greece */ + { "hu", KBD_HUNGARIAN, hu_variants }, /* Hungary */ + { "is", KBD_ICELANDIC, is_variants }, /* Iceland */ + { "il", KBD_HEBREW, il_variants }, /* Israel */ + { "it", KBD_ITALIAN, it_variants }, /* Italy */ + { "jp", KBD_JAPANESE_INPUT_SYSTEM_MS_IME2002, jp_variants }, /* Japan */ + { "kg", 0, kg_variants }, /* Kyrgyzstan */ + { "kh", 0, nullptr }, /* Cambodia */ + { "kz", KBD_KAZAKH, kz_variants }, /* Kazakhstan */ + { "la", 0, nullptr }, /* Laos */ + { "latam", KBD_LATIN_AMERICAN, latam_variants }, /* Latin America */ + { "lt", KBD_LITHUANIAN, lt_variants }, /* Lithuania */ + { "lv", KBD_LATVIAN, lv_variants }, /* Latvia */ + { "mao", KBD_MAORI, nullptr }, /* Maori */ + { "me", KBD_SERBIAN_LATIN, me_variants }, /* Montenegro */ + { "mk", KBD_FYRO_MACEDONIAN, mk_variants }, /* Macedonia */ + { "mt", KBD_MALTESE_48_KEY, mt_variants }, /* Malta */ + { "mn", KBD_MONGOLIAN_CYRILLIC, nullptr }, /* Mongolia */ + { "no", KBD_NORWEGIAN, no_variants }, /* Norway */ + { "pl", KBD_POLISH_PROGRAMMERS, pl_variants }, /* Poland */ + { "pt", KBD_PORTUGUESE, pt_variants }, /* Portugal */ + { "ro", KBD_ROMANIAN, ro_variants }, /* Romania */ + { "ru", KBD_RUSSIAN, ru_variants }, /* Russia */ + { "rs", KBD_SERBIAN_LATIN, rs_variants }, /* Serbia */ + { "si", KBD_SLOVENIAN, si_variants }, /* Slovenia */ + { "sk", KBD_SLOVAK, sk_variants }, /* Slovakia */ + { "es", KBD_SPANISH, es_variants }, /* Spain */ + { "se", KBD_SWEDISH, se_variants }, /* Sweden */ + { "ch", KBD_SWISS_GERMAN, ch_variants }, /* Switzerland */ + { "sy", KBD_SYRIAC, sy_variants }, /* Syria */ + { "tj", 0, tj_variants }, /* Tajikistan */ + { "lk", 0, lk_variants }, /* Sri Lanka */ + { "th", KBD_THAI_KEDMANEE, th_variants }, /* Thailand */ + { "tr", KBD_TURKISH_Q, tr_variants }, /* Turkey */ + { "ua", KBD_UKRAINIAN, ua_variants }, /* Ukraine */ + { "gb", KBD_UNITED_KINGDOM, gb_variants }, /* United Kingdom */ + { "uz", KBD_UZBEK_CYRILLIC, uz_variants }, /* Uzbekistan */ + { "vn", KBD_VIETNAMESE, nullptr }, /* Vietnam */ + { "kr", KBD_KOREAN_INPUT_SYSTEM_IME_2000, kr_variants }, /* Korea, Republic of */ + { "ie", KBD_UNITED_KINGDOM, ie_variants }, /* Ireland */ + { "pk", 0, pk_variants }, /* Pakistan */ + { "mv", 0, nullptr }, /* Maldives */ + { "za", KBD_US, nullptr }, /* South Africa */ + { "epo", 0, epo_variants }, /* Esperanto */ + { "np", KBD_NEPALI, nullptr }, /* Nepal */ + { "ng", 0, ng_variants }, /* Nigeria */ + { "et", 0, nullptr }, /* Ethiopia */ + { "sn", 0, nullptr }, /* Senegal */ + { "brai", 0, brai_variants }, /* Braille */ + { "tm", KBD_TURKISH_Q, tm_variants }, /* Turkmenistan */ +}; + +static uint32_t convert(int64_t val) +{ + WINPR_ASSERT(val <= UINT32_MAX); + WINPR_ASSERT(val >= INT32_MIN); + return WINPR_CXX_COMPAT_CAST(uint32_t, val); +} + +static UINT32 find_keyboard_layout_variant(const XKB_LAYOUT* layout, const char* variant) +{ + WINPR_ASSERT(layout); + WINPR_ASSERT(variant); + + const XKB_VARIANT* variants = layout->variants; + if (variants) + { + const XKB_VARIANT* var = variants; + while (var->variant && (strlen(var->variant) != 0)) + { + if (strcmp(var->variant, variant) == 0) + return convert(var->keyboardLayoutID); + var++; + } + } + + return convert(layout->keyboardLayoutID); +} + +UINT32 xf_find_keyboard_layout_in_xorg_rules(const char* layout, const char* variant) +{ + if ((layout == nullptr) || (variant == nullptr)) + return 0; + + DEBUG_X11("xkbLayout: %s\txkbVariant: %s", layout, variant); + + for (size_t i = 0; i < ARRAYSIZE(xkbLayouts); i++) + { + const XKB_LAYOUT* cur = &xkbLayouts[i]; + if (strcmp(cur->layout, layout) == 0) + return find_keyboard_layout_variant(cur, variant); + } + + return 0; +} diff --git a/third_party/FreeRDP/client/X11/xkb_layout_ids.h b/third_party/FreeRDP/client/X11/xkb_layout_ids.h new file mode 100644 index 0000000..903e56a --- /dev/null +++ b/third_party/FreeRDP/client/X11/xkb_layout_ids.h @@ -0,0 +1,28 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RDP Keyboard layout ID detection from common X11 xkb keyboard layout names + * + * Copyright 2009-2012 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_LIB_LOCALE_XKB_LAYOUT_IDS_H +#define FREERDP_LIB_LOCALE_XKB_LAYOUT_IDS_H + +#include +#include + +FREERDP_LOCAL UINT32 xf_find_keyboard_layout_in_xorg_rules(const char* layout, const char* variant); + +#endif /* FREERDP_LIB_LOCALE_XKB_LAYOUT_IDS_H */ diff --git a/third_party/FreeRDP/client/common/CMakeLists.txt b/third_party/FreeRDP/client/common/CMakeLists.txt new file mode 100644 index 0000000..dc48311 --- /dev/null +++ b/third_party/FreeRDP/client/common/CMakeLists.txt @@ -0,0 +1,127 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP Client Common +# +# Copyright 2012 Marc-Andre Moreau +# Copyright 2025 Siemens +# +# 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 "freerdp-client") +set(MODULE_PREFIX "FREERDP_CLIENT") + +set(SRCS + client.c + cmdline.c + cmdline.h + file.c + client_cliprdr_file.c + geometry.c + smartcard_cli.c +) + +foreach(FREERDP_CHANNELS_CLIENT_SRC ${FREERDP_CHANNELS_CLIENT_SRCS}) + get_filename_component(NINC ${FREERDP_CHANNELS_CLIENT_SRC} PATH) + include_directories(${NINC}) + list(APPEND SRCS "${FREERDP_CHANNELS_CLIENT_SRC}") +endforeach() + +if(UNIX) + option(WITH_SSO_MIB "Build with sso-mib support" OFF) +else() + set(WITH_SSO_MIB OFF CACHE INTERNAL "unsupported platform") +endif() + +if(WITH_SSO_MIB) + find_package(SSO_MIB REQUIRED) + if(SSO_MIB_INSTALL_LIBRARIES) + install(FILES ${SSO_MIB_INSTALL_LIBRARIES} DESTINATION ${CMAKE_INSTALL_LIBDIR}) + endif() +endif() + +if(NOT APPLE AND NOT WIN32 AND NOT ANDROID) + set(OPT_FUSE_DEFAULT ON) +else() + set(OPT_FUSE_DEFAULT OFF) +endif() + +option(WITH_FUSE "Build clipboard with FUSE file copy support" ${OPT_FUSE_DEFAULT}) +if(WITH_FUSE) + find_package(PkgConfig REQUIRED) + + pkg_check_modules(FUSE3 REQUIRED fuse3) + freerdp_client_pc_add_requires_private("fuse3") + + include_directories(SYSTEM ${FUSE3_INCLUDE_DIRS}) + add_compile_definitions(WITH_FUSE) + list(APPEND LIBS ${FUSE3_LIBRARIES}) + + add_compile_definitions(_FILE_OFFSET_BITS=64) +endif() + +include_directories(SYSTEM ${OPENSSL_INCLUDE_DIR}) + +if(WITH_SSO_MIB) + list(APPEND SRCS sso_mib_tokens.c) +endif() + +addtargetwithresourcefile(${MODULE_NAME} FALSE "${FREERDP_VERSION}" SRCS) + +if(WITH_SSO_MIB) + if(SSO_MIB_EXTERNAL_DIR) + add_dependencies(${MODULE_NAME} sso-mib-external) + endif() + + include_directories(${SSO_MIB_INCLUDE_DIRS}) + add_compile_definitions(WITH_SSO_MIB) + list(APPEND LIBS ${SSO_MIB_LIBRARIES}) +endif() + +list(APPEND LIBS freerdp winpr) + +include(CheckLibraryExists) +check_library_exists(m lround "" HAVE_LIB_M) +if(HAVE_LIB_M) + list(APPEND LIBS m) +endif() + +target_include_directories(${MODULE_NAME} INTERFACE $) +target_link_libraries(${MODULE_NAME} PRIVATE ${FREERDP_CHANNELS_CLIENT_LIBS}) +target_link_libraries(${MODULE_NAME} PUBLIC ${LIBS}) + +installwithrpath( + TARGETS + ${MODULE_NAME} + COMPONENT + libraries + EXPORT + FreeRDP-ClientTargets + ARCHIVE + DESTINATION + ${CMAKE_INSTALL_LIBDIR} + LIBRARY + DESTINATION + ${CMAKE_INSTALL_LIBDIR} + RUNTIME + DESTINATION + ${CMAKE_INSTALL_BINDIR} +) + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Client/Common") + +if(BUILD_TESTING_INTERNAL OR BUILD_TESTING) + add_subdirectory(test) +endif() + +if(WITH_MANPAGES) + add_subdirectory(man) +endif() diff --git a/third_party/FreeRDP/client/common/client.c b/third_party/FreeRDP/client/common/client.c new file mode 100644 index 0000000..59419d1 --- /dev/null +++ b/third_party/FreeRDP/client/common/client.c @@ -0,0 +1,2598 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Client Common + * + * Copyright 2012 Marc-Andre Moreau + * Copyright 2025 Siemens + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(CHANNEL_AINPUT_CLIENT) +#include +#include +#endif + +#if defined(CHANNEL_VIDEO_CLIENT) +#include +#include +#endif + +#if defined(CHANNEL_RDPGFX_CLIENT) +#include +#include +#include +#endif + +#if defined(CHANNEL_GEOMETRY_CLIENT) +#include +#include +#endif + +#if defined(CHANNEL_GEOMETRY_CLIENT) || defined(CHANNEL_VIDEO_CLIENT) +#include +#endif + +#ifdef WITH_AAD +#include +#include +#endif + +#ifdef WITH_SSO_MIB +#include "sso_mib_tokens.h" +#endif + +#include +#define TAG CLIENT_TAG("common") + +static void set_default_callbacks(freerdp* instance) +{ + WINPR_ASSERT(instance); + instance->AuthenticateEx = client_cli_authenticate_ex; + instance->ChooseSmartcard = client_cli_choose_smartcard; + instance->VerifyCertificateEx = client_cli_verify_certificate_ex; + instance->VerifyChangedCertificateEx = client_cli_verify_changed_certificate_ex; + instance->PresentGatewayMessage = client_cli_present_gateway_message; + instance->LogonErrorInfo = client_cli_logon_error_info; + instance->GetAccessToken = client_cli_get_access_token; + instance->RetryDialog = client_common_retry_dialog; +} + +static BOOL freerdp_client_common_new(freerdp* instance, rdpContext* context) +{ + RDP_CLIENT_ENTRY_POINTS* pEntryPoints = nullptr; + + WINPR_ASSERT(instance); + WINPR_ASSERT(context); + + instance->LoadChannels = freerdp_client_load_channels; + set_default_callbacks(instance); + + pEntryPoints = instance->pClientEntryPoints; + WINPR_ASSERT(pEntryPoints); + return IFCALLRESULT(TRUE, pEntryPoints->ClientNew, instance, context); +} + +static void freerdp_client_common_free(freerdp* instance, rdpContext* context) +{ + RDP_CLIENT_ENTRY_POINTS* pEntryPoints = nullptr; + + WINPR_ASSERT(instance); + WINPR_ASSERT(context); + + pEntryPoints = instance->pClientEntryPoints; + WINPR_ASSERT(pEntryPoints); + IFCALL(pEntryPoints->ClientFree, instance, context); +} + +/* Common API */ + +rdpContext* freerdp_client_context_new(const RDP_CLIENT_ENTRY_POINTS* pEntryPoints) +{ + freerdp* instance = nullptr; + rdpContext* context = nullptr; + + if (!pEntryPoints) + return nullptr; + + if (!IFCALLRESULT(TRUE, pEntryPoints->GlobalInit)) + return nullptr; + + instance = freerdp_new(); + + if (!instance) + return nullptr; + + instance->ContextSize = pEntryPoints->ContextSize; + instance->ContextNew = freerdp_client_common_new; + instance->ContextFree = freerdp_client_common_free; + instance->pClientEntryPoints = (RDP_CLIENT_ENTRY_POINTS*)malloc(pEntryPoints->Size); + + if (!instance->pClientEntryPoints) + goto out_fail; + + CopyMemory(instance->pClientEntryPoints, pEntryPoints, pEntryPoints->Size); + + if (!freerdp_context_new_ex(instance, pEntryPoints->settings)) + goto out_fail2; + + context = instance->context; + context->instance = instance; + +#if defined(WITH_CLIENT_CHANNELS) + if (freerdp_register_addin_provider(freerdp_channels_load_static_addin_entry, 0) != + CHANNEL_RC_OK) + goto out_fail2; +#endif + + return context; +out_fail2: + free(instance->pClientEntryPoints); +out_fail: + freerdp_free(instance); + return nullptr; +} + +void freerdp_client_context_free(rdpContext* context) +{ + freerdp* instance = nullptr; + + if (!context) + return; + + instance = context->instance; + + if (instance) + { + RDP_CLIENT_ENTRY_POINTS* pEntryPoints = instance->pClientEntryPoints; + freerdp_context_free(instance); + + if (pEntryPoints) + IFCALL(pEntryPoints->GlobalUninit); + + free(instance->pClientEntryPoints); + freerdp_free(instance); + } +} + +int freerdp_client_start(rdpContext* context) +{ + RDP_CLIENT_ENTRY_POINTS* pEntryPoints = nullptr; + + if (!context || !context->instance || !context->instance->pClientEntryPoints) + return ERROR_BAD_ARGUMENTS; + + if (freerdp_settings_get_bool(context->settings, FreeRDP_UseCommonStdioCallbacks)) + set_default_callbacks(context->instance); + +#ifdef WITH_SSO_MIB + rdpClientContext* client_context = (rdpClientContext*)context; + client_context->mibClientWrapper = sso_mib_new(context); + if (!client_context->mibClientWrapper) + return ERROR_INTERNAL_ERROR; +#endif + + pEntryPoints = context->instance->pClientEntryPoints; + return IFCALLRESULT(CHANNEL_RC_OK, pEntryPoints->ClientStart, context); +} + +int freerdp_client_stop(rdpContext* context) +{ + RDP_CLIENT_ENTRY_POINTS* pEntryPoints = nullptr; + + if (!context || !context->instance || !context->instance->pClientEntryPoints) + return ERROR_BAD_ARGUMENTS; + + pEntryPoints = context->instance->pClientEntryPoints; + const int rc = IFCALLRESULT(CHANNEL_RC_OK, pEntryPoints->ClientStop, context); + +#ifdef WITH_SSO_MIB + rdpClientContext* client_context = (rdpClientContext*)context; + sso_mib_free(client_context->mibClientWrapper); + client_context->mibClientWrapper = nullptr; +#endif // WITH_SSO_MIB + return rc; +} + +freerdp* freerdp_client_get_instance(rdpContext* context) +{ + if (!context || !context->instance) + return nullptr; + + return context->instance; +} + +HANDLE freerdp_client_get_thread(rdpContext* context) +{ + if (!context) + return nullptr; + + return ((rdpClientContext*)context)->thread; +} + +static BOOL freerdp_client_settings_post_process(rdpSettings* settings) +{ + /* Moved GatewayUseSameCredentials logic outside of cmdline.c, so + * that the rdp file also triggers this functionality */ + if (freerdp_settings_get_bool(settings, FreeRDP_GatewayEnabled)) + { + if (freerdp_settings_get_bool(settings, FreeRDP_GatewayUseSameCredentials)) + { + const char* Username = freerdp_settings_get_string(settings, FreeRDP_Username); + const char* Domain = freerdp_settings_get_string(settings, FreeRDP_Domain); + if (Username) + { + if (!freerdp_settings_set_string(settings, FreeRDP_GatewayUsername, Username)) + goto out_error; + } + + if (Domain) + { + if (!freerdp_settings_set_string(settings, FreeRDP_GatewayDomain, Domain)) + goto out_error; + } + + if (freerdp_settings_get_string(settings, FreeRDP_Password)) + { + if (!freerdp_settings_set_string( + settings, FreeRDP_GatewayPassword, + freerdp_settings_get_string(settings, FreeRDP_Password))) + goto out_error; + } + } + } + + /* Moved logic for Multimon and Span monitors to force fullscreen, so + * that the rdp file also triggers this functionality */ + if (freerdp_settings_get_bool(settings, FreeRDP_SpanMonitors)) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_UseMultimon, TRUE)) + goto out_error; + if (!freerdp_settings_set_bool(settings, FreeRDP_Fullscreen, TRUE)) + goto out_error; + } + else if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon)) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_Fullscreen, TRUE)) + goto out_error; + } + + /* deal with the smartcard / smartcard logon stuff */ + if (freerdp_settings_get_bool(settings, FreeRDP_SmartcardLogon)) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_TlsSecurity, TRUE)) + goto out_error; + if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectSmartCards, TRUE)) + goto out_error; + if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE)) + goto out_error; + if (!freerdp_settings_set_bool(settings, FreeRDP_PasswordIsSmartcardPin, TRUE)) + goto out_error; + } + + return TRUE; +out_error: + return FALSE; +} + +int freerdp_client_settings_parse_command_line(rdpSettings* settings, int argc, char** argv, + BOOL allowUnknown) + +{ + return freerdp_client_settings_parse_command_line_ex(settings, argc, argv, allowUnknown, + nullptr, 0, nullptr, nullptr); +} + +int freerdp_client_settings_parse_command_line_ex( + rdpSettings* settings, int argc, char** argv, BOOL allowUnknown, COMMAND_LINE_ARGUMENT_A* args, + size_t count, freerdp_command_line_handle_option_t handle_option, void* handle_userdata) +{ + int status = 0; + + if (argc < 1) + return 0; + + if (!argv) + return -1; + + status = freerdp_client_settings_parse_command_line_arguments_ex( + settings, argc, argv, allowUnknown, args, count, handle_option, handle_userdata); + + if (status < 0) + return status; + + /* This function will call logic that is applicable to the settings + * from command line parsing AND the rdp file parsing */ + if (!freerdp_client_settings_post_process(settings)) + status = -1; + + const char* name = argv[0]; + WLog_DBG(TAG, "This is [%s] %s %s", name, freerdp_get_version_string(), + freerdp_get_build_config()); + return status; +} + +int freerdp_client_settings_parse_connection_file(rdpSettings* settings, const char* filename) +{ + rdpFile* file = nullptr; + int ret = -1; + file = freerdp_client_rdp_file_new(); + + if (!file) + return -1; + + if (!freerdp_client_parse_rdp_file(file, filename)) + goto out; + + if (!freerdp_client_populate_settings_from_rdp_file(file, settings)) + goto out; + + ret = 0; +out: + freerdp_client_rdp_file_free(file); + return ret; +} + +int freerdp_client_settings_parse_connection_file_buffer(rdpSettings* settings, const BYTE* buffer, + size_t size) +{ + rdpFile* file = nullptr; + int status = -1; + file = freerdp_client_rdp_file_new(); + + if (!file) + return -1; + + if (freerdp_client_parse_rdp_file_buffer(file, buffer, size) && + freerdp_client_populate_settings_from_rdp_file(file, settings)) + { + status = 0; + } + + freerdp_client_rdp_file_free(file); + return status; +} + +int freerdp_client_settings_write_connection_file(const rdpSettings* settings, const char* filename, + BOOL unicode) +{ + rdpFile* file = nullptr; + int ret = -1; + file = freerdp_client_rdp_file_new(); + + if (!file) + return -1; + + if (!freerdp_client_populate_rdp_file_from_settings(file, settings)) + goto out; + + if (!freerdp_client_write_rdp_file(file, filename, unicode)) + goto out; + + ret = 0; +out: + freerdp_client_rdp_file_free(file); + return ret; +} + +int freerdp_client_settings_parse_assistance_file(rdpSettings* settings, int argc, char* argv[]) +{ + int status = 0; + int ret = -1; + char* filename = nullptr; + char* password = nullptr; + rdpAssistanceFile* file = nullptr; + + if (!settings || !argv || (argc < 2)) + return -1; + + filename = argv[1]; + + for (int x = 2; x < argc; x++) + { + const char* key = strstr(argv[x], "assistance:"); + + if (key) + password = strchr(key, ':') + 1; + } + + file = freerdp_assistance_file_new(); + + if (!file) + return -1; + + status = freerdp_assistance_parse_file(file, filename, password); + + if (status < 0) + goto out; + + if (!freerdp_assistance_populate_settings_from_assistance_file(file, settings)) + goto out; + + ret = 0; +out: + freerdp_assistance_file_free(file); + return ret; +} + +static int client_cli_read_string(freerdp* instance, const char* what, const char* suggestion, + char** result) +{ + WINPR_ASSERT(instance); + WINPR_ASSERT(what); + WINPR_ASSERT(result); + + size_t size = 0; + printf("%s", what); + (void)fflush(stdout); + + char* line = nullptr; + if (suggestion && strlen(suggestion) > 0) + { + line = _strdup(suggestion); + size = strlen(suggestion); + } + + const SSIZE_T rc = freerdp_interruptible_get_line(instance->context, &line, &size, stdin); + if (rc < 0) + { + char ebuffer[256] = WINPR_C_ARRAY_INIT; + WLog_ERR(TAG, "freerdp_interruptible_get_line returned %s [%d]", + winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno); + free(line); + return -1; + } + + free(*result); + *result = nullptr; + + if (line) + { + line = StrSep(&line, "\r"); + line = StrSep(&line, "\n"); + *result = line; + } + return 0; +} + +/** @brief Callback set in the rdp_freerdp structure, and used to get the user's password, + * if required to establish the connection. + * This function is actually called in credssp_ntlmssp_client_init() + * + * @see rdp_server_accept_nego() and rdp_check_fds() + * @param instance pointer to the rdp_freerdp structure that contains the connection settings + * @param username on input can contain a suggestion (must be allocated and is released by \b free + * ). On output the allocated username entered by the user. + * @param password on input can contain a suggestion (must be allocated and is released by \b free + * ). On output the allocated password entered by the user. + * @param domain on input can contain a suggestion (must be allocated and is released by \b free + * ). On output the allocated domain entered by the user. + * @return TRUE if a password was successfully entered. See freerdp_passphrase_read() for more + * details. + */ +static BOOL client_cli_authenticate_raw(freerdp* instance, rdp_auth_reason reason, char** username, + char** password, char** domain) +{ + static const size_t password_size = 512; + const char* userAuth = "Username: "; + const char* domainAuth = "Domain: "; + const char* pwdAuth = "Password: "; + BOOL pinOnly = FALSE; + BOOL queryAll = FALSE; + + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->context); + WINPR_ASSERT(instance->context->settings); + + switch (reason) + { + case AUTH_SMARTCARD_PIN: + pwdAuth = "Smartcard-Pin: "; + pinOnly = TRUE; + break; + case AUTH_RDSTLS: + queryAll = TRUE; + break; + case AUTH_TLS: + case AUTH_RDP: + case AUTH_NLA: + break; + case GW_AUTH_HTTP: + case GW_AUTH_RDG: + case GW_AUTH_RPC: + userAuth = "GatewayUsername: "; + domainAuth = "GatewayDomain: "; + pwdAuth = "GatewayPassword: "; + break; + default: + return FALSE; + } + + if (!username || !password || !domain) + return FALSE; + + if (!pinOnly) + { + const char* suggest = *username; + if (queryAll || !suggest) + { + const int rc = client_cli_read_string(instance, userAuth, suggest, username); + if (rc < 0) + goto fail; + } + } + + if (!pinOnly) + { + const char* suggest = *domain; + if (queryAll || !suggest) + { + const int rc = client_cli_read_string(instance, domainAuth, suggest, domain); + if (rc < 0) + goto fail; + } + } + + { + char* line = calloc(password_size, sizeof(char)); + + if (!line) + goto fail; + + const BOOL fromStdin = + freerdp_settings_get_bool(instance->context->settings, FreeRDP_CredentialsFromStdin); + const char* rc = + freerdp_passphrase_read(instance->context, pwdAuth, line, password_size, fromStdin); + if (rc == nullptr) + goto fail; + + if (password_size > 0) + { + free(*password); + *password = line; + } + } + + return TRUE; +fail: + free(*username); + free(*domain); + free(*password); + *username = nullptr; + *domain = nullptr; + *password = nullptr; + return FALSE; +} + +BOOL client_cli_authenticate_ex(freerdp* instance, char** username, char** password, char** domain, + rdp_auth_reason reason) +{ + WINPR_ASSERT(instance); + WINPR_ASSERT(username); + WINPR_ASSERT(password); + WINPR_ASSERT(domain); + + 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: + break; + default: + return FALSE; + } + + return client_cli_authenticate_raw(instance, reason, username, password, domain); +} + +BOOL client_cli_choose_smartcard(WINPR_ATTR_UNUSED freerdp* instance, SmartcardCertInfo** cert_list, + DWORD count, DWORD* choice, BOOL gateway) +{ + unsigned long answer = 0; + char* p = nullptr; + + printf("Multiple smartcards are available for use:\n"); + 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); + + printf("[%" PRIu32 + "] %s\n\tReader: %s\n\tUser: %s@%s\n\tSubject: %s\n\tIssuer: %s\n\tUPN: %s\n", + i, container_name, reader, cert->userHint, cert->domainHint, cert->subject, + cert->issuer, cert->upn); + + free(reader); + free(container_name); + } + + while (1) + { + char input[10] = WINPR_C_ARRAY_INIT; + + printf("\nChoose a smartcard to use for %s (0 - %" PRIu32 "): ", + gateway ? "gateway authentication" : "logon", count - 1); + (void)fflush(stdout); + if (!fgets(input, 10, stdin)) + { + WLog_ERR(TAG, "could not read from stdin"); + return FALSE; + } + + answer = strtoul(input, &p, 10); + if ((*p == '\n' && p != input) && answer < count) + { + *choice = (UINT32)answer; + return TRUE; + } + } +} + +#if defined(WITH_FREERDP_DEPRECATED) +BOOL client_cli_authenticate(freerdp* instance, char** username, char** password, char** domain) +{ + if (freerdp_settings_get_bool(instance->settings, FreeRDP_SmartcardLogon)) + { + WLog_INFO(TAG, "Authentication via smartcard"); + return TRUE; + } + + return client_cli_authenticate_raw(instance, FALSE, username, password, domain); +} + +BOOL client_cli_gw_authenticate(freerdp* instance, char** username, char** password, char** domain) +{ + return client_cli_authenticate_raw(instance, TRUE, username, password, domain); +} +#endif + +static DWORD client_cli_accept_certificate(freerdp* instance) +{ + int answer = 0; + + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->context); + + const rdpSettings* settings = instance->context->settings; + WINPR_ASSERT(settings); + + const BOOL fromStdin = freerdp_settings_get_bool(settings, FreeRDP_CredentialsFromStdin); + if (fromStdin) + return 0; + + while (1) + { + printf("Do you trust the above certificate? (Y/T/N) "); + (void)fflush(stdout); + answer = freerdp_interruptible_getc(instance->context, stdin); + + if ((answer == EOF) || feof(stdin)) + { + printf("\nError: Could not read answer from stdin.\n"); + return 0; + } + + switch (answer) + { + case 'y': + case 'Y': + answer = freerdp_interruptible_getc(instance->context, stdin); + if (answer == EOF) + return 0; + return 1; + + case 't': + case 'T': + answer = freerdp_interruptible_getc(instance->context, stdin); + if (answer == EOF) + return 0; + return 2; + + case 'n': + case 'N': + answer = freerdp_interruptible_getc(instance->context, stdin); + if (answer == EOF) + return 0; + return 0; + + default: + break; + } + + printf("\n"); + } +} + +/** Callback set in the rdp_freerdp structure, and used to make a certificate validation + * when the connection requires it. + * This function will actually be called by tls_verify_certificate(). + * @see rdp_client_connect() and freerdp_tls_connect() + * @deprecated Use client_cli_verify_certificate_ex + * @param instance - pointer to the rdp_freerdp structure that contains the connection settings + * @param common_name + * @param subject + * @param issuer + * @param fingerprint + * @param host_mismatch Indicates the certificate host does not match. + * @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise. + */ +#if defined(WITH_FREERDP_DEPRECATED) +DWORD client_cli_verify_certificate(freerdp* instance, const char* common_name, const char* subject, + const char* issuer, const char* fingerprint, BOOL host_mismatch) +{ + WINPR_UNUSED(common_name); + WINPR_UNUSED(host_mismatch); + + printf("WARNING: This callback is deprecated, migrate to client_cli_verify_certificate_ex\n"); + printf("Certificate details:\n"); + printf("\tSubject: %s\n", subject); + printf("\tIssuer: %s\n", issuer); + printf("\tThumbprint: %s\n", fingerprint); + printf("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"); + return client_cli_accept_certificate(instance); +} +#endif + +static char* client_cli_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, + "\tValid from: %s\n" + "\tValid to: %s\n" + "\tThumbprint: %s\n", + start, end, fp); + free(fp); + free(start); + free(end); + return str; +} + +/** Callback set in the rdp_freerdp structure, and used to make a certificate validation + * when the connection requires it. + * This function will actually be called by tls_verify_certificate(). + * @see rdp_client_connect() and freerdp_tls_connect() + * @param instance pointer to the rdp_freerdp structure that contains the connection settings + * @param host The host currently connecting to + * @param port The port currently connecting to + * @param common_name The common name of the certificate, should match host or an alias of it + * @param subject The subject of the certificate + * @param issuer The certificate issuer name + * @param fingerprint The fingerprint of the certificate + * @param flags See VERIFY_CERT_FLAG_* for possible values. + * + * @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise. + */ +DWORD client_cli_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 = "RDP-Server"; + + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->context); + WINPR_ASSERT(instance->context->settings); + + if (flags & VERIFY_CERT_FLAG_GATEWAY) + type = "RDP-Gateway"; + + if (flags & VERIFY_CERT_FLAG_REDIRECT) + type = "RDP-Redirect"; + + printf("Certificate details for %s:%" PRIu16 " (%s):\n", host, port, type); + printf("\tCommon Name: %s\n", common_name); + printf("\tSubject: %s\n", subject); + printf("\tIssuer: %s\n", issuer); + /* Newer versions of FreeRDP allow exposing the whole PEM by setting + * FreeRDP_CertificateCallbackPreferPEM to TRUE + */ + if (flags & VERIFY_CERT_FLAG_FP_IS_PEM) + { + char* str = client_cli_pem_cert(fingerprint); + printf("%s", str); + free(str); + } + else + printf("\tThumbprint: %s\n", fingerprint); + + printf("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"); + return client_cli_accept_certificate(instance); +} + +/** Callback set in the rdp_freerdp structure, and used to make a certificate validation + * when a stored certificate does not match the remote counterpart. + * This function will actually be called by tls_verify_certificate(). + * @see rdp_client_connect() and freerdp_tls_connect() + * @deprecated Use client_cli_verify_changed_certificate_ex + * @param instance - pointer to the rdp_freerdp structure that contains the connection settings + * @param common_name + * @param subject + * @param issuer + * @param fingerprint + * @param old_subject + * @param old_issuer + * @param old_fingerprint + * @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise. + */ +#if defined(WITH_FREERDP_DEPRECATED) +DWORD client_cli_verify_changed_certificate(freerdp* instance, const char* common_name, + const char* subject, const char* issuer, + const char* fingerprint, const char* old_subject, + const char* old_issuer, const char* old_fingerprint) +{ + WINPR_UNUSED(common_name); + + printf("WARNING: This callback is deprecated, migrate to " + "client_cli_verify_changed_certificate_ex\n"); + printf("!!! Certificate has changed !!!\n"); + printf("\n"); + printf("New Certificate details:\n"); + printf("\tSubject: %s\n", subject); + printf("\tIssuer: %s\n", issuer); + printf("\tThumbprint: %s\n", fingerprint); + printf("\n"); + printf("Old Certificate details:\n"); + printf("\tSubject: %s\n", old_subject); + printf("\tIssuer: %s\n", old_issuer); + printf("\tThumbprint: %s\n", old_fingerprint); + printf("\n"); + printf("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"); + return client_cli_accept_certificate(instance); +} +#endif + +/** Callback set in the rdp_freerdp structure, and used to make a certificate validation + * when a stored certificate does not match the remote counterpart. + * This function will actually be called by tls_verify_certificate(). + * @see rdp_client_connect() and freerdp_tls_connect() + * @param instance pointer to the rdp_freerdp structure that contains the connection + * settings + * @param host The host currently connecting to + * @param port The port currently connecting to + * @param common_name The common name of the certificate, should match host or an alias of it + * @param subject The subject of the certificate + * @param issuer The certificate issuer name + * @param fingerprint The fingerprint of the certificate + * @param old_subject The subject of the previous certificate + * @param old_issuer The previous certificate issuer name + * @param old_fingerprint The fingerprint of the previous certificate + * @param flags See VERIFY_CERT_FLAG_* for possible values. + * + * @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise. + */ +DWORD client_cli_verify_changed_certificate_ex(freerdp* instance, const char* host, UINT16 port, + const char* common_name, const char* subject, + const char* issuer, const char* fingerprint, + const char* old_subject, const char* old_issuer, + const char* old_fingerprint, DWORD flags) +{ + const char* type = "RDP-Server"; + + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->context); + WINPR_ASSERT(instance->context->settings); + + if (flags & VERIFY_CERT_FLAG_GATEWAY) + type = "RDP-Gateway"; + + if (flags & VERIFY_CERT_FLAG_REDIRECT) + type = "RDP-Redirect"; + + printf("!!!Certificate for %s:%" PRIu16 " (%s) has changed!!!\n", host, port, type); + printf("\n"); + printf("New Certificate details:\n"); + printf("\tCommon Name: %s\n", common_name); + printf("\tSubject: %s\n", subject); + printf("\tIssuer: %s\n", issuer); + /* Newer versions of FreeRDP allow exposing the whole PEM by setting + * FreeRDP_CertificateCallbackPreferPEM to TRUE + */ + if (flags & VERIFY_CERT_FLAG_FP_IS_PEM) + { + char* str = client_cli_pem_cert(fingerprint); + printf("%s", str); + free(str); + } + else + printf("\tThumbprint: %s\n", fingerprint); + printf("\n"); + printf("Old Certificate details:\n"); + printf("\tSubject: %s\n", old_subject); + printf("\tIssuer: %s\n", old_issuer); + /* Newer versions of FreeRDP allow exposing the whole PEM by setting + * FreeRDP_CertificateCallbackPreferPEM to TRUE + */ + if (flags & VERIFY_CERT_FLAG_FP_IS_PEM) + { + char* str = client_cli_pem_cert(old_fingerprint); + printf("%s", str); + free(str); + } + else + printf("\tThumbprint: %s\n", old_fingerprint); + printf("\n"); + if (flags & VERIFY_CERT_FLAG_MATCH_LEGACY_SHA1) + { + printf("\tA matching entry with legacy SHA1 was found in local known_hosts2 store.\n"); + printf("\tIf you just upgraded from a FreeRDP version before 2.0 this is expected.\n"); + printf("\tThe hashing algorithm has been upgraded from SHA1 to SHA256.\n"); + printf("\tAll manually accepted certificates must be reconfirmed!\n"); + printf("\n"); + } + printf("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"); + return client_cli_accept_certificate(instance); +} + +BOOL client_cli_present_gateway_message(freerdp* instance, UINT32 type, BOOL isDisplayMandatory, + BOOL isConsentMandatory, size_t length, + const WCHAR* message) +{ + const char* msgType = (type == GATEWAY_MESSAGE_CONSENT) ? "Consent message" : "Service message"; + + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->context); + WINPR_ASSERT(instance->context->settings); + + if (!isDisplayMandatory && !isConsentMandatory) + return TRUE; + + printf("%s:\n", msgType); +#if defined(WIN32) + printf("%.*S\n", (int)length, message); +#else + { + LPSTR msg = ConvertWCharNToUtf8Alloc(message, length / sizeof(WCHAR), nullptr); + if (!msg) + { + printf("Failed to convert message!\n"); + return FALSE; + } + printf("%s\n", msg); + free(msg); + } +#endif + + while (isConsentMandatory) + { + printf("I understand and agree to the terms of this policy (Y/N) \n"); + (void)fflush(stdout); + const int answer = freerdp_interruptible_getc(instance->context, stdin); + + if ((answer == EOF) || feof(stdin)) + { + printf("\nError: Could not read answer from stdin.\n"); + return FALSE; + } + + const int confirm = freerdp_interruptible_getc(instance->context, stdin); + switch (answer) + { + case 'y': + case 'Y': + if (confirm == EOF) + return FALSE; + return TRUE; + + case 'n': + case 'N': + return FALSE; + + default: + break; + } + + printf("\n"); + } + + return TRUE; +} + +static const char* extract_authorization_code(char* url) +{ + WINPR_ASSERT(url); + + for (char* p = strchr(url, '?'); p++ != nullptr; p = strchr(p, '&')) + { + if (strncmp(p, "code=", 5) != 0) + continue; + + char* end = nullptr; + p += 5; + + end = strchr(p, '&'); + if (end) + *end = '\0'; + + return p; + } + + return nullptr; +} + +#if defined(WITH_AAD) +static BOOL client_cli_get_rdsaad_access_token(freerdp* instance, const char* scope, + const char* req_cnf, char** token) +{ + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->context); + + size_t size = 0; + char* url = nullptr; + char* token_request = nullptr; + + WINPR_ASSERT(scope); + WINPR_ASSERT(req_cnf); + WINPR_ASSERT(token); + + BOOL rc = FALSE; + *token = nullptr; + + char* request = freerdp_client_get_aad_url((rdpClientContext*)instance->context, + FREERDP_CLIENT_AAD_AUTH_REQUEST, scope); + + printf("Browse to: %s\n", request); + free(request); + printf("Paste redirect URL here: \n"); + + if (freerdp_interruptible_get_line(instance->context, &url, &size, stdin) < 0) + goto cleanup; + + { + const char* code = extract_authorization_code(url); + if (!code) + goto cleanup; + token_request = + freerdp_client_get_aad_url((rdpClientContext*)instance->context, + FREERDP_CLIENT_AAD_TOKEN_REQUEST, scope, code, req_cnf); + } + if (!token_request) + goto cleanup; + + rc = client_common_get_access_token(instance, token_request, token); + +cleanup: + free(token_request); + free(url); + return rc && (*token != nullptr); +} + +static BOOL client_cli_get_avd_access_token(freerdp* instance, char** token) +{ + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->context); + + size_t size = 0; + char* url = nullptr; + char* token_request = nullptr; + + WINPR_ASSERT(token); + + BOOL rc = FALSE; + + *token = nullptr; + + char* request = freerdp_client_get_aad_url((rdpClientContext*)instance->context, + FREERDP_CLIENT_AAD_AVD_AUTH_REQUEST); + if (!request) + return FALSE; + printf("Browse to: %s\n", request); + free(request); + printf("Paste redirect URL here: \n"); + + if (freerdp_interruptible_get_line(instance->context, &url, &size, stdin) < 0) + goto cleanup; + + { + const char* code = extract_authorization_code(url); + if (!code) + goto cleanup; + token_request = freerdp_client_get_aad_url((rdpClientContext*)instance->context, + FREERDP_CLIENT_AAD_AVD_TOKEN_REQUEST, code); + } + + if (!token_request) + goto cleanup; + + rc = client_common_get_access_token(instance, token_request, token); + +cleanup: + free(token_request); + free(url); + return rc && (*token != nullptr); +} +#endif + +BOOL client_cli_get_access_token(freerdp* instance, AccessTokenType tokenType, char** token, + size_t count, ...) +{ + WINPR_ASSERT(instance); + WINPR_ASSERT(token); + +#if !defined(WITH_AAD) + WLog_ERR(TAG, "Build does not support AAD authentication"); + return FALSE; +#else + BOOL rc = FALSE; + WINPR_ASSERT(instance->context); + const BOOL saved = + freerdp_settings_get_bool(instance->context->settings, FreeRDP_UseCommonStdioCallbacks); + if (!freerdp_settings_set_bool(instance->context->settings, FreeRDP_UseCommonStdioCallbacks, + TRUE)) + return FALSE; + + switch (tokenType) + { + case ACCESS_TOKEN_TYPE_AAD: + { + if (count < 2) + { + WLog_ERR(TAG, + "ACCESS_TOKEN_TYPE_AAD expected 2 additional arguments, but got %" PRIuz + ", aborting", + count); + return FALSE; + } + else if (count > 2) + WLog_WARN(TAG, + "ACCESS_TOKEN_TYPE_AAD expected 2 additional arguments, but got %" PRIuz + ", ignoring", + count); + va_list ap = WINPR_C_ARRAY_INIT; + va_start(ap, count); + const char* scope = va_arg(ap, const char*); + const char* req_cnf = va_arg(ap, const char*); + rc = client_cli_get_rdsaad_access_token(instance, scope, req_cnf, token); + va_end(ap); + } + break; + case ACCESS_TOKEN_TYPE_AVD: + if (count != 0) + WLog_WARN(TAG, + "ACCESS_TOKEN_TYPE_AVD expected 0 additional arguments, but got %" PRIuz + ", ignoring", + count); + rc = client_cli_get_avd_access_token(instance, token); + break; + default: + WLog_ERR(TAG, "Unexpected value for AccessTokenType [%u], aborting", tokenType); + break; + } + + if (!freerdp_settings_set_bool(instance->context->settings, FreeRDP_UseCommonStdioCallbacks, + saved)) + return FALSE; + return rc; +#endif +} + +BOOL client_common_get_access_token(freerdp* instance, const char* request, char** token) +{ +#ifdef WITH_AAD + WINPR_ASSERT(request); + WINPR_ASSERT(token); + + BOOL ret = FALSE; + long resp_code = 0; + BYTE* response = nullptr; + size_t response_length = 0; + + wLog* log = WLog_Get(TAG); + + const char* token_ep = + freerdp_utils_aad_get_wellknown_string(instance->context, AAD_WELLKNOWN_token_endpoint); + if (!freerdp_http_request(token_ep, request, &resp_code, &response, &response_length)) + { + WLog_ERR(TAG, "access token request failed"); + return FALSE; + } + + if (resp_code != HTTP_STATUS_OK) + { + char buffer[64] = WINPR_C_ARRAY_INIT; + + WLog_Print(log, WLOG_ERROR, + "Server unwilling to provide access token; returned status code %s", + freerdp_http_status_string_format(resp_code, buffer, sizeof(buffer))); + if (response_length > 0) + WLog_Print(log, WLOG_ERROR, "[status message] %s", response); + goto cleanup; + } + + *token = freerdp_utils_aad_get_access_token(log, (const char*)response, response_length); + if (*token) + ret = TRUE; + +cleanup: + free(response); + return ret; +#else + return FALSE; +#endif +} + +SSIZE_T client_common_retry_dialog(freerdp* instance, const char* what, size_t current, + void* userarg) +{ + WINPR_UNUSED(instance); + WINPR_ASSERT(instance->context); + WINPR_UNUSED(userarg); + WINPR_ASSERT(instance); + WINPR_ASSERT(what); + + if ((strcmp(what, "arm-transport") != 0) && (strcmp(what, "connection") != 0)) + { + WLog_ERR(TAG, "Unknown module %s, aborting", what); + return -1; + } + + if (current == 0) + { + if (strcmp(what, "arm-transport") == 0) + WLog_INFO(TAG, "[%s] Starting your VM. It may take up to 5 minutes", what); + } + + const rdpSettings* settings = instance->context->settings; + const BOOL enabled = freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled); + if (!enabled) + { + WLog_WARN(TAG, "Automatic reconnection disabled, terminating. Try to connect again later"); + return -1; + } + + const size_t max = freerdp_settings_get_uint32(settings, FreeRDP_AutoReconnectMaxRetries); + const size_t delay = freerdp_settings_get_uint32(settings, FreeRDP_TcpConnectTimeout); + if (current >= max) + { + WLog_ERR(TAG, + "[%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; + } + + WLog_INFO(TAG, "[%s] retry %" PRIuz "/%" PRIuz ", delaying %" PRIuz "ms before next attempt", + what, current + 1, max, delay); + return WINPR_ASSERTING_INT_CAST(SSIZE_T, delay); +} + +BOOL client_auto_reconnect(freerdp* instance) +{ + return client_auto_reconnect_ex(instance, nullptr); +} + +BOOL client_auto_reconnect_ex(freerdp* instance, BOOL (*window_events)(freerdp* instance)) +{ + BOOL retry = TRUE; + UINT32 error = 0; + UINT32 numRetries = 0; + rdpSettings* settings = nullptr; + + if (!instance) + return FALSE; + + WINPR_ASSERT(instance->context); + + settings = instance->context->settings; + WINPR_ASSERT(settings); + + const UINT32 maxRetries = + freerdp_settings_get_uint32(settings, FreeRDP_AutoReconnectMaxRetries); + + /* Only auto reconnect on network disconnects. */ + error = freerdp_error_info(instance); + switch (error) + { + case ERRINFO_GRAPHICS_SUBSYSTEM_FAILED: + /* A network disconnect was detected */ + WLog_WARN(TAG, "Disconnected by server hitting a bug or resource limit [%s]", + freerdp_get_error_info_string(error)); + break; + case ERRINFO_SUCCESS: + /* A network disconnect was detected */ + WLog_INFO(TAG, "Network disconnect!"); + break; + default: + WLog_DBG(TAG, "Other error: %s", freerdp_get_error_info_string(error)); + return FALSE; + } + + if (!freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled)) + { + /* No auto-reconnect - just quit */ + WLog_DBG(TAG, "AutoReconnect not enabled, quitting."); + return FALSE; + } + + const UINT err = freerdp_get_last_error(instance->context); + switch (err) + { + case FREERDP_ERROR_CONNECT_LOGON_FAILURE: + case FREERDP_ERROR_CONNECT_CLIENT_REVOKED: + case FREERDP_ERROR_CONNECT_WRONG_PASSWORD: + case FREERDP_ERROR_CONNECT_ACCESS_DENIED: + case FREERDP_ERROR_CONNECT_ACCOUNT_RESTRICTION: + case FREERDP_ERROR_CONNECT_ACCOUNT_LOCKED_OUT: + case FREERDP_ERROR_CONNECT_ACCOUNT_EXPIRED: + case FREERDP_ERROR_CONNECT_NO_OR_MISSING_CREDENTIALS: + WLog_WARN(TAG, "Connection aborted: credentials do not work [%s]", + freerdp_get_last_error_name(err)); + return FALSE; + case FREERDP_ERROR_CONNECT_CANCELLED: + WLog_WARN(TAG, "Connection aborted by user"); + return FALSE; + default: + break; + } + + /* Perform an auto-reconnect. */ + while (retry) + { + /* Quit retrying if max retries has been exceeded */ + if ((maxRetries > 0) && (numRetries >= maxRetries)) + { + WLog_DBG(TAG, "AutoReconnect retries exceeded."); + return FALSE; + } + + /* Attempt the next reconnect */ + WLog_INFO(TAG, "Attempting reconnect (%" PRIu32 " of %" PRIu32 ")", numRetries, maxRetries); + + const SSIZE_T delay = + IFCALLRESULT(5000, instance->RetryDialog, instance, "connection", numRetries, nullptr); + if (delay < 0) + return FALSE; + numRetries++; + + if (freerdp_reconnect(instance)) + return TRUE; + + switch (freerdp_get_last_error(instance->context)) + { + case FREERDP_ERROR_CONNECT_CANCELLED: + WLog_WARN(TAG, "Autoreconnect aborted by user"); + return FALSE; + default: + break; + } + for (SSIZE_T x = 0; x < delay / 10; x++) + { + if (!IFCALLRESULT(TRUE, window_events, instance)) + { + WLog_ERR(TAG, "window_events failed!"); + return FALSE; + } + + Sleep(10); + } + } + + WLog_ERR(TAG, "Maximum reconnect retries exceeded"); + return FALSE; +} + +int freerdp_client_common_stop(rdpContext* context) +{ + rdpClientContext* cctx = (rdpClientContext*)context; + WINPR_ASSERT(cctx); + + freerdp_abort_connect_context(&cctx->context); + + if (cctx->thread) + { + (void)WaitForSingleObject(cctx->thread, INFINITE); + (void)CloseHandle(cctx->thread); + cctx->thread = nullptr; + } + + return 0; +} + +#if defined(CHANNEL_ENCOMSP_CLIENT) +BOOL freerdp_client_encomsp_toggle_control(EncomspClientContext* encomsp) +{ + rdpClientContext* cctx = nullptr; + BOOL state = 0; + + if (!encomsp) + return FALSE; + + cctx = (rdpClientContext*)encomsp->custom; + + state = cctx->controlToggle; + cctx->controlToggle = !cctx->controlToggle; + return freerdp_client_encomsp_set_control(encomsp, state); +} + +BOOL freerdp_client_encomsp_set_control(EncomspClientContext* encomsp, BOOL control) +{ + ENCOMSP_CHANGE_PARTICIPANT_CONTROL_LEVEL_PDU pdu = WINPR_C_ARRAY_INIT; + + if (!encomsp) + return FALSE; + + pdu.ParticipantId = encomsp->participantId; + pdu.Flags = ENCOMSP_REQUEST_VIEW; + + if (control) + pdu.Flags |= ENCOMSP_REQUEST_INTERACT; + + const UINT rc = encomsp->ChangeParticipantControlLevel(encomsp, &pdu); + return rc == CHANNEL_RC_OK; +} + +static UINT +client_encomsp_participant_created(EncomspClientContext* context, + const ENCOMSP_PARTICIPANT_CREATED_PDU* participantCreated) +{ + rdpClientContext* cctx = nullptr; + rdpSettings* settings = nullptr; + BOOL request = 0; + + if (!context || !context->custom || !participantCreated) + return ERROR_INVALID_PARAMETER; + + cctx = (rdpClientContext*)context->custom; + WINPR_ASSERT(cctx); + + settings = cctx->context.settings; + WINPR_ASSERT(settings); + + if (participantCreated->Flags & ENCOMSP_IS_PARTICIPANT) + context->participantId = participantCreated->ParticipantId; + + request = freerdp_settings_get_bool(settings, FreeRDP_RemoteAssistanceRequestControl); + if (request && (participantCreated->Flags & ENCOMSP_MAY_VIEW) && + !(participantCreated->Flags & ENCOMSP_MAY_INTERACT)) + { + if (!freerdp_client_encomsp_set_control(context, TRUE)) + return ERROR_INTERNAL_ERROR; + + /* if auto-request-control setting is enabled then only request control once upon connect, + * otherwise it will auto request control again every time server turns off control which + * is a bit annoying */ + if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteAssistanceRequestControl, FALSE)) + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +static void client_encomsp_init(rdpClientContext* cctx, EncomspClientContext* encomsp) +{ + cctx->encomsp = encomsp; + encomsp->custom = (void*)cctx; + encomsp->ParticipantCreated = client_encomsp_participant_created; +} + +static void client_encomsp_uninit(rdpClientContext* cctx, EncomspClientContext* encomsp) +{ + if (encomsp) + { + encomsp->custom = nullptr; + encomsp->ParticipantCreated = nullptr; + } + + if (cctx) + cctx->encomsp = nullptr; +} +#endif + +void freerdp_client_OnChannelConnectedEventHandler(void* context, + const ChannelConnectedEventArgs* e) +{ + rdpClientContext* cctx = (rdpClientContext*)context; + + WINPR_ASSERT(cctx); + WINPR_ASSERT(e); + + if (0) + { + } +#if defined(CHANNEL_AINPUT_CLIENT) + else if (strcmp(e->name, AINPUT_DVC_CHANNEL_NAME) == 0) + cctx->ainput = (AInputClientContext*)e->pInterface; +#endif +#if defined(CHANNEL_RDPEI_CLIENT) + else if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) == 0) + { + cctx->rdpei = (RdpeiClientContext*)e->pInterface; + } +#endif +#if defined(CHANNEL_RDPGFX_CLIENT) + else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0) + { + gdi_graphics_pipeline_init(cctx->context.gdi, (RdpgfxClientContext*)e->pInterface); + } +#endif +#if defined(CHANNEL_GEOMETRY_CLIENT) + else if (strcmp(e->name, GEOMETRY_DVC_CHANNEL_NAME) == 0) + { + gdi_video_geometry_init(cctx->context.gdi, (GeometryClientContext*)e->pInterface); + } +#endif +#if defined(CHANNEL_VIDEO_CLIENT) + else if (strcmp(e->name, VIDEO_CONTROL_DVC_CHANNEL_NAME) == 0) + { + gdi_video_control_init(cctx->context.gdi, (VideoClientContext*)e->pInterface); + } + else if (strcmp(e->name, VIDEO_DATA_DVC_CHANNEL_NAME) == 0) + { + gdi_video_data_init(cctx->context.gdi, (VideoClientContext*)e->pInterface); + } +#endif +#if defined(CHANNEL_ENCOMSP_CLIENT) + else if (strcmp(e->name, ENCOMSP_SVC_CHANNEL_NAME) == 0) + { + client_encomsp_init(cctx, (EncomspClientContext*)e->pInterface); + } +#endif +} + +void freerdp_client_OnChannelDisconnectedEventHandler(void* context, + const ChannelDisconnectedEventArgs* e) +{ + rdpClientContext* cctx = (rdpClientContext*)context; + + WINPR_ASSERT(cctx); + WINPR_ASSERT(e); + + if (0) + { + } +#if defined(CHANNEL_AINPUT_CLIENT) + else if (strcmp(e->name, AINPUT_DVC_CHANNEL_NAME) == 0) + cctx->ainput = nullptr; +#endif +#if defined(CHANNEL_RDPEI_CLIENT) + else if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) == 0) + { + cctx->rdpei = nullptr; + } +#endif +#if defined(CHANNEL_RDPGFX_CLIENT) + else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0) + { + gdi_graphics_pipeline_uninit(cctx->context.gdi, (RdpgfxClientContext*)e->pInterface); + } +#endif +#if defined(CHANNEL_GEOMETRY_CLIENT) + else if (strcmp(e->name, GEOMETRY_DVC_CHANNEL_NAME) == 0) + { + gdi_video_geometry_uninit(cctx->context.gdi, (GeometryClientContext*)e->pInterface); + } +#endif +#if defined(CHANNEL_VIDEO_CLIENT) + else if (strcmp(e->name, VIDEO_CONTROL_DVC_CHANNEL_NAME) == 0) + { + gdi_video_control_uninit(cctx->context.gdi, (VideoClientContext*)e->pInterface); + } + else if (strcmp(e->name, VIDEO_DATA_DVC_CHANNEL_NAME) == 0) + { + gdi_video_data_uninit(cctx->context.gdi, (VideoClientContext*)e->pInterface); + } +#endif +#if defined(CHANNEL_ENCOMSP_CLIENT) + else if (strcmp(e->name, ENCOMSP_SVC_CHANNEL_NAME) == 0) + { + client_encomsp_uninit(cctx, (EncomspClientContext*)e->pInterface); + } +#endif +} + +BOOL freerdp_client_send_wheel_event(rdpClientContext* cctx, UINT16 mflags) +{ + BOOL handled = FALSE; + + WINPR_ASSERT(cctx); + +#if defined(CHANNEL_AINPUT_CLIENT) + if (cctx->ainput) + { + UINT rc = 0; + UINT64 flags = 0; + INT32 x = 0; + INT32 y = 0; + INT32 value = mflags & 0xFF; + + if (mflags & PTR_FLAGS_WHEEL_NEGATIVE) + value = -1 * (0x100 - value); + + /* We have discrete steps, scale this so we can also support high + * resolution wheels. */ + value *= 0x10000; + + if (mflags & PTR_FLAGS_WHEEL) + { + flags |= AINPUT_FLAGS_WHEEL; + y = value; + } + + if (mflags & PTR_FLAGS_HWHEEL) + { + flags |= AINPUT_FLAGS_WHEEL; + x = value; + } + + WINPR_ASSERT(cctx->ainput->AInputSendInputEvent); + rc = cctx->ainput->AInputSendInputEvent(cctx->ainput, flags, x, y); + if (rc == CHANNEL_RC_OK) + handled = TRUE; + } +#endif + + if (!handled) + freerdp_input_send_mouse_event(cctx->context.input, mflags, 0, 0); + + return TRUE; +} + +#if defined(CHANNEL_AINPUT_CLIENT) +static inline BOOL ainput_send_diff_event(rdpClientContext* cctx, UINT64 flags, INT32 x, INT32 y) +{ + UINT rc = 0; + + WINPR_ASSERT(cctx); + WINPR_ASSERT(cctx->ainput); + WINPR_ASSERT(cctx->ainput->AInputSendInputEvent); + + rc = cctx->ainput->AInputSendInputEvent(cctx->ainput, flags, x, y); + + return rc == CHANNEL_RC_OK; +} +#endif + +static bool button_pressed(const rdpClientContext* cctx) +{ + WINPR_ASSERT(cctx); + for (size_t x = 0; x < ARRAYSIZE(cctx->pressed_buttons); x++) + { + const BOOL cur = cctx->pressed_buttons[x]; + if (cur) + return true; + } + return false; +} + +BOOL freerdp_client_send_button_event(rdpClientContext* cctx, BOOL relative, UINT16 mflags, INT32 x, + INT32 y) +{ + BOOL handled = FALSE; + + WINPR_ASSERT(cctx); + + if (mflags & PTR_FLAGS_BUTTON1) + cctx->pressed_buttons[0] = mflags & PTR_FLAGS_DOWN; + if (mflags & PTR_FLAGS_BUTTON2) + cctx->pressed_buttons[1] = mflags & PTR_FLAGS_DOWN; + if (mflags & PTR_FLAGS_BUTTON3) + cctx->pressed_buttons[2] = mflags & PTR_FLAGS_DOWN; + + if (((mflags & PTR_FLAGS_MOVE) != 0) && + !freerdp_settings_get_bool(cctx->context.settings, FreeRDP_MouseMotion)) + { + if (!button_pressed(cctx)) + return TRUE; + } + + const BOOL haveRelative = + freerdp_settings_get_bool(cctx->context.settings, FreeRDP_HasRelativeMouseEvent); + if (relative && haveRelative) + { + return freerdp_input_send_rel_mouse_event(cctx->context.input, mflags, + WINPR_ASSERTING_INT_CAST(int16_t, x), + WINPR_ASSERTING_INT_CAST(int16_t, y)); + } + +#if defined(CHANNEL_AINPUT_CLIENT) + if (cctx->ainput) + { + UINT64 flags = 0; + + if (cctx->mouse_grabbed && freerdp_client_use_relative_mouse_events(cctx)) + flags |= AINPUT_FLAGS_HAVE_REL; + + if (relative) + flags |= AINPUT_FLAGS_REL; + + if (mflags & PTR_FLAGS_DOWN) + flags |= AINPUT_FLAGS_DOWN; + if (mflags & PTR_FLAGS_BUTTON1) + flags |= AINPUT_FLAGS_BUTTON1; + if (mflags & PTR_FLAGS_BUTTON2) + flags |= AINPUT_FLAGS_BUTTON2; + if (mflags & PTR_FLAGS_BUTTON3) + flags |= AINPUT_FLAGS_BUTTON3; + if (mflags & PTR_FLAGS_MOVE) + flags |= AINPUT_FLAGS_MOVE; + handled = ainput_send_diff_event(cctx, flags, x, y); + } +#endif + + if (!handled) + { + if (relative) + { + cctx->lastX += x; + cctx->lastY += y; + WLog_WARN(TAG, "Relative mouse input channel not available, sending absolute!"); + } + else + { + cctx->lastX = x; + cctx->lastY = y; + } + return freerdp_input_send_mouse_event(cctx->context.input, mflags, (UINT16)cctx->lastX, + (UINT16)cctx->lastY); + } + return TRUE; +} + +BOOL freerdp_client_send_extended_button_event(rdpClientContext* cctx, BOOL relative, UINT16 mflags, + INT32 x, INT32 y) +{ + BOOL handled = FALSE; + WINPR_ASSERT(cctx); + + if (mflags & PTR_XFLAGS_BUTTON1) + cctx->pressed_buttons[3] = mflags & PTR_XFLAGS_DOWN; + if (mflags & PTR_XFLAGS_BUTTON2) + cctx->pressed_buttons[4] = mflags & PTR_XFLAGS_DOWN; + + const BOOL haveRelative = + freerdp_settings_get_bool(cctx->context.settings, FreeRDP_HasRelativeMouseEvent); + if (relative && haveRelative) + { + return freerdp_input_send_rel_mouse_event(cctx->context.input, mflags, + WINPR_ASSERTING_INT_CAST(int16_t, x), + WINPR_ASSERTING_INT_CAST(int16_t, y)); + } + +#if defined(CHANNEL_AINPUT_CLIENT) + if (cctx->ainput) + { + UINT64 flags = 0; + + if (relative) + flags |= AINPUT_FLAGS_REL; + if (mflags & PTR_XFLAGS_DOWN) + flags |= AINPUT_FLAGS_DOWN; + if (mflags & PTR_XFLAGS_BUTTON1) + flags |= AINPUT_XFLAGS_BUTTON1; + if (mflags & PTR_XFLAGS_BUTTON2) + flags |= AINPUT_XFLAGS_BUTTON2; + + handled = ainput_send_diff_event(cctx, flags, x, y); + } +#endif + + if (!handled) + { + if (relative) + { + cctx->lastX += x; + cctx->lastY += y; + WLog_WARN(TAG, "Relative mouse input channel not available, sending absolute!"); + } + else + { + cctx->lastX = x; + cctx->lastY = y; + } + freerdp_input_send_extended_mouse_event(cctx->context.input, mflags, (UINT16)cctx->lastX, + (UINT16)cctx->lastY); + } + + return TRUE; +} + +static BOOL freerdp_handle_touch_to_mouse(rdpClientContext* cctx, BOOL down, + const FreeRDP_TouchContact* contact) +{ + const UINT16 flags = PTR_FLAGS_MOVE | (down ? PTR_FLAGS_DOWN : 0); + const UINT16 xflags = down ? PTR_XFLAGS_DOWN : 0; + WINPR_ASSERT(contact); + WINPR_ASSERT(contact->x <= UINT16_MAX); + WINPR_ASSERT(contact->y <= UINT16_MAX); + + switch (contact->count) + { + case 1: + return freerdp_client_send_button_event(cctx, FALSE, flags | PTR_FLAGS_BUTTON1, + contact->x, contact->y); + case 2: + return freerdp_client_send_button_event(cctx, FALSE, flags | PTR_FLAGS_BUTTON2, + contact->x, contact->y); + case 3: + return freerdp_client_send_button_event(cctx, FALSE, flags | PTR_FLAGS_BUTTON3, + contact->x, contact->y); + case 4: + return freerdp_client_send_extended_button_event( + cctx, FALSE, xflags | PTR_XFLAGS_BUTTON1, contact->x, contact->y); + case 5: + return freerdp_client_send_extended_button_event( + cctx, FALSE, xflags | PTR_XFLAGS_BUTTON1, contact->x, contact->y); + default: + /* unmapped events, ignore */ + return TRUE; + } +} + +static BOOL freerdp_handle_touch_up(rdpClientContext* cctx, const FreeRDP_TouchContact* contact) +{ + WINPR_ASSERT(cctx); + WINPR_ASSERT(contact); + +#if defined(CHANNEL_RDPEI_CLIENT) + RdpeiClientContext* rdpei = cctx->rdpei; + + if (!rdpei) + return freerdp_handle_touch_to_mouse(cctx, FALSE, contact); + + int contactId = 0; + + if (rdpei->TouchRawEvent) + { + const UINT32 flags = RDPINPUT_CONTACT_FLAG_UP; + const UINT32 contactFlags = ((contact->flags & FREERDP_TOUCH_HAS_PRESSURE) != 0) + ? CONTACT_DATA_PRESSURE_PRESENT + : 0; + // Ensure contact position is unchanged from "engaged" to "out of range" state + const UINT rc1 = + rdpei->TouchRawEvent(rdpei, contact->id, contact->x, contact->y, &contactId, + RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE | + RDPINPUT_CONTACT_FLAG_INCONTACT, + contactFlags, contact->pressure); + if (rc1 != CHANNEL_RC_OK) + return FALSE; + + const UINT rc2 = rdpei->TouchRawEvent(rdpei, contact->id, contact->x, contact->y, + &contactId, flags, contactFlags, contact->pressure); + if (rc2 != CHANNEL_RC_OK) + return FALSE; + } + else + { + WINPR_ASSERT(rdpei->TouchEnd); + const UINT rc = rdpei->TouchEnd(rdpei, contact->id, contact->x, contact->y, &contactId); + if (rc != CHANNEL_RC_OK) + return FALSE; + } + return TRUE; +#else + WLog_WARN(TAG, "Touch event detected but RDPEI support not compiled in. Recompile with " + "-DCHANNEL_RDPEI_CLIENT=ON"); + return freerdp_handle_touch_to_mouse(cctx, FALSE, contact); +#endif +} + +static BOOL freerdp_handle_touch_down(rdpClientContext* cctx, const FreeRDP_TouchContact* contact) +{ + WINPR_ASSERT(cctx); + WINPR_ASSERT(contact); + +#if defined(CHANNEL_RDPEI_CLIENT) + RdpeiClientContext* rdpei = cctx->rdpei; + + // Emulate mouse click if touch is not possible, like in login screen + if (!rdpei) + return freerdp_handle_touch_to_mouse(cctx, TRUE, contact); + + int contactId = 0; + + if (rdpei->TouchRawEvent) + { + const UINT32 flags = RDPINPUT_CONTACT_FLAG_DOWN | RDPINPUT_CONTACT_FLAG_INRANGE | + RDPINPUT_CONTACT_FLAG_INCONTACT; + const UINT32 contactFlags = ((contact->flags & FREERDP_TOUCH_HAS_PRESSURE) != 0) + ? CONTACT_DATA_PRESSURE_PRESENT + : 0; + const UINT rc = rdpei->TouchRawEvent(rdpei, contact->id, contact->x, contact->y, &contactId, + flags, contactFlags, contact->pressure); + if (rc != CHANNEL_RC_OK) + return FALSE; + } + else + { + WINPR_ASSERT(rdpei->TouchBegin); + const UINT rc = rdpei->TouchBegin(rdpei, contact->id, contact->x, contact->y, &contactId); + if (rc != CHANNEL_RC_OK) + return FALSE; + } + + return TRUE; +#else + WLog_WARN(TAG, "Touch event detected but RDPEI support not compiled in. Recompile with " + "-DCHANNEL_RDPEI_CLIENT=ON"); + return freerdp_handle_touch_to_mouse(cctx, TRUE, contact); +#endif +} + +static BOOL freerdp_handle_touch_motion_to_mouse(rdpClientContext* cctx, + const FreeRDP_TouchContact* contact) +{ + const UINT16 flags = PTR_FLAGS_MOVE; + + WINPR_ASSERT(contact); + WINPR_ASSERT(contact->x <= UINT16_MAX); + WINPR_ASSERT(contact->y <= UINT16_MAX); + return freerdp_client_send_button_event(cctx, FALSE, flags, contact->x, contact->y); +} + +static BOOL freerdp_handle_touch_motion(rdpClientContext* cctx, const FreeRDP_TouchContact* contact) +{ + WINPR_ASSERT(cctx); + WINPR_ASSERT(contact); + +#if defined(CHANNEL_RDPEI_CLIENT) + RdpeiClientContext* rdpei = cctx->rdpei; + + if (!rdpei) + return freerdp_handle_touch_motion_to_mouse(cctx, contact); + + int contactId = 0; + + if (rdpei->TouchRawEvent) + { + const UINT32 flags = RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE | + RDPINPUT_CONTACT_FLAG_INCONTACT; + const UINT32 contactFlags = ((contact->flags & FREERDP_TOUCH_HAS_PRESSURE) != 0) + ? CONTACT_DATA_PRESSURE_PRESENT + : 0; + const UINT rc = rdpei->TouchRawEvent(rdpei, contact->id, contact->x, contact->y, &contactId, + flags, contactFlags, contact->pressure); + if (rc != CHANNEL_RC_OK) + return FALSE; + } + else + { + WINPR_ASSERT(rdpei->TouchUpdate); + const UINT rc = rdpei->TouchUpdate(rdpei, contact->id, contact->x, contact->y, &contactId); + if (rc != CHANNEL_RC_OK) + return FALSE; + } + + return TRUE; +#else + WLog_WARN(TAG, "Touch event detected but RDPEI support not compiled in. Recompile with " + "-DCHANNEL_RDPEI_CLIENT=ON"); + return freerdp_handle_touch_motion_to_mouse(cctx, contact); +#endif +} + +static BOOL freerdp_handle_touch_cancel(rdpClientContext* cctx, const FreeRDP_TouchContact* contact) +{ + WINPR_ASSERT(cctx); + WINPR_ASSERT(contact); + +#if defined(CHANNEL_RDPEI_CLIENT) + RdpeiClientContext* rdpei = cctx->rdpei; + + if (!rdpei) + return freerdp_handle_touch_to_mouse(cctx, false, contact); + + int contactId = 0; + + if (rdpei->TouchRawEvent) + { + const UINT32 flags = RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_CANCELED; + const UINT32 contactFlags = ((contact->flags & FREERDP_TOUCH_HAS_PRESSURE) != 0) + ? CONTACT_DATA_PRESSURE_PRESENT + : 0; + const UINT rc = rdpei->TouchRawEvent(rdpei, contact->id, contact->x, contact->y, &contactId, + flags, contactFlags, contact->pressure); + if (rc != CHANNEL_RC_OK) + return FALSE; + } + else + { + WINPR_ASSERT(rdpei->TouchUpdate); + const UINT rc = rdpei->TouchEnd(rdpei, contact->id, contact->x, contact->y, &contactId); + if (rc != CHANNEL_RC_OK) + return FALSE; + } + + return TRUE; +#else + WLog_WARN(TAG, "Touch event detected but RDPEI support not compiled in. Recompile with " + "-DCHANNEL_RDPEI_CLIENT=ON"); + return freerdp_handle_touch_to_mouse(cctx, false, contact); +#endif +} + +static BOOL freerdp_client_touch_update(rdpClientContext* cctx, UINT32 flags, INT32 touchId, + UINT32 pressure, INT32 x, INT32 y, + FreeRDP_TouchContact* pcontact) +{ + WINPR_ASSERT(cctx); + WINPR_ASSERT(pcontact); + + for (size_t i = 0; i < ARRAYSIZE(cctx->contacts); i++) + { + FreeRDP_TouchContact* contact = &cctx->contacts[i]; + + const BOOL newcontact = ((contact->id == 0) && ((flags & FREERDP_TOUCH_DOWN) != 0)); + if (newcontact || (contact->id == touchId)) + { + contact->id = touchId; + contact->flags = flags; + contact->pressure = pressure; + contact->x = x; + contact->y = y; + + *pcontact = *contact; + + const BOOL resetcontact = (flags & FREERDP_TOUCH_UP) != 0; + if (resetcontact) + { + FreeRDP_TouchContact empty = WINPR_C_ARRAY_INIT; + *contact = empty; + } + return TRUE; + } + } + + return FALSE; +} + +BOOL freerdp_client_handle_touch(rdpClientContext* cctx, UINT32 flags, INT32 finger, + UINT32 pressure, INT32 x, INT32 y) +{ + const UINT32 mask = + FREERDP_TOUCH_DOWN | FREERDP_TOUCH_UP | FREERDP_TOUCH_MOTION | FREERDP_TOUCH_CANCEL; + WINPR_ASSERT(cctx); + + FreeRDP_TouchContact contact = WINPR_C_ARRAY_INIT; + + if (!freerdp_client_touch_update(cctx, flags, finger, pressure, x, y, &contact)) + return FALSE; + + switch (flags & mask) + { + case FREERDP_TOUCH_DOWN: + return freerdp_handle_touch_down(cctx, &contact); + case FREERDP_TOUCH_UP: + return freerdp_handle_touch_up(cctx, &contact); + case FREERDP_TOUCH_MOTION: + return freerdp_handle_touch_motion(cctx, &contact); + case FREERDP_TOUCH_CANCEL: + return freerdp_handle_touch_cancel(cctx, &contact); + default: + WLog_WARN(TAG, "Unhandled FreeRDPTouchEventType %" PRIu32 ", ignoring", flags); + return FALSE; + } +} + +BOOL freerdp_client_load_channels(freerdp* instance) +{ + WINPR_ASSERT(instance); + WINPR_ASSERT(instance->context); + + if (!freerdp_client_load_addins(instance->context->channels, instance->context->settings)) + { + WLog_ERR(TAG, "Failed to load addins [%08" PRIx32 "]", GetLastError()); + return FALSE; + } + return TRUE; +} + +int client_cli_logon_error_info(freerdp* instance, UINT32 data, UINT32 type) +{ + 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; + + WLog_INFO(TAG, "Logon Error Info %s [%s]", str_data, str_type); + return 1; +} + +static FreeRDP_PenDevice* freerdp_client_get_pen(rdpClientContext* cctx, INT32 deviceid, + size_t* pos) +{ + WINPR_ASSERT(cctx); + + for (size_t i = 0; i < ARRAYSIZE(cctx->pens); i++) + { + FreeRDP_PenDevice* pen = &cctx->pens[i]; + if (deviceid == pen->deviceid) + { + if (pos) + *pos = i; + return pen; + } + } + return nullptr; +} + +static BOOL freerdp_client_register_pen(rdpClientContext* cctx, UINT32 flags, INT32 deviceid, + double pressure) +{ + static const INT32 null_deviceid = 0; + + WINPR_ASSERT(cctx); + WINPR_ASSERT((flags & FREERDP_PEN_REGISTER) != 0); + if (freerdp_client_is_pen(cctx, deviceid)) + { + WLog_WARN(TAG, "trying to double register pen device %" PRId32, deviceid); + return FALSE; + } + + size_t pos = 0; + FreeRDP_PenDevice* pen = freerdp_client_get_pen(cctx, null_deviceid, &pos); + if (pen) + { + const FreeRDP_PenDevice empty = WINPR_C_ARRAY_INIT; + *pen = empty; + + pen->deviceid = deviceid; + pen->max_pressure = pressure; + pen->flags = flags; + + WLog_DBG(TAG, "registered pen at index %" PRIuz, pos); + return TRUE; + } + + WLog_WARN(TAG, "No free slot for an additional pen device, skipping"); + return TRUE; +} + +BOOL freerdp_client_handle_pen(rdpClientContext* cctx, UINT32 flags, INT32 deviceid, ...) +{ +#if defined(CHANNEL_RDPEI_CLIENT) + if ((flags & FREERDP_PEN_REGISTER) != 0) + { + va_list args = WINPR_C_ARRAY_INIT; + + va_start(args, deviceid); + double pressure = va_arg(args, double); + va_end(args); + return freerdp_client_register_pen(cctx, flags, deviceid, pressure); + } + size_t pos = 0; + FreeRDP_PenDevice* pen = freerdp_client_get_pen(cctx, deviceid, &pos); + if (!pen) + { + WLog_WARN(TAG, "unregistered pen device %" PRId32 " event 0x%08" PRIx32, deviceid, flags); + return FALSE; + } + + UINT32 fieldFlags = RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT; + UINT32 penFlags = + ((pen->flags & FREERDP_PEN_IS_INVERTED) != 0) ? RDPINPUT_PEN_FLAG_INVERTED : 0; + + RdpeiClientContext* rdpei = cctx->rdpei; + WINPR_ASSERT(rdpei); + + UINT32 normalizedpressure = 1024; + INT32 x = 0; + INT32 y = 0; + UINT16 rotation = 0; + INT16 tiltX = 0; + INT16 tiltY = 0; + va_list args = WINPR_C_ARRAY_INIT; + va_start(args, deviceid); + + x = va_arg(args, INT32); + y = va_arg(args, INT32); + if ((flags & FREERDP_PEN_HAS_PRESSURE) != 0) + { + const double pressure = va_arg(args, double); + const double np = (pressure * 1024.0) / pen->max_pressure; + normalizedpressure = (UINT32)lround(np); + WLog_DBG(TAG, "pen pressure %lf -> %" PRIu32, pressure, normalizedpressure); + fieldFlags |= RDPINPUT_PEN_CONTACT_PRESSURE_PRESENT; + } + if ((flags & FREERDP_PEN_HAS_ROTATION) != 0) + { + const unsigned arg = va_arg(args, unsigned); + rotation = WINPR_ASSERTING_INT_CAST(UINT16, arg); + fieldFlags |= RDPINPUT_PEN_CONTACT_ROTATION_PRESENT; + } + if ((flags & FREERDP_PEN_HAS_TILTX) != 0) + { + const int arg = va_arg(args, int); + tiltX = WINPR_ASSERTING_INT_CAST(INT16, arg); + fieldFlags |= RDPINPUT_PEN_CONTACT_TILTX_PRESENT; + } + if ((flags & FREERDP_PEN_HAS_TILTY) != 0) + { + const int arg = va_arg(args, int); + tiltY = WINPR_ASSERTING_INT_CAST(INT16, arg); + fieldFlags |= RDPINPUT_PEN_CONTACT_TILTY_PRESENT; + } + va_end(args); + + if ((flags & FREERDP_PEN_PRESS) != 0) + { + // Ensure that only one button is pressed + if (pen->pressed) + flags = FREERDP_PEN_MOTION | + (flags & (UINT32) ~(FREERDP_PEN_PRESS | FREERDP_PEN_BARREL_PRESSED)); + else if ((flags & FREERDP_PEN_BARREL_PRESSED) != 0) + pen->flags |= FREERDP_PEN_BARREL_PRESSED; + } + else if ((flags & FREERDP_PEN_RELEASE) != 0) + { + if (!pen->pressed || + ((flags & FREERDP_PEN_BARREL_PRESSED) ^ (pen->flags & FREERDP_PEN_BARREL_PRESSED))) + flags = FREERDP_PEN_MOTION | + (flags & (UINT32) ~(FREERDP_PEN_RELEASE | FREERDP_PEN_BARREL_PRESSED)); + else + pen->flags &= (UINT32)~FREERDP_PEN_BARREL_PRESSED; + } + + flags |= pen->flags; + if ((flags & FREERDP_PEN_ERASER_PRESSED) != 0) + penFlags |= RDPINPUT_PEN_FLAG_ERASER_PRESSED; + if ((flags & FREERDP_PEN_BARREL_PRESSED) != 0) + penFlags |= RDPINPUT_PEN_FLAG_BARREL_PRESSED; + + pen->last_x = x; + pen->last_y = y; + if ((flags & FREERDP_PEN_PRESS) != 0) + { + WLog_DBG(TAG, "Pen press %" PRId32, deviceid); + pen->hovering = FALSE; + pen->pressed = TRUE; + + WINPR_ASSERT(rdpei->PenBegin); + const UINT rc = rdpei->PenBegin(rdpei, deviceid, fieldFlags, x, y, penFlags, + normalizedpressure, rotation, tiltX, tiltY); + return rc == CHANNEL_RC_OK; + } + else if ((flags & FREERDP_PEN_MOTION) != 0) + { + UINT rc = ERROR_INTERNAL_ERROR; + if (pen->pressed) + { + WLog_DBG(TAG, "Pen update %" PRId32, deviceid); + + // TODO: what if no rotation is supported but tilt is? + WINPR_ASSERT(rdpei->PenUpdate); + rc = rdpei->PenUpdate(rdpei, deviceid, fieldFlags, x, y, penFlags, normalizedpressure, + rotation, tiltX, tiltY); + } + else if (pen->hovering) + { + WLog_DBG(TAG, "Pen hover update %" PRId32, deviceid); + + WINPR_ASSERT(rdpei->PenHoverUpdate); + rc = rdpei->PenHoverUpdate(rdpei, deviceid, RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT, x, y, + penFlags, normalizedpressure, rotation, tiltX, tiltY); + } + else + { + WLog_DBG(TAG, "Pen hover begin %" PRId32, deviceid); + pen->hovering = TRUE; + + WINPR_ASSERT(rdpei->PenHoverBegin); + rc = rdpei->PenHoverBegin(rdpei, deviceid, RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT, x, y, + penFlags, normalizedpressure, rotation, tiltX, tiltY); + } + return rc == CHANNEL_RC_OK; + } + else if ((flags & FREERDP_PEN_RELEASE) != 0) + { + WLog_DBG(TAG, "Pen release %" PRId32, deviceid); + pen->pressed = FALSE; + pen->hovering = TRUE; + + WINPR_ASSERT(rdpei->PenUpdate); + const UINT rc = rdpei->PenUpdate(rdpei, deviceid, fieldFlags, x, y, penFlags, + normalizedpressure, rotation, tiltX, tiltY); + if (rc != CHANNEL_RC_OK) + return FALSE; + WINPR_ASSERT(rdpei->PenEnd); + const UINT re = rdpei->PenEnd(rdpei, deviceid, RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT, x, y, + penFlags, normalizedpressure, rotation, tiltX, tiltY); + return re == CHANNEL_RC_OK; + } + + WLog_WARN(TAG, "Invalid pen %" PRId32 " flags 0x%08" PRIx32, deviceid, flags); +#else + WLog_WARN(TAG, "Pen event detected but RDPEI support not compiled in. Recompile with " + "-DCHANNEL_RDPEI_CLIENT=ON"); +#endif + + return FALSE; +} + +BOOL freerdp_client_pen_cancel_all(rdpClientContext* cctx) +{ + WINPR_ASSERT(cctx); + +#if defined(CHANNEL_RDPEI_CLIENT) + RdpeiClientContext* rdpei = cctx->rdpei; + + if (!rdpei) + return FALSE; + + for (size_t i = 0; i < ARRAYSIZE(cctx->pens); i++) + { + FreeRDP_PenDevice* pen = &cctx->pens[i]; + if (pen->hovering) + { + WLog_DBG(TAG, "unhover pen %" PRId32, pen->deviceid); + pen->hovering = FALSE; + const UINT rc = + rdpei->PenHoverCancel(rdpei, pen->deviceid, 0, pen->last_x, pen->last_y); + if (rc != CHANNEL_RC_OK) + return FALSE; + } + } + return TRUE; +#else + WLog_WARN(TAG, "Pen event detected but RDPEI support not compiled in. Recompile with " + "-DCHANNEL_RDPEI_CLIENT=ON"); + return FALSE; +#endif +} + +BOOL freerdp_client_is_pen(rdpClientContext* cctx, INT32 deviceid) +{ + WINPR_ASSERT(cctx); + + if (deviceid == 0) + return FALSE; + + for (size_t x = 0; x < ARRAYSIZE(cctx->pens); x++) + { + const FreeRDP_PenDevice* pen = &cctx->pens[x]; + if (pen->deviceid == deviceid) + return TRUE; + } + + return FALSE; +} + +BOOL freerdp_client_use_relative_mouse_events(rdpClientContext* cctx) +{ + WINPR_ASSERT(cctx); + + const rdpSettings* settings = cctx->context.settings; + const BOOL useRelative = freerdp_settings_get_bool(settings, FreeRDP_MouseUseRelativeMove); + const BOOL haveRelative = freerdp_settings_get_bool(settings, FreeRDP_HasRelativeMouseEvent); + BOOL ainput = FALSE; +#if defined(CHANNEL_AINPUT_CLIENT) + ainput = cctx->ainput != nullptr; +#endif + + return useRelative && (haveRelative || ainput); +} + +#if defined(WITH_AAD) +WINPR_ATTR_MALLOC(free, 1) +WINPR_ATTR_NODISCARD +static char* get_redirect_uri(const rdpSettings* settings) +{ + char* redirect_uri = nullptr; + const bool cli = freerdp_settings_get_bool(settings, FreeRDP_UseCommonStdioCallbacks); + if (cli) + { + const char* redirect_fmt = + freerdp_settings_get_string(settings, FreeRDP_GatewayAvdAccessAadFormat); + const BOOL useTenant = freerdp_settings_get_bool(settings, FreeRDP_GatewayAvdUseTenantid); + const char* tenantid = "common"; + if (useTenant) + tenantid = freerdp_settings_get_string(settings, FreeRDP_GatewayAvdAadtenantid); + + if (tenantid && redirect_fmt) + { + const char* url = + freerdp_settings_get_string(settings, FreeRDP_GatewayAzureActiveDirectory); + + size_t redirect_len = 0; + winpr_asprintf(&redirect_uri, &redirect_len, redirect_fmt, url, tenantid); + } + } + else + { + const char* client_id = freerdp_settings_get_string(settings, FreeRDP_GatewayAvdClientID); + const char* redirect_fmt = + freerdp_settings_get_string(settings, FreeRDP_GatewayAvdAccessTokenFormat); + + size_t redirect_len = 0; + winpr_asprintf(&redirect_uri, &redirect_len, redirect_fmt, client_id); + } + return redirect_uri; +} + +static char* avd_auth_request(rdpClientContext* cctx, WINPR_ATTR_UNUSED va_list ap) +{ + const rdpSettings* settings = cctx->context.settings; + const char* client_id = freerdp_settings_get_string(settings, FreeRDP_GatewayAvdClientID); + const char* ep = freerdp_utils_aad_get_wellknown_string(&cctx->context, + AAD_WELLKNOWN_authorization_endpoint); + const char* scope = freerdp_settings_get_string(settings, FreeRDP_GatewayAvdScope); + + if (!client_id || !ep || !scope) + return nullptr; + + char* redirect_uri = get_redirect_uri(settings); + if (!redirect_uri) + return nullptr; + + char* url = nullptr; + size_t urllen = 0; + winpr_asprintf(&url, &urllen, "%s?client_id=%s&response_type=code&scope=%s&redirect_uri=%s", ep, + client_id, scope, redirect_uri); + free(redirect_uri); + return url; +} + +static char* avd_token_request(rdpClientContext* cctx, WINPR_ATTR_UNUSED va_list ap) +{ + const rdpSettings* settings = cctx->context.settings; + const char* client_id = freerdp_settings_get_string(settings, FreeRDP_GatewayAvdClientID); + const char* ep = freerdp_utils_aad_get_wellknown_string(&cctx->context, + AAD_WELLKNOWN_authorization_endpoint); + const char* scope = freerdp_settings_get_string(settings, FreeRDP_GatewayAvdScope); + + if (!client_id || !ep || !scope) + return nullptr; + + char* redirect_uri = get_redirect_uri(settings); + if (!redirect_uri) + return nullptr; + + char* url = nullptr; + size_t urllen = 0; + + const char* code = va_arg(ap, const char*); + winpr_asprintf(&url, &urllen, + "grant_type=authorization_code&code=%s&client_id=%s&scope=%s&redirect_uri=%s", + code, client_id, scope, redirect_uri); + free(redirect_uri); + return url; +} + +static char* aad_auth_request(rdpClientContext* cctx, WINPR_ATTR_UNUSED va_list ap) +{ + const rdpSettings* settings = cctx->context.settings; + char* url = nullptr; + size_t urllen = 0; + char* redirect_uri = get_redirect_uri(settings); + + const char* client_id = freerdp_settings_get_string(settings, FreeRDP_GatewayAvdClientID); + if (!client_id || !redirect_uri) + goto cleanup; + + { + const char* scope = va_arg(ap, const char*); + if (!scope) + goto cleanup; + + { + const char* ep = freerdp_utils_aad_get_wellknown_string( + &cctx->context, AAD_WELLKNOWN_authorization_endpoint); + winpr_asprintf(&url, &urllen, + "%s?client_id=%s&response_type=code&scope=%s&redirect_uri=%s", ep, + client_id, scope, redirect_uri); + } + } + +cleanup: + free(redirect_uri); + return url; +} + +static char* aad_token_request(rdpClientContext* cctx, WINPR_ATTR_UNUSED va_list ap) +{ + const rdpSettings* settings = cctx->context.settings; + const char* client_id = freerdp_settings_get_string(settings, FreeRDP_GatewayAvdClientID); + const char* ep = freerdp_utils_aad_get_wellknown_string(&cctx->context, + AAD_WELLKNOWN_authorization_endpoint); + const char* scope = va_arg(ap, const char*); + const char* code = va_arg(ap, const char*); + const char* req_cnf = va_arg(ap, const char*); + + if (!client_id || !ep || !scope || !code || !req_cnf) + return nullptr; + + char* redirect_uri = get_redirect_uri(settings); + if (!redirect_uri) + return nullptr; + + char* url = nullptr; + size_t urllen = 0; + + winpr_asprintf( + &url, &urllen, + "grant_type=authorization_code&code=%s&client_id=%s&scope=%s&redirect_uri=%s&req_cnf=%s", + code, client_id, scope, redirect_uri, req_cnf); + free(redirect_uri); + return url; +} +#endif + +char* freerdp_client_get_aad_url(rdpClientContext* cctx, freerdp_client_aad_type type, ...) +{ + WINPR_ASSERT(cctx); + char* str = nullptr; + + va_list ap = WINPR_C_ARRAY_INIT; + va_start(ap, type); + switch (type) + { +#if defined(WITH_AAD) + case FREERDP_CLIENT_AAD_AUTH_REQUEST: + str = aad_auth_request(cctx, ap); + break; + case FREERDP_CLIENT_AAD_TOKEN_REQUEST: + str = aad_token_request(cctx, ap); + break; + case FREERDP_CLIENT_AAD_AVD_AUTH_REQUEST: + str = avd_auth_request(cctx, ap); + break; + case FREERDP_CLIENT_AAD_AVD_TOKEN_REQUEST: + str = avd_token_request(cctx, ap); + break; +#endif + default: + break; + } + va_end(ap); + return str; +} diff --git a/third_party/FreeRDP/client/common/client_cliprdr_file.c b/third_party/FreeRDP/client/common/client_cliprdr_file.c new file mode 100644 index 0000000..5078461 --- /dev/null +++ b/third_party/FreeRDP/client/common/client_cliprdr_file.c @@ -0,0 +1,2571 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * X11 Clipboard Redirection + * + * Copyright 2010-2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2023 Armin Novak + * Copyright 2023 Pascal Nowack + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +#ifdef WITH_FUSE +#define FUSE_USE_VERSION 30 +#include +#endif + +#if defined(WITH_FUSE) +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#define NO_CLIP_DATA_ID (UINT64_C(1) << 32) +#define WIN32_FILETIME_TO_UNIX_EPOCH INT64_C(11644473600) + +#ifdef WITH_DEBUG_CLIPRDR +#define DEBUG_CLIPRDR(log, ...) WLog_Print(log, WLOG_DEBUG, __VA_ARGS__) +#else +#define DEBUG_CLIPRDR(log, ...) \ + do \ + { \ + } while (0) +#endif + +#if defined(WITH_FUSE) +typedef enum eFuseLowlevelOperationType +{ + FUSE_LL_OPERATION_NONE, + FUSE_LL_OPERATION_LOOKUP, + FUSE_LL_OPERATION_GETATTR, + FUSE_LL_OPERATION_READ, +} FuseLowlevelOperationType; + +typedef struct sCliprdrFuseFile CliprdrFuseFile; + +struct sCliprdrFuseFile +{ + CliprdrFuseFile* parent; + wArrayList* children; + + size_t filename_len; + const char* filename; + + size_t filename_with_root_len; + char* filename_with_root; + UINT32 list_idx; + fuse_ino_t ino; + + BOOL is_directory; + BOOL is_readonly; + + BOOL has_size; + UINT64 size; + + BOOL has_last_write_time; + INT64 last_write_time_unix; + + BOOL has_clip_data_id; + UINT32 clip_data_id; +}; + +typedef struct +{ + CliprdrFileContext* file_context; + + CliprdrFuseFile* clip_data_dir; + + BOOL has_clip_data_id; + UINT32 clip_data_id; +} CliprdrFuseClipDataEntry; + +typedef struct +{ + CliprdrFileContext* file_context; + + wArrayList* fuse_files; + + BOOL all_files; + BOOL has_clip_data_id; + UINT32 clip_data_id; +} FuseFileClearContext; + +typedef struct +{ + FuseLowlevelOperationType operation_type; + CliprdrFuseFile* fuse_file; + fuse_req_t fuse_req; + UINT32 stream_id; +} CliprdrFuseRequest; + +typedef struct +{ + CliprdrFuseFile* parent; + char* parent_path; +} CliprdrFuseFindParentContext; +#endif + +typedef struct +{ + char* name; + FILE* fp; + INT64 size; + CliprdrFileContext* context; +} CliprdrLocalFile; + +typedef struct +{ + UINT32 lockId; + BOOL locked; + size_t count; + CliprdrLocalFile* files; + CliprdrFileContext* context; +} CliprdrLocalStream; + +struct cliprdr_file_context +{ +#if defined(WITH_FUSE) + /* FUSE related**/ + HANDLE fuse_start_sync; + HANDLE fuse_stop_sync; + HANDLE fuse_thread; + struct fuse_session* fuse_sess; +#if FUSE_USE_VERSION < 30 + struct fuse_chan* ch; +#endif + + wHashTable* inode_table; + wHashTable* clip_data_table; + wHashTable* request_table; + + CliprdrFuseClipDataEntry* clip_data_entry_without_id; + UINT32 current_clip_data_id; + + fuse_ino_t next_ino; + UINT32 next_clip_data_id; + UINT32 next_stream_id; +#endif + + /* File clipping */ + BOOL file_formats_registered; + UINT32 file_capability_flags; + + UINT32 local_lock_id; + + wHashTable* local_streams; + wLog* log; + void* clipboard; + CliprdrClientContext* context; + char* path; + char* exposed_path; + BYTE server_data_hash[WINPR_SHA256_DIGEST_LENGTH]; + BYTE client_data_hash[WINPR_SHA256_DIGEST_LENGTH]; +}; + +#if defined(WITH_FUSE) +static CliprdrFuseFile* get_fuse_file_by_ino(CliprdrFileContext* file_context, fuse_ino_t fuse_ino); + +static void fuse_file_free(void* data) +{ + CliprdrFuseFile* fuse_file = data; + + if (!fuse_file) + return; + + ArrayList_Free(fuse_file->children); + free(fuse_file->filename_with_root); + + free(fuse_file); +} + +WINPR_ATTR_FORMAT_ARG(1, 2) +WINPR_ATTR_MALLOC(fuse_file_free, 1) +WINPR_ATTR_NODISCARD +static CliprdrFuseFile* fuse_file_new(WINPR_FORMAT_ARG const char* fmt, ...) +{ + CliprdrFuseFile* file = calloc(1, sizeof(CliprdrFuseFile)); + if (!file) + return nullptr; + + file->children = ArrayList_New(FALSE); + if (!file->children) + goto fail; + + WINPR_ASSERT(fmt); + + { + va_list ap = WINPR_C_ARRAY_INIT; + va_start(ap, fmt); + const int rc = + winpr_vasprintf(&file->filename_with_root, &file->filename_with_root_len, fmt, ap); + va_end(ap); + + if (rc < 0) + goto fail; + } + + if (file->filename_with_root && (file->filename_with_root_len > 0)) + { + file->filename_len = 0; + file->filename = strrchr(file->filename_with_root, '/') + 1; + if (file->filename) + file->filename_len = strnlen(file->filename, file->filename_with_root_len); + } + return file; +fail: + fuse_file_free(file); + return nullptr; +} + +static void clip_data_entry_free(void* data) +{ + CliprdrFuseClipDataEntry* clip_data_entry = data; + + if (!clip_data_entry) + return; + + if (clip_data_entry->has_clip_data_id) + { + CliprdrFileContext* file_context = clip_data_entry->file_context; + CLIPRDR_UNLOCK_CLIPBOARD_DATA unlock_clipboard_data = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(file_context); + + unlock_clipboard_data.common.msgType = CB_UNLOCK_CLIPDATA; + unlock_clipboard_data.clipDataId = clip_data_entry->clip_data_id; + + const UINT rc = file_context->context->ClientUnlockClipboardData(file_context->context, + &unlock_clipboard_data); + if (rc != CHANNEL_RC_OK) + WLog_Print(file_context->log, WLOG_DEBUG, + "ClientUnlockClipboardData failed with %" PRIu32, rc); + + clip_data_entry->has_clip_data_id = FALSE; + + WLog_Print(file_context->log, WLOG_DEBUG, "Destroyed ClipDataEntry with id %u", + clip_data_entry->clip_data_id); + } + + free(clip_data_entry); +} + +static BOOL does_server_support_clipdata_locking(CliprdrFileContext* file_context) +{ + WINPR_ASSERT(file_context); + + return (cliprdr_file_context_remote_get_flags(file_context) & CB_CAN_LOCK_CLIPDATA) != 0; +} + +static UINT32 get_next_free_clip_data_id(CliprdrFileContext* file_context) +{ + UINT32 clip_data_id = 0; + + WINPR_ASSERT(file_context); + + HashTable_Lock(file_context->inode_table); + clip_data_id = file_context->next_clip_data_id; + while (clip_data_id == 0 || + HashTable_GetItemValue(file_context->clip_data_table, (void*)(uintptr_t)clip_data_id)) + ++clip_data_id; + + file_context->next_clip_data_id = clip_data_id + 1; + HashTable_Unlock(file_context->inode_table); + + return clip_data_id; +} + +static CliprdrFuseClipDataEntry* clip_data_entry_new(CliprdrFileContext* file_context, + BOOL needs_clip_data_id) +{ + CliprdrFuseClipDataEntry* clip_data_entry = nullptr; + CLIPRDR_LOCK_CLIPBOARD_DATA lock_clipboard_data = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(file_context); + + clip_data_entry = calloc(1, sizeof(CliprdrFuseClipDataEntry)); + if (!clip_data_entry) + return nullptr; + + clip_data_entry->file_context = file_context; + clip_data_entry->clip_data_id = get_next_free_clip_data_id(file_context); + + if (!needs_clip_data_id) + return clip_data_entry; + + lock_clipboard_data.common.msgType = CB_LOCK_CLIPDATA; + lock_clipboard_data.clipDataId = clip_data_entry->clip_data_id; + + if (file_context->context->ClientLockClipboardData(file_context->context, &lock_clipboard_data)) + { + HashTable_Lock(file_context->inode_table); + clip_data_entry_free(clip_data_entry); + HashTable_Unlock(file_context->inode_table); + return nullptr; + } + clip_data_entry->has_clip_data_id = TRUE; + + WLog_Print(file_context->log, WLOG_DEBUG, "Created ClipDataEntry with id %u", + clip_data_entry->clip_data_id); + + return clip_data_entry; +} + +static BOOL should_remove_fuse_file(CliprdrFuseFile* fuse_file, BOOL all_files, + BOOL has_clip_data_id, UINT32 clip_data_id) +{ + if (all_files) + return TRUE; + + if (fuse_file->ino == FUSE_ROOT_ID) + return FALSE; + if (!fuse_file->has_clip_data_id && !has_clip_data_id) + return TRUE; + if (fuse_file->has_clip_data_id && has_clip_data_id && + (fuse_file->clip_data_id == clip_data_id)) + return TRUE; + + return FALSE; +} + +static BOOL maybe_clear_fuse_request(const void* key, void* value, void* arg) +{ + CliprdrFuseRequest* fuse_request = value; + FuseFileClearContext* clear_context = arg; + CliprdrFileContext* file_context = clear_context->file_context; + CliprdrFuseFile* fuse_file = fuse_request->fuse_file; + + WINPR_ASSERT(file_context); + + if (!should_remove_fuse_file(fuse_file, clear_context->all_files, + clear_context->has_clip_data_id, clear_context->clip_data_id)) + return TRUE; + + DEBUG_CLIPRDR(file_context->log, "Clearing FileContentsRequest for file \"%s\"", + fuse_file->filename_with_root); + + fuse_reply_err(fuse_request->fuse_req, EIO); + HashTable_Remove(file_context->request_table, key); + + return TRUE; +} + +static BOOL maybe_steal_inode(const void* key, void* value, void* arg) +{ + CliprdrFuseFile* fuse_file = value; + FuseFileClearContext* clear_context = arg; + CliprdrFileContext* file_context = clear_context->file_context; + + WINPR_ASSERT(file_context); + + if (should_remove_fuse_file(fuse_file, clear_context->all_files, + clear_context->has_clip_data_id, clear_context->clip_data_id)) + { + if (!ArrayList_Append(clear_context->fuse_files, fuse_file)) + WLog_Print(file_context->log, WLOG_ERROR, + "Failed to append FUSE file to list for deletion"); + + HashTable_Remove(file_context->inode_table, key); + } + + return TRUE; +} + +static BOOL notify_delete_child(void* data, WINPR_ATTR_UNUSED size_t index, va_list ap) +{ + CliprdrFuseFile* child = data; + + WINPR_ASSERT(child); + + CliprdrFileContext* file_context = va_arg(ap, CliprdrFileContext*); + CliprdrFuseFile* parent = va_arg(ap, CliprdrFuseFile*); + + WINPR_ASSERT(file_context); + WINPR_ASSERT(parent); + + WINPR_ASSERT(file_context->fuse_sess); + fuse_lowlevel_notify_delete(file_context->fuse_sess, parent->ino, child->ino, child->filename, + child->filename_len); + + return TRUE; +} + +static BOOL invalidate_inode(void* data, WINPR_ATTR_UNUSED size_t index, va_list ap) +{ + CliprdrFuseFile* fuse_file = data; + + WINPR_ASSERT(fuse_file); + + CliprdrFileContext* file_context = va_arg(ap, CliprdrFileContext*); + WINPR_ASSERT(file_context); + WINPR_ASSERT(file_context->fuse_sess); + + ArrayList_ForEach(fuse_file->children, notify_delete_child, file_context, fuse_file); + + DEBUG_CLIPRDR(file_context->log, "Invalidating inode %lu for file \"%s\"", fuse_file->ino, + fuse_file->filename); + fuse_lowlevel_notify_inval_inode(file_context->fuse_sess, fuse_file->ino, 0, 0); + WLog_Print(file_context->log, WLOG_DEBUG, "Inode %lu invalidated", fuse_file->ino); + + return TRUE; +} + +static void clear_selection(CliprdrFileContext* file_context, BOOL all_selections, + CliprdrFuseClipDataEntry* clip_data_entry) +{ + FuseFileClearContext clear_context = WINPR_C_ARRAY_INIT; + CliprdrFuseFile* clip_data_dir = nullptr; + + WINPR_ASSERT(file_context); + + clear_context.file_context = file_context; + clear_context.fuse_files = ArrayList_New(FALSE); + WINPR_ASSERT(clear_context.fuse_files); + + wObject* aobj = ArrayList_Object(clear_context.fuse_files); + WINPR_ASSERT(aobj); + aobj->fnObjectFree = fuse_file_free; + + if (clip_data_entry) + { + clip_data_dir = clip_data_entry->clip_data_dir; + clip_data_entry->clip_data_dir = nullptr; + + WINPR_ASSERT(clip_data_dir); + + CliprdrFuseFile* root_dir = get_fuse_file_by_ino(file_context, FUSE_ROOT_ID); + if (root_dir) + ArrayList_Remove(root_dir->children, clip_data_dir); + + clear_context.has_clip_data_id = clip_data_dir->has_clip_data_id; + clear_context.clip_data_id = clip_data_dir->clip_data_id; + } + clear_context.all_files = all_selections; + + if (clip_data_entry && clip_data_entry->has_clip_data_id) + WLog_Print(file_context->log, WLOG_DEBUG, "Clearing selection for clipDataId %u", + clip_data_entry->clip_data_id); + else + WLog_Print(file_context->log, WLOG_DEBUG, "Clearing selection%s", + all_selections ? "s" : ""); + + HashTable_Foreach(file_context->request_table, maybe_clear_fuse_request, &clear_context); + HashTable_Foreach(file_context->inode_table, maybe_steal_inode, &clear_context); + HashTable_Unlock(file_context->inode_table); + + if (file_context->fuse_sess) + { + /* + * fuse_lowlevel_notify_inval_inode() is a blocking operation. If we receive a + * FUSE request (e.g. read()), then FUSE would block in read(), since the + * mutex of the inode_table would still be locked, if we wouldn't unlock it + * here. + * So, to avoid a deadlock here, unlock the mutex and reply all incoming + * operations with -ENOENT until the invalidation process is complete. + */ + ArrayList_ForEach(clear_context.fuse_files, invalidate_inode, file_context); + CliprdrFuseFile* root_dir = get_fuse_file_by_ino(file_context, FUSE_ROOT_ID); + if (clip_data_dir && root_dir) + { + fuse_lowlevel_notify_delete(file_context->fuse_sess, root_dir->ino, clip_data_dir->ino, + clip_data_dir->filename, clip_data_dir->filename_len); + } + } + ArrayList_Free(clear_context.fuse_files); + + HashTable_Lock(file_context->inode_table); + if (clip_data_entry && clip_data_entry->has_clip_data_id) + WLog_Print(file_context->log, WLOG_DEBUG, "Selection cleared for clipDataId %u", + clip_data_entry->clip_data_id); + else + WLog_Print(file_context->log, WLOG_DEBUG, "Selection%s cleared", all_selections ? "s" : ""); +} + +static void clear_entry_selection(CliprdrFuseClipDataEntry* clip_data_entry) +{ + WINPR_ASSERT(clip_data_entry); + + if (!clip_data_entry->clip_data_dir) + return; + + clear_selection(clip_data_entry->file_context, FALSE, clip_data_entry); +} + +static void clear_no_cdi_entry(CliprdrFileContext* file_context) +{ + WINPR_ASSERT(file_context); + + WINPR_ASSERT(file_context->inode_table); + HashTable_Lock(file_context->inode_table); + if (file_context->clip_data_entry_without_id) + { + clear_entry_selection(file_context->clip_data_entry_without_id); + + clip_data_entry_free(file_context->clip_data_entry_without_id); + file_context->clip_data_entry_without_id = nullptr; + } + HashTable_Unlock(file_context->inode_table); +} + +static BOOL clear_clip_data_entries(WINPR_ATTR_UNUSED const void* key, void* value, + WINPR_ATTR_UNUSED void* arg) +{ + clear_entry_selection(value); + + return TRUE; +} + +static void clear_cdi_entries(CliprdrFileContext* file_context) +{ + WINPR_ASSERT(file_context); + + HashTable_Lock(file_context->inode_table); + HashTable_Foreach(file_context->clip_data_table, clear_clip_data_entries, nullptr); + + HashTable_Clear(file_context->clip_data_table); + HashTable_Unlock(file_context->inode_table); +} + +static UINT prepare_clip_data_entry_with_id(CliprdrFileContext* file_context) +{ + CliprdrFuseClipDataEntry* clip_data_entry = nullptr; + + WINPR_ASSERT(file_context); + + clip_data_entry = clip_data_entry_new(file_context, TRUE); + if (!clip_data_entry) + { + WLog_Print(file_context->log, WLOG_ERROR, "Failed to create clipDataEntry"); + return ERROR_INTERNAL_ERROR; + } + + HashTable_Lock(file_context->inode_table); + if (!HashTable_Insert(file_context->clip_data_table, + (void*)(uintptr_t)clip_data_entry->clip_data_id, clip_data_entry)) + { + WLog_Print(file_context->log, WLOG_ERROR, "Failed to insert clipDataEntry"); + clip_data_entry_free(clip_data_entry); + HashTable_Unlock(file_context->inode_table); + return ERROR_INTERNAL_ERROR; + } + HashTable_Unlock(file_context->inode_table); + + // HashTable_Insert owns clip_data_entry + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc) + file_context->current_clip_data_id = clip_data_entry->clip_data_id; + + return CHANNEL_RC_OK; +} + +static UINT prepare_clip_data_entry_without_id(CliprdrFileContext* file_context) +{ + WINPR_ASSERT(file_context); + WINPR_ASSERT(!file_context->clip_data_entry_without_id); + + file_context->clip_data_entry_without_id = clip_data_entry_new(file_context, FALSE); + if (!file_context->clip_data_entry_without_id) + { + WLog_Print(file_context->log, WLOG_ERROR, "Failed to create clipDataEntry"); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} +#endif + +UINT cliprdr_file_context_notify_new_server_format_list(CliprdrFileContext* file_context) +{ + UINT rc = CHANNEL_RC_OK; + WINPR_ASSERT(file_context); + WINPR_ASSERT(file_context->context); + +#if defined(WITH_FUSE) + clear_no_cdi_entry(file_context); + /* TODO: assign timeouts to old locks instead */ + clear_cdi_entries(file_context); + + if (does_server_support_clipdata_locking(file_context)) + rc = prepare_clip_data_entry_with_id(file_context); + else + rc = prepare_clip_data_entry_without_id(file_context); +#endif + return rc; +} + +UINT cliprdr_file_context_notify_new_client_format_list(CliprdrFileContext* file_context) +{ + WINPR_ASSERT(file_context); + WINPR_ASSERT(file_context->context); + +#if defined(WITH_FUSE) + clear_no_cdi_entry(file_context); + /* TODO: assign timeouts to old locks instead */ + clear_cdi_entries(file_context); +#endif + + return CHANNEL_RC_OK; +} + +static CliprdrLocalStream* cliprdr_local_stream_new(CliprdrFileContext* context, UINT32 streamID, + const char* data, size_t size); +static void cliprdr_file_session_terminate(CliprdrFileContext* file, BOOL stop_thread); +static BOOL local_stream_discard(const void* key, void* value, void* arg); + +WINPR_ATTR_FORMAT_ARG(6, 7) +static void writelog(wLog* log, DWORD level, const char* fname, const char* fkt, size_t line, + WINPR_FORMAT_ARG const char* fmt, ...) +{ + if (!WLog_IsLevelActive(log, level)) + return; + + va_list ap = WINPR_C_ARRAY_INIT; + va_start(ap, fmt); + WLog_PrintTextMessageVA(log, level, line, fname, fkt, fmt, ap); + va_end(ap); +} + +#if defined(WITH_FUSE) +static CliprdrFuseFile* fuse_file_new_root(CliprdrFileContext* file_context); +static void cliprdr_file_fuse_lookup(fuse_req_t req, fuse_ino_t parent, const char* name); +static void cliprdr_file_fuse_getattr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi); +static void cliprdr_file_fuse_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, + struct fuse_file_info* fi); +static void cliprdr_file_fuse_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, + struct fuse_file_info* fi); +static void cliprdr_file_fuse_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi); +static void cliprdr_file_fuse_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi); + +static const struct fuse_lowlevel_ops cliprdr_file_fuse_oper = { + .lookup = cliprdr_file_fuse_lookup, + .getattr = cliprdr_file_fuse_getattr, + .readdir = cliprdr_file_fuse_readdir, + .open = cliprdr_file_fuse_open, + .read = cliprdr_file_fuse_read, + .opendir = cliprdr_file_fuse_opendir, +}; + +CliprdrFuseFile* get_fuse_file_by_ino(CliprdrFileContext* file_context, fuse_ino_t fuse_ino) +{ + WINPR_ASSERT(file_context); + + return HashTable_GetItemValue(file_context->inode_table, (void*)(uintptr_t)fuse_ino); +} + +static CliprdrFuseFile* +get_fuse_file_by_name_from_parent(WINPR_ATTR_UNUSED CliprdrFileContext* file_context, + CliprdrFuseFile* parent, const char* name) +{ + WINPR_ASSERT(file_context); + WINPR_ASSERT(parent); + + for (size_t i = 0; i < ArrayList_Count(parent->children); ++i) + { + CliprdrFuseFile* child = ArrayList_GetItem(parent->children, i); + + WINPR_ASSERT(child); + + if (strncmp(name, child->filename, child->filename_len + 1) == 0) + return child; + } + + DEBUG_CLIPRDR(file_context->log, "Requested file \"%s\" in directory \"%s\" does not exist", + name, parent->filename); + + return nullptr; +} + +static CliprdrFuseRequest* cliprdr_fuse_request_new(CliprdrFileContext* file_context, + CliprdrFuseFile* fuse_file, fuse_req_t fuse_req, + FuseLowlevelOperationType operation_type) +{ + CliprdrFuseRequest* fuse_request = nullptr; + UINT32 stream_id = file_context->next_stream_id; + + WINPR_ASSERT(file_context); + WINPR_ASSERT(fuse_file); + + fuse_request = calloc(1, sizeof(CliprdrFuseRequest)); + if (!fuse_request) + { + WLog_Print(file_context->log, WLOG_ERROR, "Failed to allocate FUSE request for file \"%s\"", + fuse_file->filename_with_root); + return nullptr; + } + + fuse_request->fuse_file = fuse_file; + fuse_request->fuse_req = fuse_req; + fuse_request->operation_type = operation_type; + + while (stream_id == 0 || + HashTable_GetItemValue(file_context->request_table, (void*)(uintptr_t)stream_id)) + ++stream_id; + fuse_request->stream_id = stream_id; + + file_context->next_stream_id = stream_id + 1; + + if (!HashTable_Insert(file_context->request_table, (void*)(uintptr_t)fuse_request->stream_id, + fuse_request)) + { + WLog_Print(file_context->log, WLOG_ERROR, "Failed to track FUSE request for file \"%s\"", + fuse_file->filename_with_root); + free(fuse_request); + return nullptr; + } + + return fuse_request; +} + +static BOOL request_file_size_async(CliprdrFileContext* file_context, CliprdrFuseFile* fuse_file, + fuse_req_t fuse_req, FuseLowlevelOperationType operation_type) +{ + CliprdrFuseRequest* fuse_request = nullptr; + CLIPRDR_FILE_CONTENTS_REQUEST file_contents_request = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(file_context); + WINPR_ASSERT(fuse_file); + + fuse_request = cliprdr_fuse_request_new(file_context, fuse_file, fuse_req, operation_type); + if (!fuse_request) + return FALSE; + + file_contents_request.common.msgType = CB_FILECONTENTS_REQUEST; + file_contents_request.streamId = fuse_request->stream_id; + file_contents_request.listIndex = fuse_file->list_idx; + file_contents_request.dwFlags = FILECONTENTS_SIZE; + file_contents_request.cbRequested = 0x8; + file_contents_request.haveClipDataId = fuse_file->has_clip_data_id; + file_contents_request.clipDataId = fuse_file->clip_data_id; + + if (file_context->context->ClientFileContentsRequest(file_context->context, + &file_contents_request)) + { + WLog_Print(file_context->log, WLOG_ERROR, + "Failed to send FileContentsRequest for file \"%s\"", + fuse_file->filename_with_root); + HashTable_Remove(file_context->request_table, (void*)(uintptr_t)fuse_request->stream_id); + return FALSE; + } + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc) + DEBUG_CLIPRDR(file_context->log, "Requested file size for file \"%s\" with stream id %u", + fuse_file->filename, fuse_request->stream_id); + + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc) + return TRUE; +} + +static void write_file_attributes(CliprdrFuseFile* fuse_file, struct stat* attr) +{ + memset(attr, 0, sizeof(struct stat)); + + if (!fuse_file) + return; + + attr->st_ino = fuse_file->ino; + if (fuse_file->is_directory) + { + attr->st_mode = S_IFDIR | (fuse_file->is_readonly ? 0555 : 0755); + attr->st_nlink = 2; + } + else + { + attr->st_mode = S_IFREG | (fuse_file->is_readonly ? 0444 : 0644); + attr->st_nlink = 1; + attr->st_size = WINPR_ASSERTING_INT_CAST(off_t, fuse_file->size); + } + attr->st_uid = getuid(); + attr->st_gid = getgid(); + attr->st_atime = attr->st_mtime = attr->st_ctime = + (fuse_file->has_last_write_time ? fuse_file->last_write_time_unix : time(nullptr)); +} + +static void cliprdr_file_fuse_lookup(fuse_req_t fuse_req, fuse_ino_t parent_ino, const char* name) +{ + CliprdrFileContext* file_context = fuse_req_userdata(fuse_req); + CliprdrFuseFile* parent = nullptr; + CliprdrFuseFile* fuse_file = nullptr; + struct fuse_entry_param entry = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(file_context); + + HashTable_Lock(file_context->inode_table); + if (!(parent = get_fuse_file_by_ino(file_context, parent_ino)) || !parent->is_directory) + { + HashTable_Unlock(file_context->inode_table); + fuse_reply_err(fuse_req, ENOENT); + return; + } + if (!(fuse_file = get_fuse_file_by_name_from_parent(file_context, parent, name))) + { + HashTable_Unlock(file_context->inode_table); + fuse_reply_err(fuse_req, ENOENT); + return; + } + + DEBUG_CLIPRDR(file_context->log, "lookup() has been called for \"%s\"", name); + DEBUG_CLIPRDR(file_context->log, "Parent is \"%s\", child is \"%s\"", + parent->filename_with_root, fuse_file->filename_with_root); + + if (!fuse_file->is_directory && !fuse_file->has_size) + { + BOOL result = 0; + + result = + request_file_size_async(file_context, fuse_file, fuse_req, FUSE_LL_OPERATION_LOOKUP); + HashTable_Unlock(file_context->inode_table); + + if (!result) + fuse_reply_err(fuse_req, EIO); + + return; + } + + entry.ino = fuse_file->ino; + write_file_attributes(fuse_file, &entry.attr); + entry.attr_timeout = 1.0; + entry.entry_timeout = 1.0; + HashTable_Unlock(file_context->inode_table); + + fuse_reply_entry(fuse_req, &entry); +} + +static void cliprdr_file_fuse_getattr(fuse_req_t fuse_req, fuse_ino_t fuse_ino, + WINPR_ATTR_UNUSED struct fuse_file_info* file_info) +{ + CliprdrFileContext* file_context = fuse_req_userdata(fuse_req); + CliprdrFuseFile* fuse_file = nullptr; + struct stat attr = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(file_context); + + HashTable_Lock(file_context->inode_table); + if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino))) + { + HashTable_Unlock(file_context->inode_table); + fuse_reply_err(fuse_req, ENOENT); + return; + } + + DEBUG_CLIPRDR(file_context->log, "getattr() has been called for file \"%s\"", + fuse_file->filename_with_root); + + if (!fuse_file->is_directory && !fuse_file->has_size) + { + BOOL result = 0; + + result = + request_file_size_async(file_context, fuse_file, fuse_req, FUSE_LL_OPERATION_GETATTR); + HashTable_Unlock(file_context->inode_table); + + if (!result) + fuse_reply_err(fuse_req, EIO); + + return; + } + + write_file_attributes(fuse_file, &attr); + HashTable_Unlock(file_context->inode_table); + + fuse_reply_attr(fuse_req, &attr, 1.0); +} + +static void cliprdr_file_fuse_open(fuse_req_t fuse_req, fuse_ino_t fuse_ino, + struct fuse_file_info* file_info) +{ + CliprdrFileContext* file_context = fuse_req_userdata(fuse_req); + CliprdrFuseFile* fuse_file = nullptr; + + WINPR_ASSERT(file_context); + + HashTable_Lock(file_context->inode_table); + if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino))) + { + HashTable_Unlock(file_context->inode_table); + fuse_reply_err(fuse_req, ENOENT); + return; + } + if (fuse_file->is_directory) + { + HashTable_Unlock(file_context->inode_table); + fuse_reply_err(fuse_req, EISDIR); + return; + } + HashTable_Unlock(file_context->inode_table); + + if ((file_info->flags & O_ACCMODE) != O_RDONLY) + { + fuse_reply_err(fuse_req, EACCES); + return; + } + + /* Important for KDE to get file correctly */ + file_info->direct_io = 1; + + fuse_reply_open(fuse_req, file_info); +} + +static BOOL request_file_range_async(CliprdrFileContext* file_context, CliprdrFuseFile* fuse_file, + fuse_req_t fuse_req, off_t offset, size_t requested_size) +{ + CLIPRDR_FILE_CONTENTS_REQUEST file_contents_request = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(file_context); + WINPR_ASSERT(fuse_file); + + if (requested_size > UINT32_MAX) + return FALSE; + + CliprdrFuseRequest* fuse_request = + cliprdr_fuse_request_new(file_context, fuse_file, fuse_req, FUSE_LL_OPERATION_READ); + if (!fuse_request) + return FALSE; + + file_contents_request.common.msgType = CB_FILECONTENTS_REQUEST; + file_contents_request.streamId = fuse_request->stream_id; + file_contents_request.listIndex = fuse_file->list_idx; + file_contents_request.dwFlags = FILECONTENTS_RANGE; + file_contents_request.nPositionLow = (UINT32)(offset & 0xFFFFFFFF); + file_contents_request.nPositionHigh = (UINT32)((offset >> 32) & 0xFFFFFFFF); + file_contents_request.cbRequested = (UINT32)requested_size; + file_contents_request.haveClipDataId = fuse_file->has_clip_data_id; + file_contents_request.clipDataId = fuse_file->clip_data_id; + + if (file_context->context->ClientFileContentsRequest(file_context->context, + &file_contents_request)) + { + WLog_Print(file_context->log, WLOG_ERROR, + "Failed to send FileContentsRequest for file \"%s\"", + fuse_file->filename_with_root); + HashTable_Remove(file_context->request_table, (void*)(uintptr_t)fuse_request->stream_id); + return FALSE; + } + + // file_context->request_table owns fuse_request + // NOLINTBEGIN(clang-analyzer-unix.Malloc) + DEBUG_CLIPRDR( + file_context->log, + "Requested file range (%zu Bytes at offset %lu) for file \"%s\" with stream id %u", + requested_size, offset, fuse_file->filename, fuse_request->stream_id); + + return TRUE; + // NOLINTEND(clang-analyzer-unix.Malloc) +} + +static void cliprdr_file_fuse_read(fuse_req_t fuse_req, fuse_ino_t fuse_ino, size_t size, + off_t offset, WINPR_ATTR_UNUSED struct fuse_file_info* file_info) +{ + CliprdrFileContext* file_context = fuse_req_userdata(fuse_req); + CliprdrFuseFile* fuse_file = nullptr; + BOOL result = 0; + + WINPR_ASSERT(file_context); + + HashTable_Lock(file_context->inode_table); + if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino))) + { + HashTable_Unlock(file_context->inode_table); + fuse_reply_err(fuse_req, ENOENT); + return; + } + if (fuse_file->is_directory) + { + HashTable_Unlock(file_context->inode_table); + fuse_reply_err(fuse_req, EISDIR); + return; + } + if (!fuse_file->has_size || (offset < 0) || ((size_t)offset > fuse_file->size)) + { + HashTable_Unlock(file_context->inode_table); + fuse_reply_err(fuse_req, EINVAL); + return; + } + + size = MIN(size, 8ULL * 1024ULL * 1024ULL); + + result = request_file_range_async(file_context, fuse_file, fuse_req, offset, size); + HashTable_Unlock(file_context->inode_table); + + if (!result) + fuse_reply_err(fuse_req, EIO); +} + +static void cliprdr_file_fuse_opendir(fuse_req_t fuse_req, fuse_ino_t fuse_ino, + struct fuse_file_info* file_info) +{ + CliprdrFileContext* file_context = fuse_req_userdata(fuse_req); + CliprdrFuseFile* fuse_file = nullptr; + + WINPR_ASSERT(file_context); + + HashTable_Lock(file_context->inode_table); + if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino))) + { + HashTable_Unlock(file_context->inode_table); + fuse_reply_err(fuse_req, ENOENT); + return; + } + if (!fuse_file->is_directory) + { + HashTable_Unlock(file_context->inode_table); + fuse_reply_err(fuse_req, ENOTDIR); + return; + } + HashTable_Unlock(file_context->inode_table); + + if ((file_info->flags & O_ACCMODE) != O_RDONLY) + { + fuse_reply_err(fuse_req, EACCES); + return; + } + + fuse_reply_open(fuse_req, file_info); +} + +static void cliprdr_file_fuse_readdir(fuse_req_t fuse_req, fuse_ino_t fuse_ino, size_t max_size, + off_t offset, + WINPR_ATTR_UNUSED struct fuse_file_info* file_info) +{ + CliprdrFileContext* file_context = fuse_req_userdata(fuse_req); + CliprdrFuseFile* fuse_file = nullptr; + CliprdrFuseFile* child = nullptr; + struct stat attr = WINPR_C_ARRAY_INIT; + size_t written_size = 0; + size_t entry_size = 0; + char* filename = nullptr; + char* buf = nullptr; + + WINPR_ASSERT(file_context); + + HashTable_Lock(file_context->inode_table); + if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino))) + { + HashTable_Unlock(file_context->inode_table); + fuse_reply_err(fuse_req, ENOENT); + return; + } + if (!fuse_file->is_directory) + { + HashTable_Unlock(file_context->inode_table); + fuse_reply_err(fuse_req, ENOTDIR); + return; + } + + DEBUG_CLIPRDR(file_context->log, "Reading directory \"%s\" at offset %lu", + fuse_file->filename_with_root, offset); + + if ((offset < 0) || ((size_t)offset >= ArrayList_Count(fuse_file->children))) + { + HashTable_Unlock(file_context->inode_table); + fuse_reply_buf(fuse_req, nullptr, 0); + return; + } + + buf = calloc(max_size, sizeof(char)); + if (!buf) + { + HashTable_Unlock(file_context->inode_table); + fuse_reply_err(fuse_req, ENOMEM); + return; + } + written_size = 0; + + for (off_t i = offset; i < 2; ++i) + { + if (i == 0) + { + write_file_attributes(fuse_file, &attr); + filename = "."; + } + else if (i == 1) + { + write_file_attributes(fuse_file->parent, &attr); + attr.st_ino = fuse_file->parent ? attr.st_ino : FUSE_ROOT_ID; + attr.st_mode = fuse_file->parent ? attr.st_mode : 0555; + filename = ".."; + } + else + { + WINPR_ASSERT(FALSE); + } + + /** + * buf needs to be large enough to hold the entry. If it's not, then the + * entry is not filled in but the size of the entry is still returned. + */ + entry_size = fuse_add_direntry(fuse_req, buf + written_size, max_size - written_size, + filename, &attr, i + 1); + if (entry_size > max_size - written_size) + break; + + written_size += entry_size; + } + + for (size_t j = 0, i = 2; j < ArrayList_Count(fuse_file->children); ++j, ++i) + { + if (i < (size_t)offset) + continue; + + child = ArrayList_GetItem(fuse_file->children, j); + + write_file_attributes(child, &attr); + entry_size = + fuse_add_direntry(fuse_req, buf + written_size, max_size - written_size, + child->filename, &attr, WINPR_ASSERTING_INT_CAST(off_t, i + 1)); + if (entry_size > max_size - written_size) + break; + + written_size += entry_size; + } + HashTable_Unlock(file_context->inode_table); + + fuse_reply_buf(fuse_req, buf, written_size); + free(buf); +} + +static void fuse_abort(int sig, const char* signame, void* context) +{ + CliprdrFileContext* file = (CliprdrFileContext*)context; + + if (file) + { + WLog_Print(file->log, WLOG_INFO, "signal %s [%d] aborting session", signame, sig); + cliprdr_file_session_terminate(file, FALSE); + } +} + +static DWORD WINAPI cliprdr_file_fuse_thread(LPVOID arg) +{ + CliprdrFileContext* file = (CliprdrFileContext*)arg; + + WINPR_ASSERT(file); + + DEBUG_CLIPRDR(file->log, "Starting fuse with mountpoint '%s'", file->path); + + struct fuse_args args = FUSE_ARGS_INIT(0, nullptr); + fuse_opt_add_arg(&args, file->path); + file->fuse_sess = fuse_session_new(&args, &cliprdr_file_fuse_oper, + sizeof(cliprdr_file_fuse_oper), (void*)file); + (void)SetEvent(file->fuse_start_sync); + + if (file->fuse_sess != nullptr) + { + if (freerdp_add_signal_cleanup_handler(file, fuse_abort)) + { + if (0 == fuse_session_mount(file->fuse_sess, file->path)) + { + fuse_session_loop(file->fuse_sess); + fuse_session_unmount(file->fuse_sess); + } + freerdp_del_signal_cleanup_handler(file, fuse_abort); + } + + WLog_Print(file->log, WLOG_DEBUG, "Waiting for FUSE stop sync"); + if (WaitForSingleObject(file->fuse_stop_sync, INFINITE) == WAIT_FAILED) + WLog_Print(file->log, WLOG_ERROR, "Failed to wait for stop sync"); + fuse_session_destroy(file->fuse_sess); + } + fuse_opt_free_args(&args); + + DEBUG_CLIPRDR(file->log, "Quitting fuse with mountpoint '%s'", file->path); + + ExitThread(0); + return 0; +} + +static UINT cliprdr_file_context_server_file_contents_response( + CliprdrClientContext* cliprdr_context, + const CLIPRDR_FILE_CONTENTS_RESPONSE* file_contents_response) +{ + CliprdrFileContext* file_context = nullptr; + CliprdrFuseRequest* fuse_request = nullptr; + struct fuse_entry_param entry = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(cliprdr_context); + WINPR_ASSERT(file_contents_response); + + file_context = cliprdr_context->custom; + WINPR_ASSERT(file_context); + + HashTable_Lock(file_context->inode_table); + fuse_request = HashTable_GetItemValue(file_context->request_table, + (void*)(uintptr_t)file_contents_response->streamId); + if (!fuse_request) + { + HashTable_Unlock(file_context->inode_table); + return CHANNEL_RC_OK; + } + + if (!(file_contents_response->common.msgFlags & CB_RESPONSE_OK)) + { + WLog_Print(file_context->log, WLOG_WARN, + "FileContentsRequests for file \"%s\" was unsuccessful", + fuse_request->fuse_file->filename); + + fuse_reply_err(fuse_request->fuse_req, EIO); + HashTable_Remove(file_context->request_table, + (void*)(uintptr_t)file_contents_response->streamId); + HashTable_Unlock(file_context->inode_table); + return CHANNEL_RC_OK; + } + + if ((fuse_request->operation_type == FUSE_LL_OPERATION_LOOKUP || + fuse_request->operation_type == FUSE_LL_OPERATION_GETATTR) && + file_contents_response->cbRequested != sizeof(UINT64)) + { + WLog_Print(file_context->log, WLOG_WARN, + "Received invalid file size for file \"%s\" from the client", + fuse_request->fuse_file->filename); + fuse_reply_err(fuse_request->fuse_req, EIO); + HashTable_Remove(file_context->request_table, + (void*)(uintptr_t)file_contents_response->streamId); + HashTable_Unlock(file_context->inode_table); + return CHANNEL_RC_OK; + } + + if (fuse_request->operation_type == FUSE_LL_OPERATION_LOOKUP || + fuse_request->operation_type == FUSE_LL_OPERATION_GETATTR) + { + DEBUG_CLIPRDR(file_context->log, "Received file size for file \"%s\" with stream id %u", + fuse_request->fuse_file->filename, file_contents_response->streamId); + + fuse_request->fuse_file->size = *((const UINT64*)file_contents_response->requestedData); + fuse_request->fuse_file->has_size = TRUE; + + entry.ino = fuse_request->fuse_file->ino; + write_file_attributes(fuse_request->fuse_file, &entry.attr); + entry.attr_timeout = 1.0; + entry.entry_timeout = 1.0; + } + else if (fuse_request->operation_type == FUSE_LL_OPERATION_READ) + { + DEBUG_CLIPRDR(file_context->log, "Received file range for file \"%s\" with stream id %u", + fuse_request->fuse_file->filename, file_contents_response->streamId); + } + HashTable_Unlock(file_context->inode_table); + + switch (fuse_request->operation_type) + { + case FUSE_LL_OPERATION_NONE: + break; + case FUSE_LL_OPERATION_LOOKUP: + fuse_reply_entry(fuse_request->fuse_req, &entry); + break; + case FUSE_LL_OPERATION_GETATTR: + fuse_reply_attr(fuse_request->fuse_req, &entry.attr, entry.attr_timeout); + break; + case FUSE_LL_OPERATION_READ: + fuse_reply_buf(fuse_request->fuse_req, + (const char*)file_contents_response->requestedData, + file_contents_response->cbRequested); + break; + default: + break; + } + + HashTable_Remove(file_context->request_table, + (void*)(uintptr_t)file_contents_response->streamId); + + return CHANNEL_RC_OK; +} +#endif + +static UINT cliprdr_file_context_send_file_contents_failure( + CliprdrFileContext* file, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + CLIPRDR_FILE_CONTENTS_RESPONSE response = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(file); + WINPR_ASSERT(fileContentsRequest); + + const UINT64 offset = (((UINT64)fileContentsRequest->nPositionHigh) << 32) | + ((UINT64)fileContentsRequest->nPositionLow); + writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__, + "server file contents request [lockID %" PRIu32 ", streamID %" PRIu32 + ", index %" PRIu32 "] offset %" PRIu64 ", size %" PRIu32 " failed", + fileContentsRequest->clipDataId, fileContentsRequest->streamId, + fileContentsRequest->listIndex, offset, fileContentsRequest->cbRequested); + + response.common.msgFlags = CB_RESPONSE_FAIL; + response.streamId = fileContentsRequest->streamId; + + WINPR_ASSERT(file->context); + WINPR_ASSERT(file->context->ClientFileContentsResponse); + return file->context->ClientFileContentsResponse(file->context, &response); +} + +static UINT +cliprdr_file_context_send_contents_response(CliprdrFileContext* file, + const CLIPRDR_FILE_CONTENTS_REQUEST* request, + const void* data, size_t size) +{ + if (size > UINT32_MAX) + return ERROR_INVALID_PARAMETER; + + CLIPRDR_FILE_CONTENTS_RESPONSE response = { .streamId = request->streamId, + .requestedData = data, + .cbRequested = (UINT32)size, + .common.msgFlags = CB_RESPONSE_OK }; + + WINPR_ASSERT(request); + WINPR_ASSERT(file); + + WLog_Print(file->log, WLOG_DEBUG, "send contents response streamID=%" PRIu32 ", size=%" PRIu32, + response.streamId, response.cbRequested); + WINPR_ASSERT(file->context); + WINPR_ASSERT(file->context->ClientFileContentsResponse); + return file->context->ClientFileContentsResponse(file->context, &response); +} + +static BOOL dump_streams(const void* key, void* value, WINPR_ATTR_UNUSED void* arg) +{ + const UINT32* ukey = key; + CliprdrLocalStream* cur = value; + + writelog(cur->context->log, WLOG_WARN, __FILE__, __func__, __LINE__, + "[key %" PRIu32 "] lockID %" PRIu32 ", count %" PRIuz ", locked %d", *ukey, + cur->lockId, cur->count, cur->locked); + for (size_t x = 0; x < cur->count; x++) + { + const CliprdrLocalFile* file = &cur->files[x]; + writelog(cur->context->log, WLOG_WARN, __FILE__, __func__, __LINE__, + "file [%" PRIuz "] %s: %" PRId64, x, file->name, file->size); + } + return TRUE; +} + +static CliprdrLocalFile* file_info_for_request(CliprdrFileContext* file, UINT32 lockId, + UINT32 listIndex) +{ + WINPR_ASSERT(file); + + CliprdrLocalStream* cur = HashTable_GetItemValue(file->local_streams, &lockId); + if (cur) + { + if (listIndex < cur->count) + { + CliprdrLocalFile* f = &cur->files[listIndex]; + return f; + } + else + { + writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__, + "invalid entry index for lockID %" PRIu32 ", index %" PRIu32 " [count %" PRIuz + "] [locked %d]", + lockId, listIndex, cur->count, cur->locked); + } + } + else + { + writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__, + "missing entry for lockID %" PRIu32 ", index %" PRIu32, lockId, listIndex); + HashTable_Foreach(file->local_streams, dump_streams, file); + } + + return nullptr; +} + +static CliprdrLocalFile* file_for_request(CliprdrFileContext* file, UINT32 lockId, UINT32 listIndex) +{ + CliprdrLocalFile* f = file_info_for_request(file, lockId, listIndex); + if (f) + { + if (!f->fp) + { + const char* name = f->name; + f->fp = winpr_fopen(name, "rb"); + } + if (!f->fp) + { + char ebuffer[256] = WINPR_C_ARRAY_INIT; + writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__, + "[lockID %" PRIu32 ", index %" PRIu32 + "] failed to open file '%s' [size %" PRId64 "] %s [%d]", + lockId, listIndex, f->name, f->size, + winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno); + return nullptr; + } + } + + return f; +} + +static void cliprdr_local_file_try_close(CliprdrLocalFile* file, UINT res, UINT64 offset, + UINT64 size) +{ + WINPR_ASSERT(file); + + if (res != 0) + { + WINPR_ASSERT(file->context); + WLog_Print(file->context->log, WLOG_DEBUG, "closing file %s after error %" PRIu32, + file->name, res); + } + else if (((file->size > 0) && (offset + size >= (UINT64)file->size))) + { + WINPR_ASSERT(file->context); + WLog_Print(file->context->log, WLOG_DEBUG, "closing file %s after read", file->name); + } + else + { + // TODO: we need to keep track of open files to avoid running out of file descriptors + // TODO: for the time being just close again. + } + if (file->fp) + (void)fclose(file->fp); + file->fp = nullptr; +} + +static UINT cliprdr_file_context_server_file_size_request( + CliprdrFileContext* file, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + WINPR_ASSERT(fileContentsRequest); + + if (fileContentsRequest->cbRequested != sizeof(UINT64)) + { + WLog_Print(file->log, WLOG_WARN, "unexpected FILECONTENTS_SIZE request: %" PRIu32 " bytes", + fileContentsRequest->cbRequested); + } + + HashTable_Lock(file->local_streams); + + UINT res = CHANNEL_RC_OK; + CliprdrLocalFile* rfile = + file_for_request(file, fileContentsRequest->clipDataId, fileContentsRequest->listIndex); + if (!rfile) + res = cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest); + else + { + if (_fseeki64(rfile->fp, 0, SEEK_END) < 0) + res = cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest); + else + { + const INT64 size = _ftelli64(rfile->fp); + rfile->size = size; + cliprdr_local_file_try_close(rfile, res, 0, 0); + + res = cliprdr_file_context_send_contents_response(file, fileContentsRequest, &size, + sizeof(size)); + } + } + + HashTable_Unlock(file->local_streams); + return res; +} + +static UINT cliprdr_file_context_server_file_range_request( + CliprdrFileContext* file, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + BYTE* data = nullptr; + + WINPR_ASSERT(fileContentsRequest); + + HashTable_Lock(file->local_streams); + const UINT64 offset = (((UINT64)fileContentsRequest->nPositionHigh) << 32) | + ((UINT64)fileContentsRequest->nPositionLow); + + CliprdrLocalFile* rfile = + file_for_request(file, fileContentsRequest->clipDataId, fileContentsRequest->listIndex); + if (!rfile) + goto fail; + + if (_fseeki64(rfile->fp, WINPR_ASSERTING_INT_CAST(int64_t, offset), SEEK_SET) < 0) + goto fail; + + data = malloc(fileContentsRequest->cbRequested); + if (!data) + goto fail; + + { + const size_t r = fread(data, 1, fileContentsRequest->cbRequested, rfile->fp); + const UINT rc = + cliprdr_file_context_send_contents_response(file, fileContentsRequest, data, r); + free(data); + + cliprdr_local_file_try_close(rfile, rc, offset, fileContentsRequest->cbRequested); + HashTable_Unlock(file->local_streams); + return rc; + } +fail: + if (rfile) + cliprdr_local_file_try_close(rfile, ERROR_INTERNAL_ERROR, offset, + fileContentsRequest->cbRequested); + free(data); + HashTable_Unlock(file->local_streams); + return cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest); +} + +static void cliprdr_local_stream_free(void* obj); + +static UINT change_lock(CliprdrFileContext* file, UINT32 lockId, BOOL lock) +{ + UINT rc = CHANNEL_RC_OK; + + WINPR_ASSERT(file); + + HashTable_Lock(file->local_streams); + + { + CliprdrLocalStream* stream = HashTable_GetItemValue(file->local_streams, &lockId); + if (lock && !stream) + { + stream = cliprdr_local_stream_new(file, lockId, nullptr, 0); + if (!HashTable_Insert(file->local_streams, &lockId, stream)) + { + rc = ERROR_INTERNAL_ERROR; + cliprdr_local_stream_free(stream); + stream = nullptr; + } + file->local_lock_id = lockId; + } + if (stream) + { + stream->locked = lock; + stream->lockId = lockId; + } + } + + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert ownership stream + if (!lock) + { + if (!HashTable_Foreach(file->local_streams, local_stream_discard, file)) + rc = ERROR_INTERNAL_ERROR; + } + + HashTable_Unlock(file->local_streams); + return rc; +} + +static UINT cliprdr_file_context_lock(CliprdrClientContext* context, + const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(lockClipboardData); + CliprdrFileContext* file = (context->custom); + return change_lock(file, lockClipboardData->clipDataId, TRUE); +} + +static UINT cliprdr_file_context_unlock(CliprdrClientContext* context, + const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(unlockClipboardData); + CliprdrFileContext* file = (context->custom); + return change_lock(file, unlockClipboardData->clipDataId, FALSE); +} + +static UINT cliprdr_file_context_server_file_contents_request( + CliprdrClientContext* context, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + UINT error = NO_ERROR; + + WINPR_ASSERT(context); + WINPR_ASSERT(fileContentsRequest); + + CliprdrFileContext* file = (context->custom); + WINPR_ASSERT(file); + + /* + * MS-RDPECLIP 2.2.5.3 File Contents Request PDU (CLIPRDR_FILECONTENTS_REQUEST): + * The FILECONTENTS_SIZE and FILECONTENTS_RANGE flags MUST NOT be set at the same time. + */ + if ((fileContentsRequest->dwFlags & (FILECONTENTS_SIZE | FILECONTENTS_RANGE)) == + (FILECONTENTS_SIZE | FILECONTENTS_RANGE)) + { + WLog_Print(file->log, WLOG_ERROR, "invalid CLIPRDR_FILECONTENTS_REQUEST.dwFlags"); + return cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest); + } + + if (fileContentsRequest->dwFlags & FILECONTENTS_SIZE) + error = cliprdr_file_context_server_file_size_request(file, fileContentsRequest); + + if (fileContentsRequest->dwFlags & FILECONTENTS_RANGE) + error = cliprdr_file_context_server_file_range_request(file, fileContentsRequest); + + if (error) + { + WLog_Print(file->log, WLOG_ERROR, "failed to handle CLIPRDR_FILECONTENTS_REQUEST: 0x%08X", + error); + return cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest); + } + + return CHANNEL_RC_OK; +} + +BOOL cliprdr_file_context_init(CliprdrFileContext* file, CliprdrClientContext* cliprdr) +{ + WINPR_ASSERT(file); + WINPR_ASSERT(cliprdr); + + cliprdr->custom = file; + file->context = cliprdr; + + cliprdr->ServerLockClipboardData = cliprdr_file_context_lock; + cliprdr->ServerUnlockClipboardData = cliprdr_file_context_unlock; + cliprdr->ServerFileContentsRequest = cliprdr_file_context_server_file_contents_request; +#if defined(WITH_FUSE) + cliprdr->ServerFileContentsResponse = cliprdr_file_context_server_file_contents_response; + + CliprdrFuseFile* root_dir = fuse_file_new_root(file); + return root_dir != nullptr; +#else + return TRUE; +#endif +} + +#if defined(WITH_FUSE) +static void clear_all_selections(CliprdrFileContext* file_context) +{ + WINPR_ASSERT(file_context); + WINPR_ASSERT(file_context->inode_table); + + HashTable_Lock(file_context->inode_table); + clear_selection(file_context, TRUE, nullptr); + + HashTable_Clear(file_context->clip_data_table); + HashTable_Unlock(file_context->inode_table); +} +#endif + +BOOL cliprdr_file_context_uninit(CliprdrFileContext* file, CliprdrClientContext* cliprdr) +{ + WINPR_ASSERT(file); + WINPR_ASSERT(cliprdr); + + // Clear all data before the channel is closed + // the cleanup handlers are dependent on a working channel. +#if defined(WITH_FUSE) + if (file->inode_table) + { + clear_no_cdi_entry(file); + clear_all_selections(file); + } +#endif + + HashTable_Clear(file->local_streams); + + file->context = nullptr; + +#if defined(WITH_FUSE) + cliprdr->ServerFileContentsResponse = nullptr; +#endif + + return TRUE; +} + +static BOOL cliprdr_file_content_changed_and_update(void* ihash, size_t hsize, const void* data, + size_t size) +{ + + BYTE hash[WINPR_SHA256_DIGEST_LENGTH] = WINPR_C_ARRAY_INIT; + + if (hsize < sizeof(hash)) + return FALSE; + + if (!winpr_Digest(WINPR_MD_SHA256, data, size, hash, sizeof(hash))) + return FALSE; + + const BOOL changed = memcmp(hash, ihash, sizeof(hash)) != 0; + if (changed) + memcpy(ihash, hash, sizeof(hash)); + return changed; +} + +static BOOL cliprdr_file_client_content_changed_and_update(CliprdrFileContext* file, + const void* data, size_t size) +{ + WINPR_ASSERT(file); + return cliprdr_file_content_changed_and_update(file->client_data_hash, + sizeof(file->client_data_hash), data, size); +} + +#if defined(WITH_FUSE) +static fuse_ino_t get_next_free_inode(CliprdrFileContext* file_context) +{ + fuse_ino_t ino = 0; + + WINPR_ASSERT(file_context); + + ino = file_context->next_ino; + while (ino == 0 || ino == FUSE_ROOT_ID || + HashTable_GetItemValue(file_context->inode_table, (void*)(uintptr_t)ino)) + ++ino; + + file_context->next_ino = ino + 1; + + return ino; +} + +static CliprdrFuseFile* clip_data_dir_new(CliprdrFileContext* file_context, BOOL has_clip_data_id, + UINT32 clip_data_id) +{ + WINPR_ASSERT(file_context); + + UINT64 data_id = clip_data_id; + if (!has_clip_data_id) + data_id = NO_CLIP_DATA_ID; + + CliprdrFuseFile* clip_data_dir = fuse_file_new("/%" PRIu64, data_id); + if (!clip_data_dir) + return nullptr; + + clip_data_dir->ino = get_next_free_inode(file_context); + clip_data_dir->is_directory = TRUE; + clip_data_dir->is_readonly = TRUE; + clip_data_dir->has_clip_data_id = has_clip_data_id; + clip_data_dir->clip_data_id = clip_data_id; + + CliprdrFuseFile* root_dir = get_fuse_file_by_ino(file_context, FUSE_ROOT_ID); + if (!root_dir) + { + WLog_Print(file_context->log, WLOG_ERROR, "FUSE root directory missing"); + fuse_file_free(clip_data_dir); + return nullptr; + } + if (!ArrayList_Append(root_dir->children, clip_data_dir)) + { + WLog_Print(file_context->log, WLOG_ERROR, "Failed to append FUSE file"); + fuse_file_free(clip_data_dir); + return nullptr; + } + clip_data_dir->parent = root_dir; + + if (!HashTable_Insert(file_context->inode_table, (void*)(uintptr_t)clip_data_dir->ino, + clip_data_dir)) + { + WLog_Print(file_context->log, WLOG_ERROR, "Failed to insert inode into inode table"); + ArrayList_Remove(root_dir->children, clip_data_dir); + fuse_file_free(clip_data_dir); + return nullptr; + } + + return clip_data_dir; +} + +static char* get_parent_path(const char* filepath) +{ + const char* base = strrchr(filepath, '/'); + WINPR_ASSERT(base); + + while ((base > filepath) && (*base == '/')) + --base; + + WINPR_ASSERT(base >= filepath); + const size_t parent_path_length = 1ULL + (size_t)(base - filepath); + char* parent_path = calloc(parent_path_length + 1, sizeof(char)); + if (!parent_path) + return nullptr; + + memcpy(parent_path, filepath, parent_path_length); + + return parent_path; +} + +static BOOL is_fuse_file_not_parent(WINPR_ATTR_UNUSED const void* key, void* value, void* arg) +{ + CliprdrFuseFile* fuse_file = value; + CliprdrFuseFindParentContext* find_context = arg; + + if (!fuse_file->is_directory) + return TRUE; + + if (strncmp(find_context->parent_path, fuse_file->filename_with_root, + fuse_file->filename_with_root_len + 1) == 0) + { + find_context->parent = fuse_file; + return FALSE; + } + + return TRUE; +} + +static CliprdrFuseFile* get_parent_directory(CliprdrFileContext* file_context, const char* path) +{ + CliprdrFuseFindParentContext find_context = WINPR_C_ARRAY_INIT; + + WINPR_ASSERT(file_context); + WINPR_ASSERT(path); + + find_context.parent_path = get_parent_path(path); + if (!find_context.parent_path) + return nullptr; + + WINPR_ASSERT(!find_context.parent); + + if (HashTable_Foreach(file_context->inode_table, is_fuse_file_not_parent, &find_context)) + { + free(find_context.parent_path); + return nullptr; + } + WINPR_ASSERT(find_context.parent); + + free(find_context.parent_path); + + return find_context.parent; +} + +// NOLINTBEGIN(clang-analyzer-unix.Malloc) HashTable_Insert owns fuse_file +static BOOL selection_handle_file(CliprdrFileContext* file_context, + CliprdrFuseClipDataEntry* clip_data_entry, uint32_t index, + const FILEDESCRIPTORW* file) +{ + + WINPR_ASSERT(file_context); + WINPR_ASSERT(clip_data_entry); + WINPR_ASSERT(file); + + CliprdrFuseFile* clip_data_dir = clip_data_entry->clip_data_dir; + WINPR_ASSERT(clip_data_dir); + + char filename[ARRAYSIZE(file->cFileName) * 8] = WINPR_C_ARRAY_INIT; + const SSIZE_T filenamelen = ConvertWCharNToUtf8(file->cFileName, ARRAYSIZE(file->cFileName), + filename, ARRAYSIZE(filename) - 1); + if (filenamelen < 0) + { + WLog_Print(file_context->log, WLOG_ERROR, "Failed to convert filename"); + return FALSE; + } + + for (size_t j = 0; filename[j]; ++j) + { + if (filename[j] == '\\') + filename[j] = '/'; + } + + BOOL crc = FALSE; + CliprdrFuseFile* fuse_file = fuse_file_new( + "%.*s/%s", WINPR_ASSERTING_INT_CAST(int, clip_data_dir->filename_with_root_len), + clip_data_dir->filename_with_root, filename); + if (!fuse_file) + { + WLog_Print(file_context->log, WLOG_ERROR, "Failed to create FUSE file"); + goto end; + } + + fuse_file->parent = get_parent_directory(file_context, fuse_file->filename_with_root); + if (!fuse_file->parent) + { + WLog_Print(file_context->log, WLOG_ERROR, "Found no parent for FUSE file"); + goto end; + } + + if (!ArrayList_Append(fuse_file->parent->children, fuse_file)) + { + WLog_Print(file_context->log, WLOG_ERROR, "Failed to append FUSE file"); + goto end; + } + + fuse_file->list_idx = index; + fuse_file->ino = get_next_free_inode(file_context); + fuse_file->has_clip_data_id = clip_data_entry->has_clip_data_id; + fuse_file->clip_data_id = clip_data_entry->clip_data_id; + if (file->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + fuse_file->is_directory = TRUE; + if (file->dwFileAttributes & FILE_ATTRIBUTE_READONLY) + fuse_file->is_readonly = TRUE; + if (file->dwFlags & FD_FILESIZE) + { + fuse_file->size = ((UINT64)file->nFileSizeHigh << 32) + file->nFileSizeLow; + fuse_file->has_size = TRUE; + } + if (file->dwFlags & FD_WRITESTIME) + { + INT64 filetime = 0; + + filetime = file->ftLastWriteTime.dwHighDateTime; + filetime <<= 32; + filetime += file->ftLastWriteTime.dwLowDateTime; + + fuse_file->last_write_time_unix = + 1LL * filetime / (10LL * 1000LL * 1000LL) - WIN32_FILETIME_TO_UNIX_EPOCH; + fuse_file->has_last_write_time = TRUE; + } + + if (!HashTable_Insert(file_context->inode_table, (void*)(uintptr_t)fuse_file->ino, fuse_file)) + { + WLog_Print(file_context->log, WLOG_ERROR, "Failed to insert inode into inode table"); + goto end; + } + + crc = TRUE; + +end: + if (!crc) + { + fuse_file_free(fuse_file); + clear_entry_selection(clip_data_entry); + return FALSE; + } + return TRUE; +} +// NOLINTEND(clang-analyzer-unix.Malloc) HashTable_Insert owns fuse_file + +static BOOL set_selection_for_clip_data_entry(CliprdrFileContext* file_context, + CliprdrFuseClipDataEntry* clip_data_entry, + const FILEDESCRIPTORW* files, UINT32 n_files) +{ + WINPR_ASSERT(file_context); + WINPR_ASSERT(clip_data_entry); + WINPR_ASSERT(files); + + if (clip_data_entry->has_clip_data_id) + WLog_Print(file_context->log, WLOG_DEBUG, "Setting selection for clipDataId %u", + clip_data_entry->clip_data_id); + else + WLog_Print(file_context->log, WLOG_DEBUG, "Setting selection"); + + for (UINT32 i = 0; i < n_files; ++i) + { + const FILEDESCRIPTORW* file = &files[i]; + if (!selection_handle_file(file_context, clip_data_entry, i, file)) + return FALSE; + } + + if (clip_data_entry->has_clip_data_id) + WLog_Print(file_context->log, WLOG_DEBUG, "Selection set for clipDataId %u", + clip_data_entry->clip_data_id); + else + WLog_Print(file_context->log, WLOG_DEBUG, "Selection set"); + + return TRUE; +} + +static BOOL update_exposed_path(CliprdrFileContext* file_context, wClipboard* clip, + CliprdrFuseClipDataEntry* clip_data_entry) +{ + wClipboardDelegate* delegate = nullptr; + CliprdrFuseFile* clip_data_dir = nullptr; + + WINPR_ASSERT(file_context); + WINPR_ASSERT(clip); + WINPR_ASSERT(clip_data_entry); + + delegate = ClipboardGetDelegate(clip); + WINPR_ASSERT(delegate); + + clip_data_dir = clip_data_entry->clip_data_dir; + WINPR_ASSERT(clip_data_dir); + + free(file_context->exposed_path); + file_context->exposed_path = GetCombinedPath(file_context->path, clip_data_dir->filename); + if (file_context->exposed_path) + WLog_Print(file_context->log, WLOG_DEBUG, "Updated exposed path to \"%s\"", + file_context->exposed_path); + + delegate->basePath = file_context->exposed_path; + + return delegate->basePath != nullptr; +} +#endif + +BOOL cliprdr_file_context_update_server_data(CliprdrFileContext* file_context, wClipboard* clip, + const void* data, size_t size) +{ +#if defined(WITH_FUSE) + CliprdrFuseClipDataEntry* clip_data_entry = nullptr; + FILEDESCRIPTORW* files = nullptr; + UINT32 n_files = 0; + BOOL rc = FALSE; + + WINPR_ASSERT(file_context); + WINPR_ASSERT(clip); + if (size > UINT32_MAX) + return FALSE; + + if (cliprdr_parse_file_list(data, (UINT32)size, &files, &n_files)) + { + WLog_Print(file_context->log, WLOG_ERROR, "Failed to parse file list"); + return FALSE; + } + + HashTable_Lock(file_context->inode_table); + HashTable_Lock(file_context->clip_data_table); + if (does_server_support_clipdata_locking(file_context)) + clip_data_entry = HashTable_GetItemValue( + file_context->clip_data_table, (void*)(uintptr_t)file_context->current_clip_data_id); + else + clip_data_entry = file_context->clip_data_entry_without_id; + + WINPR_ASSERT(clip_data_entry); + + clear_entry_selection(clip_data_entry); + WINPR_ASSERT(!clip_data_entry->clip_data_dir); + + clip_data_entry->clip_data_dir = + clip_data_dir_new(file_context, does_server_support_clipdata_locking(file_context), + file_context->current_clip_data_id); + if (!clip_data_entry->clip_data_dir) + goto fail; + if (!update_exposed_path(file_context, clip, clip_data_entry)) + goto fail; + if (!set_selection_for_clip_data_entry(file_context, clip_data_entry, files, n_files)) + goto fail; + + rc = TRUE; + +fail: + HashTable_Unlock(file_context->clip_data_table); + HashTable_Unlock(file_context->inode_table); + free(files); + return rc; +#else + return FALSE; +#endif +} + +void* cliprdr_file_context_get_context(CliprdrFileContext* file) +{ + WINPR_ASSERT(file); + return file->clipboard; +} + +void cliprdr_file_session_terminate(CliprdrFileContext* file, BOOL stop_thread) +{ + if (!file) + return; + +#if defined(WITH_FUSE) + WINPR_ASSERT(file->fuse_stop_sync); + + WLog_Print(file->log, WLOG_DEBUG, "Setting FUSE exit flag"); + if (file->fuse_sess) + fuse_session_exit(file->fuse_sess); + + if (stop_thread) + { + WLog_Print(file->log, WLOG_DEBUG, "Setting FUSE stop event"); + (void)SetEvent(file->fuse_stop_sync); + } +#endif + /* not elegant but works for umounting FUSE + fuse_chan must receive an oper buf to unblock fuse_session_receive_buf function. + */ +#if defined(WITH_FUSE) + WLog_Print(file->log, WLOG_DEBUG, "Forcing FUSE to check exit flag"); +#endif + (void)winpr_PathFileExists(file->path); +} + +void cliprdr_file_context_free(CliprdrFileContext* file) +{ + if (!file) + return; + +#if defined(WITH_FUSE) + if (file->fuse_thread) + { + WINPR_ASSERT(file->fuse_stop_sync); + + WLog_Print(file->log, WLOG_DEBUG, "Stopping FUSE thread"); + cliprdr_file_session_terminate(file, TRUE); + + WLog_Print(file->log, WLOG_DEBUG, "Waiting on FUSE thread"); + (void)WaitForSingleObject(file->fuse_thread, INFINITE); + (void)CloseHandle(file->fuse_thread); + } + if (file->fuse_stop_sync) + (void)CloseHandle(file->fuse_stop_sync); + if (file->fuse_start_sync) + (void)CloseHandle(file->fuse_start_sync); + + HashTable_Free(file->request_table); + HashTable_Free(file->clip_data_table); + HashTable_Free(file->inode_table); + +#endif + HashTable_Free(file->local_streams); + winpr_RemoveDirectory(file->path); + free(file->path); + free(file->exposed_path); + free(file); +} + +static BOOL create_base_path(CliprdrFileContext* file) +{ + WINPR_ASSERT(file); + + char base[64] = WINPR_C_ARRAY_INIT; + (void)_snprintf(base, sizeof(base), "com.freerdp.client.cliprdr.%" PRIu32, + GetCurrentProcessId()); + + file->path = GetKnownSubPath(KNOWN_PATH_TEMP, base); + if (!file->path) + return FALSE; + + if (!winpr_PathFileExists(file->path) && !winpr_PathMakePath(file->path, nullptr)) + { + WLog_Print(file->log, WLOG_ERROR, "Failed to create directory '%s'", file->path); + return FALSE; + } + return TRUE; +} + +static void cliprdr_local_file_free(CliprdrLocalFile* file) +{ + const CliprdrLocalFile empty = WINPR_C_ARRAY_INIT; + if (!file) + return; + if (file->fp) + { + WLog_Print(file->context->log, WLOG_DEBUG, "closing file %s, discarding entry", file->name); + (void)fclose(file->fp); + } + free(file->name); + *file = empty; +} + +static BOOL cliprdr_local_file_new(CliprdrFileContext* context, CliprdrLocalFile* f, + const char* path) +{ + const CliprdrLocalFile empty = WINPR_C_ARRAY_INIT; + WINPR_ASSERT(f); + WINPR_ASSERT(context); + WINPR_ASSERT(path); + + *f = empty; + f->context = context; + f->name = winpr_str_url_decode(path, strlen(path)); + if (!f->name) + goto fail; + + return TRUE; +fail: + cliprdr_local_file_free(f); + return FALSE; +} + +static void cliprdr_local_files_free(CliprdrLocalStream* stream) +{ + WINPR_ASSERT(stream); + + for (size_t x = 0; x < stream->count; x++) + cliprdr_local_file_free(&stream->files[x]); + free(stream->files); + + stream->files = nullptr; + stream->count = 0; +} + +static void cliprdr_local_stream_free(void* obj) +{ + CliprdrLocalStream* stream = (CliprdrLocalStream*)obj; + if (stream) + cliprdr_local_files_free(stream); + + free(stream); +} + +static BOOL append_entry(CliprdrLocalStream* stream, const char* path) +{ + CliprdrLocalFile* tmp = realloc(stream->files, sizeof(CliprdrLocalFile) * (stream->count + 1)); + if (!tmp) + return FALSE; + stream->files = tmp; + CliprdrLocalFile* f = &stream->files[stream->count++]; + + return cliprdr_local_file_new(stream->context, f, path); +} + +static BOOL is_directory(const char* path) +{ + WCHAR* wpath = ConvertUtf8ToWCharAlloc(path, nullptr); + if (!wpath) + return FALSE; + + HANDLE hFile = CreateFileW(wpath, 0, FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, nullptr); + free(wpath); + + if (hFile == INVALID_HANDLE_VALUE) + return FALSE; + + BY_HANDLE_FILE_INFORMATION fileInformation = WINPR_C_ARRAY_INIT; + const BOOL status = GetFileInformationByHandle(hFile, &fileInformation); + (void)CloseHandle(hFile); + if (!status) + return FALSE; + + return (fileInformation.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; +} + +static BOOL add_directory(CliprdrLocalStream* stream, const char* path) +{ + char* wildcardpath = GetCombinedPath(path, "*"); + if (!wildcardpath) + return FALSE; + WCHAR* wpath = ConvertUtf8ToWCharAlloc(wildcardpath, nullptr); + free(wildcardpath); + if (!wpath) + return FALSE; + + WIN32_FIND_DATAW FindFileData = WINPR_C_ARRAY_INIT; + HANDLE hFind = FindFirstFileW(wpath, &FindFileData); + free(wpath); + + if (hFind == INVALID_HANDLE_VALUE) + return FALSE; + + BOOL rc = FALSE; + char* next = nullptr; + + WCHAR dotbuffer[6] = WINPR_C_ARRAY_INIT; + WCHAR dotdotbuffer[6] = WINPR_C_ARRAY_INIT; + const WCHAR* dot = InitializeConstWCharFromUtf8(".", dotbuffer, ARRAYSIZE(dotbuffer)); + const WCHAR* dotdot = InitializeConstWCharFromUtf8("..", dotdotbuffer, ARRAYSIZE(dotdotbuffer)); + do + { + if (_wcscmp(FindFileData.cFileName, dot) == 0) + continue; + if (_wcscmp(FindFileData.cFileName, dotdot) == 0) + continue; + + char cFileName[MAX_PATH] = WINPR_C_ARRAY_INIT; + (void)ConvertWCharNToUtf8(FindFileData.cFileName, ARRAYSIZE(FindFileData.cFileName), + cFileName, ARRAYSIZE(cFileName)); + + free(next); + next = GetCombinedPath(path, cFileName); + if (!next) + goto fail; + + if (!append_entry(stream, next)) + goto fail; + if (is_directory(next)) + { + if (!add_directory(stream, next)) + goto fail; + } + } while (FindNextFileW(hFind, &FindFileData)); + + rc = TRUE; +fail: + free(next); + FindClose(hFind); + + return rc; +} + +static BOOL cliprdr_local_stream_update(CliprdrLocalStream* stream, const char* data, size_t size) +{ + BOOL rc = FALSE; + WINPR_ASSERT(stream); + if (size == 0) + return TRUE; + + cliprdr_local_files_free(stream); + + stream->files = calloc(size, sizeof(CliprdrLocalFile)); + if (!stream->files) + return FALSE; + + char* copy = strndup(data, size); + if (!copy) + return FALSE; + + char* saveptr = nullptr; + char* ptr = strtok_s(copy, "\r\n", &saveptr); + while (ptr) + { + const char* name = ptr; + if (strncmp("file:///", ptr, 8) == 0) + name = &ptr[7]; + else if (strncmp("file:/", ptr, 6) == 0) + name = &ptr[5]; + + if (!append_entry(stream, name)) + goto fail; + + if (is_directory(name)) + { + const BOOL res = add_directory(stream, name); + if (!res) + goto fail; + } + ptr = strtok_s(nullptr, "\r\n", &saveptr); + } + + rc = TRUE; +fail: + free(copy); + return rc; +} + +CliprdrLocalStream* cliprdr_local_stream_new(CliprdrFileContext* context, UINT32 streamID, + const char* data, size_t size) +{ + WINPR_ASSERT(context); + CliprdrLocalStream* stream = calloc(1, sizeof(CliprdrLocalStream)); + if (!stream) + return nullptr; + + stream->context = context; + if (!cliprdr_local_stream_update(stream, data, size)) + goto fail; + + stream->lockId = streamID; + return stream; + +fail: + cliprdr_local_stream_free(stream); + return nullptr; +} + +static UINT32 UINTPointerHash(const void* id) +{ + WINPR_ASSERT(id); + return *((const UINT32*)id); +} + +static BOOL UINTPointerCompare(const void* pointer1, const void* pointer2) +{ + if (!pointer1 || !pointer2) + return pointer1 == pointer2; + + const UINT32* a = pointer1; + const UINT32* b = pointer2; + return *a == *b; +} + +static void* UINTPointerClone(const void* other) +{ + const UINT32* src = other; + if (!src) + return nullptr; + + UINT32* copy = calloc(1, sizeof(UINT32)); + if (!copy) + return nullptr; + + *copy = *src; + return copy; +} + +#if defined(WITH_FUSE) +CliprdrFuseFile* fuse_file_new_root(CliprdrFileContext* file_context) +{ + CliprdrFuseFile* root_dir = fuse_file_new("/"); + if (!root_dir) + return nullptr; + + root_dir->ino = FUSE_ROOT_ID; + root_dir->is_directory = TRUE; + root_dir->is_readonly = TRUE; + + if (!HashTable_Insert(file_context->inode_table, (void*)(uintptr_t)root_dir->ino, root_dir)) + { + fuse_file_free(root_dir); + return nullptr; + } + + return root_dir; +} +#endif + +CliprdrFileContext* cliprdr_file_context_new(void* context) +{ + CliprdrFileContext* file = calloc(1, sizeof(CliprdrFileContext)); + if (!file) + return nullptr; + + file->log = WLog_Get(CLIENT_TAG("common.cliprdr.file")); + file->clipboard = context; + + file->local_streams = HashTable_New(FALSE); + if (!file->local_streams) + goto fail; + + if (!HashTable_SetHashFunction(file->local_streams, UINTPointerHash)) + goto fail; + + { + wObject* hkobj = HashTable_KeyObject(file->local_streams); + WINPR_ASSERT(hkobj); + hkobj->fnObjectEquals = UINTPointerCompare; + hkobj->fnObjectFree = free; + hkobj->fnObjectNew = UINTPointerClone; + } + + { + wObject* hobj = HashTable_ValueObject(file->local_streams); + WINPR_ASSERT(hobj); + hobj->fnObjectFree = cliprdr_local_stream_free; + } + +#if defined(WITH_FUSE) + file->inode_table = HashTable_New(FALSE); + file->clip_data_table = HashTable_New(FALSE); + file->request_table = HashTable_New(FALSE); + if (!file->inode_table || !file->clip_data_table || !file->request_table) + goto fail; + + { + wObject* ctobj = HashTable_ValueObject(file->request_table); + WINPR_ASSERT(ctobj); + ctobj->fnObjectFree = free; + } + { + wObject* ctobj = HashTable_ValueObject(file->clip_data_table); + WINPR_ASSERT(ctobj); + ctobj->fnObjectFree = clip_data_entry_free; + } +#endif + + if (!create_base_path(file)) + goto fail; + +#if defined(WITH_FUSE) + if (!(file->fuse_start_sync = CreateEvent(nullptr, TRUE, FALSE, nullptr))) + goto fail; + if (!(file->fuse_stop_sync = CreateEvent(nullptr, TRUE, FALSE, nullptr))) + goto fail; + if (!(file->fuse_thread = CreateThread(nullptr, 0, cliprdr_file_fuse_thread, file, 0, nullptr))) + goto fail; + + if (WaitForSingleObject(file->fuse_start_sync, INFINITE) == WAIT_FAILED) + WLog_Print(file->log, WLOG_ERROR, "Failed to wait for start sync"); +#endif + return file; + +fail: + WINPR_PRAGMA_DIAG_PUSH + WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC + cliprdr_file_context_free(file); + WINPR_PRAGMA_DIAG_POP + return nullptr; +} + +BOOL local_stream_discard(const void* key, void* value, void* arg) +{ + CliprdrFileContext* file = arg; + CliprdrLocalStream* stream = value; + WINPR_ASSERT(file); + WINPR_ASSERT(stream); + + if (!stream->locked) + HashTable_Remove(file->local_streams, key); + return TRUE; +} + +BOOL cliprdr_file_context_clear(CliprdrFileContext* file) +{ + WINPR_ASSERT(file); + + WLog_Print(file->log, WLOG_DEBUG, "clear file clipboard..."); + + HashTable_Lock(file->local_streams); + HashTable_Foreach(file->local_streams, local_stream_discard, file); + HashTable_Unlock(file->local_streams); + + memset(file->server_data_hash, 0, sizeof(file->server_data_hash)); + memset(file->client_data_hash, 0, sizeof(file->client_data_hash)); + return TRUE; +} + +BOOL cliprdr_file_context_update_client_data(CliprdrFileContext* file, const char* data, + size_t size) +{ + BOOL rc = FALSE; + + WINPR_ASSERT(file); + if (!cliprdr_file_client_content_changed_and_update(file, data, size)) + return TRUE; + + if (!cliprdr_file_context_clear(file)) + return FALSE; + + UINT32 lockId = file->local_lock_id; + + HashTable_Lock(file->local_streams); + CliprdrLocalStream* stream = HashTable_GetItemValue(file->local_streams, &lockId); + + WLog_Print(file->log, WLOG_DEBUG, "update client file list (stream=%p)...", (void*)stream); + if (stream) + rc = cliprdr_local_stream_update(stream, data, size); + else + { + stream = cliprdr_local_stream_new(file, lockId, data, size); + rc = HashTable_Insert(file->local_streams, &stream->lockId, stream); + if (!rc) + cliprdr_local_stream_free(stream); + } + // HashTable_Insert owns stream + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc) + HashTable_Unlock(file->local_streams); + return rc; +} + +UINT32 cliprdr_file_context_current_flags(CliprdrFileContext* file) +{ + WINPR_ASSERT(file); + + if ((file->file_capability_flags & CB_STREAM_FILECLIP_ENABLED) == 0) + return 0; + + if (!file->file_formats_registered) + return 0; + + return CB_STREAM_FILECLIP_ENABLED | CB_FILECLIP_NO_FILE_PATHS | + CB_HUGE_FILE_SUPPORT_ENABLED; // | CB_CAN_LOCK_CLIPDATA; +} + +BOOL cliprdr_file_context_set_locally_available(CliprdrFileContext* file, BOOL available) +{ + WINPR_ASSERT(file); + file->file_formats_registered = available; + return TRUE; +} + +BOOL cliprdr_file_context_remote_set_flags(CliprdrFileContext* file, UINT32 flags) +{ + WINPR_ASSERT(file); + file->file_capability_flags = flags; + return TRUE; +} + +UINT32 cliprdr_file_context_remote_get_flags(CliprdrFileContext* file) +{ + WINPR_ASSERT(file); + return file->file_capability_flags; +} + +BOOL cliprdr_file_context_has_local_support(CliprdrFileContext* file) +{ + WINPR_UNUSED(file); + +#if defined(WITH_FUSE) + return TRUE; +#else + return FALSE; +#endif +} diff --git a/third_party/FreeRDP/client/common/cmdline.c b/third_party/FreeRDP/client/common/cmdline.c new file mode 100644 index 0000000..941cc9d --- /dev/null +++ b/third_party/FreeRDP/client/common/cmdline.c @@ -0,0 +1,6386 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Client Command-Line Interface + * + * Copyright 2012 Marc-Andre Moreau + * Copyright 2014 Norbert Federa + * Copyright 2016 Armin Novak + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(CHANNEL_AINPUT_CLIENT) +#include +#endif + +#include +#include + +#include +#include +#include + +#include +#include "cmdline.h" + +#include +#define TAG CLIENT_TAG("common.cmdline") + +static const char str_force[] = "force"; + +static const char* credential_args[] = { "p", "smartcard-logon", +#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE) + "gp", "gat", +#endif + "pth", "reconnect-cookie", + "assistance" }; + +static const char* option_starts_with(const char* what, const char* val); +static BOOL option_ends_with(const char* str, const char* ext); +static BOOL option_equals(const char* what, const char* val); + +static BOOL freerdp_client_print_codepages(const char* arg) +{ + size_t count = 0; + DWORD column = 2; + const char* filter = nullptr; + RDP_CODEPAGE* pages = nullptr; + + if (arg) + { + filter = strchr(arg, ','); + if (!filter) + filter = arg; + else + filter++; + } + pages = freerdp_keyboard_get_matching_codepages(column, filter, &count); + if (!pages) + return TRUE; + + printf("%-10s %-8s %-60s %-36s %-48s\n", "", "", "", "", + ""); + for (size_t x = 0; x < count; x++) + { + const RDP_CODEPAGE* page = &pages[x]; + char buffer[2048] = WINPR_C_ARRAY_INIT; + + if (strnlen(page->subLanguageSymbol, ARRAYSIZE(page->subLanguageSymbol)) > 0) + (void)_snprintf(buffer, sizeof(buffer), "[%s|%s]", page->primaryLanguageSymbol, + page->subLanguageSymbol); + else + (void)_snprintf(buffer, sizeof(buffer), "[%s]", page->primaryLanguageSymbol); + printf("id=0x%04" PRIx16 ": [%-6s] %-60s %-36s %-48s\n", page->id, page->locale, buffer, + page->primaryLanguage, page->subLanguage); + } + freerdp_codepages_free(pages); + return TRUE; +} + +static BOOL freerdp_path_valid(const char* path, BOOL* special) +{ + const char DynamicDrives[] = "DynamicDrives"; + BOOL isPath = FALSE; + BOOL isSpecial = 0; + if (!path) + return FALSE; + + isSpecial = (option_equals("*", path) || option_equals(DynamicDrives, path) || + option_equals("%", path)); + if (!isSpecial) + isPath = winpr_PathFileExists(path); + + if (special) + *special = isSpecial; + + return isSpecial || isPath; +} + +static BOOL freerdp_sanitize_drive_name(char* name, const char* invalid, const char* replacement) +{ + if (!name || !invalid || !replacement) + return FALSE; + if (strlen(invalid) != strlen(replacement)) + return FALSE; + + while (*invalid != '\0') + { + const char what = *invalid++; + const char with = *replacement++; + + char* cur = name; + while ((cur = strchr(cur, what)) != nullptr) + *cur = with; + } + return TRUE; +} + +static char* name_from_path(const char* path) +{ + const char* name = "nullptr"; + if (path) + { + if (option_equals("%", path)) + name = "home"; + else if (option_equals("*", path)) + name = "hotplug-all"; + else if (option_equals("DynamicDrives", path)) + name = "hotplug"; + else + name = path; + } + return _strdup(name); +} + +static BOOL freerdp_client_add_drive(rdpSettings* settings, const char* path, const char* name) +{ + char* dname = nullptr; + RDPDR_DEVICE* device = nullptr; + + if (name) + { + BOOL skip = FALSE; + if (path) + { + switch (path[0]) + { + case '*': + case '%': + skip = TRUE; + break; + default: + break; + } + } + /* Path was entered as secondary argument, swap */ + if (!skip && winpr_PathFileExists(name)) + { + if (!winpr_PathFileExists(path) || (!PathIsRelativeA(name) && PathIsRelativeA(path))) + { + const char* tmp = path; + path = name; + name = tmp; + } + } + } + + if (name) + dname = _strdup(name); + else /* We need a name to send to the server. */ + dname = name_from_path(path); + + if (freerdp_sanitize_drive_name(dname, "\\/", "__")) + { + const char* args[] = { dname, path }; + device = freerdp_device_new(RDPDR_DTYP_FILESYSTEM, ARRAYSIZE(args), args); + } + free(dname); + if (!device) + goto fail; + + if (!path) + goto fail; + + { + BOOL isSpecial = FALSE; + BOOL isPath = freerdp_path_valid(path, &isSpecial); + + if (!isPath && !isSpecial) + { + WLog_WARN(TAG, "Invalid drive to redirect: '%s' does not exist, skipping.", path); + freerdp_device_free(device); + } + else if (!freerdp_device_collection_add(settings, device)) + goto fail; + } + + return TRUE; + +fail: + freerdp_device_free(device); + return FALSE; +} + +static BOOL value_to_int(const char* value, LONGLONG* result, LONGLONG min, LONGLONG max) +{ + long long rc = 0; + + if (!value || !result) + return FALSE; + + errno = 0; + rc = _strtoi64(value, nullptr, 0); + + if (errno != 0) + return FALSE; + + if ((rc < min) || (rc > max)) + return FALSE; + + *result = rc; + return TRUE; +} + +static BOOL value_to_uint(const char* value, ULONGLONG* result, ULONGLONG min, ULONGLONG max) +{ + unsigned long long rc = 0; + + if (!value || !result) + return FALSE; + + errno = 0; + rc = _strtoui64(value, nullptr, 0); + + if (errno != 0) + return FALSE; + + if ((rc < min) || (rc > max)) + return FALSE; + + *result = rc; + return TRUE; +} + +BOOL freerdp_client_print_version(void) +{ + printf("This is FreeRDP version %s (%s)\n", FREERDP_VERSION_FULL, FREERDP_GIT_REVISION); + return TRUE; +} + +BOOL freerdp_client_print_version_ex(int argc, char** argv) +{ + WINPR_ASSERT(argc >= 0); + WINPR_ASSERT(argv || (argc == 0)); + const char* name = (argc > 0) ? argv[0] : "argc < 1"; + printf("This is FreeRDP version [%s] %s (%s)\n", name, FREERDP_VERSION_FULL, + FREERDP_GIT_REVISION); + return TRUE; +} + +BOOL freerdp_client_print_buildconfig(void) +{ + printf("%s", freerdp_get_build_config()); + return TRUE; +} + +BOOL freerdp_client_print_buildconfig_ex(int argc, char** argv) +{ + WINPR_ASSERT(argc >= 0); + WINPR_ASSERT(argv || (argc == 0)); + const char* name = (argc > 0) ? argv[0] : "argc < 1"; + printf("[%s] %s", name, freerdp_get_build_config()); + return TRUE; +} + +static void freerdp_client_print_scancodes(void) +{ + printf("RDP scancodes and their name for use with /kbd:remap\n"); + + for (UINT32 x = 0; x < UINT16_MAX; x++) + { + const char* name = freerdp_keyboard_scancode_name(x); + if (name) + printf("0x%04" PRIx32 " --> %s\n", x, name); + } +} + +static BOOL is_delimiter(char c, const char* delimiters) +{ + char d = 0; + while ((d = *delimiters++) != '\0') + { + if (c == d) + return TRUE; + } + return FALSE; +} + +static const char* get_last(const char* start, size_t len, const char* delimiters) +{ + const char* last = nullptr; + for (size_t x = 0; x < len; x++) + { + char c = start[x]; + if (is_delimiter(c, delimiters)) + last = &start[x]; + } + return last; +} + +static SSIZE_T next_delimiter(const char* text, size_t len, size_t max, const char* delimiters) +{ + if (len < max) + return -1; + + const char* last = get_last(text, max, delimiters); + if (!last) + return -1; + + return (SSIZE_T)(last - text); +} + +static SSIZE_T forced_newline_at(const char* text, size_t len, size_t limit, + const char* force_newline) +{ + char d = 0; + while ((d = *force_newline++) != '\0') + { + const char* tok = strchr(text, d); + if (tok) + { + const size_t offset = WINPR_ASSERTING_INT_CAST(size_t, tok - text); + if ((offset > len) || (offset > limit)) + continue; + return (SSIZE_T)(offset); + } + } + return -1; +} + +static BOOL print_align(size_t start_offset, size_t* current) +{ + WINPR_ASSERT(current); + if (*current < start_offset) + { + const int rc = printf("%*c", (int)(start_offset - *current), ' '); + if (rc < 0) + return FALSE; + *current += (size_t)rc; + } + return TRUE; +} + +static char* print_token(char* text, size_t start_offset, size_t* current, size_t limit, + const char* delimiters, const char* force_newline) +{ + int rc = 0; + const size_t tlen = strnlen(text, limit); + size_t len = tlen; + const SSIZE_T force_at = forced_newline_at(text, len, limit - *current, force_newline); + BOOL isForce = (force_at >= 0); + + if (isForce) + len = MIN(len, (size_t)force_at); + + if (!print_align(start_offset, current)) + return nullptr; + + const SSIZE_T delim = next_delimiter(text, len, limit - *current, delimiters); + const BOOL isDelim = delim > 0; + if (isDelim) + { + len = MIN(len, (size_t)delim + 1); + } + + rc = printf("%.*s", (int)len, text); + if (rc < 0) + return nullptr; + + if (isForce || isDelim) + { + printf("\n"); + *current = 0; + + const size_t offset = len + ((isForce && (force_at == 0)) ? 1 : 0); + return &text[offset]; + } + + *current += (size_t)rc; + + if (tlen == (size_t)rc) + return nullptr; + return &text[(size_t)rc]; +} + +static size_t print_optionals(const char* text, size_t start_offset, size_t current) +{ + const size_t limit = 80; + char* str = _strdup(text); + char* cur = str; + + do + { + cur = print_token(cur, start_offset + 1, ¤t, limit, "[], ", "\r\n"); + } while (cur != nullptr); + + free(str); + return current; +} + +static size_t print_description(const char* text, size_t start_offset, size_t current) +{ + const size_t limit = 80; + char* str = _strdup(text); + char* cur = str; + + while (cur != nullptr) + cur = print_token(cur, start_offset, ¤t, limit, " ", "\r\n"); + + free(str); + const int rc = printf("\n"); + if (rc >= 0) + { + const size_t src = WINPR_ASSERTING_INT_CAST(size_t, rc); + WINPR_ASSERT(SIZE_MAX - src > current); + current += src; + } + return current; +} + +static int cmp_cmdline_args(const void* pva, const void* pvb) +{ + const COMMAND_LINE_ARGUMENT_A* a = (const COMMAND_LINE_ARGUMENT_A*)pva; + const COMMAND_LINE_ARGUMENT_A* b = (const COMMAND_LINE_ARGUMENT_A*)pvb; + + if (!a->Name && !b->Name) + return 0; + if (!a->Name) + return 1; + if (!b->Name) + return -1; + return strcmp(a->Name, b->Name); +} + +static void freerdp_client_print_command_line_args(COMMAND_LINE_ARGUMENT_A* parg, size_t count) +{ + if (!parg) + return; + + qsort(parg, count, sizeof(COMMAND_LINE_ARGUMENT_A), cmp_cmdline_args); + + const COMMAND_LINE_ARGUMENT_A* arg = parg; + do + { + int rc = 0; + size_t pos = 0; + const size_t description_offset = 30 + 8; + + if (arg->Flags & (COMMAND_LINE_VALUE_BOOL | COMMAND_LINE_VALUE_FLAG)) + { + if ((arg->Flags & (uint32_t)~COMMAND_LINE_VALUE_BOOL) == 0) + rc = printf(" %s%s", arg->Default ? "-" : "+", arg->Name); + else if ((arg->Flags & COMMAND_LINE_VALUE_OPTIONAL) != 0) + rc = printf(" [%s|/]%s", arg->Default ? "-" : "+", arg->Name); + else + { + rc = printf(" %s%s", arg->Default ? "-" : "+", arg->Name); + } + } + else + rc = printf(" /%s", arg->Name); + + if (rc < 0) + return; + pos += (size_t)rc; + + if ((arg->Flags & COMMAND_LINE_VALUE_REQUIRED) || + (arg->Flags & COMMAND_LINE_VALUE_OPTIONAL)) + { + if (arg->Format) + { + if (arg->Flags & COMMAND_LINE_VALUE_OPTIONAL) + { + rc = printf("[:"); + if (rc < 0) + return; + pos += (size_t)rc; + pos = print_optionals(arg->Format, pos, pos); + rc = printf("]"); + if (rc < 0) + return; + pos += (size_t)rc; + } + else + { + rc = printf(":"); + if (rc < 0) + return; + pos += (size_t)rc; + pos = print_optionals(arg->Format, pos, pos); + } + + if (pos > description_offset) + { + printf("\n"); + pos = 0; + } + } + } + + rc = printf("%*c", (int)(description_offset - pos), ' '); + if (rc < 0) + return; + pos += (size_t)rc; + + if (arg->Flags & COMMAND_LINE_VALUE_BOOL) + { + rc = printf("%s ", arg->Default ? "Disable" : "Enable"); + if (rc < 0) + return; + pos += (size_t)rc; + } + + print_description(arg->Text, description_offset, pos); + } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr); +} + +BOOL freerdp_client_print_command_line_help(int argc, char** argv) +{ + return freerdp_client_print_command_line_help_ex(argc, argv, nullptr); +} + +static COMMAND_LINE_ARGUMENT_A* create_merged_args(const COMMAND_LINE_ARGUMENT_A* custom, + SSIZE_T count, size_t* pcount) +{ + WINPR_ASSERT(pcount); + if (count < 0) + { + const COMMAND_LINE_ARGUMENT_A* cur = custom; + count = 0; + while (cur && cur->Name) + { + count++; + cur++; + } + } + + COMMAND_LINE_ARGUMENT_A* largs = + calloc((size_t)count + ARRAYSIZE(global_cmd_args), sizeof(COMMAND_LINE_ARGUMENT_A)); + *pcount = 0; + if (!largs) + return nullptr; + + size_t lcount = 0; + const COMMAND_LINE_ARGUMENT_A* cur = custom; + while (cur && cur->Name) + { + largs[lcount++] = *cur++; + } + + cur = global_cmd_args; + while (cur && cur->Name) + { + largs[lcount++] = *cur++; + } + *pcount = lcount; + return largs; +} + +BOOL freerdp_client_print_command_line_help_ex(int argc, char** argv, + const COMMAND_LINE_ARGUMENT_A* custom) +{ + const char* name = freerdp_getApplicationDetailsString(); + + /* allocate a merged copy of implementation defined and default arguments */ + size_t lcount = 0; + COMMAND_LINE_ARGUMENT_A* largs = create_merged_args(custom, -1, &lcount); + if (!largs) + return FALSE; + + if (argc > 0) + name = argv[0]; + + printf("\n"); + printf("%s - A Free Remote Desktop Protocol Implementation\n", name); + printf("See www.freerdp.com for more information\n"); + printf("\n"); + printf("Usage: %s [file] [options] [/v:[:port]]\n", argv[0]); + printf("\n"); + printf("Syntax:\n"); + printf(" /flag (enables flag)\n"); + printf(" /option: (specifies option with value)\n"); + printf(" +toggle -toggle (enables or disables toggle, where '/' is a synonym of '+')\n"); + printf("\n"); + + freerdp_client_print_command_line_args(largs, lcount); + free(largs); + + printf("\n"); + printf("Examples:\n"); + printf(" %s connection.rdp /p:Pwd123! /f\n", name); + printf(" %s /u:CONTOSO\\JohnDoe /p:Pwd123! /v:rdp.contoso.com\n", name); + printf(" %s /u:JohnDoe /p:Pwd123! /w:1366 /h:768 /v:192.168.1.100:4489\n", name); + printf(" %s /u:JohnDoe /p:Pwd123! /vmconnect:C824F53E-95D2-46C6-9A18-23A5BB403532 " + "/v:192.168.1.100\n", + name); + printf(" %s /u:\\AzureAD\\user@corp.example /p:pwd /v:host\n", name); + printf("Use a generic pipe as transport:"); + printf(" %s /v:/path/to/pipe\n", name); + printf("Use a external socket:"); + printf(" %s /v:|:1234\n", name); + printf("\n"); + printf("Clipboard Redirection: +clipboard\n"); + printf("\n"); + printf("Drive Redirection: /drive:home,/home/user\n"); + printf("Smartcard Redirection: /smartcard:\n"); + printf("Smartcard logon with Kerberos authentication: /smartcard-logon /sec:nla\n"); + +#if defined(CHANNEL_SERIAL_CLIENT) + printf("Serial Port Redirection: /serial:,,[SerCx2|SerCx|Serial],[permissive]\n"); + printf("Serial Port Redirection: /serial:COM1,/dev/ttyS0\n"); +#endif +#if defined(CHANNEL_PARALLEL_CLIENT) + printf("Parallel Port Redirection: /parallel:,\n"); +#endif + printf("Printer Redirection: /printer:,,[default]\n"); + printf("TCP redirection: /rdp2tcp:/usr/bin/rdp2tcp\n"); + printf("\n"); + printf("Audio Output Redirection: /sound:sys:oss,dev:1,format:1\n"); + printf("Audio Output Redirection: /sound:sys:alsa\n"); + printf("Audio Input Redirection: /microphone:sys:oss,dev:1,format:1\n"); + printf("Audio Input Redirection: /microphone:sys:alsa\n"); + printf("\n"); + printf("Multimedia Redirection: /video\n"); +#ifdef CHANNEL_URBDRC_CLIENT + printf("USB Device Redirection: /usb:id:054c:0268#4669:6e6b,addr:04:0c\n"); +#endif + printf("\n"); + printf("For Gateways, the https_proxy environment variable is respected:\n"); +#ifdef _WIN32 + printf(" set HTTPS_PROXY=http://proxy.contoso.com:3128/\n"); +#else + printf(" export https_proxy=http://proxy.contoso.com:3128/\n"); +#endif + printf(" %s /gateway:g:rdp.contoso.com ...\n", name); + printf("\n"); + printf("More documentation is coming, in the meantime consult source files\n"); + printf("\n"); + return TRUE; +} + +static BOOL option_is_rdp_file(const char* option) +{ + WINPR_ASSERT(option); + + if (option_ends_with(option, ".rdp")) + return TRUE; + if (option_ends_with(option, ".rdpw")) + return TRUE; + return FALSE; +} + +static BOOL option_is_incident_file(const char* option) +{ + WINPR_ASSERT(option); + + return (option_ends_with(option, ".msrcIncident")); +} + +static int freerdp_client_command_line_pre_filter(void* context, int index, int argc, LPSTR* argv) +{ + if (index == 1) + { + size_t length = 0; + rdpSettings* settings = nullptr; + + if (argc <= index) + return -1; + + length = strlen(argv[index]); + + if (length > 4) + { + if (option_is_rdp_file(argv[index])) + { + settings = (rdpSettings*)context; + + if (!freerdp_settings_set_string(settings, FreeRDP_ConnectionFile, argv[index])) + return COMMAND_LINE_ERROR_MEMORY; + + return 1; + } + } + + if (length > 13) + { + if (option_is_incident_file(argv[index])) + { + settings = (rdpSettings*)context; + + if (!freerdp_settings_set_string(settings, FreeRDP_AssistanceFile, argv[index])) + return COMMAND_LINE_ERROR_MEMORY; + + return 1; + } + } + } + + return 0; +} + +BOOL freerdp_client_add_device_channel(rdpSettings* settings, size_t count, + const char* const* params) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(params); + WINPR_ASSERT(count > 0); + + if (option_equals(params[0], "drive")) + { + BOOL rc = 0; + if (count < 2) + return FALSE; + + if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE)) + return FALSE; + if (count < 3) + rc = freerdp_client_add_drive(settings, params[1], nullptr); + else + rc = freerdp_client_add_drive(settings, params[2], params[1]); + + return rc; + } + else if (option_equals(params[0], "printer")) + { + RDPDR_DEVICE* printer = nullptr; + + if (count < 1) + return FALSE; + + if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectPrinters, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE)) + return FALSE; + + printer = freerdp_device_new(RDPDR_DTYP_PRINT, count - 1, ¶ms[1]); + if (!printer) + return FALSE; + + if (!freerdp_device_collection_add(settings, printer)) + { + freerdp_device_free(printer); + return FALSE; + } + + return TRUE; + } + else if (option_equals(params[0], "smartcard")) + { + RDPDR_DEVICE* smartcard = nullptr; + + if (count < 1) + return FALSE; + + if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectSmartCards, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE)) + return FALSE; + + smartcard = freerdp_device_new(RDPDR_DTYP_SMARTCARD, count - 1, ¶ms[1]); + + if (!smartcard) + return FALSE; + + if (!freerdp_device_collection_add(settings, smartcard)) + { + freerdp_device_free(smartcard); + return FALSE; + } + + return TRUE; + } +#if defined(CHANNEL_SERIAL_CLIENT) + else if (option_equals(params[0], "serial")) + { + RDPDR_DEVICE* serial = nullptr; + + if (count < 1) + return FALSE; + + if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectSerialPorts, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE)) + return FALSE; + + serial = freerdp_device_new(RDPDR_DTYP_SERIAL, count - 1, ¶ms[1]); + + if (!serial) + return FALSE; + + if (!freerdp_device_collection_add(settings, serial)) + { + freerdp_device_free(serial); + return FALSE; + } + + return TRUE; + } +#endif + else if (option_equals(params[0], "parallel")) + { + RDPDR_DEVICE* parallel = nullptr; + + if (count < 1) + return FALSE; + + if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectParallelPorts, TRUE)) + return FALSE; + if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE)) + return FALSE; + + parallel = freerdp_device_new(RDPDR_DTYP_PARALLEL, count - 1, ¶ms[1]); + + if (!parallel) + return FALSE; + + if (!freerdp_device_collection_add(settings, parallel)) + { + freerdp_device_free(parallel); + return FALSE; + } + + return TRUE; + } + + return FALSE; +} + +BOOL freerdp_client_del_static_channel(rdpSettings* settings, const char* name) +{ + return freerdp_static_channel_collection_del(settings, name); +} + +BOOL freerdp_client_add_static_channel(rdpSettings* settings, size_t count, + const char* const* params) +{ + ADDIN_ARGV* _args = nullptr; + + if (!settings || !params || !params[0] || (count > INT_MAX)) + return FALSE; + + if (freerdp_static_channel_collection_find(settings, params[0])) + return TRUE; + + _args = freerdp_addin_argv_new(count, params); + + if (!_args) + return FALSE; + + if (!freerdp_static_channel_collection_add(settings, _args)) + goto fail; + + return TRUE; +fail: + freerdp_addin_argv_free(_args); + return FALSE; +} + +BOOL freerdp_client_del_dynamic_channel(rdpSettings* settings, const char* name) +{ + return freerdp_dynamic_channel_collection_del(settings, name); +} + +BOOL freerdp_client_add_dynamic_channel(rdpSettings* settings, size_t count, + const char* const* params) +{ + ADDIN_ARGV* _args = nullptr; + + if (!settings || !params || !params[0] || (count > INT_MAX)) + return FALSE; + + if (freerdp_dynamic_channel_collection_find(settings, params[0])) + return TRUE; + + _args = freerdp_addin_argv_new(count, params); + + if (!_args) + return FALSE; + + if (!freerdp_dynamic_channel_collection_add(settings, _args)) + goto fail; + + return TRUE; + +fail: + freerdp_addin_argv_free(_args); + return FALSE; +} + +static BOOL read_pem_file(rdpSettings* settings, FreeRDP_Settings_Keys_String id, const char* file) +{ + size_t length = 0; + char* pem = crypto_read_pem(file, &length); + if (!pem || (length == 0)) + { + free(pem); + return FALSE; + } + + BOOL rc = freerdp_settings_set_string_len(settings, id, pem, length); + free(pem); + return rc; +} + +/** @brief suboption type */ +typedef enum +{ + CMDLINE_SUBOPTION_STRING, + CMDLINE_SUBOPTION_FILE, +} CmdLineSubOptionType; + +typedef BOOL (*CmdLineSubOptionCb)(const char* value, rdpSettings* settings); +typedef struct +{ + const char* optname; + FreeRDP_Settings_Keys_String id; + CmdLineSubOptionType opttype; + WINPR_ATTR_NODISCARD CmdLineSubOptionCb cb; +} CmdLineSubOptions; + +static BOOL parseSubOptions(rdpSettings* settings, const CmdLineSubOptions* opts, size_t count, + const char* arg) +{ + BOOL found = FALSE; + + for (size_t xx = 0; xx < count; xx++) + { + const CmdLineSubOptions* opt = &opts[xx]; + + if (option_starts_with(opt->optname, arg)) + { + const size_t optlen = strlen(opt->optname); + const char* val = &arg[optlen]; + BOOL status = 0; + + switch (opt->opttype) + { + case CMDLINE_SUBOPTION_STRING: + status = freerdp_settings_set_string(settings, opt->id, val); + break; + case CMDLINE_SUBOPTION_FILE: + status = read_pem_file(settings, opt->id, val); + break; + default: + WLog_ERR(TAG, "invalid subOption type"); + return FALSE; + } + + if (!status) + return FALSE; + + if (opt->cb && !opt->cb(val, settings)) + return FALSE; + + found = TRUE; + break; + } + } + + if (!found) + WLog_ERR(TAG, "option %s not handled", arg); + + return found; +} + +#define fail_at(arg, rc) fail_at_((arg), (rc), __FILE__, __func__, __LINE__) +static int fail_at_(const COMMAND_LINE_ARGUMENT_A* arg, int rc, const char* file, const char* fkt, + size_t line) +{ + if (rc == 0) + return rc; + + const DWORD level = WLOG_ERROR; + wLog* log = WLog_Get(TAG); + if (WLog_IsLevelActive(log, level)) + WLog_PrintTextMessage(log, level, line, file, fkt, + "Command line parsing failed at '%s' value '%s' [%d]", arg->Name, + arg->Value, rc); + return rc; +} + +static int freerdp_client_command_line_post_filter_int(void* context, COMMAND_LINE_ARGUMENT_A* arg) +{ + rdpSettings* settings = (rdpSettings*)context; + int status = CHANNEL_RC_OK; + BOOL enable = (arg->Value != nullptr); + + CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "a") + { + size_t count = 0; + char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count); + + if (!freerdp_client_add_device_channel(settings, count, (const char* const*)ptr)) + status = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE)) + status = COMMAND_LINE_ERROR; + + CommandLineParserFree(ptr); + if (status) + return fail_at(arg, status); + } + CommandLineSwitchCase(arg, "kerberos") + { + size_t count = 0; + + char** ptr = CommandLineParseCommaSeparatedValuesEx("kerberos", arg->Value, &count); + if (ptr) + { + const CmdLineSubOptions opts[] = { + { "kdc-url:", FreeRDP_KerberosKdcUrl, CMDLINE_SUBOPTION_STRING, nullptr }, + { "start-time:", FreeRDP_KerberosStartTime, CMDLINE_SUBOPTION_STRING, nullptr }, + { "lifetime:", FreeRDP_KerberosLifeTime, CMDLINE_SUBOPTION_STRING, nullptr }, + { "renewable-lifetime:", FreeRDP_KerberosRenewableLifeTime, + CMDLINE_SUBOPTION_STRING, nullptr }, + { "cache:", FreeRDP_KerberosCache, CMDLINE_SUBOPTION_STRING, nullptr }, + { "armor:", FreeRDP_KerberosArmor, CMDLINE_SUBOPTION_STRING, nullptr }, + { "pkinit-anchors:", FreeRDP_PkinitAnchors, CMDLINE_SUBOPTION_STRING, nullptr }, + { "pkcs11-module:", FreeRDP_Pkcs11Module, CMDLINE_SUBOPTION_STRING, nullptr } + }; + + for (size_t x = 1; x < count; x++) + { + const char* cur = ptr[x]; + if (!parseSubOptions(settings, opts, ARRAYSIZE(opts), cur)) + { + CommandLineParserFree(ptr); + return fail_at(arg, COMMAND_LINE_ERROR_UNEXPECTED_VALUE); + } + } + } + CommandLineParserFree(ptr); + } + + CommandLineSwitchCase(arg, "vc") + { + size_t count = 0; + char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count); + if (!freerdp_client_add_static_channel(settings, count, (const char* const*)ptr)) + status = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + CommandLineParserFree(ptr); + if (status) + return fail_at(arg, status); + } + CommandLineSwitchCase(arg, "dvc") + { + size_t count = 0; + char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count); + if (!freerdp_client_add_dynamic_channel(settings, count, (const char* const*)ptr)) + status = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + CommandLineParserFree(ptr); + if (status) + return fail_at(arg, status); + } + CommandLineSwitchCase(arg, "drive") + { + size_t count = 0; + char** ptr = CommandLineParseCommaSeparatedValuesEx(arg->Name, arg->Value, &count); + if (!freerdp_client_add_device_channel(settings, count, (const char* const*)ptr)) + status = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + CommandLineParserFree(ptr); + if (status) + return fail_at(arg, status); + } +#if defined(CHANNEL_SERIAL_CLIENT) + CommandLineSwitchCase(arg, "serial") + { + size_t count = 0; + char** ptr = CommandLineParseCommaSeparatedValuesEx(arg->Name, arg->Value, &count); + if (!freerdp_client_add_device_channel(settings, count, (const char* const*)ptr)) + status = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + CommandLineParserFree(ptr); + if (status) + return fail_at(arg, status); + } +#endif +#if defined(CHANNEL_PARALLEL_CLIENT) + CommandLineSwitchCase(arg, "parallel") + { + size_t count = 0; + char** ptr = CommandLineParseCommaSeparatedValuesEx(arg->Name, arg->Value, &count); + if (!freerdp_client_add_device_channel(settings, count, (const char* const*)ptr)) + status = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + CommandLineParserFree(ptr); + if (status) + return fail_at(arg, status); + } +#endif + CommandLineSwitchCase(arg, "smartcard") + { + size_t count = 0; + char** ptr = CommandLineParseCommaSeparatedValuesEx(arg->Name, arg->Value, &count); + if (!freerdp_client_add_device_channel(settings, count, (const char* const*)ptr)) + status = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + CommandLineParserFree(ptr); + if (status) + return fail_at(arg, status); + } + CommandLineSwitchCase(arg, "printer") + { + size_t count = 0; + char** ptr = CommandLineParseCommaSeparatedValuesEx(arg->Name, arg->Value, &count); + if (!freerdp_client_add_device_channel(settings, count, (const char* const*)ptr)) + status = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + CommandLineParserFree(ptr); + if (status) + return fail_at(arg, status); + } + CommandLineSwitchCase(arg, "usb") + { + size_t count = 0; + char** ptr = + CommandLineParseCommaSeparatedValuesEx(URBDRC_CHANNEL_NAME, arg->Value, &count); + if (!freerdp_client_add_dynamic_channel(settings, count, (const char* const*)ptr)) + status = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + CommandLineParserFree(ptr); + if (status) + return fail_at(arg, status); + } + CommandLineSwitchCase(arg, "multitouch") + { + if (!freerdp_settings_set_bool(settings, FreeRDP_MultiTouchInput, enable)) + return fail_at(arg, COMMAND_LINE_ERROR); + } + CommandLineSwitchCase(arg, "gestures") + { + if (!freerdp_settings_set_bool(settings, FreeRDP_MultiTouchGestures, enable)) + return fail_at(arg, COMMAND_LINE_ERROR); + } + CommandLineSwitchCase(arg, "echo") + { + if (!freerdp_settings_set_bool(settings, FreeRDP_SupportEchoChannel, enable)) + return fail_at(arg, COMMAND_LINE_ERROR); + } + CommandLineSwitchCase(arg, "ssh-agent") + { + if (!freerdp_settings_set_bool(settings, FreeRDP_SupportSSHAgentChannel, enable)) + return fail_at(arg, COMMAND_LINE_ERROR); + } + CommandLineSwitchCase(arg, "disp") + { + if (!freerdp_settings_set_bool(settings, FreeRDP_SupportDisplayControl, enable)) + return fail_at(arg, COMMAND_LINE_ERROR); + } + CommandLineSwitchCase(arg, "geometry") + { + if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGeometryTracking, enable)) + return fail_at(arg, COMMAND_LINE_ERROR); + } + CommandLineSwitchCase(arg, "video") + { + if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGeometryTracking, + enable)) /* this requires geometry tracking */ + return fail_at(arg, COMMAND_LINE_ERROR); + if (!freerdp_settings_set_bool(settings, FreeRDP_SupportVideoOptimized, enable)) + return fail_at(arg, COMMAND_LINE_ERROR); + } + CommandLineSwitchCase(arg, "sound") + { + size_t count = 0; + char** ptr = + CommandLineParseCommaSeparatedValuesEx(RDPSND_CHANNEL_NAME, arg->Value, &count); + if (!freerdp_client_add_static_channel(settings, count, (const char* const*)ptr)) + status = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + if (!freerdp_client_add_dynamic_channel(settings, count, (const char* const*)ptr)) + status = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + CommandLineParserFree(ptr); + if (status) + return fail_at(arg, status); + } + CommandLineSwitchCase(arg, "microphone") + { + size_t count = 0; + char** ptr = CommandLineParseCommaSeparatedValuesEx(AUDIN_CHANNEL_NAME, arg->Value, &count); + if (!freerdp_client_add_dynamic_channel(settings, count, (const char* const*)ptr)) + status = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + CommandLineParserFree(ptr); + if (status) + return fail_at(arg, status); + } +#if defined(CHANNEL_TSMF_CLIENT) + CommandLineSwitchCase(arg, "multimedia") + { + size_t count = 0; + char** ptr = CommandLineParseCommaSeparatedValuesEx("tsmf", arg->Value, &count); + if (!freerdp_client_add_dynamic_channel(settings, count, (const char* const*)ptr)) + status = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + CommandLineParserFree(ptr); + if (status) + return fail_at(arg, status); + } +#endif + CommandLineSwitchCase(arg, "heartbeat") + { + if (!freerdp_settings_set_bool(settings, FreeRDP_SupportHeartbeatPdu, enable)) + return fail_at(arg, COMMAND_LINE_ERROR); + } + CommandLineSwitchCase(arg, "multitransport") + { + if (!freerdp_settings_set_bool(settings, FreeRDP_SupportMultitransport, enable)) + return fail_at(arg, COMMAND_LINE_ERROR); + + UINT32 flags = 0; + if (freerdp_settings_get_bool(settings, FreeRDP_SupportMultitransport)) + flags = + (TRANSPORT_TYPE_UDP_FECR | TRANSPORT_TYPE_UDP_FECL | TRANSPORT_TYPE_UDP_PREFERRED); + + if (!freerdp_settings_set_uint32(settings, FreeRDP_MultitransportFlags, flags)) + return fail_at(arg, COMMAND_LINE_ERROR); + } + CommandLineSwitchEnd(arg) + + return status; +} + +static int freerdp_client_command_line_post_filter(void* context, COMMAND_LINE_ARGUMENT_A* arg) +{ + int status = freerdp_client_command_line_post_filter_int(context, arg); + return status == CHANNEL_RC_OK ? 1 : -1; +} + +static BOOL freerdp_parse_username_ptr(const char* username, const char** user, size_t* userlen, + const char** domain, size_t* domainlen) +{ + WINPR_ASSERT(user); + WINPR_ASSERT(userlen); + WINPR_ASSERT(domain); + WINPR_ASSERT(domainlen); + + if (!username) + return FALSE; + + const char* p = strchr(username, '\\'); + + *user = nullptr; + *userlen = 0; + + *domain = nullptr; + *domainlen = 0; + + if (p) + { + const size_t length = (size_t)(p - username); + *user = &p[1]; + *userlen = strlen(*user); + + *domain = username; + *domainlen = length; + } + else + { + /* Do not break up the name for '@'; both credSSP and the + * ClientInfo PDU expect 'user@corp.net' to be transmitted + * as username 'user@corp.net', domain empty (not nullptr!). + */ + *user = username; + *userlen = strlen(username); + } + + return TRUE; +} + +static BOOL freerdp_parse_username_settings(const char* username, rdpSettings* settings, + FreeRDP_Settings_Keys_String userID, + FreeRDP_Settings_Keys_String domainID) +{ + const char* user = nullptr; + const char* domain = nullptr; + size_t userlen = 0; + size_t domainlen = 0; + + const BOOL rc = freerdp_parse_username_ptr(username, &user, &userlen, &domain, &domainlen); + if (!rc) + return FALSE; + if (!freerdp_settings_set_string_len(settings, userID, user, userlen)) + return FALSE; + return freerdp_settings_set_string_len(settings, domainID, domain, domainlen); +} + +BOOL freerdp_parse_username(const char* username, char** puser, char** pdomain) +{ + const char* user = nullptr; + const char* domain = nullptr; + size_t userlen = 0; + size_t domainlen = 0; + + *puser = nullptr; + *pdomain = nullptr; + + const BOOL rc = freerdp_parse_username_ptr(username, &user, &userlen, &domain, &domainlen); + if (!rc) + return FALSE; + + if (userlen > 0) + { + *puser = strndup(user, userlen); + if (!*puser) + return FALSE; + } + + if (domainlen > 0) + { + *pdomain = strndup(domain, domainlen); + if (!*pdomain) + { + free(*puser); + *puser = nullptr; + return FALSE; + } + } + + return TRUE; +} + +BOOL freerdp_parse_hostname(const char* hostname, char** host, int* port) +{ + char* p = nullptr; + p = strrchr(hostname, ':'); + + if (p) + { + size_t length = (size_t)(p - hostname); + LONGLONG val = 0; + + if (!value_to_int(p + 1, &val, 1, UINT16_MAX)) + return FALSE; + + *host = (char*)calloc(length + 1UL, sizeof(char)); + + if (!(*host)) + return FALSE; + + CopyMemory(*host, hostname, length); + (*host)[length] = '\0'; + *port = (UINT16)val; + } + else + { + *host = _strdup(hostname); + + if (!(*host)) + return FALSE; + + *port = -1; + } + + return TRUE; +} + +static BOOL freerdp_apply_connection_type(rdpSettings* settings, UINT32 type) +{ + struct network_settings + { + FreeRDP_Settings_Keys_Bool id; + BOOL value[7]; + }; + const struct network_settings config[] = { + { FreeRDP_DisableWallpaper, { TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE } }, + { FreeRDP_AllowFontSmoothing, { FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE } }, + { FreeRDP_AllowDesktopComposition, { FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE } }, + { FreeRDP_DisableFullWindowDrag, { TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE } }, + { FreeRDP_DisableMenuAnims, { TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE } }, + { FreeRDP_DisableThemes, { TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE } } + }; + + switch (type) + { + case CONNECTION_TYPE_INVALID: + return TRUE; + + case CONNECTION_TYPE_MODEM: + case CONNECTION_TYPE_BROADBAND_LOW: + case CONNECTION_TYPE_BROADBAND_HIGH: + case CONNECTION_TYPE_SATELLITE: + case CONNECTION_TYPE_WAN: + case CONNECTION_TYPE_LAN: + case CONNECTION_TYPE_AUTODETECT: + break; + default: + WLog_WARN(TAG, "Unknown ConnectionType %" PRIu32 ", aborting", type); + return FALSE; + } + + for (size_t x = 0; x < ARRAYSIZE(config); x++) + { + const struct network_settings* cur = &config[x]; + if (!freerdp_settings_set_bool(settings, cur->id, cur->value[type - 1])) + return FALSE; + } + return TRUE; +} + +BOOL freerdp_set_connection_type(rdpSettings* settings, UINT32 type) +{ + + if (!freerdp_settings_set_uint32(settings, FreeRDP_ConnectionType, type)) + return FALSE; + + switch (type) + { + case CONNECTION_TYPE_INVALID: + case CONNECTION_TYPE_MODEM: + case CONNECTION_TYPE_BROADBAND_LOW: + case CONNECTION_TYPE_SATELLITE: + case CONNECTION_TYPE_BROADBAND_HIGH: + case CONNECTION_TYPE_WAN: + case CONNECTION_TYPE_LAN: + if (!freerdp_apply_connection_type(settings, type)) + return FALSE; + break; + case CONNECTION_TYPE_AUTODETECT: + if (!freerdp_apply_connection_type(settings, type)) + return FALSE; + /* Automatically activate GFX and RFX codec support */ +#ifdef WITH_GFX_H264 + if (!freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444v2, TRUE) || + !freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444, TRUE) || + !freerdp_settings_set_bool(settings, FreeRDP_GfxH264, TRUE)) + return FALSE; +#endif + if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE) || + !freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, TRUE)) + return FALSE; + break; + default: + WLog_WARN(TAG, "Unknown ConnectionType %" PRIu32 ", aborting", type); + return FALSE; + } + + return TRUE; +} + +static UINT32 freerdp_get_keyboard_layout_for_type(const char* name, WINPR_ATTR_UNUSED DWORD type) +{ + UINT32 res = 0; + size_t count = 0; + RDP_KEYBOARD_LAYOUT* layouts = + freerdp_keyboard_get_layouts(RDP_KEYBOARD_LAYOUT_TYPE_STANDARD, &count); + + if (!layouts || (count == 0)) + goto fail; + + for (size_t x = 0; x < count; x++) + { + const RDP_KEYBOARD_LAYOUT* layout = &layouts[x]; + if (option_equals(layout->name, name)) + { + res = layout->code; + break; + } + } + +fail: + freerdp_keyboard_layouts_free(layouts, count); + return res; +} + +static UINT32 freerdp_map_keyboard_layout_name_to_id(const char* name) +{ + const UINT32 variants[] = { RDP_KEYBOARD_LAYOUT_TYPE_STANDARD, RDP_KEYBOARD_LAYOUT_TYPE_VARIANT, + RDP_KEYBOARD_LAYOUT_TYPE_IME }; + + for (size_t x = 0; x < ARRAYSIZE(variants); x++) + { + UINT32 rc = freerdp_get_keyboard_layout_for_type(name, variants[x]); + if (rc > 0) + return rc; + } + + return 0; +} + +static int freerdp_detect_command_line_pre_filter(void* context, int index, int argc, LPSTR* argv) +{ + size_t length = 0; + WINPR_UNUSED(context); + + if (index == 1) + { + if (argc < index) + return -1; + + length = strlen(argv[index]); + + if (length > 4) + { + if (option_is_rdp_file(argv[index])) + { + return 1; + } + } + + if (length > 13) + { + if (option_is_incident_file(argv[index])) + { + return 1; + } + } + } + + return 0; +} + +static int freerdp_detect_windows_style_command_line_syntax(int argc, char** argv, size_t* count, + BOOL ignoreUnknown) +{ + int status = 0; + DWORD flags = 0; + int detect_status = 0; + const COMMAND_LINE_ARGUMENT_A* arg = nullptr; + COMMAND_LINE_ARGUMENT_A largs[ARRAYSIZE(global_cmd_args)]; + memcpy(largs, global_cmd_args, sizeof(global_cmd_args)); + + flags = COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_SILENCE_PARSER; + flags |= COMMAND_LINE_SIGIL_SLASH | COMMAND_LINE_SIGIL_PLUS_MINUS; + + if (ignoreUnknown) + { + flags |= COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + } + + *count = 0; + detect_status = 0; + CommandLineClearArgumentsA(largs); + status = CommandLineParseArgumentsA(argc, argv, largs, flags, nullptr, + freerdp_detect_command_line_pre_filter, nullptr); + + if (status < 0) + return status; + + arg = largs; + + do + { + if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT)) + continue; + + (*count)++; + } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr); + + return detect_status; +} + +static int freerdp_detect_posix_style_command_line_syntax(int argc, char** argv, size_t* count, + BOOL ignoreUnknown) +{ + int status = 0; + DWORD flags = 0; + int detect_status = 0; + const COMMAND_LINE_ARGUMENT_A* arg = nullptr; + COMMAND_LINE_ARGUMENT_A largs[ARRAYSIZE(global_cmd_args)]; + memcpy(largs, global_cmd_args, sizeof(global_cmd_args)); + + flags = COMMAND_LINE_SEPARATOR_SPACE | COMMAND_LINE_SILENCE_PARSER; + flags |= COMMAND_LINE_SIGIL_DASH | COMMAND_LINE_SIGIL_DOUBLE_DASH; + flags |= COMMAND_LINE_SIGIL_ENABLE_DISABLE; + + if (ignoreUnknown) + { + flags |= COMMAND_LINE_IGN_UNKNOWN_KEYWORD; + } + + *count = 0; + detect_status = 0; + CommandLineClearArgumentsA(largs); + status = CommandLineParseArgumentsA(argc, argv, largs, flags, nullptr, + freerdp_detect_command_line_pre_filter, nullptr); + + if (status < 0) + return status; + + arg = largs; + + do + { + if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT)) + continue; + + (*count)++; + } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr); + + return detect_status; +} + +static BOOL freerdp_client_detect_command_line(int argc, char** argv, DWORD* flags) +{ + size_t posix_cli_count = 0; + size_t windows_cli_count = 0; + const BOOL ignoreUnknown = TRUE; + const int windows_cli_status = freerdp_detect_windows_style_command_line_syntax( + argc, argv, &windows_cli_count, ignoreUnknown); + const int posix_cli_status = + freerdp_detect_posix_style_command_line_syntax(argc, argv, &posix_cli_count, ignoreUnknown); + + /* Default is POSIX syntax */ + *flags = COMMAND_LINE_SEPARATOR_SPACE; + *flags |= COMMAND_LINE_SIGIL_DASH | COMMAND_LINE_SIGIL_DOUBLE_DASH; + *flags |= COMMAND_LINE_SIGIL_ENABLE_DISABLE; + + if (posix_cli_status <= COMMAND_LINE_STATUS_PRINT) + return FALSE; + + /* Check, if this may be windows style syntax... */ + if ((windows_cli_count && (windows_cli_count >= posix_cli_count)) || + (windows_cli_status <= COMMAND_LINE_STATUS_PRINT)) + { + windows_cli_count = 1; + *flags = COMMAND_LINE_SEPARATOR_COLON; + *flags |= COMMAND_LINE_SIGIL_SLASH | COMMAND_LINE_SIGIL_PLUS_MINUS; + } + + WLog_DBG(TAG, "windows: %d/%" PRIuz " posix: %d/%" PRIuz "", windows_cli_status, + windows_cli_count, posix_cli_status, posix_cli_count); + if ((posix_cli_count == 0) && (windows_cli_count == 0)) + { + if ((posix_cli_status == COMMAND_LINE_ERROR) && (windows_cli_status == COMMAND_LINE_ERROR)) + return TRUE; + } + return FALSE; +} + +int freerdp_client_settings_command_line_status_print(rdpSettings* settings, int status, int argc, + char** argv) +{ + return freerdp_client_settings_command_line_status_print_ex(settings, status, argc, argv, + nullptr); +} + +static void freerdp_client_print_keyboard_type_list(const char* msg, DWORD type) +{ + size_t count = 0; + RDP_KEYBOARD_LAYOUT* layouts = nullptr; + layouts = freerdp_keyboard_get_layouts(type, &count); + + printf("\n%s\n", msg); + + for (size_t x = 0; x < count; x++) + { + const RDP_KEYBOARD_LAYOUT* layout = &layouts[x]; + printf("0x%08" PRIX32 "\t%s\n", layout->code, layout->name); + } + + freerdp_keyboard_layouts_free(layouts, count); +} + +static void freerdp_client_print_keyboard_list(void) +{ + freerdp_client_print_keyboard_type_list("Keyboard Layouts", RDP_KEYBOARD_LAYOUT_TYPE_STANDARD); + freerdp_client_print_keyboard_type_list("Keyboard Layout Variants", + RDP_KEYBOARD_LAYOUT_TYPE_VARIANT); + freerdp_client_print_keyboard_type_list("Keyboard Layout Variants", + RDP_KEYBOARD_LAYOUT_TYPE_IME); +} + +static void freerdp_client_print_timezone_list(void) +{ + DWORD index = 0; + DYNAMIC_TIME_ZONE_INFORMATION info = WINPR_C_ARRAY_INIT; + while (EnumDynamicTimeZoneInformation(index++, &info) != ERROR_NO_MORE_ITEMS) + { + char TimeZoneKeyName[ARRAYSIZE(info.TimeZoneKeyName) + 1] = WINPR_C_ARRAY_INIT; + + (void)ConvertWCharNToUtf8(info.TimeZoneKeyName, ARRAYSIZE(info.TimeZoneKeyName), + TimeZoneKeyName, ARRAYSIZE(TimeZoneKeyName)); + printf("%" PRIu32 ": '%s'\n", index, TimeZoneKeyName); + } +} + +static void freerdp_client_print_tune_list(const rdpSettings* settings) +{ + SSIZE_T type = 0; + + for (SSIZE_T x = 0; x < FreeRDP_Settings_StableAPI_MAX; x++) + { + const char* name = freerdp_settings_get_name_for_key(x); + type = freerdp_settings_get_type_for_key(x); + + // NOLINTBEGIN(clang-analyzer-optin.core.EnumCastOutOfRange) + switch (type) + { + case RDP_SETTINGS_TYPE_BOOL: + printf("%" PRIdz "\t%50s\tBOOL\t%s\n", x, name, + freerdp_settings_get_bool(settings, (FreeRDP_Settings_Keys_Bool)x) + ? "TRUE" + : "FALSE"); + break; + case RDP_SETTINGS_TYPE_UINT16: + printf("%" PRIdz "\t%50s\tUINT16\t%" PRIu16 "\n", x, name, + freerdp_settings_get_uint16(settings, (FreeRDP_Settings_Keys_UInt16)x)); + break; + case RDP_SETTINGS_TYPE_INT16: + printf("%" PRIdz "\t%50s\tINT16\t%" PRId16 "\n", x, name, + freerdp_settings_get_int16(settings, (FreeRDP_Settings_Keys_Int16)x)); + break; + case RDP_SETTINGS_TYPE_UINT32: + printf("%" PRIdz "\t%50s\tUINT32\t%" PRIu32 "\n", x, name, + freerdp_settings_get_uint32(settings, (FreeRDP_Settings_Keys_UInt32)x)); + break; + case RDP_SETTINGS_TYPE_INT32: + printf("%" PRIdz "\t%50s\tINT32\t%" PRId32 "\n", x, name, + freerdp_settings_get_int32(settings, (FreeRDP_Settings_Keys_Int32)x)); + break; + case RDP_SETTINGS_TYPE_UINT64: + printf("%" PRIdz "\t%50s\tUINT64\t%" PRIu64 "\n", x, name, + freerdp_settings_get_uint64(settings, (FreeRDP_Settings_Keys_UInt64)x)); + break; + case RDP_SETTINGS_TYPE_INT64: + printf("%" PRIdz "\t%50s\tINT64\t%" PRId64 "\n", x, name, + freerdp_settings_get_int64(settings, (FreeRDP_Settings_Keys_Int64)x)); + break; + case RDP_SETTINGS_TYPE_STRING: + printf("%" PRIdz "\t%50s\tSTRING\t%s" + "\n", + x, name, + freerdp_settings_get_string(settings, (FreeRDP_Settings_Keys_String)x)); + break; + case RDP_SETTINGS_TYPE_POINTER: + printf("%" PRIdz "\t%50s\tPOINTER\t%p" + "\n", + x, name, + freerdp_settings_get_pointer(settings, (FreeRDP_Settings_Keys_Pointer)x)); + break; + default: + break; + } + // NOLINTEND(clang-analyzer-optin.core.EnumCastOutOfRange) + } +} + +int freerdp_client_settings_command_line_status_print_ex(rdpSettings* settings, int status, + int argc, char** argv, + const COMMAND_LINE_ARGUMENT_A* custom) +{ + const COMMAND_LINE_ARGUMENT_A* arg = nullptr; + COMMAND_LINE_ARGUMENT_A largs[ARRAYSIZE(global_cmd_args)]; + memcpy(largs, global_cmd_args, sizeof(global_cmd_args)); + + if (status == COMMAND_LINE_STATUS_PRINT_VERSION) + { + freerdp_client_print_version(); + goto out; + } + + if (status == COMMAND_LINE_STATUS_PRINT_BUILDCONFIG) + { + freerdp_client_print_version_ex(argc, argv); + freerdp_client_print_buildconfig_ex(argc, argv); + goto out; + } + else if (status == COMMAND_LINE_STATUS_PRINT) + { + (void)CommandLineParseArgumentsA(argc, argv, largs, 0x112, nullptr, nullptr, nullptr); + + arg = CommandLineFindArgumentA(largs, "list"); + WINPR_ASSERT(arg); + + if (arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT) + { + if (option_equals("timezones", arg->Value)) + freerdp_client_print_timezone_list(); + else if (option_equals("tune", arg->Value)) + freerdp_client_print_tune_list(settings); + else if (option_equals("kbd", arg->Value)) + freerdp_client_print_keyboard_list(); + else if (option_starts_with("kbd-lang", arg->Value)) + { + const char* val = nullptr; + if (option_starts_with("kbd-lang:", arg->Value)) + val = &arg->Value[9]; + else if (!option_equals("kbd-lang", arg->Value)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + if (val && strchr(val, ',')) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + freerdp_client_print_codepages(val); + } + else if (option_equals("kbd-scancode", arg->Value)) + freerdp_client_print_scancodes(); + else if (option_equals("monitor", arg->Value)) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_ListMonitors, TRUE)) + return COMMAND_LINE_ERROR; + } + else if (option_starts_with("smartcard", arg->Value)) + { + BOOL opts = FALSE; + if (option_starts_with("smartcard:", arg->Value)) + opts = TRUE; + else if (!option_equals("smartcard", arg->Value)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + if (opts) + { + const char* sub = strchr(arg->Value, ':') + 1; + const CmdLineSubOptions options[] = { { "pkinit-anchors:", + FreeRDP_PkinitAnchors, + CMDLINE_SUBOPTION_STRING, nullptr }, + { "pkcs11-module:", FreeRDP_Pkcs11Module, + CMDLINE_SUBOPTION_STRING, nullptr } }; + + size_t count = 0; + + char** ptr = CommandLineParseCommaSeparatedValuesEx("smartcard", sub, &count); + if (!ptr) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + if (count < 2) + { + CommandLineParserFree(ptr); + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + + for (size_t x = 1; x < count; x++) + { + const char* cur = ptr[x]; + if (!parseSubOptions(settings, options, ARRAYSIZE(options), cur)) + { + CommandLineParserFree(ptr); + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + } + + CommandLineParserFree(ptr); + } + + freerdp_smartcard_list(settings); + } + else + { + freerdp_client_print_command_line_help_ex(argc, argv, custom); + return COMMAND_LINE_ERROR; + } + } +#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE) + arg = CommandLineFindArgumentA(largs, "tune-list"); + WINPR_ASSERT(arg); + + if (arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT) + { + WLog_WARN(TAG, "Option /tune-list is deprecated, use /list:tune instead"); + freerdp_client_print_tune_list(settings); + } + + arg = CommandLineFindArgumentA(largs, "kbd-lang-list"); + WINPR_ASSERT(arg); + + if (arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT) + { + WLog_WARN(TAG, "Option /kbd-lang-list is deprecated, use /list:kbd-lang instead"); + freerdp_client_print_codepages(arg->Value); + } + + arg = CommandLineFindArgumentA(largs, "kbd-list"); + WINPR_ASSERT(arg); + + if (arg->Flags & COMMAND_LINE_VALUE_PRESENT) + { + WLog_WARN(TAG, "Option /kbd-list is deprecated, use /list:kbd instead"); + freerdp_client_print_keyboard_list(); + } + + arg = CommandLineFindArgumentA(largs, "monitor-list"); + WINPR_ASSERT(arg); + + if (arg->Flags & COMMAND_LINE_VALUE_PRESENT) + { + WLog_WARN(TAG, "Option /monitor-list is deprecated, use /list:monitor instead"); + if (!freerdp_settings_set_bool(settings, FreeRDP_ListMonitors, TRUE)) + return COMMAND_LINE_ERROR; + } + + arg = CommandLineFindArgumentA(largs, "smartcard-list"); + WINPR_ASSERT(arg); + + if (arg->Flags & COMMAND_LINE_VALUE_PRESENT) + { + WLog_WARN(TAG, "Option /smartcard-list is deprecated, use /list:smartcard instead"); + freerdp_smartcard_list(settings); + } + + arg = CommandLineFindArgumentA(largs, "kbd-scancode-list"); + WINPR_ASSERT(arg); + + if (arg->Flags & COMMAND_LINE_VALUE_PRESENT) + { + WLog_WARN(TAG, + "Option /kbd-scancode-list is deprecated, use /list:kbd-scancode instead"); + freerdp_client_print_scancodes(); + goto out; + } +#endif + goto out; + } + else if (status < 0) + { + freerdp_client_print_command_line_help_ex(argc, argv, custom); + goto out; + } + +out: + if (status <= COMMAND_LINE_STATUS_PRINT && status >= COMMAND_LINE_STATUS_PRINT_LAST) + return 0; + return status; +} + +/** + * parses a string value with the format x + * + * @param input input string + * @param v1 pointer to output v1 + * @param v2 pointer to output v2 + * @return if the parsing was successful + */ +static BOOL parseSizeValue(const char* input, unsigned long* v1, unsigned long* v2) +{ + const char* xcharpos = nullptr; + char* endPtr = nullptr; + unsigned long v = 0; + errno = 0; + v = strtoul(input, &endPtr, 10); + + if ((v == 0 || v == ULONG_MAX) && (errno != 0)) + return FALSE; + + if (v1) + *v1 = v; + + xcharpos = strchr(input, 'x'); + + if (!xcharpos || xcharpos != endPtr) + return FALSE; + + errno = 0; + v = strtoul(xcharpos + 1, &endPtr, 10); + + if ((v == 0 || v == ULONG_MAX) && (errno != 0)) + return FALSE; + + if (*endPtr != '\0') + return FALSE; + + if (v2) + *v2 = v; + + return TRUE; +} + +static BOOL prepare_default_settings(rdpSettings* settings, COMMAND_LINE_ARGUMENT_A* args, + BOOL rdp_file) +{ + const char* arguments[] = { "network", "gfx", "rfx", "bpp" }; + WINPR_ASSERT(settings); + WINPR_ASSERT(args); + + if (rdp_file) + return FALSE; + + for (size_t x = 0; x < ARRAYSIZE(arguments); x++) + { + const char* arg = arguments[x]; + const COMMAND_LINE_ARGUMENT_A* p = CommandLineFindArgumentA(args, arg); + if (p && (p->Flags & COMMAND_LINE_ARGUMENT_PRESENT)) + return FALSE; + } + + return freerdp_set_connection_type(settings, CONNECTION_TYPE_AUTODETECT); +} + +static BOOL setSmartcardEmulation(WINPR_ATTR_UNUSED const char* value, rdpSettings* settings) +{ + return freerdp_settings_set_bool(settings, FreeRDP_SmartcardEmulation, TRUE); +} + +const char* option_starts_with(const char* what, const char* val) +{ + WINPR_ASSERT(what); + WINPR_ASSERT(val); + const size_t wlen = strlen(what); + + if (_strnicmp(what, val, wlen) != 0) + return nullptr; + return &val[wlen]; +} + +BOOL option_ends_with(const char* str, const char* ext) +{ + WINPR_ASSERT(str); + WINPR_ASSERT(ext); + const size_t strLen = strlen(str); + const size_t extLen = strlen(ext); + + if (strLen < extLen) + return FALSE; + + return _strnicmp(&str[strLen - extLen], ext, extLen) == 0; +} + +BOOL option_equals(const char* what, const char* val) +{ + WINPR_ASSERT(what); + WINPR_ASSERT(val); + return _stricmp(what, val) == 0; +} + +typedef enum +{ + PARSE_ON, + PARSE_OFF, + PARSE_NONE, + PARSE_FAIL +} PARSE_ON_OFF_RESULT; + +static PARSE_ON_OFF_RESULT parse_on_off_option(const char* value) +{ + WINPR_ASSERT(value); + const char* sep = strchr(value, ':'); + if (!sep) + return PARSE_NONE; + if (option_equals("on", &sep[1])) + return PARSE_ON; + if (option_equals("off", &sep[1])) + return PARSE_OFF; + return PARSE_FAIL; +} + +typedef enum +{ + CLIP_DIR_PARSE_ALL, + CLIP_DIR_PARSE_OFF, + CLIP_DIR_PARSE_LOCAL, + CLIP_DIR_PARSE_REMOTE, + CLIP_DIR_PARSE_FAIL +} PARSE_CLIP_DIR_RESULT; + +static PARSE_CLIP_DIR_RESULT parse_clip_direciton_to_option(const char* value) +{ + WINPR_ASSERT(value); + const char* sep = strchr(value, ':'); + if (!sep) + return CLIP_DIR_PARSE_FAIL; + if (option_equals("all", &sep[1])) + return CLIP_DIR_PARSE_ALL; + if (option_equals("off", &sep[1])) + return CLIP_DIR_PARSE_OFF; + if (option_equals("local", &sep[1])) + return CLIP_DIR_PARSE_LOCAL; + if (option_equals("remote", &sep[1])) + return CLIP_DIR_PARSE_REMOTE; + return CLIP_DIR_PARSE_FAIL; +} + +static int parse_tls_ciphers(rdpSettings* settings, const char* Value) +{ + const char* ciphers = nullptr; + if (!Value) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + if (option_equals(Value, "netmon")) + { + ciphers = "ALL:!ECDH:!ADH:!DHE"; + } + else if (option_equals(Value, "ma")) + { + ciphers = "AES128-SHA"; + } + else + { + ciphers = Value; + } + + if (!freerdp_settings_set_string(settings, FreeRDP_AllowedTlsCiphers, ciphers)) + return COMMAND_LINE_ERROR_MEMORY; + return 0; +} + +static int parse_tls_seclevel(rdpSettings* settings, const char* Value) +{ + LONGLONG val = 0; + + if (!value_to_int(Value, &val, 0, 5)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + if (!freerdp_settings_set_uint32(settings, FreeRDP_TlsSecLevel, (UINT32)val)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + return 0; +} + +static int parse_tls_secrets_file(rdpSettings* settings, const char* Value) +{ + if (!Value) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + if (!freerdp_settings_set_string(settings, FreeRDP_TlsSecretsFile, Value)) + return COMMAND_LINE_ERROR_MEMORY; + return 0; +} + +static int parse_tls_enforce(rdpSettings* settings, const char* Value) +{ + UINT16 version = TLS1_2_VERSION; + + if (Value) + { + struct map_t + { + const char* name; + UINT16 version; + }; + const struct map_t map[] = { { "1.0", TLS1_VERSION }, + { "1.1", TLS1_1_VERSION }, + { "1.2", TLS1_2_VERSION } +#if defined(TLS1_3_VERSION) + , + { "1.3", TLS1_3_VERSION } +#endif + }; + + const struct map_t* found = nullptr; + for (size_t x = 0; x < ARRAYSIZE(map); x++) + { + const struct map_t* cur = &map[x]; + if (option_equals(cur->name, Value)) + { + found = cur; + break; + } + } + + if (!found) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + version = found->version; + } + + if (!(freerdp_settings_set_uint16(settings, FreeRDP_TLSMinVersion, version) && + freerdp_settings_set_uint16(settings, FreeRDP_TLSMaxVersion, version))) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + return 0; +} + +static int parse_tls_cipher_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + int rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "tls") + { + if (option_starts_with("ciphers:", arg->Value)) + rc = fail_at(arg, parse_tls_ciphers(settings, &arg->Value[8])); + else if (option_starts_with("seclevel:", arg->Value)) + rc = fail_at(arg, parse_tls_seclevel(settings, &arg->Value[9])); + else if (option_starts_with("secrets-file:", arg->Value)) + rc = fail_at(arg, parse_tls_secrets_file(settings, &arg->Value[13])); + else if (option_starts_with("enforce:", arg->Value)) + rc = fail_at(arg, parse_tls_enforce(settings, &arg->Value[8])); + } + +#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE) + CommandLineSwitchCase(arg, "tls-ciphers") + { + WLog_WARN(TAG, "Option /tls-ciphers is deprecated, use /tls:ciphers instead"); + rc = fail_at(arg, parse_tls_ciphers(settings, arg->Value)); + } + CommandLineSwitchCase(arg, "tls-seclevel") + { + WLog_WARN(TAG, "Option /tls-seclevel is deprecated, use /tls:seclevel instead"); + rc = fail_at(arg, parse_tls_seclevel(settings, arg->Value)); + } + CommandLineSwitchCase(arg, "tls-secrets-file") + { + WLog_WARN(TAG, "Option /tls-secrets-file is deprecated, use /tls:secrets-file instead"); + rc = fail_at(arg, parse_tls_secrets_file(settings, arg->Value)); + } + CommandLineSwitchCase(arg, "enforce-tlsv1_2") + { + WLog_WARN(TAG, "Option /enforce-tlsv1_2 is deprecated, use /tls:enforce:1.2 instead"); + rc = fail_at(arg, parse_tls_enforce(settings, "1.2")); + } +#endif + CommandLineSwitchDefault(arg) + { + } + CommandLineSwitchEnd(arg) + + return rc; +} + +static int parse_tls_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + size_t count = 0; + char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count); + for (size_t x = 0; x < count; x++) + { + COMMAND_LINE_ARGUMENT_A larg = *arg; + larg.Value = ptr[x]; + + int rc = parse_tls_cipher_options(settings, &larg); + if (rc != 0) + { + CommandLineParserFree(ptr); + return rc; + } + } + CommandLineParserFree(ptr); + return 0; +} + +static int parse_gfx_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, TRUE)) + return COMMAND_LINE_ERROR; + + if (arg->Value) + { + int rc = CHANNEL_RC_OK; + size_t count = 0; + char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count); + if (!ptr || (count == 0)) + rc = COMMAND_LINE_ERROR; + else + { + BOOL GfxH264 = FALSE; + BOOL GfxAVC444 = FALSE; + BOOL RemoteFxCodec = FALSE; + BOOL GfxProgressive = FALSE; + BOOL codecSelected = FALSE; + + for (size_t x = 0; x < count; x++) + { + const char* val = ptr[x]; +#ifdef WITH_GFX_H264 + if (option_starts_with("AVC444", val)) + { + const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val); + if (bval == PARSE_FAIL) + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + else + GfxAVC444 = bval != PARSE_OFF; + codecSelected = TRUE; + } + else if (option_starts_with("AVC420", val)) + { + const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val); + if (bval == PARSE_FAIL) + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + else + GfxH264 = bval != PARSE_OFF; + codecSelected = TRUE; + } + else +#endif + if (option_starts_with("RFX", val)) + { + const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val); + if (bval == PARSE_FAIL) + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + else + RemoteFxCodec = bval != PARSE_OFF; + codecSelected = TRUE; + } + else if (option_starts_with("progressive", val)) + { + const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val); + if (bval == PARSE_FAIL) + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + else + GfxProgressive = bval != PARSE_OFF; + codecSelected = TRUE; + } + else if (option_starts_with("mask:", val)) + { + ULONGLONG v = 0; + const char* uv = &val[5]; + if (!value_to_uint(uv, &v, 0, UINT32_MAX)) + rc = COMMAND_LINE_ERROR; + else + { + if (!freerdp_settings_set_uint32(settings, FreeRDP_GfxCapsFilter, + (UINT32)v)) + rc = COMMAND_LINE_ERROR; + } + } + else if (option_starts_with("small-cache", val)) + { + const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val); + if (bval == PARSE_FAIL) + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + else if (!freerdp_settings_set_bool(settings, FreeRDP_GfxSmallCache, + bval != PARSE_OFF)) + rc = COMMAND_LINE_ERROR; + } + else if (option_starts_with("thin-client", val)) + { + const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val); + if (bval == PARSE_FAIL) + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + else if (!freerdp_settings_set_bool(settings, FreeRDP_GfxThinClient, + bval != PARSE_OFF)) + rc = COMMAND_LINE_ERROR; + if ((rc == CHANNEL_RC_OK) && (bval > 0)) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_GfxSmallCache, + bval != PARSE_OFF)) + rc = COMMAND_LINE_ERROR; + } + } + else if (option_starts_with("frame-ack", val)) + { + const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val); + if (bval == PARSE_FAIL) + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + else if (!freerdp_settings_set_bool(settings, FreeRDP_GfxSuspendFrameAck, + bval == PARSE_OFF)) + rc = COMMAND_LINE_ERROR; + } + else + rc = COMMAND_LINE_ERROR; + } + + if ((rc == CHANNEL_RC_OK) && codecSelected) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444, GfxAVC444)) + rc = COMMAND_LINE_ERROR; + if (!freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444v2, GfxAVC444)) + rc = COMMAND_LINE_ERROR; + if (!freerdp_settings_set_bool(settings, FreeRDP_GfxH264, GfxH264)) + rc = COMMAND_LINE_ERROR; + if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, RemoteFxCodec)) + rc = COMMAND_LINE_ERROR; + if (!freerdp_settings_set_bool(settings, FreeRDP_GfxProgressive, GfxProgressive)) + rc = COMMAND_LINE_ERROR; + } + } + CommandLineParserFree(ptr); + if (rc != CHANNEL_RC_OK) + return rc; + } + return CHANNEL_RC_OK; +} + +static int parse_kbd_layout(rdpSettings* settings, const char* value) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(value); + + int rc = 0; + LONGLONG ival = 0; + const BOOL isInt = value_to_int(value, &ival, 1, UINT32_MAX); + if (!isInt) + { + ival = freerdp_map_keyboard_layout_name_to_id(value); + + if (ival == 0) + { + WLog_ERR(TAG, "Could not identify keyboard layout: %s", value); + WLog_ERR(TAG, "Use /list:kbd to list available layouts"); + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + } + + if (rc == 0) + { + if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardLayout, (UINT32)ival)) + rc = COMMAND_LINE_ERROR; + } + return rc; +} + +#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE) +static int parse_codec_cache_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + if (!arg->Value) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheV3Enabled, TRUE)) + return COMMAND_LINE_ERROR; + + if (option_equals(arg->Value, "rfx")) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE)) + return COMMAND_LINE_ERROR; + } + else if (option_equals(arg->Value, "nsc")) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_NSCodec, TRUE)) + return COMMAND_LINE_ERROR; + } + +#if defined(WITH_JPEG) + else if (option_equals(arg->Value, "jpeg")) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_JpegCodec, TRUE)) + return COMMAND_LINE_ERROR; + + if (freerdp_settings_get_uint32(settings, FreeRDP_JpegQuality) == 0) + { + if (!freerdp_settings_set_uint32(settings, FreeRDP_JpegQuality, 75)) + return COMMAND_LINE_ERROR; + } + } + +#endif + return 0; +} +#endif + +static BOOL check_kbd_remap_valid(const char* token) +{ + UINT32 key = 0; + UINT32 value = 0; + + WINPR_ASSERT(token); + /* The remapping is only allowed for scancodes, so maximum is 999=999 */ + if (strlen(token) > 10) + return FALSE; + + if (!freerdp_extract_key_value(token, &key, &value)) + { + WLog_WARN(TAG, "/kbd:remap invalid entry '%s'", token); + return FALSE; + } + return TRUE; +} + +static int parse_host_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + if (!arg->Value) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + if (!freerdp_settings_set_string(settings, FreeRDP_ServerHostname, nullptr)) + return COMMAND_LINE_ERROR_MEMORY; + char* p = strchr(arg->Value, '['); + + /* ipv4 */ + if (!p) + { + const char scheme[] = "://"; + const char* val = strstr(arg->Value, scheme); + if (val) + val += strnlen(scheme, sizeof(scheme)); + else + val = arg->Value; + p = strchr(val, ':'); + + if (p) + { + LONGLONG lval = 0; + size_t length = 0; + + if (!value_to_int(&p[1], &lval, 1, UINT16_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + length = (size_t)(p - arg->Value); + if (!freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, (UINT16)lval)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + if (!freerdp_settings_set_string_len(settings, FreeRDP_ServerHostname, arg->Value, + length)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + else + { + if (!freerdp_settings_set_string(settings, FreeRDP_ServerHostname, arg->Value)) + return COMMAND_LINE_ERROR_MEMORY; + } + } + else /* ipv6 */ + { + size_t length = 0; + char* p2 = strchr(arg->Value, ']'); + + /* not a valid [] ipv6 addr found */ + if (!p2) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + length = (size_t)(p2 - p); + if (!freerdp_settings_set_string_len(settings, FreeRDP_ServerHostname, p + 1, length - 1)) + return COMMAND_LINE_ERROR_MEMORY; + + if (*(p2 + 1) == ':') + { + LONGLONG val = 0; + + if (!value_to_int(&p2[2], &val, 0, UINT16_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + if (!freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, (UINT16)val)) + return COMMAND_LINE_ERROR; + } + + printf("hostname %s port %" PRIu32 "\n", + freerdp_settings_get_string(settings, FreeRDP_ServerHostname), + freerdp_settings_get_uint32(settings, FreeRDP_ServerPort)); + } + return 0; +} + +static int parse_redirect_prefer_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + size_t count = 0; + char* cur = arg->Value; + if (!arg->Value) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + if (!freerdp_settings_set_uint32(settings, FreeRDP_RedirectionPreferType, 0)) + return COMMAND_LINE_ERROR; + + UINT32 value = 0; + do + { + UINT32 mask = 0; + char* next = strchr(cur, ','); + + if (next) + { + *next = '\0'; + next++; + } + + if (option_equals("fqdn", cur)) + mask = 0x06U; + else if (option_equals("ip", cur)) + mask = 0x05U; + else if (option_equals("netbios", cur)) + mask = 0x03U; + else + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + cur = next; + mask = (mask & 0x07); + value |= mask << (count * 3); + count++; + } while (cur != nullptr); + + if (!freerdp_settings_set_uint32(settings, FreeRDP_RedirectionPreferType, value)) + return COMMAND_LINE_ERROR; + + if (count > 3) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + return 0; +} + +static int parse_prevent_session_lock_options(rdpSettings* settings, + const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + if (!freerdp_settings_set_uint32(settings, FreeRDP_FakeMouseMotionInterval, 180)) + return COMMAND_LINE_ERROR_MEMORY; + + if (arg->Flags & COMMAND_LINE_VALUE_PRESENT) + { + LONGLONG val = 0; + + if (!value_to_int(arg->Value, &val, 1, UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + if (!freerdp_settings_set_uint32(settings, FreeRDP_FakeMouseMotionInterval, (UINT32)val)) + return COMMAND_LINE_ERROR_MEMORY; + } + + return 0; +} + +static int parse_vmconnect_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + if (!freerdp_settings_set_bool(settings, FreeRDP_VmConnectMode, TRUE)) + return COMMAND_LINE_ERROR; + + UINT32 port = freerdp_settings_get_uint32(settings, FreeRDP_ServerPort); + if (port == 3389) + port = 2179; + + if (!freerdp_settings_set_uint32(settings, FreeRDP_ServerPort, port)) + return COMMAND_LINE_ERROR; + if (!freerdp_settings_set_bool(settings, FreeRDP_NegotiateSecurityLayer, FALSE)) + return COMMAND_LINE_ERROR; + + if (arg->Flags & COMMAND_LINE_VALUE_PRESENT) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_SendPreconnectionPdu, TRUE)) + return COMMAND_LINE_ERROR; + + if (!freerdp_settings_set_string(settings, FreeRDP_PreconnectionBlob, arg->Value)) + return COMMAND_LINE_ERROR_MEMORY; + } + return 0; +} + +static int parse_size_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + int status = 0; + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + if (!arg->Value) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + char* p = strchr(arg->Value, 'x'); + + if (p) + { + unsigned long w = 0; + unsigned long h = 0; + + if (!parseSizeValue(arg->Value, &w, &h) || (w > UINT16_MAX) || (h > UINT16_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, (UINT32)w)) + return COMMAND_LINE_ERROR; + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, (UINT32)h)) + return COMMAND_LINE_ERROR; + } + else + { + char* str = _strdup(arg->Value); + if (!str) + return COMMAND_LINE_ERROR_MEMORY; + + p = strchr(str, '%'); + + if (p) + { + BOOL partial = FALSE; + + status = COMMAND_LINE_ERROR; + if (strchr(p, 'w')) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_PercentScreenUseWidth, TRUE)) + goto fail; + partial = TRUE; + } + + if (strchr(p, 'h')) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_PercentScreenUseHeight, TRUE)) + goto fail; + partial = TRUE; + } + + if (!partial) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_PercentScreenUseWidth, TRUE)) + goto fail; + if (!freerdp_settings_set_bool(settings, FreeRDP_PercentScreenUseHeight, TRUE)) + goto fail; + } + + *p = '\0'; + { + LONGLONG val = 0; + + if (!value_to_int(str, &val, 0, 100)) + { + status = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + goto fail; + } + + if (!freerdp_settings_set_uint32(settings, FreeRDP_PercentScreen, (UINT32)val)) + goto fail; + } + + status = 0; + } + + fail: + free(str); + } + + return status; +} + +static int parse_monitors_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + if (arg->Flags & COMMAND_LINE_VALUE_PRESENT) + { + size_t count = 0; + UINT32* MonitorIds = nullptr; + char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count); + + if (!ptr) + return COMMAND_LINE_ERROR_MEMORY; + + if (count > 16) + count = 16; + + if (!freerdp_settings_set_pointer_len(settings, FreeRDP_MonitorIds, nullptr, count)) + { + CommandLineParserFree(ptr); + return FALSE; + } + + MonitorIds = freerdp_settings_get_pointer_array_writable(settings, FreeRDP_MonitorIds, 0); + for (UINT32 i = 0; i < count; i++) + { + LONGLONG val = 0; + + if (!value_to_int(ptr[i], &val, 0, UINT16_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + MonitorIds[i] = (UINT32)val; + } + + CommandLineParserFree(ptr); + } + + return 0; +} + +static int parse_dynamic_resolution_options(rdpSettings* settings, + const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + const BOOL val = arg->Value != nullptr; + + if (val && freerdp_settings_get_bool(settings, FreeRDP_SmartSizing)) + { + WLog_ERR(TAG, "Smart sizing and dynamic resolution are mutually exclusive options"); + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + + if (!freerdp_settings_set_bool(settings, FreeRDP_SupportDisplayControl, val)) + return COMMAND_LINE_ERROR; + if (!freerdp_settings_set_bool(settings, FreeRDP_DynamicResolutionUpdate, val)) + return COMMAND_LINE_ERROR; + + return 0; +} + +static int parse_smart_sizing_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + if (freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate)) + { + WLog_ERR(TAG, "Smart sizing and dynamic resolution are mutually exclusive options"); + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + + if (!freerdp_settings_set_bool(settings, FreeRDP_SmartSizing, TRUE)) + return COMMAND_LINE_ERROR; + + if (arg->Value) + { + unsigned long w = 0; + unsigned long h = 0; + + if (!parseSizeValue(arg->Value, &w, &h) || (w > UINT16_MAX) || (h > UINT16_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + if (!freerdp_settings_set_uint32(settings, FreeRDP_SmartSizingWidth, (UINT32)w)) + return COMMAND_LINE_ERROR; + if (!freerdp_settings_set_uint32(settings, FreeRDP_SmartSizingHeight, (UINT32)h)) + return COMMAND_LINE_ERROR; + } + return 0; +} + +static int parse_bpp_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + LONGLONG val = 0; + + if (!value_to_int(arg->Value, &val, 0, UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + switch (val) + { + case 32: + case 24: + case 16: + case 15: + case 8: + if (!freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, (UINT32)val)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + break; + + default: + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + return 0; +} + +static int parse_kbd_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + int rc = CHANNEL_RC_OK; + size_t count = 0; + char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count); + if (!ptr || (count == 0)) + rc = COMMAND_LINE_ERROR; + else + { + for (size_t x = 0; x < count; x++) + { + const char* val = ptr[x]; + + if (option_starts_with("remap:", val)) + { + /* Append this new occurrence to the already existing list */ + char* now = _strdup(&val[6]); + const char* old = + freerdp_settings_get_string(settings, FreeRDP_KeyboardRemappingList); + + /* Basic sanity test. Entries must be like =, e.g. 1=2 */ + if (!check_kbd_remap_valid(now)) + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + else if (old) + { + const size_t olen = strlen(old); + const size_t alen = strlen(now); + const size_t tlen = olen + alen + 2; + char* tmp = calloc(tlen, sizeof(char)); + if (!tmp) + rc = COMMAND_LINE_ERROR_MEMORY; + else + (void)_snprintf(tmp, tlen, "%s,%s", old, now); + free(now); + now = tmp; + } + + if (rc == 0) + { + if (!freerdp_settings_set_string(settings, FreeRDP_KeyboardRemappingList, now)) + rc = COMMAND_LINE_ERROR; + } + free(now); + } + else if (option_starts_with("layout:", val)) + { + rc = parse_kbd_layout(settings, &val[7]); + } + else if (option_starts_with("lang:", val)) + { + LONGLONG ival = 0; + const BOOL isInt = value_to_int(&val[5], &ival, 1, UINT32_MAX); + if (!isInt) + ival = freerdp_get_locale_id_from_string(&val[5]); + + if (ival <= 0) + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + else if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardCodePage, + (UINT32)ival)) + rc = COMMAND_LINE_ERROR; + } + else if (option_starts_with("type:", val)) + { + LONGLONG ival = 0; + const BOOL isInt = value_to_int(&val[5], &ival, 1, UINT32_MAX); + if (!isInt) + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + else if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardType, (UINT32)ival)) + rc = COMMAND_LINE_ERROR; + } + else if (option_starts_with("subtype:", val)) + { + LONGLONG ival = 0; + const BOOL isInt = value_to_int(&val[8], &ival, 1, UINT32_MAX); + if (!isInt) + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + else if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardSubType, + (UINT32)ival)) + rc = COMMAND_LINE_ERROR; + } + else if (option_starts_with("fn-key:", val)) + { + LONGLONG ival = 0; + const BOOL isInt = value_to_int(&val[7], &ival, 1, UINT32_MAX); + if (!isInt) + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + else if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardFunctionKey, + (UINT32)ival)) + rc = COMMAND_LINE_ERROR; + } + else if (option_starts_with("unicode", val)) + { + const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val); + if (bval == PARSE_FAIL) + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + else if (!freerdp_settings_set_bool(settings, FreeRDP_UnicodeInput, + bval != PARSE_OFF)) + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + else if (option_starts_with("pipe:", val)) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_UnicodeInput, TRUE)) + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + else if (!freerdp_settings_set_string(settings, FreeRDP_KeyboardPipeName, &val[5])) + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } +#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE) + else if (count == 1) + { + /* Legacy, allow /kbd: for setting keyboard layout */ + rc = parse_kbd_layout(settings, val); + } +#endif + else + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + if (rc != 0) + break; + } + } + CommandLineParserFree(ptr); + return rc; +} + +static int parse_proxy_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + /* initial value */ + if (!freerdp_settings_set_uint32(settings, FreeRDP_ProxyType, PROXY_TYPE_HTTP)) + return COMMAND_LINE_ERROR_MEMORY; + + if (arg->Flags & COMMAND_LINE_VALUE_PRESENT) + { + const char* cur = arg->Value; + + if (!cur) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + /* value is [scheme://][user:password@]hostname:port */ + if (!proxy_parse_uri(settings, cur)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + else + { + WLog_ERR(TAG, "Option http-proxy needs argument."); + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + return 0; +} + +static int parse_dump_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + BOOL failed = FALSE; + size_t count = 0; + char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count); + if (!ptr) + failed = TRUE; + else + { + BOOL modernsyntax = FALSE; + BOOL oldsyntax = FALSE; + for (size_t x = 0; (x < count) && !failed; x++) + { + const char* carg = ptr[x]; + if (option_starts_with("file:", carg)) + { + const char* val = &carg[5]; + if (oldsyntax) + failed = TRUE; + else if (!freerdp_settings_set_string(settings, FreeRDP_TransportDumpFile, val)) + failed = TRUE; + modernsyntax = TRUE; + } + else if (option_equals("replay", carg)) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_TransportDump, FALSE)) + failed = TRUE; + else if (!freerdp_settings_set_bool(settings, FreeRDP_TransportDumpReplay, TRUE)) + failed = TRUE; + } + else if (option_equals("record", carg)) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_TransportDump, TRUE)) + failed = TRUE; + else if (!freerdp_settings_set_bool(settings, FreeRDP_TransportDumpReplay, FALSE)) + failed = TRUE; + } + else if (option_equals("nodelay", carg)) + { + if (oldsyntax) + failed = TRUE; + else if (!freerdp_settings_set_bool(settings, FreeRDP_TransportDumpReplayNodelay, + TRUE)) + failed = TRUE; + modernsyntax = TRUE; + } + else + { + /* compat: + * support syntax record, and replay, + */ + if (modernsyntax) + failed = TRUE; + else if (!freerdp_settings_set_string(settings, FreeRDP_TransportDumpFile, carg)) + failed = TRUE; + oldsyntax = TRUE; + } + } + + if (oldsyntax && (count != 2)) + failed = TRUE; + } + CommandLineParserFree(ptr); + if (failed) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + return 0; +} + +static int parse_clipboard_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + if (arg->Value == BoolValueTrue || arg->Value == BoolValueFalse) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectClipboard, + (arg->Value == BoolValueTrue))) + return COMMAND_LINE_ERROR; + } + else + { + int rc = 0; + size_t count = 0; + char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count); + for (size_t x = 0; (x < count) && (rc == 0); x++) + { + const char* usesel = "use-selection:"; + + const char* cur = ptr[x]; + if (option_starts_with(usesel, cur)) + { + const char* val = &cur[strlen(usesel)]; + if (!freerdp_settings_set_string(settings, FreeRDP_ClipboardUseSelection, val)) + rc = COMMAND_LINE_ERROR_MEMORY; + if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectClipboard, TRUE)) + return COMMAND_LINE_ERROR; + } + else if (option_starts_with("direction-to", cur)) + { + const UINT32 mask = + freerdp_settings_get_uint32(settings, FreeRDP_ClipboardFeatureMask) & + (uint32_t)~(CLIPRDR_FLAG_LOCAL_TO_REMOTE | CLIPRDR_FLAG_REMOTE_TO_LOCAL); + const PARSE_CLIP_DIR_RESULT bval = parse_clip_direciton_to_option(cur); + UINT32 bflags = 0; + switch (bval) + { + case CLIP_DIR_PARSE_ALL: + bflags |= CLIPRDR_FLAG_LOCAL_TO_REMOTE | CLIPRDR_FLAG_REMOTE_TO_LOCAL; + break; + case CLIP_DIR_PARSE_LOCAL: + bflags |= CLIPRDR_FLAG_REMOTE_TO_LOCAL; + break; + case CLIP_DIR_PARSE_REMOTE: + bflags |= CLIPRDR_FLAG_LOCAL_TO_REMOTE; + break; + case CLIP_DIR_PARSE_OFF: + break; + case CLIP_DIR_PARSE_FAIL: + default: + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + break; + } + + if (!freerdp_settings_set_uint32(settings, FreeRDP_ClipboardFeatureMask, + mask | bflags)) + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + else if (option_starts_with("files-to", cur)) + { + const UINT32 mask = + freerdp_settings_get_uint32(settings, FreeRDP_ClipboardFeatureMask) & + (uint32_t)~(CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES | + CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES); + const PARSE_CLIP_DIR_RESULT bval = parse_clip_direciton_to_option(cur); + UINT32 bflags = 0; + switch (bval) + { + case CLIP_DIR_PARSE_ALL: + bflags |= + CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES | CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES; + break; + case CLIP_DIR_PARSE_LOCAL: + bflags |= CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES; + break; + case CLIP_DIR_PARSE_REMOTE: + bflags |= CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES; + break; + case CLIP_DIR_PARSE_OFF: + break; + case CLIP_DIR_PARSE_FAIL: + default: + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + break; + } + + if (!freerdp_settings_set_uint32(settings, FreeRDP_ClipboardFeatureMask, + mask | bflags)) + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + else + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + CommandLineParserFree(ptr); + + if (rc) + return rc; + } + return 0; +} + +static int parse_audio_mode_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + LONGLONG val = 0; + + if (!value_to_int(arg->Value, &val, 0, UINT32_MAX)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + switch (val) + { + case AUDIO_MODE_REDIRECT: + if (!freerdp_settings_set_bool(settings, FreeRDP_AudioPlayback, TRUE)) + return COMMAND_LINE_ERROR; + break; + + case AUDIO_MODE_PLAY_ON_SERVER: + if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteConsoleAudio, TRUE)) + return COMMAND_LINE_ERROR; + break; + + case AUDIO_MODE_NONE: + if (!freerdp_settings_set_bool(settings, FreeRDP_AudioPlayback, FALSE)) + return COMMAND_LINE_ERROR; + if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteConsoleAudio, FALSE)) + return COMMAND_LINE_ERROR; + break; + + default: + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + return 0; +} + +static int parse_network_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + UINT32 type = CONNECTION_TYPE_INVALID; + + if (option_equals(arg->Value, "invalid")) + type = CONNECTION_TYPE_INVALID; + else if (option_equals(arg->Value, "modem")) + type = CONNECTION_TYPE_MODEM; + else if (option_equals(arg->Value, "broadband")) + type = CONNECTION_TYPE_BROADBAND_HIGH; + else if (option_equals(arg->Value, "broadband-low")) + type = CONNECTION_TYPE_BROADBAND_LOW; + else if (option_equals(arg->Value, "broadband-high")) + type = CONNECTION_TYPE_BROADBAND_HIGH; + else if (option_equals(arg->Value, "wan")) + type = CONNECTION_TYPE_WAN; + else if (option_equals(arg->Value, "lan")) + type = CONNECTION_TYPE_LAN; + else if ((option_equals(arg->Value, "autodetect")) || (option_equals(arg->Value, "auto")) || + (option_equals(arg->Value, "detect"))) + { + type = CONNECTION_TYPE_AUTODETECT; + } + else + { + LONGLONG val = 0; + + if (!value_to_int(arg->Value, &val, 0, 7)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + type = (UINT32)val; + } + + if (!freerdp_set_connection_type(settings, type)) + return COMMAND_LINE_ERROR; + return 0; +} + +static int parse_sec_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + size_t count = 0; + char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count); + if (count == 0) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + FreeRDP_Settings_Keys_Bool singleOptionWithoutOnOff = FreeRDP_BOOL_UNUSED; + for (size_t x = 0; x < count; x++) + { + const char* cur = ptr[x]; + const PARSE_ON_OFF_RESULT bval = parse_on_off_option(cur); + if (bval == PARSE_FAIL) + { + CommandLineParserFree(ptr); + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + + const BOOL val = bval != PARSE_OFF; + FreeRDP_Settings_Keys_Bool id = FreeRDP_BOOL_UNUSED; + if (option_starts_with("rdp", cur)) /* Standard RDP */ + { + id = FreeRDP_RdpSecurity; + if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, val)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + else if (option_starts_with("tls", cur)) /* TLS */ + id = FreeRDP_TlsSecurity; + else if (option_starts_with("nla", cur)) /* NLA */ + id = FreeRDP_NlaSecurity; + else if (option_starts_with("ext", cur)) /* NLA Extended */ + id = FreeRDP_ExtSecurity; + else if (option_equals("aad", cur)) /* RDSAAD */ + id = FreeRDP_AadSecurity; + else + { + WLog_ERR(TAG, "unknown protocol security: %s", arg->Value); + CommandLineParserFree(ptr); + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + + if ((bval == PARSE_NONE) && (count == 1)) + singleOptionWithoutOnOff = id; + if (!freerdp_settings_set_bool(settings, id, val)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + + if (singleOptionWithoutOnOff != FreeRDP_BOOL_UNUSED) + { + const FreeRDP_Settings_Keys_Bool options[] = { FreeRDP_AadSecurity, + FreeRDP_UseRdpSecurityLayer, + FreeRDP_RdpSecurity, FreeRDP_NlaSecurity, + FreeRDP_TlsSecurity }; + + for (size_t i = 0; i < ARRAYSIZE(options); i++) + { + if (!freerdp_settings_set_bool(settings, options[i], FALSE)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + + if (!freerdp_settings_set_bool(settings, singleOptionWithoutOnOff, TRUE)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + if (singleOptionWithoutOnOff == FreeRDP_RdpSecurity) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_UseRdpSecurityLayer, TRUE)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + } + CommandLineParserFree(ptr); + return 0; +} + +static int parse_encryption_methods_options(rdpSettings* settings, + const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + if (arg->Flags & COMMAND_LINE_VALUE_PRESENT) + { + size_t count = 0; + char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count); + + UINT32 EncryptionMethods = 0; + for (UINT32 i = 0; i < count; i++) + { + if (option_equals(ptr[i], "40")) + EncryptionMethods |= ENCRYPTION_METHOD_40BIT; + else if (option_equals(ptr[i], "56")) + EncryptionMethods |= ENCRYPTION_METHOD_56BIT; + else if (option_equals(ptr[i], "128")) + EncryptionMethods |= ENCRYPTION_METHOD_128BIT; + else if (option_equals(ptr[i], "FIPS")) + EncryptionMethods |= ENCRYPTION_METHOD_FIPS; + else + WLog_ERR(TAG, "unknown encryption method '%s'", ptr[i]); + } + + if (!freerdp_settings_set_uint32(settings, FreeRDP_EncryptionMethods, EncryptionMethods)) + return COMMAND_LINE_ERROR; + CommandLineParserFree(ptr); + } + return 0; +} + +static int parse_cert_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + int rc = 0; + size_t count = 0; + char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count); + for (size_t x = 0; (x < count) && (rc == 0); x++) + { + const char deny[] = "deny"; + const char ignore[] = "ignore"; + const char tofu[] = "tofu"; + const char name[] = "name:"; + const char fingerprints[] = "fingerprint:"; + + const char* cur = ptr[x]; + if (option_equals(deny, cur)) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_AutoDenyCertificate, TRUE)) + return COMMAND_LINE_ERROR; + } + else if (option_equals(ignore, cur)) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_IgnoreCertificate, TRUE)) + return COMMAND_LINE_ERROR; + } + else if (option_equals(tofu, cur)) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_AutoAcceptCertificate, TRUE)) + return COMMAND_LINE_ERROR; + } + else if (option_starts_with(name, cur)) + { + const char* val = &cur[strnlen(name, sizeof(name))]; + if (!freerdp_settings_set_string(settings, FreeRDP_CertificateName, val)) + rc = COMMAND_LINE_ERROR_MEMORY; + } + else if (option_starts_with(fingerprints, cur)) + { + const char* val = &cur[strnlen(fingerprints, sizeof(fingerprints))]; + if (!freerdp_settings_append_string(settings, FreeRDP_CertificateAcceptedFingerprints, + ",", val)) + rc = COMMAND_LINE_ERROR_MEMORY; + } + else + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + CommandLineParserFree(ptr); + + return rc; +} + +static int parse_mouse_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + size_t count = 0; + char** ptr = CommandLineParseCommaSeparatedValuesEx("mouse", arg->Value, &count); + int rc = 0; + if (ptr) + { + for (size_t x = 1; x < count; x++) + { + const char* cur = ptr[x]; + + const PARSE_ON_OFF_RESULT bval = parse_on_off_option(cur); + if (bval == PARSE_FAIL) + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + else + { + const BOOL val = bval != PARSE_OFF; + + if (option_starts_with("relative", cur)) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_MouseUseRelativeMove, val)) + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + else if (option_starts_with("grab", cur)) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_GrabMouse, val)) + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + } + + if (rc != 0) + break; + } + } + CommandLineParserFree(ptr); + + return rc; +} + +static int parse_floatbar_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + /* Defaults are enabled, visible, sticky, fullscreen */ + UINT32 Floatbar = 0x0017; + + if (arg->Value) + { + char* start = arg->Value; + + do + { + char* cur = start; + start = strchr(start, ','); + + if (start) + { + *start = '\0'; + start = start + 1; + } + + /* sticky:[on|off] */ + if (option_starts_with("sticky:", cur)) + { + Floatbar &= ~0x02u; + + const PARSE_ON_OFF_RESULT bval = parse_on_off_option(cur); + switch (bval) + { + case PARSE_ON: + case PARSE_NONE: + Floatbar |= 0x02u; + break; + case PARSE_OFF: + Floatbar &= ~0x02u; + break; + case PARSE_FAIL: + default: + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + } + /* default:[visible|hidden] */ + else if (option_starts_with("default:", cur)) + { + const char* val = cur + 8; + Floatbar &= ~0x04u; + + if (option_equals("visible", val)) + Floatbar |= 0x04u; + else if (option_equals("hidden", val)) + Floatbar &= ~0x04u; + else + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + /* show:[always|fullscreen|window] */ + else if (option_starts_with("show:", cur)) + { + const char* val = cur + 5; + Floatbar &= ~0x30u; + + if (option_equals("always", val)) + Floatbar |= 0x30u; + else if (option_equals("fullscreen", val)) + Floatbar |= 0x10u; + else if (option_equals("window", val)) + Floatbar |= 0x20u; + else + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + else + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } while (start); + } + if (!freerdp_settings_set_uint32(settings, FreeRDP_Floatbar, Floatbar)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + return 0; +} + +static int parse_reconnect_cookie_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + BYTE* base64 = nullptr; + size_t length = 0; + if (!arg->Value) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + crypto_base64_decode((const char*)(arg->Value), strlen(arg->Value), &base64, &length); + + if ((base64 != nullptr) && (length == sizeof(ARC_SC_PRIVATE_PACKET))) + { + if (!freerdp_settings_set_pointer_len(settings, FreeRDP_ServerAutoReconnectCookie, base64, + 1)) + return COMMAND_LINE_ERROR; + } + else + { + WLog_ERR(TAG, "reconnect-cookie: invalid base64 '%s'", arg->Value); + } + + free(base64); + return 0; +} + +static BOOL set_monitor_override(rdpSettings* settings, uint64_t flag) +{ + const FreeRDP_Settings_Keys_UInt64 key = FreeRDP_MonitorOverrideFlags; + uint64_t mask = freerdp_settings_get_uint64(settings, key); + mask |= flag; + return freerdp_settings_set_uint64(settings, key, mask); +} + +static int parse_scale_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + LONGLONG val = 0; + + if (!value_to_int(arg->Value, &val, 100, 180)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + switch (val) + { + case 100: + case 140: + case 180: + if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopScaleFactor, (UINT32)val)) + return COMMAND_LINE_ERROR; + if (!freerdp_settings_set_uint32(settings, FreeRDP_DeviceScaleFactor, (UINT32)val)) + return COMMAND_LINE_ERROR; + if (!set_monitor_override(settings, FREERDP_MONITOR_OVERRIDE_DESKTOP_SCALE | + FREERDP_MONITOR_OVERRIDE_DEVICE_SCALE)) + return fail_at(arg, COMMAND_LINE_ERROR); + break; + + default: + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + return 0; +} + +static int parse_scale_device_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + LONGLONG val = 0; + + if (!value_to_int(arg->Value, &val, 100, 180)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + switch (val) + { + case 100: + case 140: + case 180: + if (!freerdp_settings_set_uint32(settings, FreeRDP_DeviceScaleFactor, (UINT32)val)) + return COMMAND_LINE_ERROR; + if (!set_monitor_override(settings, FREERDP_MONITOR_OVERRIDE_DEVICE_SCALE)) + return fail_at(arg, COMMAND_LINE_ERROR); + break; + + default: + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + return 0; +} + +static int parse_smartcard_logon_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + size_t count = 0; + + if (!freerdp_settings_set_bool(settings, FreeRDP_SmartcardLogon, TRUE)) + return COMMAND_LINE_ERROR; + + char** ptr = CommandLineParseCommaSeparatedValuesEx("smartcard-logon", arg->Value, &count); + if (ptr) + { + const CmdLineSubOptions opts[] = { + { "cert:", FreeRDP_SmartcardCertificate, CMDLINE_SUBOPTION_FILE, + setSmartcardEmulation }, + { "key:", FreeRDP_SmartcardPrivateKey, CMDLINE_SUBOPTION_FILE, setSmartcardEmulation }, + { "pin:", FreeRDP_Password, CMDLINE_SUBOPTION_STRING, nullptr }, + { "csp:", FreeRDP_CspName, CMDLINE_SUBOPTION_STRING, nullptr }, + { "reader:", FreeRDP_ReaderName, CMDLINE_SUBOPTION_STRING, nullptr }, + { "card:", FreeRDP_CardName, CMDLINE_SUBOPTION_STRING, nullptr }, + { "container:", FreeRDP_ContainerName, CMDLINE_SUBOPTION_STRING, nullptr } + }; + + for (size_t x = 1; x < count; x++) + { + const char* cur = ptr[x]; + if (!parseSubOptions(settings, opts, ARRAYSIZE(opts), cur)) + { + CommandLineParserFree(ptr); + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + } + } + } + CommandLineParserFree(ptr); + return 0; +} + +static int parse_tune_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + size_t count = 0; + char** ptr = CommandLineParseCommaSeparatedValuesEx("tune", arg->Value, &count); + if (!ptr) + return COMMAND_LINE_ERROR; + for (size_t x = 1; x < count; x++) + { + const char* cur = ptr[x]; + char* sep = strchr(cur, ':'); + if (!sep) + { + CommandLineParserFree(ptr); + return COMMAND_LINE_ERROR; + } + *sep++ = '\0'; + if (!freerdp_settings_set_value_for_name(settings, cur, sep)) + { + CommandLineParserFree(ptr); + return COMMAND_LINE_ERROR; + } + } + + CommandLineParserFree(ptr); + return 0; +} + +static int parse_app_option_program(rdpSettings* settings, const char* cmd) +{ + const FreeRDP_Settings_Keys_Bool ids[] = { FreeRDP_RemoteApplicationMode, + FreeRDP_RemoteAppLanguageBarSupported, + FreeRDP_Workarea, FreeRDP_DisableWallpaper, + FreeRDP_DisableFullWindowDrag }; + + if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationProgram, cmd)) + return COMMAND_LINE_ERROR_MEMORY; + + for (size_t y = 0; y < ARRAYSIZE(ids); y++) + { + if (!freerdp_settings_set_bool(settings, ids[y], TRUE)) + return COMMAND_LINE_ERROR; + } + return CHANNEL_RC_OK; +} + +static int parse_aad_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + int rc = CHANNEL_RC_OK; + size_t count = 0; + char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count); + if (!ptr || (count == 0)) + rc = COMMAND_LINE_ERROR; + else + { + struct app_map + { + const char* name; + SSIZE_T id; + int (*fkt)(rdpSettings* settings, const char* value); + }; + const struct app_map amap[] = { + { "tenantid:", FreeRDP_GatewayAvdAadtenantid, nullptr }, + { "ad:", FreeRDP_GatewayAzureActiveDirectory, nullptr }, + { "avd-access:", FreeRDP_GatewayAvdAccessAadFormat, nullptr }, + { "avd-token:", FreeRDP_GatewayAvdAccessTokenFormat, nullptr }, + { "avd-scope:", FreeRDP_GatewayAvdScope, nullptr } + + }; + for (size_t x = 0; x < count; x++) + { + BOOL handled = FALSE; + const char* val = ptr[x]; + + if (option_starts_with("use-tenantid", val)) + { + PARSE_ON_OFF_RESULT bval = parse_on_off_option(val); + if (bval == PARSE_FAIL) + { + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + break; + } + else + { + if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayAvdUseTenantid, + bval != PARSE_OFF)) + { + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + break; + } + } + continue; + } + for (size_t y = 0; y < ARRAYSIZE(amap); y++) + { + const struct app_map* cur = &amap[y]; + if (option_starts_with(cur->name, val)) + { + const char* xval = &val[strlen(cur->name)]; + if (cur->fkt) + rc = cur->fkt(settings, xval); + else + { + const char* name = freerdp_settings_get_name_for_key(cur->id); + if (!freerdp_settings_set_value_for_name(settings, name, xval)) + rc = COMMAND_LINE_ERROR_MEMORY; + } + + handled = TRUE; + break; + } + } + + if (!handled) + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + if (rc != 0) + break; + } + } + + CommandLineParserFree(ptr); + return rc; +} + +static int parse_app_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + int rc = CHANNEL_RC_OK; + size_t count = 0; + char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count); + if (!ptr || (count == 0)) + rc = COMMAND_LINE_ERROR; + else + { + struct app_map + { + const char* name; + SSIZE_T id; + int (*fkt)(rdpSettings* settings, const char* value); + }; + const struct app_map amap[] = { + { "program:", FreeRDP_RemoteApplicationProgram, parse_app_option_program }, + { "workdir:", FreeRDP_RemoteApplicationWorkingDir, nullptr }, + { "name:", FreeRDP_RemoteApplicationName, nullptr }, + { "icon:", FreeRDP_RemoteApplicationIcon, nullptr }, + { "cmd:", FreeRDP_RemoteApplicationCmdLine, nullptr }, + { "file:", FreeRDP_RemoteApplicationFile, nullptr }, + { "guid:", FreeRDP_RemoteApplicationGuid, nullptr }, + { "hidef:", FreeRDP_HiDefRemoteApp, nullptr } + }; + for (size_t x = 0; x < count; x++) + { + BOOL handled = FALSE; + const char* val = ptr[x]; + + for (size_t y = 0; y < ARRAYSIZE(amap); y++) + { + const struct app_map* cur = &amap[y]; + if (option_starts_with(cur->name, val)) + { + const char* xval = &val[strlen(cur->name)]; + if (cur->fkt) + rc = cur->fkt(settings, xval); + else + { + const char* name = freerdp_settings_get_name_for_key(cur->id); + if (!freerdp_settings_set_value_for_name(settings, name, xval)) + rc = COMMAND_LINE_ERROR_MEMORY; + } + + handled = TRUE; + break; + } + } + +#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE) + if (!handled && (count == 1)) + { + /* Legacy path, allow /app:command and /app:||command syntax */ + rc = parse_app_option_program(settings, val); + } + else +#endif + if (!handled) + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + if (rc != 0) + break; + } + } + + CommandLineParserFree(ptr); + return rc; +} + +static int parse_cache_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + int rc = CHANNEL_RC_OK; + size_t count = 0; + char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count); + if (!ptr || (count == 0)) + return COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + + for (size_t x = 0; x < count; x++) + { + const char* val = ptr[x]; + + if (option_starts_with("codec:", val)) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheV3Enabled, TRUE)) + rc = COMMAND_LINE_ERROR; + else if (option_equals(arg->Value, "rfx")) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteFxCodec, TRUE)) + rc = COMMAND_LINE_ERROR; + } + else if (option_equals(arg->Value, "nsc")) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_NSCodec, TRUE)) + rc = COMMAND_LINE_ERROR; + } + +#if defined(WITH_JPEG) + else if (option_equals(arg->Value, "jpeg")) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_JpegCodec, TRUE)) + rc = COMMAND_LINE_ERROR; + + if (freerdp_settings_get_uint32(settings, FreeRDP_JpegQuality) == 0) + { + if (!freerdp_settings_set_uint32(settings, FreeRDP_JpegQuality, 75)) + return COMMAND_LINE_ERROR; + } + } + +#endif + } + else if (option_starts_with("persist-file:", val)) + { + + if (!freerdp_settings_set_string(settings, FreeRDP_BitmapCachePersistFile, &val[13])) + rc = COMMAND_LINE_ERROR_MEMORY; + else if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCachePersistEnabled, TRUE)) + rc = COMMAND_LINE_ERROR; + } + else + { + const PARSE_ON_OFF_RESULT bval = parse_on_off_option(val); + if (bval == PARSE_FAIL) + rc = COMMAND_LINE_ERROR_UNEXPECTED_VALUE; + else + { + if (option_starts_with("bitmap", val)) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheEnabled, + bval != PARSE_OFF)) + rc = COMMAND_LINE_ERROR; + } + else if (option_starts_with("glyph", val)) + { + if (!freerdp_settings_set_uint32(settings, FreeRDP_GlyphSupportLevel, + bval != PARSE_OFF ? GLYPH_SUPPORT_FULL + : GLYPH_SUPPORT_NONE)) + rc = COMMAND_LINE_ERROR; + } + else if (option_starts_with("persist", val)) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCachePersistEnabled, + bval != PARSE_OFF)) + rc = COMMAND_LINE_ERROR; + } + else if (option_starts_with("offscreen", val)) + { + if (!freerdp_settings_set_uint32(settings, FreeRDP_OffscreenSupportLevel, + bval != PARSE_OFF)) + rc = COMMAND_LINE_ERROR; + } + } + } + } + + CommandLineParserFree(ptr); + return rc; +} + +static BOOL parse_gateway_host_option(rdpSettings* settings, const char* host) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(host); + + char* name = nullptr; + int port = -1; + if (!freerdp_parse_hostname(host, &name, &port)) + return FALSE; + const BOOL rc = freerdp_settings_set_string(settings, FreeRDP_GatewayHostname, name); + free(name); + if (!rc) + return FALSE; + if (port != -1) + { + if (!freerdp_settings_set_uint32(settings, FreeRDP_GatewayPort, (UINT32)port)) + return FALSE; + } + if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayUseSameCredentials, TRUE)) + return FALSE; + if (!freerdp_set_gateway_usage_method(settings, TSC_PROXY_MODE_DIRECT)) + return FALSE; + + return TRUE; +} + +static BOOL parse_gateway_cred_option(rdpSettings* settings, const char* value, + FreeRDP_Settings_Keys_String what) +{ + WINPR_ASSERT(settings); + WINPR_ASSERT(value); + + switch (what) + { + case FreeRDP_GatewayUsername: + if (!freerdp_parse_username_settings(value, settings, FreeRDP_GatewayUsername, + FreeRDP_GatewayDomain)) + return FALSE; + break; + default: + if (!freerdp_settings_set_string(settings, what, value)) + return FALSE; + break; + } + + return freerdp_settings_set_bool(settings, FreeRDP_GatewayUseSameCredentials, FALSE); +} + +static BOOL parse_gateway_type_option(rdpSettings* settings, const char* value) +{ + BOOL rc = FALSE; + + WINPR_ASSERT(settings); + WINPR_ASSERT(value); + + if (option_equals(value, "rpc")) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayRpcTransport, TRUE) || + !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpTransport, FALSE) || + !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpUseWebsockets, FALSE) || + !freerdp_settings_set_bool(settings, FreeRDP_GatewayArmTransport, FALSE)) + return FALSE; + rc = TRUE; + } + else + { + if (option_equals(value, "http")) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayRpcTransport, FALSE) || + !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpTransport, TRUE) || + !freerdp_settings_set_bool(settings, FreeRDP_GatewayArmTransport, FALSE)) + return FALSE; + rc = TRUE; + } + else if (option_equals(value, "auto")) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayRpcTransport, TRUE) || + !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpTransport, TRUE) || + !freerdp_settings_set_bool(settings, FreeRDP_GatewayArmTransport, FALSE)) + return FALSE; + rc = TRUE; + } + else if (option_equals(value, "arm")) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayRpcTransport, FALSE) || + !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpTransport, FALSE) || + !freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpUseWebsockets, FALSE) || + !freerdp_settings_set_bool(settings, FreeRDP_GatewayArmTransport, TRUE)) + return FALSE; + rc = TRUE; + } + } + return rc; +} + +static BOOL parse_gateway_usage_option(rdpSettings* settings, const char* value) +{ + UINT32 type = 0; + + WINPR_ASSERT(settings); + WINPR_ASSERT(value); + + if (option_equals(value, "none")) + type = TSC_PROXY_MODE_NONE_DIRECT; + else if (option_equals(value, "direct")) + type = TSC_PROXY_MODE_DIRECT; + else if (option_equals(value, "detect")) + type = TSC_PROXY_MODE_DETECT; + else if (option_equals(value, "default")) + type = TSC_PROXY_MODE_DEFAULT; + else + { + LONGLONG val = 0; + + if (!value_to_int(value, &val, TSC_PROXY_MODE_NONE_DIRECT, TSC_PROXY_MODE_NONE_DETECT)) + return FALSE; + } + + return freerdp_set_gateway_usage_method(settings, type); +} + +static char* unescape(const char* str) +{ + char* copy = _strdup(str); + if (!copy) + return nullptr; + + bool escaped = false; + char* dst = copy; + while (*str != '\0') + { + char cur = *str++; + + switch (cur) + { + case '\\': + if (!escaped) + { + escaped = true; + continue; + } + // fallthrough + WINPR_FALLTHROUGH + default: + *dst++ = cur; + escaped = false; + break; + } + } + + *dst = '\0'; + + return copy; +} + +static BOOL parse_gateway_options(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + char* argval = nullptr; + BOOL rc = FALSE; + + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + size_t count = 0; + char** ptr = CommandLineParseCommaSeparatedValues(arg->Value, &count); + if (count == 0) + return TRUE; + WINPR_ASSERT(ptr); + + if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayEnabled, TRUE)) + goto fail; + + { + BOOL allowHttpOpts = FALSE; + for (size_t x = 0; x < count; x++) + { + BOOL validOption = FALSE; + free(argval); + argval = unescape(ptr[x]); + if (!argval) + goto fail; + + const char* gw = option_starts_with("g:", argval); + if (gw) + { + if (!parse_gateway_host_option(settings, gw)) + goto fail; + validOption = TRUE; + allowHttpOpts = FALSE; + } + + const char* gu = option_starts_with("u:", argval); + if (gu) + { + if (!parse_gateway_cred_option(settings, gu, FreeRDP_GatewayUsername)) + goto fail; + validOption = TRUE; + allowHttpOpts = FALSE; + } + + const char* gd = option_starts_with("d:", argval); + if (gd) + { + if (!parse_gateway_cred_option(settings, gd, FreeRDP_GatewayDomain)) + goto fail; + validOption = TRUE; + allowHttpOpts = FALSE; + } + + const char* gp = option_starts_with("p:", argval); + if (gp) + { + if (!parse_gateway_cred_option(settings, gp, FreeRDP_GatewayPassword)) + goto fail; + validOption = TRUE; + allowHttpOpts = FALSE; + } + + const char* gt = option_starts_with("type:", argval); + if (gt) + { + if (!parse_gateway_type_option(settings, gt)) + goto fail; + validOption = TRUE; + allowHttpOpts = freerdp_settings_get_bool(settings, FreeRDP_GatewayHttpTransport); + } + + const char* gat = option_starts_with("access-token:", argval); + if (gat) + { + if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAccessToken, gat)) + goto fail; + validOption = TRUE; + allowHttpOpts = FALSE; + } + + const char* bearer = option_starts_with("bearer:", argval); + if (bearer) + { + if (!freerdp_settings_set_string(settings, FreeRDP_GatewayHttpExtAuthBearer, + bearer)) + goto fail; + validOption = TRUE; + allowHttpOpts = FALSE; + } + + const char* gwurl = option_starts_with("url:", argval); + if (gwurl) + { + if (!freerdp_settings_set_string(settings, FreeRDP_GatewayUrl, gwurl)) + goto fail; + if (!freerdp_set_gateway_usage_method(settings, TSC_PROXY_MODE_DIRECT)) + goto fail; + validOption = TRUE; + allowHttpOpts = FALSE; + } + + const char* um = option_starts_with("usage-method:", argval); + if (um) + { + if (!parse_gateway_usage_option(settings, um)) + goto fail; + validOption = TRUE; + allowHttpOpts = FALSE; + } + + if (allowHttpOpts) + { + if (option_equals(argval, "no-websockets")) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpUseWebsockets, + FALSE)) + goto fail; + validOption = TRUE; + } + else if (option_equals(argval, "extauth-sspi-ntlm")) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_GatewayHttpExtAuthSspiNtlm, + TRUE)) + goto fail; + validOption = TRUE; + } + } + + if (!validOption) + goto fail; + } + } + + rc = TRUE; +fail: + free(argval); + CommandLineParserFree(ptr); + return rc; +} + +static void fill_credential_string(COMMAND_LINE_ARGUMENT_A* args, const char* value) +{ + WINPR_ASSERT(args); + WINPR_ASSERT(value); + + const COMMAND_LINE_ARGUMENT_A* arg = CommandLineFindArgumentA(args, value); + if (!arg) + return; + + if (arg->Flags & COMMAND_LINE_VALUE_PRESENT) + FillMemory(arg->Value, strlen(arg->Value), '*'); +} + +static void fill_credential_strings(COMMAND_LINE_ARGUMENT_A* args) +{ + for (size_t x = 0; x < ARRAYSIZE(credential_args); x++) + { + const char* cred = credential_args[x]; + fill_credential_string(args, cred); + } + + const COMMAND_LINE_ARGUMENT_A* arg = CommandLineFindArgumentA(args, "gateway"); + if (arg && ((arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT) != 0)) + { + const char* gwcreds[] = { "p:", "access-token:" }; + char* saveptr = nullptr; + char* tok = strtok_s(arg->Value, ",", &saveptr); + while (tok) + { + for (size_t x = 0; x < ARRAYSIZE(gwcreds); x++) + { + const char* opt = gwcreds[x]; + if (option_starts_with(opt, tok)) + { + char* val = &tok[strlen(opt)]; + FillMemory(val, strlen(val), '*'); + } + } + tok = strtok_s(nullptr, ",", &saveptr); + } + } +} + +static int parse_command_line_option_uint32(rdpSettings* settings, + const COMMAND_LINE_ARGUMENT_A* arg, + FreeRDP_Settings_Keys_UInt32 key, LONGLONG min, + LONGLONG max) +{ + LONGLONG val = 0; + + if (!value_to_int(arg->Value, &val, min, max)) + return fail_at(arg, COMMAND_LINE_ERROR_UNEXPECTED_VALUE); + + if (!freerdp_settings_set_uint32(settings, key, (UINT32)val)) + return fail_at(arg, COMMAND_LINE_ERROR); + return 0; +} + +#if defined(WITH_FREERDP_DEPRECATED_COMMANDLINE) +static int parse_deprecated_command_line(rdpSettings* settings, const COMMAND_LINE_ARGUMENT_A* arg) +{ + int status = 0; + + WINPR_ASSERT(settings); + WINPR_ASSERT(arg); + + BOOL enable = arg->Value ? TRUE : FALSE; + CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "gfx-thin-client") + { + WLog_WARN(TAG, "/gfx-thin-client is deprecated, use /gfx:thin-client[:on|off] instead"); + if (!freerdp_settings_set_bool(settings, FreeRDP_GfxThinClient, enable)) + return fail_at(arg, COMMAND_LINE_ERROR); + + if (freerdp_settings_get_bool(settings, FreeRDP_GfxThinClient)) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_GfxSmallCache, TRUE)) + return fail_at(arg, COMMAND_LINE_ERROR); + } + + if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, TRUE)) + return fail_at(arg, COMMAND_LINE_ERROR); + } + CommandLineSwitchCase(arg, "gfx-small-cache") + { + WLog_WARN(TAG, "/gfx-small-cache is deprecated, use /gfx:small-cache[:on|off] instead"); + if (!freerdp_settings_set_bool(settings, FreeRDP_GfxSmallCache, enable)) + return fail_at(arg, COMMAND_LINE_ERROR); + + if (enable) + if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, TRUE)) + return fail_at(arg, COMMAND_LINE_ERROR); + } + CommandLineSwitchCase(arg, "gfx-progressive") + { + WLog_WARN(TAG, "/gfx-progressive is deprecated, use /gfx:progressive[:on|off] instead"); + if (!freerdp_settings_set_bool(settings, FreeRDP_GfxProgressive, enable)) + return fail_at(arg, COMMAND_LINE_ERROR); + if (!freerdp_settings_set_bool(settings, FreeRDP_GfxThinClient, !enable)) + return fail_at(arg, COMMAND_LINE_ERROR); + + if (enable) + { + if (!freerdp_settings_set_bool(settings, FreeRDP_SupportGraphicsPipeline, TRUE)) + return fail_at(arg, COMMAND_LINE_ERROR); + } + } +#ifdef WITH_GFX_H264 + CommandLineSwitchCase(arg, "gfx-h264") + { + WLog_WARN(TAG, "/gfx-h264 is deprecated, use /gfx:avc420 instead"); + int rc = parse_gfx_options(settings, arg); + if (rc != 0) + return fail_at(arg, rc); + } +#endif + CommandLineSwitchCase(arg, "app-workdir") + { + WLog_WARN(TAG, + "/app-workdir: is deprecated, use /app:workdir: instead"); + if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationWorkingDir, arg->Value)) + return fail_at(arg, COMMAND_LINE_ERROR_MEMORY); + } + CommandLineSwitchCase(arg, "app-name") + { + WLog_WARN(TAG, "/app-name: is deprecated, use /app:name: instead"); + if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationName, arg->Value)) + return fail_at(arg, COMMAND_LINE_ERROR_MEMORY); + } + CommandLineSwitchCase(arg, "app-icon") + { + WLog_WARN(TAG, "/app-icon: is deprecated, use /app:icon: instead"); + if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationIcon, arg->Value)) + return fail_at(arg, COMMAND_LINE_ERROR_MEMORY); + } + CommandLineSwitchCase(arg, "app-cmd") + { + WLog_WARN(TAG, "/app-cmd: is deprecated, use /app:cmd: instead"); + if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationCmdLine, arg->Value)) + return fail_at(arg, COMMAND_LINE_ERROR_MEMORY); + } + CommandLineSwitchCase(arg, "app-file") + { + WLog_WARN(TAG, "/app-file: is deprecated, use /app:file: instead"); + if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationFile, arg->Value)) + return fail_at(arg, COMMAND_LINE_ERROR_MEMORY); + } + CommandLineSwitchCase(arg, "app-guid") + { + WLog_WARN(TAG, "/app-guid: is deprecated, use /app:guid: instead"); + if (!freerdp_settings_set_string(settings, FreeRDP_RemoteApplicationGuid, arg->Value)) + return fail_at(arg, COMMAND_LINE_ERROR_MEMORY); + } + CommandLineSwitchCase(arg, "g") + { + if (!parse_gateway_host_option(settings, arg->Value)) + return fail_at(arg, COMMAND_LINE_ERROR); + } + CommandLineSwitchCase(arg, "gu") + { + if (!parse_gateway_cred_option(settings, arg->Value, FreeRDP_GatewayUsername)) + return fail_at(arg, COMMAND_LINE_ERROR_UNEXPECTED_VALUE); + } + CommandLineSwitchCase(arg, "gd") + { + if (!parse_gateway_cred_option(settings, arg->Value, FreeRDP_GatewayDomain)) + return fail_at(arg, COMMAND_LINE_ERROR_UNEXPECTED_VALUE); + } + CommandLineSwitchCase(arg, "gp") + { + if (!parse_gateway_cred_option(settings, arg->Value, FreeRDP_GatewayPassword)) + return fail_at(arg, COMMAND_LINE_ERROR_UNEXPECTED_VALUE); + } + CommandLineSwitchCase(arg, "gt") + { + if (!parse_gateway_type_option(settings, arg->Value)) + return fail_at(arg, COMMAND_LINE_ERROR_UNEXPECTED_VALUE); + } + CommandLineSwitchCase(arg, "gat") + { + if (!freerdp_settings_set_string(settings, FreeRDP_GatewayAccessToken, arg->Value)) + return fail_at(arg, COMMAND_LINE_ERROR_MEMORY); + } + CommandLineSwitchCase(arg, "gateway-usage-method") + { + if (!parse_gateway_usage_option(settings, arg->Value)) + return fail_at(arg, COMMAND_LINE_ERROR_UNEXPECTED_VALUE); + } + CommandLineSwitchCase(arg, "kbd-remap") + { + WLog_WARN(TAG, "/kbd-remap:=,= is deprecated, use " + "/kbd:remap:=,remap:=,... instead"); + if (!freerdp_settings_set_string(settings, FreeRDP_KeyboardRemappingList, arg->Value)) + return fail_at(arg, COMMAND_LINE_ERROR_MEMORY); + } + CommandLineSwitchCase(arg, "kbd-lang") + { + LONGLONG val = 0; + + WLog_WARN(TAG, "/kbd-lang: is deprecated, use /kbd:lang: instead"); + if (!value_to_int(arg->Value, &val, 1, UINT32_MAX)) + { + WLog_ERR(TAG, "Could not identify keyboard active language %s", arg->Value); + WLog_ERR(TAG, "Use /list:kbd-lang to list available layouts"); + return fail_at(arg, COMMAND_LINE_ERROR_UNEXPECTED_VALUE); + } + + if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardCodePage, (UINT32)val)) + return fail_at(arg, COMMAND_LINE_ERROR); + } + CommandLineSwitchCase(arg, "kbd-type") + { + WLog_WARN(TAG, "/kbd-type: is deprecated, use /kbd:type: instead"); + const int rc = + parse_command_line_option_uint32(settings, arg, FreeRDP_KeyboardType, 0, UINT32_MAX); + if (rc != 0) + return fail_at(arg, rc); + } + CommandLineSwitchCase(arg, "kbd-unicode") + { + WLog_WARN(TAG, "/kbd-unicode is deprecated, use /kbd:unicode[:on|off] instead"); + if (!freerdp_settings_set_bool(settings, FreeRDP_UnicodeInput, enable)) + return fail_at(arg, COMMAND_LINE_ERROR_UNEXPECTED_VALUE); + } + CommandLineSwitchCase(arg, "kbd-subtype") + { + WLog_WARN(TAG, "/kbd-subtype: is deprecated, use /kbd:subtype: instead"); + const int rc = + parse_command_line_option_uint32(settings, arg, FreeRDP_KeyboardSubType, 0, UINT32_MAX); + if (rc != 0) + return fail_at(arg, rc); + } + CommandLineSwitchCase(arg, "kbd-fn-key") + { + WLog_WARN(TAG, "/kbd-fn-key: is deprecated, use /kbd:fn-key: instead"); + const int rc = parse_command_line_option_uint32(settings, arg, FreeRDP_KeyboardFunctionKey, + 0, UINT32_MAX); + if (rc != 0) + return fail_at(arg, rc); + } + CommandLineSwitchCase(arg, "bitmap-cache") + { + WLog_WARN(TAG, "/bitmap-cache is deprecated, use /cache:bitmap[:on|off] instead"); + if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCacheEnabled, enable)) + return fail_at(arg, COMMAND_LINE_ERROR); + } + CommandLineSwitchCase(arg, "persist-cache") + { + WLog_WARN(TAG, "/persist-cache is deprecated, use /cache:persist[:on|off] instead"); + if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCachePersistEnabled, enable)) + return fail_at(arg, COMMAND_LINE_ERROR_UNEXPECTED_VALUE); + } + CommandLineSwitchCase(arg, "persist-cache-file") + { + WLog_WARN(TAG, "/persist-cache-file: is deprecated, use " + "/cache:persist-file: instead"); + if (!freerdp_settings_set_string(settings, FreeRDP_BitmapCachePersistFile, arg->Value)) + return fail_at(arg, COMMAND_LINE_ERROR_MEMORY); + + if (!freerdp_settings_set_bool(settings, FreeRDP_BitmapCachePersistEnabled, TRUE)) + return fail_at(arg, COMMAND_LINE_ERROR_UNEXPECTED_VALUE); + } + CommandLineSwitchCase(arg, "offscreen-cache") + { + WLog_WARN(TAG, "/bitmap-cache is deprecated, use /cache:bitmap[:on|off] instead"); + if (!freerdp_settings_set_uint32(settings, FreeRDP_OffscreenSupportLevel, (UINT32)enable)) + return fail_at(arg, COMMAND_LINE_ERROR); + } + CommandLineSwitchCase(arg, "glyph-cache") + { + WLog_WARN(TAG, "/glyph-cache is deprecated, use /cache:glyph[:on|off] instead"); + if (!freerdp_settings_set_uint32(settings, FreeRDP_GlyphSupportLevel, + arg->Value ? GLYPH_SUPPORT_FULL : GLYPH_SUPPORT_NONE)) + return fail_at(arg, COMMAND_LINE_ERROR); + } + CommandLineSwitchCase(arg, "codec-cache") + { + WLog_WARN(TAG, "/codec-cache: