Milestone 5: deliver embedded RDP sessions and lifecycle hardening

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

View File

@@ -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)

View File

@@ -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

View File

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

View File

@@ -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();
};

View File

@@ -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<Profile> 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<Profile> 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<Profile> 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)) {

View File

@@ -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

View File

@@ -4,8 +4,6 @@
#include "profile_repository.h"
#include "session_window.h"
#include <algorithm>
#include <QAbstractItemView>
#include <QHBoxLayout>
#include <QLabel>
@@ -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("<none>") : 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("<none>")
: profile.username.trimmed());
}
return profile.username.isEmpty() ? QStringLiteral("<none>") : 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<SessionWindow>& 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();
}

View File

@@ -11,7 +11,6 @@
#include <optional>
#include <QPointer>
#include <unordered_map>
#include <vector>
class QListWidget;
class QListWidgetItem;
@@ -33,7 +32,7 @@ private:
QPushButton* m_newButton;
QPushButton* m_editButton;
QPushButton* m_deleteButton;
std::vector<QPointer<SessionWindow>> m_sessionWindows;
QPointer<SessionWindow> m_sessionWindow;
std::unique_ptr<ProfileRepository> m_repository;
std::unordered_map<qint64, Profile> m_profileCache;

207
src/rdp_display_widget.cpp Normal file
View File

@@ -0,0 +1,207 @@
#include "rdp_display_widget.h"
#include <QKeyEvent>
#include <QMouseEvent>
#include <QPainter>
#include <QResizeEvent>
#include <QTimer>
#include <QWheelEvent>
#include <QtGlobal>
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<int>(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<int>(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<int>(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<int>(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<int>(normalizedX * remote.width()), remote.width() - 1);
const int remoteY = qBound(0, static_cast<int>(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);
}

50
src/rdp_display_widget.h Normal file
View File

@@ -0,0 +1,50 @@
#ifndef ORBITHUB_RDP_DISPLAY_WIDGET_H
#define ORBITHUB_RDP_DISPLAY_WIDGET_H
#include <QImage>
#include <QWidget>
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

1809
src/rdp_session_backend.cpp Normal file

File diff suppressed because it is too large Load Diff

108
src/rdp_session_backend.h Normal file
View File

@@ -0,0 +1,108 @@
#ifndef ORBITHUB_RDP_SESSION_BACKEND_H
#define ORBITHUB_RDP_SESSION_BACKEND_H
#include "session_backend.h"
#include <atomic>
#include <cstdint>
#include <deque>
#include <mutex>
#include <thread>
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<InputEvent> 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

View File

@@ -3,8 +3,10 @@
#include "profile_repository.h"
#include <QImage>
#include <QObject>
#include <QString>
#include <QtGlobal>
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;

View File

@@ -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<SessionBackend> createSessionBackend(const Profile& profile)
if (profile.protocol.compare(QStringLiteral("SSH"), Qt::CaseInsensitive) == 0) {
return std::make_unique<SshSessionBackend>(profile);
}
if (profile.protocol.compare(QStringLiteral("RDP"), Qt::CaseInsensitive) == 0) {
return std::make_unique<RdpSessionBackend>(profile);
}
return std::make_unique<UnsupportedSessionBackend>(profile);
}

View File

@@ -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<SessionConnectOptions> 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<SessionConnectOptions> 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();
}
}
}

View File

@@ -5,6 +5,7 @@
#include "session_backend.h"
#include <QWidget>
#include <QtGlobal>
#include <optional>
@@ -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;

View File

@@ -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<QAction*> 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()));

View File

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

124
third_party/FreeRDP/.clang-format vendored Normal file
View File

@@ -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
...

138
third_party/FreeRDP/.clang-tidy vendored Normal file
View File

@@ -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'
...

3
third_party/FreeRDP/.gitattributes vendored Normal file
View File

@@ -0,0 +1,3 @@
.gitattributes export-ignore
.gitignore export-ignore
.github export-ignore

View File

@@ -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 <freerdp-devel@lists.sourceforge.net>
* 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!_

View File

@@ -0,0 +1,7 @@
---
name: Backport
about: Create a issue to request/track a backport
---
Related pull request for master:

View File

@@ -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 <freerdp-devel@lists.sourceforge.net>
* 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!_

View File

@@ -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.

View File

@@ -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 <freerdp-devel@lists.sourceforge.net> 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!

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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}}"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

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

View File

@@ -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

View File

@@ -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

View File

@@ -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

5
third_party/FreeRDP/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
**/CMakeCache.txt
**/CMakeFiles
build
checker
abi-checker

98
third_party/FreeRDP/CMakeCPack.cmake vendored Normal file
View File

@@ -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)

View File

@@ -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()

603
third_party/FreeRDP/CMakeLists.txt vendored Normal file
View File

@@ -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 <otavio@ossystems.com.br>
# Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
# 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("$<$<CONFIG:Debug>: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("$<$<CONFIG:Debug>:-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")

1481
third_party/FreeRDP/ChangeLog vendored Normal file

File diff suppressed because it is too large Load Diff

202
third_party/FreeRDP/LICENSE vendored Normal file
View File

@@ -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.

54
third_party/FreeRDP/README.md vendored Normal file
View File

@@ -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

114
third_party/FreeRDP/SECURITY.md vendored Normal file
View File

@@ -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

View File

@@ -0,0 +1,292 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
include(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 $<INSTALL_INTERFACE:include>)
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()

View File

@@ -0,0 +1,27 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2022 Armin Novak <anovak@thincast.com>
# 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()

View File

@@ -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}
)

View File

@@ -0,0 +1,27 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2022 Armin Novak <anovak@thincast.com>
# 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")

View File

@@ -0,0 +1,200 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Advanced Input Virtual Channel Extension
*
* Copyright 2022 Armin Novak <anovak@thincast.com>
* 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 <freerdp/config.h>
#include <stdio.h>
#include <stdlib.h>
#include <winpr/crt.h>
#include <winpr/assert.h>
#include <winpr/stream.h>
#include <winpr/sysinfo.h>
#include "ainput_main.h"
#include <freerdp/channels/log.h>
#include <freerdp/client/channels.h>
#include <freerdp/client/ainput.h>
#include <freerdp/channels/ainput.h>
#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);
}

View File

@@ -0,0 +1,40 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Advanced Input Virtual Channel Extension
*
* Copyright 2022 Armin Novak <anovak@thincast.com>
* 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 <freerdp/config.h>
#include <freerdp/dvc.h>
#include <freerdp/types.h>
#include <freerdp/addin.h>
#include <freerdp/channels/log.h>
#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 */

View File

@@ -0,0 +1,60 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Audio Input Redirection Virtual Channel
*
* Copyright 2022 Armin Novak <anovak@thincast.com>
* 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 <winpr/string.h>
#include <freerdp/channels/ainput.h>
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 */

View File

@@ -0,0 +1,24 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2022 Armin Novak <anovak@thincast.com>
# 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")

View File

@@ -0,0 +1,596 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Advanced Input Virtual Channel Extension
*
* Copyright 2022 Armin Novak <anovak@thincast.com>
* 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 <freerdp/config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winpr/crt.h>
#include <winpr/assert.h>
#include <winpr/synch.h>
#include <winpr/thread.h>
#include <winpr/stream.h>
#include <winpr/sysinfo.h>
#include <freerdp/freerdp.h>
#include <freerdp/channels/ainput.h>
#include <freerdp/server/ainput.h>
#include <freerdp/channels/log.h>
#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);
}

View File

@@ -0,0 +1,26 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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()

View File

@@ -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}
)

View File

@@ -0,0 +1,58 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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()

View File

@@ -0,0 +1,31 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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 "")

View File

@@ -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 <martin.haimberger@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <freerdp/config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winpr/crt.h>
#include <winpr/synch.h>
#include <winpr/thread.h>
#include <winpr/cmdline.h>
#include <winpr/wlog.h>
#include <alsa/asoundlib.h>
#include <freerdp/freerdp.h>
#include <freerdp/addin.h>
#include <freerdp/channels/rdpsnd.h>
#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, "<device>", 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;
}

File diff suppressed because it is too large Load Diff

View File

@@ -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 <freerdp/config.h>
#include <freerdp/dvc.h>
#include <freerdp/types.h>
#include <freerdp/addin.h>
#include <freerdp/channels/log.h>
#include <freerdp/client/audin.h>
#define TAG CHANNELS_TAG("audin.client")
#endif /* FREERDP_CHANNEL_AUDIN_CLIENT_MAIN_H */

View File

@@ -0,0 +1,31 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright (c) 2015 Armin Novak <armin.novak@thincast.com>
# 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 "")

View File

@@ -0,0 +1,335 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Audio Input Redirection Virtual Channel - iOS implementation
*
* Copyright (c) 2015 Armin Novak <armin.novak@thincast.com>
* 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 <freerdp/config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winpr/crt.h>
#include <winpr/synch.h>
#include <winpr/string.h>
#include <winpr/thread.h>
#include <winpr/debug.h>
#include <winpr/cmdline.h>
#import <AVFoundation/AVFoundation.h>
#define __COREFOUNDATION_CFPLUGINCOM__ 1
#define IUNKNOWN_C_GUTS \
void *_reserved; \
void *QueryInterface; \
void *AddRef; \
void *Release
#include <CoreAudio/CoreAudioTypes.h>
#include <AudioToolbox/AudioToolbox.h>
#include <AudioToolbox/AudioQueue.h>
#include <freerdp/addin.h>
#include <freerdp/channels/rdpsnd.h>
#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;
}

View File

@@ -0,0 +1,32 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright (c) 2015 Armin Novak <armin.novak@thincast.com>
# 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 "")

View File

@@ -0,0 +1,465 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Audio Input Redirection Virtual Channel - Mac OS X implementation
*
* Copyright (c) 2015 Armin Novak <armin.novak@thincast.com>
* 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 <freerdp/config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winpr/crt.h>
#include <winpr/synch.h>
#include <winpr/string.h>
#include <winpr/thread.h>
#include <winpr/debug.h>
#include <winpr/cmdline.h>
#import <AVFoundation/AVFoundation.h>
#define __COREFOUNDATION_CFPLUGINCOM__ 1
#define IUNKNOWN_C_GUTS \
void *_reserved; \
void *QueryInterface; \
void *AddRef; \
void *Release
#include <CoreAudio/CoreAudioTypes.h>
#include <CoreAudio/CoreAudio.h>
#include <AudioToolbox/AudioToolbox.h>
#include <AudioToolbox/AudioQueue.h>
#include <freerdp/addin.h>
#include <freerdp/channels/rdpsnd.h>
#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, "<device>", 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;
}

View File

@@ -0,0 +1,29 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2013 Armin Novak <armin.novak@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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 "")

View File

@@ -0,0 +1,334 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Audio Input Redirection Virtual Channel - OpenSL ES implementation
*
* Copyright 2013 Armin Novak <armin.novak@gmail.com>
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <freerdp/config.h>
#include <winpr/assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winpr/crt.h>
#include <winpr/cmdline.h>
#include <freerdp/freerdp.h>
#include <freerdp/addin.h>
#include <freerdp/channels/rdpsnd.h>
#include <SLES/OpenSLES.h>
#include <freerdp/client/audin.h>
#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, "<device>", 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;
}

View File

@@ -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 <organization> 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 <COPYRIGHT HOLDER> 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 <winpr/assert.h>
#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);
}

View File

@@ -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 <organization> 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 <COPYRIGHT HOLDER> 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 <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <freerdp/api.h>
#include <stdlib.h>
#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 */

View File

@@ -0,0 +1,31 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright (c) 2015 Rozhuk Ivan <rozhuk.im@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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 "")

View File

@@ -0,0 +1,449 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Audio Input Redirection Virtual Channel - OSS implementation
*
* Copyright (c) 2015 Rozhuk Ivan <rozhuk.im@gmail.com>
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <freerdp/config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winpr/crt.h>
#include <winpr/synch.h>
#include <winpr/string.h>
#include <winpr/thread.h>
#include <winpr/cmdline.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <limits.h>
#include <unistd.h>
#include <oss-includes.h>
#include <sys/ioctl.h>
#include <freerdp/freerdp.h>
#include <freerdp/addin.h>
#include <freerdp/channels/rdpsnd.h>
#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, "<device>", 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;
}

View File

@@ -0,0 +1,30 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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 "")

View File

@@ -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 <martin.haimberger@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <freerdp/config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winpr/crt.h>
#include <winpr/cmdline.h>
#include <winpr/wlog.h>
#include <winpr/cast.h>
#include <pulse/pulseaudio.h>
#include <freerdp/types.h>
#include <freerdp/addin.h>
#include <freerdp/freerdp.h>
#include <freerdp/codec/audio.h>
#include <freerdp/client/audin.h>
#include <freerdp/utils/helpers.h>
#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, "<device>", nullptr, nullptr, -1, nullptr,
"audio device name" },
{ "client_name", COMMAND_LINE_VALUE_REQUIRED, "<client_name>", nullptr, nullptr, -1,
nullptr, "name of pulse client" },
{ "stream_name", COMMAND_LINE_VALUE_REQUIRED, "<stream_name>", 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;
}

View File

@@ -0,0 +1,30 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright (c) 2015 Rozhuk Ivan <rozhuk.im@gmail.com>
# Copyright (c) 2020 Ingo Feinerer <feinerer@logic.at>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT 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 "")

View File

@@ -0,0 +1,353 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Audio Input Redirection Virtual Channel - sndio implementation
*
* Copyright (c) 2015 Rozhuk Ivan <rozhuk.im@gmail.com>
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
* Copyright 2020 Ingo Feinerer <feinerer@logic.at>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <freerdp/config.h>
#include <sndio.h>
#include <winpr/cmdline.h>
#include <freerdp/freerdp.h>
#include <freerdp/channels/rdpsnd.h>
#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;
}

View File

@@ -0,0 +1,26 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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 "")

View File

@@ -0,0 +1,568 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Audio Input Redirection Virtual Channel - WinMM implementation
*
* Copyright 2013 Zhang Zhaolong <zhangzl2013@126.com>
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <freerdp/config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <mmsystem.h>
#include <winpr/crt.h>
#include <winpr/wtsapi.h>
#include <winpr/cmdline.h>
#include <freerdp/freerdp.h>
#include <freerdp/addin.h>
#include <freerdp/client/audin.h>
#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, "<device>", 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;
}

View File

@@ -0,0 +1,23 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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")

View File

@@ -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 <martin.haimberger@thincast.com>
* Copyright 2022 Pascal Nowack <Pascal.Nowack@gmx.de>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <freerdp/config.h>
#include <winpr/crt.h>
#include <winpr/assert.h>
#include <winpr/synch.h>
#include <winpr/thread.h>
#include <winpr/stream.h>
#include <freerdp/freerdp.h>
#include <freerdp/server/server-common.h>
#include <freerdp/server/audin.h>
#include <freerdp/channels/log.h>
#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;
}

View File

@@ -0,0 +1,165 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
# Copyright 2024 Armin Novak <anovak@thincast.com>
# Copyright 2024 Thincast Technologies GmbH
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set(MODULE_NAME "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)

View File

@@ -0,0 +1,759 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Channel Addins
*
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <freerdp/config.h>
#include <winpr/crt.h>
#include <winpr/assert.h>
#include <winpr/path.h>
#include <winpr/string.h>
#include <winpr/file.h>
#include <winpr/synch.h>
#include <winpr/library.h>
#include <winpr/collections.h>
#include <freerdp/freerdp.h>
#include <freerdp/addin.h>
#include <freerdp/build-config.h>
#include <freerdp/client/channels.h>
#include "tables.h"
#include "addin.h"
#include <freerdp/channels/log.h>
#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;
/* <name>-client.<extension> */
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;
/* <name>-client-<subsystem>.<extension> */
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;
/* <name>-client-<subsystem>-<type>.<extension> */
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;
}

View File

@@ -0,0 +1,36 @@
/*
* FreeRDP: A Remote Desktop Protocol Implementation
* Channel Addins
*
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <winpr/wtypes.h>
#include <winpr/stream.h>
#include <freerdp/api.h>
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);

View File

@@ -0,0 +1,215 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Dynamic channel
*
* Copyright 2022 David Fort <contact@hardening-consulting.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <freerdp/config.h>
#include <freerdp/log.h>
#include <freerdp/client/channels.h>
#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;
}

View File

@@ -0,0 +1,33 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Static Entry Point Tables
*
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <freerdp/dvc.h>
#include <freerdp/channels/rdpdr.h>
#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}

View File

@@ -0,0 +1,107 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Static Entry Point Tables
*
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <winpr/platform.h>
#include <winpr/wtsapi.h>
#include <freerdp/svc.h>
#include <freerdp/dvc.h>
#include <freerdp/channels/rdpdr.h>
/* 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

View File

@@ -0,0 +1,26 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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()

View File

@@ -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}
)

View File

@@ -0,0 +1,25 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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")

View File

@@ -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 <martin.haimberger@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <freerdp/config.h>
#include <winpr/crt.h>
#include <winpr/print.h>
#include <winpr/clipboard.h>
#include <freerdp/types.h>
#include <freerdp/freerdp.h>
#include <freerdp/settings.h>
#include <freerdp/constants.h>
#include <freerdp/client/cliprdr.h>
#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;
}

View File

@@ -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 <martin.haimberger@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FREERDP_CHANNEL_CLIPRDR_CLIENT_FORMAT_H
#define FREERDP_CHANNEL_CLIPRDR_CLIENT_FORMAT_H
#include <winpr/wtypes.h>
#include <winpr/stream.h>
#include <freerdp/api.h>
#include <freerdp/channels/cliprdr.h>
#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 */

File diff suppressed because it is too large Load Diff

View File

@@ -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 <martin.haimberger@thincast.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FREERDP_CHANNEL_CLIPRDR_CLIENT_MAIN_H
#define FREERDP_CHANNEL_CLIPRDR_CLIENT_MAIN_H
#include <winpr/stream.h>
#include <freerdp/svc.h>
#include <freerdp/addin.h>
#include <freerdp/channels/log.h>
#include <freerdp/client/cliprdr.h>
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 */

View File

@@ -0,0 +1,565 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Cliprdr common
*
* Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
* Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <winpr/crt.h>
#include <winpr/stream.h>
#include <freerdp/channels/log.h>
#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;
}
}

View File

@@ -0,0 +1,97 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Cliprdr common
*
* Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
* Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FREERDP_CHANNEL_RDPECLIP_COMMON_H
#define FREERDP_CHANNEL_RDPECLIP_COMMON_H
#include <winpr/crt.h>
#include <winpr/stream.h>
#include <freerdp/channels/cliprdr.h>
#include <freerdp/api.h>
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 */

View File

@@ -0,0 +1,23 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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")

File diff suppressed because it is too large Load Diff

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