Add FreeRDP backend for remote desktop support
This commit is contained in:
parent
9faea17c73
commit
fd0d7d0907
|
@ -1,6 +1,7 @@
|
|||
image: alpine/edge
|
||||
packages:
|
||||
- eudev-dev
|
||||
- freerdp-dev
|
||||
- ffmpeg-dev
|
||||
- libcap-dev
|
||||
- libinput-dev
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
image: archlinux
|
||||
packages:
|
||||
- clang
|
||||
- freerdp
|
||||
- ffmpeg
|
||||
- libcap
|
||||
- libinput
|
||||
|
|
|
@ -21,6 +21,9 @@
|
|||
#if WLR_HAS_X11_BACKEND
|
||||
#include <wlr/backend/x11.h>
|
||||
#endif
|
||||
#if WLR_HAS_RDP_BACKEND
|
||||
#include <wlr/backend/rdp.h>
|
||||
#endif
|
||||
|
||||
void wlr_backend_init(struct wlr_backend *backend,
|
||||
const struct wlr_backend_impl *impl) {
|
||||
|
@ -134,6 +137,38 @@ static struct wlr_backend *attempt_headless_backend(
|
|||
return backend;
|
||||
}
|
||||
|
||||
#if WLR_HAS_RDP_BACKEND
|
||||
static struct wlr_backend *attempt_rdp_backend(struct wl_display *display,
|
||||
wlr_renderer_create_func_t create_renderer_func) {
|
||||
const char *cert_path = getenv("WLR_RDP_TLS_CERT_PATH");
|
||||
const char *key_path = getenv("WLR_RDP_TLS_KEY_PATH");
|
||||
if (!cert_path || !key_path) {
|
||||
wlr_log(WLR_ERROR, "The RDP backend requires WLR_RDP_TLS_CERT_PATH "
|
||||
"and WLR_RDP_TLS_KEY_PATH to be set.");
|
||||
return NULL;
|
||||
}
|
||||
struct wlr_backend *backend = wlr_rdp_backend_create(
|
||||
display, create_renderer_func, cert_path, key_path);
|
||||
const char *address = getenv("WLR_RDP_ADDRESS");
|
||||
if (address) {
|
||||
wlr_rdp_backend_set_address(backend, address);
|
||||
}
|
||||
const char *_port = getenv("WLR_RDP_PORT");
|
||||
if (_port) {
|
||||
char *endptr;
|
||||
int port = strtol(_port, &endptr, 10);
|
||||
if (*endptr || port <= 0 || port >= 1024) {
|
||||
wlr_log(WLR_ERROR, "Expected WLR_RDP_PORT to be a "
|
||||
"positive integer less than 1024");
|
||||
wlr_backend_destroy(backend);
|
||||
return NULL;
|
||||
}
|
||||
wlr_rdp_backend_set_port(backend, port);
|
||||
}
|
||||
return backend;
|
||||
}
|
||||
#endif
|
||||
|
||||
static struct wlr_backend *attempt_noop_backend(struct wl_display *display) {
|
||||
struct wlr_backend *backend = wlr_noop_backend_create(display);
|
||||
if (backend == NULL) {
|
||||
|
@ -185,6 +220,10 @@ static struct wlr_backend *attempt_backend_by_name(struct wl_display *display,
|
|||
#endif
|
||||
} else if (strcmp(name, "headless") == 0) {
|
||||
return attempt_headless_backend(display, create_renderer_func);
|
||||
#if WLR_HAS_RDP_BACKEND
|
||||
} else if (strcmp(name, "rdp") == 0) {
|
||||
return attempt_rdp_backend(display, create_renderer_func);
|
||||
#endif
|
||||
} else if (strcmp(name, "noop") == 0) {
|
||||
return attempt_noop_backend(display);
|
||||
} else if (strcmp(name, "drm") == 0 || strcmp(name, "libinput") == 0) {
|
||||
|
|
|
@ -53,6 +53,22 @@ if logind.found()
|
|||
backend_deps += logind
|
||||
endif
|
||||
|
||||
if freerdp.found() and winpr2.found()
|
||||
backend_files += files(
|
||||
'rdp/backend.c',
|
||||
'rdp/keyboard.c',
|
||||
'rdp/listener.c',
|
||||
'rdp/output.c',
|
||||
'rdp/peer.c',
|
||||
'rdp/pointer.c',
|
||||
)
|
||||
backend_deps += [
|
||||
freerdp,
|
||||
winpr2
|
||||
]
|
||||
conf_data.set10('WLR_HAS_RDP_BACKEND', true)
|
||||
endif
|
||||
|
||||
subdir('x11')
|
||||
|
||||
lib_wlr_backend = static_library(
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <wlr/render/egl.h>
|
||||
#include <wlr/render/gles2.h>
|
||||
#include <wlr/util/log.h>
|
||||
#include "backend/rdp.h"
|
||||
#include "glapi.h"
|
||||
#include "util/signal.h"
|
||||
|
||||
struct wlr_rdp_backend *rdp_backend_from_backend(
|
||||
struct wlr_backend *wlr_backend) {
|
||||
assert(wlr_backend_is_rdp(wlr_backend));
|
||||
return (struct wlr_rdp_backend *)wlr_backend;
|
||||
}
|
||||
|
||||
static bool backend_start(struct wlr_backend *wlr_backend) {
|
||||
struct wlr_rdp_backend *backend =
|
||||
rdp_backend_from_backend(wlr_backend);
|
||||
assert(backend->listener == NULL);
|
||||
wlr_log(WLR_INFO, "Starting RDP backend");
|
||||
if (!rdp_configure_listener(backend)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void wlr_rdp_backend_set_address(struct wlr_backend *wlr_backend,
|
||||
const char *address) {
|
||||
struct wlr_rdp_backend *backend =
|
||||
rdp_backend_from_backend(wlr_backend);
|
||||
assert(backend->listener == NULL);
|
||||
backend->address = strdup(address);
|
||||
}
|
||||
|
||||
void wlr_rdp_backend_set_port(struct wlr_backend *wlr_backend, int port) {
|
||||
struct wlr_rdp_backend *backend =
|
||||
rdp_backend_from_backend(wlr_backend);
|
||||
assert(backend->listener == NULL);
|
||||
backend->port = port;
|
||||
}
|
||||
|
||||
static void backend_destroy(struct wlr_backend *wlr_backend) {
|
||||
struct wlr_rdp_backend *backend =
|
||||
rdp_backend_from_backend(wlr_backend);
|
||||
if (!wlr_backend) {
|
||||
return;
|
||||
}
|
||||
|
||||
wl_list_remove(&backend->display_destroy.link);
|
||||
|
||||
struct wlr_rdp_peer_context *client;
|
||||
wl_list_for_each(client, &backend->clients, link) {
|
||||
freerdp_peer_context_free(client->peer);
|
||||
freerdp_peer_free(client->peer);
|
||||
}
|
||||
|
||||
wlr_signal_emit_safe(&wlr_backend->events.destroy, backend);
|
||||
|
||||
wlr_renderer_destroy(backend->renderer);
|
||||
wlr_egl_finish(&backend->egl);
|
||||
free(backend->address);
|
||||
free(backend);
|
||||
}
|
||||
|
||||
static struct wlr_renderer *backend_get_renderer(
|
||||
struct wlr_backend *wlr_backend) {
|
||||
struct wlr_rdp_backend *backend =
|
||||
rdp_backend_from_backend(wlr_backend);
|
||||
return backend->renderer;
|
||||
}
|
||||
|
||||
static const struct wlr_backend_impl backend_impl = {
|
||||
.start = backend_start,
|
||||
.destroy = backend_destroy,
|
||||
.get_renderer = backend_get_renderer,
|
||||
};
|
||||
|
||||
static void handle_display_destroy(struct wl_listener *listener, void *data) {
|
||||
struct wlr_rdp_backend *backend =
|
||||
wl_container_of(listener, backend, display_destroy);
|
||||
backend_destroy(&backend->backend);
|
||||
}
|
||||
|
||||
struct wlr_backend *wlr_rdp_backend_create(struct wl_display *display,
|
||||
wlr_renderer_create_func_t create_renderer_func,
|
||||
const char *tls_cert_path, const char *tls_key_path) {
|
||||
wlr_log(WLR_INFO, "Creating RDP backend");
|
||||
|
||||
struct wlr_rdp_backend *backend =
|
||||
calloc(1, sizeof(struct wlr_rdp_backend));
|
||||
if (!backend) {
|
||||
wlr_log(WLR_ERROR, "Failed to allocate wlr_rdp_backend");
|
||||
return NULL;
|
||||
}
|
||||
wlr_backend_init(&backend->backend, &backend_impl);
|
||||
backend->display = display;
|
||||
backend->tls_cert_path = tls_cert_path;
|
||||
backend->tls_key_path = tls_key_path;
|
||||
backend->address = strdup("127.0.0.1");
|
||||
backend->port = 3389;
|
||||
wl_list_init(&backend->clients);
|
||||
|
||||
static const EGLint config_attribs[] = {
|
||||
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
|
||||
EGL_ALPHA_SIZE, 0,
|
||||
EGL_BLUE_SIZE, 1,
|
||||
EGL_GREEN_SIZE, 1,
|
||||
EGL_RED_SIZE, 1,
|
||||
EGL_NONE,
|
||||
};
|
||||
|
||||
if (!create_renderer_func) {
|
||||
create_renderer_func = wlr_renderer_autocreate;
|
||||
}
|
||||
|
||||
backend->renderer = create_renderer_func(&backend->egl,
|
||||
EGL_PLATFORM_SURFACELESS_MESA, NULL, (EGLint*)config_attribs, 0);
|
||||
if (!backend->renderer) {
|
||||
wlr_log(WLR_ERROR, "Failed to create renderer");
|
||||
free(backend);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
backend->display_destroy.notify = handle_display_destroy;
|
||||
wl_display_add_destroy_listener(display, &backend->display_destroy);
|
||||
|
||||
return &backend->backend;
|
||||
}
|
||||
|
||||
bool wlr_backend_is_rdp(struct wlr_backend *backend) {
|
||||
return backend->impl == &backend_impl;
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
#include <stdlib.h>
|
||||
#include <wayland-server.h>
|
||||
#include <wlr/types/wlr_input_device.h>
|
||||
#include <wlr/types/wlr_keyboard.h>
|
||||
#include <wlr/interfaces/wlr_keyboard.h>
|
||||
#include <wlr/interfaces/wlr_input_device.h>
|
||||
#include <wlr/util/log.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#include "backend/rdp.h"
|
||||
#include "util/signal.h"
|
||||
|
||||
struct rdp_to_xkb_keyboard_layout {
|
||||
UINT32 rdp_layout_code;
|
||||
const char *xkb_layout;
|
||||
const char *xkb_variant;
|
||||
};
|
||||
|
||||
/* table reversed from
|
||||
https://github.com/awakecoding/FreeRDP/blob/master/libfreerdp/locale/xkb_layout_ids.c#L811 */
|
||||
static struct rdp_to_xkb_keyboard_layout rdp_keyboards[] = {
|
||||
{KBD_ARABIC_101, "ara", 0},
|
||||
{KBD_BULGARIAN, 0, 0},
|
||||
{KBD_CHINESE_TRADITIONAL_US, 0, 0},
|
||||
{KBD_CZECH, "cz", 0},
|
||||
{KBD_CZECH_PROGRAMMERS, "cz", "bksl"},
|
||||
{KBD_CZECH_QWERTY, "cz", "qwerty"},
|
||||
{KBD_DANISH, "dk", 0},
|
||||
{KBD_GERMAN, "de", 0},
|
||||
{KBD_GERMAN_NEO, "de", "neo"},
|
||||
{KBD_GERMAN_IBM, "de", "qwerty"},
|
||||
{KBD_GREEK, "gr", 0},
|
||||
{KBD_GREEK_220, "gr", "simple"},
|
||||
{KBD_GREEK_319, "gr", "extended"},
|
||||
{KBD_GREEK_POLYTONIC, "gr", "polytonic"},
|
||||
{KBD_US, "us", 0},
|
||||
{KBD_US_ENGLISH_TABLE_FOR_IBM_ARABIC_238_L, "ara", "buckwalter"},
|
||||
{KBD_SPANISH, "es", 0},
|
||||
{KBD_SPANISH_VARIATION, "es", "nodeadkeys"},
|
||||
{KBD_FINNISH, "fi", 0},
|
||||
{KBD_FRENCH, "fr", 0},
|
||||
{KBD_HEBREW, "il", 0},
|
||||
{KBD_HUNGARIAN, "hu", 0},
|
||||
{KBD_HUNGARIAN_101_KEY, "hu", "standard"},
|
||||
{KBD_ICELANDIC, "is", 0},
|
||||
{KBD_ITALIAN, "it", 0},
|
||||
{KBD_ITALIAN_142, "it", "nodeadkeys"},
|
||||
{KBD_JAPANESE, "jp", 0},
|
||||
{KBD_JAPANESE_INPUT_SYSTEM_MS_IME2002, "jp", "kana"},
|
||||
{KBD_KOREAN, "kr", 0},
|
||||
{KBD_KOREAN_INPUT_SYSTEM_IME_2000, "kr", "kr104"},
|
||||
{KBD_DUTCH, "nl", 0},
|
||||
{KBD_NORWEGIAN, "no", 0},
|
||||
{KBD_POLISH_PROGRAMMERS, "pl", 0},
|
||||
{KBD_POLISH_214, "pl", "qwertz"},
|
||||
{KBD_ROMANIAN, "ro", 0},
|
||||
{KBD_RUSSIAN, "ru", 0},
|
||||
{KBD_RUSSIAN_TYPEWRITER, "ru", "typewriter"},
|
||||
{KBD_CROATIAN, "hr", 0},
|
||||
{KBD_SLOVAK, "sk", 0},
|
||||
{KBD_SLOVAK_QWERTY, "sk", "qwerty"},
|
||||
{KBD_ALBANIAN, 0, 0},
|
||||
{KBD_SWEDISH, "se", 0},
|
||||
{KBD_THAI_KEDMANEE, "th", 0},
|
||||
{KBD_THAI_KEDMANEE_NON_SHIFTLOCK, "th", "tis"},
|
||||
{KBD_TURKISH_Q, "tr", 0},
|
||||
{KBD_TURKISH_F, "tr", "f"},
|
||||
{KBD_URDU, "in", "urd-phonetic3"},
|
||||
{KBD_UKRAINIAN, "ua", 0},
|
||||
{KBD_BELARUSIAN, "by", 0},
|
||||
{KBD_SLOVENIAN, "si", 0},
|
||||
{KBD_ESTONIAN, "ee", 0},
|
||||
{KBD_LATVIAN, "lv", 0},
|
||||
{KBD_LITHUANIAN_IBM, "lt", "ibm"},
|
||||
{KBD_FARSI, "af", 0},
|
||||
{KBD_VIETNAMESE, "vn", 0},
|
||||
{KBD_ARMENIAN_EASTERN, "am", 0},
|
||||
{KBD_AZERI_LATIN, 0, 0},
|
||||
{KBD_FYRO_MACEDONIAN, "mk", 0},
|
||||
{KBD_GEORGIAN, "ge", 0},
|
||||
{KBD_FAEROESE, 0, 0},
|
||||
{KBD_DEVANAGARI_INSCRIPT, 0, 0},
|
||||
{KBD_MALTESE_47_KEY, 0, 0},
|
||||
{KBD_NORWEGIAN_WITH_SAMI, "no", "smi"},
|
||||
{KBD_KAZAKH, "kz", 0},
|
||||
{KBD_KYRGYZ_CYRILLIC, "kg", "phonetic"},
|
||||
{KBD_TATAR, "ru", "tt"},
|
||||
{KBD_BENGALI, "bd", 0},
|
||||
{KBD_BENGALI_INSCRIPT, "bd", "probhat"},
|
||||
{KBD_PUNJABI, 0, 0},
|
||||
{KBD_GUJARATI, "in", "guj"},
|
||||
{KBD_TAMIL, "in", "tam"},
|
||||
{KBD_TELUGU, "in", "tel"},
|
||||
{KBD_KANNADA, "in", "kan"},
|
||||
{KBD_MALAYALAM, "in", "mal"},
|
||||
{KBD_HINDI_TRADITIONAL, "in", 0},
|
||||
{KBD_MARATHI, 0, 0},
|
||||
{KBD_MONGOLIAN_CYRILLIC, "mn", 0},
|
||||
{KBD_UNITED_KINGDOM_EXTENDED, "gb", "intl"},
|
||||
{KBD_SYRIAC, "syc", 0},
|
||||
{KBD_SYRIAC_PHONETIC, "syc", "syc_phonetic"},
|
||||
{KBD_NEPALI, "np", 0},
|
||||
{KBD_PASHTO, "af", "ps"},
|
||||
{KBD_DIVEHI_PHONETIC, 0, 0},
|
||||
{KBD_LUXEMBOURGISH, 0, 0},
|
||||
{KBD_MAORI, "mao", 0},
|
||||
{KBD_CHINESE_SIMPLIFIED_US, 0, 0},
|
||||
{KBD_SWISS_GERMAN, "ch", "de_nodeadkeys"},
|
||||
{KBD_UNITED_KINGDOM, "gb", 0},
|
||||
{KBD_LATIN_AMERICAN, "latam", 0},
|
||||
{KBD_BELGIAN_FRENCH, "be", 0},
|
||||
{KBD_BELGIAN_PERIOD, "be", "oss_sundeadkeys"},
|
||||
{KBD_PORTUGUESE, "pt", 0},
|
||||
{KBD_SERBIAN_LATIN, "rs", 0},
|
||||
{KBD_AZERI_CYRILLIC, "az", "cyrillic"},
|
||||
{KBD_SWEDISH_WITH_SAMI, "se", "smi"},
|
||||
{KBD_UZBEK_CYRILLIC, "af", "uz"},
|
||||
{KBD_INUKTITUT_LATIN, "ca", "ike"},
|
||||
{KBD_CANADIAN_FRENCH_LEGACY, "ca", "fr-legacy"},
|
||||
{KBD_SERBIAN_CYRILLIC, "rs", 0},
|
||||
{KBD_CANADIAN_FRENCH, "ca", "fr-legacy"},
|
||||
{KBD_SWISS_FRENCH, "ch", "fr"},
|
||||
{KBD_BOSNIAN, "ba", 0},
|
||||
{KBD_IRISH, 0, 0},
|
||||
{KBD_BOSNIAN_CYRILLIC, "ba", "us"},
|
||||
{KBD_UNITED_STATES_DVORAK, "us", "dvorak"},
|
||||
{KBD_PORTUGUESE_BRAZILIAN_ABNT2, "br", "nativo"},
|
||||
{KBD_CANADIAN_MULTILINGUAL_STANDARD, "ca", "multix"},
|
||||
{KBD_GAELIC, "ie", "CloGaelach"},
|
||||
|
||||
{0x00000000, 0, 0},
|
||||
};
|
||||
|
||||
/* taken from 2.2.7.1.6 Input Capability Set (TS_INPUT_CAPABILITYSET) */
|
||||
static char *rdp_keyboard_types[] = {
|
||||
"", /* 0: unused */
|
||||
"", /* 1: IBM PC/XT or compatible (83-key) keyboard */
|
||||
"", /* 2: Olivetti "ICO" (102-key) keyboard */
|
||||
"", /* 3: IBM PC/AT (84-key) or similar keyboard */
|
||||
"pc102",/* 4: IBM enhanced (101- or 102-key) keyboard */
|
||||
"", /* 5: Nokia 1050 and similar keyboards */
|
||||
"", /* 6: Nokia 9140 and similar keyboards */
|
||||
"" /* 7: Japanese keyboard */
|
||||
};
|
||||
|
||||
static void keyboard_destroy(struct wlr_input_device *wlr_device) {
|
||||
struct wlr_rdp_keyboard *keyboard =
|
||||
(struct wlr_rdp_keyboard *)wlr_device->keyboard;
|
||||
xkb_keymap_unref(keyboard->keymap);
|
||||
free(keyboard);
|
||||
}
|
||||
|
||||
static struct wlr_input_device_impl input_device_impl = {
|
||||
.destroy = keyboard_destroy,
|
||||
};
|
||||
|
||||
struct wlr_rdp_input_device *wlr_rdp_keyboard_create(
|
||||
struct wlr_rdp_backend *backend, rdpSettings *settings) {
|
||||
struct wlr_rdp_input_device *device =
|
||||
calloc(1, sizeof(struct wlr_rdp_input_device));
|
||||
if (!device) {
|
||||
wlr_log(WLR_ERROR, "Failed to allcoate RDP input device");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int vendor = 0;
|
||||
int product = 0;
|
||||
const char *name = "rdp";
|
||||
struct wlr_input_device *wlr_device = &device->wlr_input_device;
|
||||
wlr_input_device_init(wlr_device, WLR_INPUT_DEVICE_KEYBOARD,
|
||||
&input_device_impl, name, vendor, product);
|
||||
|
||||
struct wlr_rdp_keyboard *keyboard =
|
||||
calloc(1, sizeof(struct wlr_rdp_keyboard));
|
||||
if (!keyboard) {
|
||||
wlr_log(WLR_ERROR, "Failed to allocate RDP pointer device");
|
||||
goto error;
|
||||
}
|
||||
wlr_device->keyboard = (struct wlr_keyboard *)keyboard;
|
||||
wlr_keyboard_init(wlr_device->keyboard, NULL);
|
||||
|
||||
wlr_log(WLR_DEBUG, "RDP keyboard layout: 0x%x type: 0x%x subtype: 0x%x "
|
||||
"function_keys 0x%x", settings->KeyboardLayout,
|
||||
settings->KeyboardType, settings->KeyboardSubType,
|
||||
settings->KeyboardFunctionKey);
|
||||
|
||||
// We need to set up an XKB context and jump through some hoops to convert
|
||||
// RDP input events into scancodes
|
||||
struct xkb_rule_names xkb_rule_names = { 0 };
|
||||
if (settings->KeyboardType <= 7) {
|
||||
xkb_rule_names.model = rdp_keyboard_types[settings->KeyboardType];
|
||||
}
|
||||
for (int i = 0; rdp_keyboards[i].rdp_layout_code; ++i) {
|
||||
if (rdp_keyboards[i].rdp_layout_code == settings->KeyboardLayout) {
|
||||
xkb_rule_names.layout = rdp_keyboards[i].xkb_layout;
|
||||
xkb_rule_names.variant = rdp_keyboards[i].xkb_variant;
|
||||
wlr_log(WLR_DEBUG, "Mapped RDP keyboard to xkb layout %s variant "
|
||||
"%s", xkb_rule_names.layout, xkb_rule_names.variant);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (xkb_rule_names.layout) {
|
||||
struct xkb_context *xkb_context = xkb_context_new(0);
|
||||
if (!xkb_context) {
|
||||
wlr_log(WLR_DEBUG, "Failed to allocate xkb context");
|
||||
goto error;
|
||||
}
|
||||
keyboard->keymap =
|
||||
xkb_keymap_new_from_names(xkb_context, &xkb_rule_names, 0);
|
||||
xkb_context_unref(xkb_context);
|
||||
}
|
||||
|
||||
wlr_keyboard_set_keymap(wlr_device->keyboard, keyboard->keymap);
|
||||
|
||||
wlr_signal_emit_safe(&backend->backend.events.new_input, wlr_device);
|
||||
return device;
|
||||
error:
|
||||
wlr_input_device_destroy(wlr_device);
|
||||
return NULL;
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
#include <stdlib.h>
|
||||
#include <wlr/util/log.h>
|
||||
#include "backend/rdp.h"
|
||||
|
||||
static int rdp_incoming_peer(
|
||||
freerdp_listener *listener, freerdp_peer *client) {
|
||||
struct wlr_rdp_backend *backend =
|
||||
(struct wlr_rdp_backend *)listener->param4;
|
||||
if (rdp_peer_init(client, backend) < 0) {
|
||||
wlr_log(WLR_ERROR, "Error initializing incoming peer");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int rdp_listener_activity(int fd, uint32_t mask, void *data) {
|
||||
freerdp_listener *listener = data;
|
||||
if (!(mask & WL_EVENT_READABLE)) {
|
||||
return 0;
|
||||
}
|
||||
if (!listener->CheckFileDescriptor(listener)) {
|
||||
wlr_log(WLR_ERROR, "Failed to check FreeRDP file descriptor");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool rdp_configure_listener(struct wlr_rdp_backend *backend) {
|
||||
backend->listener = freerdp_listener_new();
|
||||
if (!backend->listener) {
|
||||
wlr_log(WLR_ERROR, "Failed to allocate FreeRDP listener");
|
||||
return false;
|
||||
}
|
||||
backend->listener->PeerAccepted = rdp_incoming_peer;
|
||||
backend->listener->param4 = backend;
|
||||
if (!backend->listener->Open(backend->listener,
|
||||
backend->address, backend->port)) {
|
||||
wlr_log(WLR_ERROR, "Failed to bind to RDP socket");
|
||||
return false;
|
||||
}
|
||||
int rcount = 0;
|
||||
void *rfds[MAX_FREERDP_FDS];
|
||||
if (!backend->listener->GetFileDescriptor(
|
||||
backend->listener, rfds, &rcount)) {
|
||||
wlr_log(WLR_ERROR, "Failed to get FreeRDP file descriptors");
|
||||
return false;
|
||||
}
|
||||
struct wl_event_loop *event_loop =
|
||||
wl_display_get_event_loop(backend->display);
|
||||
int i;
|
||||
for (i = 0; i < rcount; ++i) {
|
||||
int fd = (int)(long)(rfds[i]);
|
||||
backend->listener_events[i] = wl_event_loop_add_fd(
|
||||
event_loop, fd, WL_EVENT_READABLE, rdp_listener_activity,
|
||||
backend->listener);
|
||||
}
|
||||
for (; i < MAX_FREERDP_FDS; ++i) {
|
||||
backend->listener_events[i] = NULL;
|
||||
}
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,288 @@
|
|||
#include <assert.h>
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
#include <stdlib.h>
|
||||
#include <wayland-server.h>
|
||||
#include <wlr/types/wlr_output.h>
|
||||
#include <wlr/interfaces/wlr_output.h>
|
||||
#include <wlr/util/log.h>
|
||||
#include <wlr/render/wlr_renderer.h>
|
||||
#include "backend/rdp.h"
|
||||
#include "util/signal.h"
|
||||
|
||||
static struct wlr_rdp_output *rdp_output_from_output(
|
||||
struct wlr_output *wlr_output) {
|
||||
assert(wlr_output_is_rdp(wlr_output));
|
||||
return (struct wlr_rdp_output *)wlr_output;
|
||||
}
|
||||
|
||||
static EGLSurface egl_create_surface(struct wlr_egl *egl, unsigned int width,
|
||||
unsigned int height) {
|
||||
EGLint attribs[] = {
|
||||
EGL_WIDTH, width,
|
||||
EGL_HEIGHT, height,
|
||||
EGL_NONE,
|
||||
};
|
||||
EGLSurface surf = eglCreatePbufferSurface(egl->display, egl->config, attribs);
|
||||
if (surf == EGL_NO_SURFACE) {
|
||||
wlr_log(WLR_ERROR, "Failed to create EGL surface");
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
return surf;
|
||||
}
|
||||
|
||||
static bool output_set_custom_mode(struct wlr_output *wlr_output, int32_t width,
|
||||
int32_t height, int32_t refresh) {
|
||||
struct wlr_rdp_output *output =
|
||||
rdp_output_from_output(wlr_output);
|
||||
struct wlr_rdp_backend *backend = output->backend;
|
||||
|
||||
if (refresh <= 0) {
|
||||
refresh = 60 * 1000; // 60 Hz
|
||||
}
|
||||
|
||||
wlr_egl_destroy_surface(&backend->egl, output->egl_surface);
|
||||
|
||||
output->egl_surface = egl_create_surface(&backend->egl, width, height);
|
||||
if (output->egl_surface == EGL_NO_SURFACE) {
|
||||
wlr_log(WLR_ERROR, "Failed to recreate EGL surface");
|
||||
wlr_output_destroy(wlr_output);
|
||||
return false;
|
||||
}
|
||||
|
||||
output->frame_delay = 1000000 / refresh;
|
||||
|
||||
if (output->shadow_surface) {
|
||||
pixman_image_unref(output->shadow_surface);
|
||||
}
|
||||
output->shadow_surface = pixman_image_create_bits(PIXMAN_x8r8g8b8,
|
||||
width, height, NULL, width * 4);
|
||||
|
||||
wlr_output_update_custom_mode(&output->wlr_output, width, height, refresh);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void output_transform(struct wlr_output *wlr_output,
|
||||
enum wl_output_transform transform) {
|
||||
struct wlr_rdp_output *output =
|
||||
rdp_output_from_output(wlr_output);
|
||||
output->wlr_output.transform = transform;
|
||||
}
|
||||
|
||||
static bool output_make_current(struct wlr_output *wlr_output, int *buffer_age) {
|
||||
struct wlr_rdp_output *output =
|
||||
rdp_output_from_output(wlr_output);
|
||||
return wlr_egl_make_current(&output->backend->egl, output->egl_surface,
|
||||
buffer_age);
|
||||
}
|
||||
|
||||
static bool rfx_swap_buffers(
|
||||
struct wlr_rdp_output *output, pixman_region32_t *damage) {
|
||||
struct wlr_rdp_peer_context *context = output->context;
|
||||
freerdp_peer *peer = context->peer;
|
||||
rdpUpdate *update = peer->update;
|
||||
|
||||
Stream_Clear(context->encode_stream);
|
||||
Stream_SetPosition(context->encode_stream, 0);
|
||||
int width = damage->extents.x2 - damage->extents.x1;
|
||||
int height = damage->extents.y2 - damage->extents.y1;
|
||||
|
||||
SURFACE_BITS_COMMAND cmd;
|
||||
cmd.skipCompression = TRUE;
|
||||
cmd.destLeft = damage->extents.x1;
|
||||
cmd.destTop = damage->extents.y1;
|
||||
cmd.destRight = damage->extents.x2;
|
||||
cmd.destBottom = damage->extents.y2;
|
||||
cmd.bmp.bpp = pixman_image_get_depth(output->shadow_surface);
|
||||
cmd.bmp.codecID = peer->settings->RemoteFxCodecId;
|
||||
cmd.bmp.width = width;
|
||||
cmd.bmp.height = height;
|
||||
|
||||
uint32_t *ptr = pixman_image_get_data(output->shadow_surface) +
|
||||
damage->extents.x1 + damage->extents.y1 *
|
||||
(pixman_image_get_stride(output->shadow_surface) / sizeof(uint32_t));
|
||||
|
||||
RFX_RECT *rfx_rect;
|
||||
int nrects;
|
||||
pixman_box32_t *rects =
|
||||
pixman_region32_rectangles(damage, &nrects);
|
||||
rfx_rect = realloc(context->rfx_rects, nrects * sizeof(*rfx_rect));
|
||||
if (rfx_rect == NULL) {
|
||||
wlr_log(WLR_ERROR, "RDP swap buffers failed: could not realloc rects");
|
||||
return false;
|
||||
}
|
||||
context->rfx_rects = rfx_rect;
|
||||
|
||||
for (int i = 0; i < nrects; ++i) {
|
||||
pixman_box32_t *region = &rects[i];
|
||||
rfx_rect = &context->rfx_rects[i];
|
||||
rfx_rect->x = region->x1 - damage->extents.x1;
|
||||
rfx_rect->y = region->y1 - damage->extents.y1;
|
||||
rfx_rect->width = region->x2 - region->x1;
|
||||
rfx_rect->height = region->y2 - region->y1;
|
||||
}
|
||||
|
||||
rfx_compose_message(context->rfx_context, context->encode_stream,
|
||||
context->rfx_rects, nrects, (BYTE *)ptr, width, height,
|
||||
pixman_image_get_stride(output->shadow_surface));
|
||||
cmd.bmp.bitmapDataLength = Stream_GetPosition(context->encode_stream);
|
||||
cmd.bmp.bitmapData = Stream_Buffer(context->encode_stream);
|
||||
|
||||
update->SurfaceBits(update->context, &cmd);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool nsc_swap_buffers(
|
||||
struct wlr_rdp_output *output, pixman_region32_t *damage) {
|
||||
struct wlr_rdp_peer_context *context = output->context;
|
||||
freerdp_peer *peer = context->peer;
|
||||
rdpUpdate *update = peer->update;
|
||||
|
||||
Stream_Clear(context->encode_stream);
|
||||
Stream_SetPosition(context->encode_stream, 0);
|
||||
int width = damage->extents.x2 - damage->extents.x1;
|
||||
int height = damage->extents.y2 - damage->extents.y1;
|
||||
|
||||
SURFACE_BITS_COMMAND cmd;
|
||||
cmd.skipCompression = TRUE;
|
||||
cmd.destLeft = damage->extents.x1;
|
||||
cmd.destTop = damage->extents.y1;
|
||||
cmd.destRight = damage->extents.x2;
|
||||
cmd.destBottom = damage->extents.y2;
|
||||
cmd.bmp.bpp = pixman_image_get_depth(output->shadow_surface);
|
||||
cmd.bmp.codecID = peer->settings->NSCodecId;
|
||||
cmd.bmp.width = width;
|
||||
cmd.bmp.height = height;
|
||||
|
||||
uint32_t *ptr = pixman_image_get_data(output->shadow_surface) +
|
||||
damage->extents.x1 + damage->extents.y1 *
|
||||
(pixman_image_get_stride(output->shadow_surface) / sizeof(uint32_t));
|
||||
|
||||
nsc_compose_message(context->nsc_context, context->encode_stream,
|
||||
(BYTE *)ptr, width, height,
|
||||
pixman_image_get_stride(output->shadow_surface));
|
||||
|
||||
cmd.bmp.bitmapDataLength = Stream_GetPosition(context->encode_stream);
|
||||
cmd.bmp.bitmapData = Stream_Buffer(context->encode_stream);
|
||||
|
||||
update->SurfaceBits(update->context, &cmd);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool output_swap_buffers(
|
||||
struct wlr_output *wlr_output, pixman_region32_t *damage) {
|
||||
if (!pixman_region32_not_empty(damage)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
struct wlr_rdp_output *output =
|
||||
rdp_output_from_output(wlr_output);
|
||||
|
||||
// Update shadow buffer
|
||||
int width = damage->extents.x2 - damage->extents.x1;
|
||||
int height = damage->extents.y2 - damage->extents.y1;
|
||||
struct wlr_renderer *renderer =
|
||||
wlr_backend_get_renderer(&output->backend->backend);
|
||||
// TODO performance: add support for flags
|
||||
if (!wlr_renderer_read_pixels(renderer, WL_SHM_FORMAT_XRGB8888,
|
||||
NULL, pixman_image_get_stride(output->shadow_surface),
|
||||
width, height, damage->extents.x1, damage->extents.y1,
|
||||
damage->extents.x1, damage->extents.y1,
|
||||
pixman_image_get_data(output->shadow_surface))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Send along to clients
|
||||
bool ret = false;
|
||||
rdpSettings *settings = output->context->peer->settings;
|
||||
if (settings->RemoteFxCodec) {
|
||||
ret = rfx_swap_buffers(output, damage);
|
||||
} else if (settings->NSCodec) {
|
||||
ret = nsc_swap_buffers(output, damage);
|
||||
} else {
|
||||
// This would perform like ass so why bother
|
||||
wlr_log(WLR_ERROR, "Raw updates are not supported; use rfx or nsc");
|
||||
}
|
||||
wlr_output_send_present(wlr_output, NULL);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void output_destroy(struct wlr_output *wlr_output) {
|
||||
struct wlr_rdp_output *output =
|
||||
rdp_output_from_output(wlr_output);
|
||||
if (output->frame_timer) {
|
||||
wl_event_source_remove(output->frame_timer);
|
||||
}
|
||||
wlr_egl_destroy_surface(&output->backend->egl, output->egl_surface);
|
||||
if (output->shadow_surface) {
|
||||
pixman_image_unref(output->shadow_surface);
|
||||
}
|
||||
free(output);
|
||||
}
|
||||
|
||||
static const struct wlr_output_impl output_impl = {
|
||||
.set_custom_mode = output_set_custom_mode,
|
||||
.transform = output_transform,
|
||||
.destroy = output_destroy,
|
||||
.make_current = output_make_current,
|
||||
.swap_buffers = output_swap_buffers,
|
||||
};
|
||||
|
||||
bool wlr_output_is_rdp(struct wlr_output *wlr_output) {
|
||||
return wlr_output->impl == &output_impl;
|
||||
}
|
||||
|
||||
static int signal_frame(void *data) {
|
||||
struct wlr_rdp_output *output = data;
|
||||
wlr_output_send_frame(&output->wlr_output);
|
||||
wl_event_source_timer_update(output->frame_timer, output->frame_delay);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct wlr_rdp_output *wlr_rdp_output_create(struct wlr_rdp_backend *backend,
|
||||
struct wlr_rdp_peer_context *context, unsigned int width,
|
||||
unsigned int height) {
|
||||
struct wlr_rdp_output *output =
|
||||
calloc(1, sizeof(struct wlr_rdp_output));
|
||||
if (output == NULL) {
|
||||
wlr_log(WLR_ERROR, "Failed to allocate wlr_rdp_output");
|
||||
return NULL;
|
||||
}
|
||||
output->backend = backend;
|
||||
output->context = context;
|
||||
wlr_output_init(&output->wlr_output, &backend->backend, &output_impl,
|
||||
backend->display);
|
||||
struct wlr_output *wlr_output = &output->wlr_output;
|
||||
|
||||
output->egl_surface = egl_create_surface(&backend->egl, width, height);
|
||||
if (output->egl_surface == EGL_NO_SURFACE) {
|
||||
wlr_log(WLR_ERROR, "Failed to create EGL surface");
|
||||
goto error;
|
||||
}
|
||||
|
||||
output_set_custom_mode(wlr_output, width, height, 0);
|
||||
strncpy(wlr_output->make, "RDP", sizeof(wlr_output->make));
|
||||
strncpy(wlr_output->model, "RDP", sizeof(wlr_output->model));
|
||||
snprintf(wlr_output->name, sizeof(wlr_output->name), "RDP-%d",
|
||||
wl_list_length(&backend->clients));
|
||||
|
||||
if (!wlr_egl_make_current(&output->backend->egl, output->egl_surface,
|
||||
NULL)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
wlr_renderer_begin(backend->renderer, wlr_output->width, wlr_output->height);
|
||||
wlr_renderer_clear(backend->renderer, (float[]){ 1.0, 1.0, 1.0, 1.0 });
|
||||
wlr_renderer_end(backend->renderer);
|
||||
|
||||
struct wl_event_loop *ev = wl_display_get_event_loop(backend->display);
|
||||
output->frame_timer = wl_event_loop_add_timer(ev, signal_frame, output);
|
||||
wl_event_source_timer_update(output->frame_timer, output->frame_delay);
|
||||
wlr_output_update_enabled(wlr_output, true);
|
||||
wlr_signal_emit_safe(&backend->backend.events.new_output, wlr_output);
|
||||
return output;
|
||||
|
||||
error:
|
||||
wlr_output_destroy(&output->wlr_output);
|
||||
return NULL;
|
||||
}
|
|
@ -0,0 +1,360 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
#include <linux/input.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <wlr/types/wlr_output.h>
|
||||
#include <wlr/interfaces/wlr_input_device.h>
|
||||
#include <wlr/interfaces/wlr_keyboard.h>
|
||||
#include <wlr/interfaces/wlr_output.h>
|
||||
#include <wlr/util/log.h>
|
||||
#include "backend/rdp.h"
|
||||
#include "util/signal.h"
|
||||
|
||||
static BOOL xf_peer_capabilities(freerdp_peer *client) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL xf_peer_post_connect(freerdp_peer *client) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL xf_peer_activate(freerdp_peer *client) {
|
||||
struct wlr_rdp_peer_context *context =
|
||||
(struct wlr_rdp_peer_context *)client->context;
|
||||
struct wlr_rdp_backend *backend = context->backend;
|
||||
rdpSettings *settings = client->settings;
|
||||
|
||||
if (!settings->SurfaceCommandsEnabled) {
|
||||
wlr_log(WLR_ERROR, "RDP peer does not support SurfaceCommands");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
context->output = wlr_rdp_output_create(backend, context,
|
||||
(int)settings->DesktopWidth, (int)settings->DesktopHeight);
|
||||
if (!context->output) {
|
||||
wlr_log(WLR_ERROR, "Failed to allcoate output for RDP peer");
|
||||
return FALSE;
|
||||
}
|
||||
rfx_context_reset(context->rfx_context,
|
||||
context->output->wlr_output.width,
|
||||
context->output->wlr_output.height);
|
||||
nsc_context_reset(context->nsc_context,
|
||||
context->output->wlr_output.width,
|
||||
context->output->wlr_output.height);
|
||||
|
||||
if (context->flags & RDP_PEER_ACTIVATED) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
context->pointer = wlr_rdp_pointer_create(backend, context);
|
||||
if (!context->pointer) {
|
||||
wlr_log(WLR_ERROR, "Failed to allocate pointer for RDP peer");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Use wlroots' software cursors instead of remote cursors
|
||||
POINTER_SYSTEM_UPDATE pointer_system;
|
||||
rdpPointerUpdate *pointer = client->update->pointer;
|
||||
pointer_system.type = SYSPTR_NULL;
|
||||
pointer->PointerSystem(client->context, &pointer_system);
|
||||
|
||||
context->keyboard = wlr_rdp_keyboard_create(backend, settings);
|
||||
if (!context->keyboard) {
|
||||
wlr_log(WLR_ERROR, "Failed to allocate keyboard for RDP peer");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
context->flags |= RDP_PEER_ACTIVATED;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static int xf_suppress_output(rdpContext *context,
|
||||
BYTE allow, const RECTANGLE_16 *area) {
|
||||
struct wlr_rdp_peer_context *peer_context =
|
||||
(struct wlr_rdp_peer_context *)context;
|
||||
if (allow) {
|
||||
peer_context->flags |= RDP_PEER_OUTPUT_ENABLED;
|
||||
} else {
|
||||
peer_context->flags &= ~RDP_PEER_OUTPUT_ENABLED;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int xf_input_synchronize_event(rdpInput *input, UINT32 flags) {
|
||||
struct wlr_rdp_peer_context *context =
|
||||
(struct wlr_rdp_peer_context *)input->context;
|
||||
wlr_output_damage_whole(&context->output->wlr_output);
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline int64_t timespec_to_msec(const struct timespec *a) {
|
||||
return (int64_t)a->tv_sec * 1000 + a->tv_nsec / 1000000;
|
||||
}
|
||||
|
||||
static int xf_input_mouse_event(rdpInput *input,
|
||||
UINT16 flags, UINT16 x, UINT16 y) {
|
||||
struct wlr_rdp_peer_context *context =
|
||||
(struct wlr_rdp_peer_context *)input->context;
|
||||
struct wlr_input_device *wlr_device = &context->pointer->wlr_input_device;
|
||||
struct wlr_pointer *pointer = wlr_device->pointer;
|
||||
bool frame = false;
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
|
||||
if (flags & PTR_FLAGS_MOVE) {
|
||||
struct wlr_event_pointer_motion_absolute event = { 0 };
|
||||
event.device = wlr_device;
|
||||
event.time_msec = timespec_to_msec(&now);
|
||||
event.x = x / (double)context->output->wlr_output.width;
|
||||
event.y = y / (double)context->output->wlr_output.height;
|
||||
wlr_signal_emit_safe(&pointer->events.motion_absolute, &event);
|
||||
frame = true;
|
||||
}
|
||||
|
||||
uint32_t button = 0;
|
||||
if (flags & PTR_FLAGS_BUTTON1) {
|
||||
button = BTN_LEFT;
|
||||
} else if (flags & PTR_FLAGS_BUTTON2) {
|
||||
button = BTN_RIGHT;
|
||||
} else if (flags & PTR_FLAGS_BUTTON3) {
|
||||
button = BTN_MIDDLE;
|
||||
}
|
||||
|
||||
if (button) {
|
||||
struct wlr_event_pointer_button event = { 0 };
|
||||
event.device = wlr_device;
|
||||
event.time_msec = timespec_to_msec(&now);
|
||||
event.button = button;
|
||||
event.state = (flags & PTR_FLAGS_DOWN) ?
|
||||
WLR_BUTTON_PRESSED : WLR_BUTTON_RELEASED;
|
||||
wlr_signal_emit_safe(&pointer->events.button, &event);
|
||||
frame = true;
|
||||
}
|
||||
|
||||
if (flags & PTR_FLAGS_WHEEL) {
|
||||
double value = -(flags & 0xFF) / 120.0;
|
||||
if (flags & PTR_FLAGS_WHEEL_NEGATIVE) {
|
||||
value = -value;
|
||||
}
|
||||
struct wlr_event_pointer_axis event = { 0 };
|
||||
event.device = &context->pointer->wlr_input_device;
|
||||
event.time_msec = timespec_to_msec(&now);
|
||||
event.source = WLR_AXIS_SOURCE_WHEEL;
|
||||
event.orientation = WLR_AXIS_ORIENTATION_VERTICAL;
|
||||
event.delta = value;
|
||||
event.delta_discrete = (int32_t)value;
|
||||
wlr_signal_emit_safe(&pointer->events.axis, &event);
|
||||
frame = true;
|
||||
}
|
||||
|
||||
if (frame) {
|
||||
wlr_signal_emit_safe(&pointer->events.frame, pointer);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int xf_input_extended_mouse_event(
|
||||
rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) {
|
||||
struct wlr_rdp_peer_context *context =
|
||||
(struct wlr_rdp_peer_context *)input->context;
|
||||
struct wlr_input_device *wlr_device = &context->pointer->wlr_input_device;
|
||||
struct wlr_pointer *pointer = wlr_device->pointer;
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
struct wlr_event_pointer_motion_absolute event = { 0 };
|
||||
event.device = wlr_device;
|
||||
event.time_msec = timespec_to_msec(&now);
|
||||
event.x = x / (double)context->output->wlr_output.width;
|
||||
event.y = y / (double)context->output->wlr_output.height;
|
||||
wlr_signal_emit_safe(&pointer->events.motion_absolute, &event);
|
||||
wlr_signal_emit_safe(&pointer->events.frame, pointer);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) {
|
||||
struct wlr_rdp_peer_context *context =
|
||||
(struct wlr_rdp_peer_context *)input->context;
|
||||
struct wlr_input_device *wlr_device = &context->keyboard->wlr_input_device;
|
||||
struct wlr_keyboard *keyboard = wlr_device->keyboard;
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
|
||||
if (!(context->flags & RDP_PEER_ACTIVATED)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool notify = false;
|
||||
enum wlr_key_state state;
|
||||
if ((flags & KBD_FLAGS_DOWN)) {
|
||||
state = WLR_KEY_PRESSED;
|
||||
notify = true;
|
||||
} else if ((flags & KBD_FLAGS_RELEASE)) {
|
||||
state = WLR_KEY_RELEASED;
|
||||
notify = true;
|
||||
}
|
||||
|
||||
if (notify) {
|
||||
uint32_t full_code = code;
|
||||
if (flags & KBD_FLAGS_EXTENDED) {
|
||||
full_code |= KBD_FLAGS_EXTENDED;
|
||||
}
|
||||
uint32_t vk_code = GetVirtualKeyCodeFromVirtualScanCode(full_code, 4);
|
||||
if (flags & KBD_FLAGS_EXTENDED) {
|
||||
vk_code |= KBDEXT;
|
||||
}
|
||||
uint32_t scan_code = GetKeycodeFromVirtualKeyCode(
|
||||
vk_code, KEYCODE_TYPE_EVDEV);
|
||||
struct wlr_event_keyboard_key event = { 0 };
|
||||
event.time_msec = timespec_to_msec(&now);
|
||||
event.keycode = scan_code - 8;
|
||||
event.state = state;
|
||||
event.update_state = true;
|
||||
wlr_keyboard_notify_key(keyboard, &event);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int xf_input_unicode_keyboard_event(rdpInput *input,
|
||||
UINT16 flags, UINT16 code) {
|
||||
wlr_log(WLR_DEBUG, "Unhandled RDP unicode keyboard event "
|
||||
"(flags:0x%X code:0x%X)\n", flags, code);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int rdp_client_activity(int fd, uint32_t mask, void *data) {
|
||||
freerdp_peer *client = (freerdp_peer *)data;
|
||||
if (!client->CheckFileDescriptor(client)) {
|
||||
wlr_log(WLR_ERROR,
|
||||
"Unable to check client file descriptor for %p", client);
|
||||
freerdp_peer_context_free(client);
|
||||
freerdp_peer_free(client);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rdp_peer_context_new(
|
||||
freerdp_peer *client, struct wlr_rdp_peer_context *context) {
|
||||
context->peer = client;
|
||||
context->flags = RDP_PEER_OUTPUT_ENABLED;
|
||||
context->rfx_context = rfx_context_new(TRUE);
|
||||
if (!context->rfx_context) {
|
||||
return false;
|
||||
}
|
||||
context->rfx_context->mode = RLGR3;
|
||||
context->rfx_context->width = client->settings->DesktopWidth;
|
||||
context->rfx_context->height = client->settings->DesktopHeight;
|
||||
rfx_context_set_pixel_format(context->rfx_context, PIXEL_FORMAT_BGRA32);
|
||||
|
||||
context->nsc_context = nsc_context_new();
|
||||
if (!context->nsc_context) {
|
||||
rfx_context_free(context->rfx_context);
|
||||
return false;
|
||||
}
|
||||
|
||||
nsc_context_set_pixel_format(context->nsc_context, PIXEL_FORMAT_BGRA32);
|
||||
|
||||
context->encode_stream = Stream_New(NULL, 65536);
|
||||
if (!context->encode_stream) {
|
||||
nsc_context_free(context->nsc_context);
|
||||
rfx_context_free(context->rfx_context);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void rdp_peer_context_free(
|
||||
freerdp_peer *client, struct wlr_rdp_peer_context *context) {
|
||||
if (!context) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_FREERDP_FDS; ++i) {
|
||||
if (context->events[i]) {
|
||||
wl_event_source_remove(context->events[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (context->flags & RDP_PEER_ACTIVATED) {
|
||||
wlr_output_destroy(&context->output->wlr_output);
|
||||
wlr_input_device_destroy(&context->pointer->wlr_input_device);
|
||||
wlr_input_device_destroy(&context->keyboard->wlr_input_device);
|
||||
}
|
||||
|
||||
wl_list_remove(&context->link);
|
||||
wlr_output_destroy(&context->output->wlr_output);
|
||||
|
||||
Stream_Free(context->encode_stream, TRUE);
|
||||
nsc_context_free(context->nsc_context);
|
||||
rfx_context_free(context->rfx_context);
|
||||
free(context->rfx_rects);
|
||||
}
|
||||
|
||||
int rdp_peer_init(freerdp_peer *client,
|
||||
struct wlr_rdp_backend *backend) {
|
||||
client->ContextSize = sizeof(struct wlr_rdp_peer_context);
|
||||
client->ContextNew = (psPeerContextNew)rdp_peer_context_new;
|
||||
client->ContextFree = (psPeerContextFree)rdp_peer_context_free;
|
||||
freerdp_peer_context_new(client);
|
||||
|
||||
struct wlr_rdp_peer_context *peer_context =
|
||||
(struct wlr_rdp_peer_context *)client->context;
|
||||
peer_context->backend = backend;
|
||||
|
||||
client->settings->CertificateFile = strdup(backend->tls_cert_path);
|
||||
client->settings->PrivateKeyFile = strdup(backend->tls_key_path);
|
||||
client->settings->NlaSecurity = FALSE;
|
||||
|
||||
if (!client->Initialize(client)) {
|
||||
wlr_log(WLR_ERROR, "Failed to initialize FreeRDP peer");
|
||||
goto err_init;
|
||||
}
|
||||
|
||||
client->settings->OsMajorType = OSMAJORTYPE_UNIX;
|
||||
client->settings->OsMinorType = OSMINORTYPE_PSEUDO_XSERVER;
|
||||
client->settings->ColorDepth = 32;
|
||||
client->settings->RefreshRect = TRUE;
|
||||
client->settings->RemoteFxCodec = TRUE;
|
||||
client->settings->NSCodec = TRUE;
|
||||
client->settings->FrameMarkerCommandEnabled = TRUE;
|
||||
client->settings->SurfaceFrameMarkerEnabled = TRUE;
|
||||
|
||||
client->Capabilities = xf_peer_capabilities;
|
||||
client->PostConnect = xf_peer_post_connect;
|
||||
client->Activate = xf_peer_activate;
|
||||
|
||||
client->update->SuppressOutput = (pSuppressOutput)xf_suppress_output;
|
||||
|
||||
client->input->SynchronizeEvent = xf_input_synchronize_event;
|
||||
client->input->MouseEvent = xf_input_mouse_event;
|
||||
client->input->ExtendedMouseEvent = xf_input_extended_mouse_event;
|
||||
client->input->KeyboardEvent = xf_input_keyboard_event;
|
||||
client->input->UnicodeKeyboardEvent = xf_input_unicode_keyboard_event;
|
||||
|
||||
int rcount = 0;
|
||||
void *rfds[MAX_FREERDP_FDS];
|
||||
if (!client->GetFileDescriptor(client, rfds, &rcount)) {
|
||||
wlr_log(WLR_ERROR, "Unable to retrieve client file descriptors");
|
||||
goto err_init;
|
||||
}
|
||||
struct wl_event_loop *event_loop =
|
||||
wl_display_get_event_loop(backend->display);
|
||||
int i;
|
||||
for (i = 0; i < rcount; ++i) {
|
||||
int fd = (int)(long)(rfds[i]);
|
||||
peer_context->events[i] = wl_event_loop_add_fd(
|
||||
event_loop, fd, WL_EVENT_READABLE, rdp_client_activity,
|
||||
client);
|
||||
}
|
||||
for (; i < MAX_FREERDP_FDS; ++i) {
|
||||
peer_context->events[i] = NULL;
|
||||
}
|
||||
|
||||
wl_list_insert(&backend->clients, &peer_context->link);
|
||||
return 0;
|
||||
|
||||
err_init:
|
||||
client->Close(client);
|
||||
return -1;
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <wayland-server.h>
|
||||
#include <wlr/types/wlr_input_device.h>
|
||||
#include <wlr/types/wlr_pointer.h>
|
||||
#include <wlr/interfaces/wlr_pointer.h>
|
||||
#include <wlr/interfaces/wlr_input_device.h>
|
||||
#include <wlr/util/log.h>
|
||||
#include "backend/rdp.h"
|
||||
#include "util/signal.h"
|
||||
|
||||
static struct wlr_input_device_impl input_device_impl = { 0 };
|
||||
|
||||
struct wlr_rdp_input_device *wlr_rdp_pointer_create(
|
||||
struct wlr_rdp_backend *backend, struct wlr_rdp_peer_context *context) {
|
||||
struct wlr_rdp_input_device *device =
|
||||
calloc(1, sizeof(struct wlr_rdp_input_device));
|
||||
if (!device) {
|
||||
wlr_log(WLR_ERROR, "Failed to allocate RDP input device");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int vendor = 0;
|
||||
int product = 0;
|
||||
const char *name = "rdp";
|
||||
struct wlr_input_device *wlr_device = &device->wlr_input_device;
|
||||
wlr_input_device_init(wlr_device, WLR_INPUT_DEVICE_POINTER,
|
||||
&input_device_impl, name, vendor, product);
|
||||
|
||||
if (!(wlr_device->pointer = calloc(1, sizeof(struct wlr_pointer)))) {
|
||||
wlr_log(WLR_ERROR, "Failed to allocate RDP pointer device");
|
||||
return NULL;
|
||||
}
|
||||
wlr_device->output_name = strdup(context->output->wlr_output.name);
|
||||
wlr_pointer_init(wlr_device->pointer, NULL);
|
||||
|
||||
wlr_signal_emit_safe(&backend->backend.events.new_input, wlr_device);
|
||||
return device;
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
wlroots reads these environment variables
|
||||
|
||||
wlroots specific
|
||||
----------------
|
||||
# wlroots specific
|
||||
|
||||
* *WLR_DRM_DEVICES*: specifies the DRM devices (as a colon separated list)
|
||||
instead of auto probing them. The first existing device in this list is
|
||||
considered the primary DRM device.
|
||||
|
@ -12,23 +12,54 @@ wlroots specific
|
|||
* *WLR_LIBINPUT_NO_DEVICES*: set to 1 to not fail without any input devices
|
||||
* *WLR_BACKENDS*: comma-separated list of backends to use (available backends:
|
||||
wayland, x11, headless, noop)
|
||||
* *WLR_WL_OUTPUTS*: when using the wayland backend specifies the number of outputs
|
||||
* *WLR_X11_OUTPUTS*: when using the X11 backend specifies the number of outputs
|
||||
* *WLR_HEADLESS_OUTPUTS*: when using the headless backend specifies the number
|
||||
of outputs
|
||||
* *WLR_NO_HARDWARE_CURSORS*: set to 1 to use software cursors instead of
|
||||
hardware cursors
|
||||
* *WLR_SESSION*: specifies the wlr\_session to be used (available sessions:
|
||||
logind/systemd, direct)
|
||||
* *WLR_DIRECT_TTY*: specifies the tty to be used (instead of using /dev/tty)
|
||||
|
||||
rootston specific
|
||||
------------------
|
||||
# Headless backend
|
||||
|
||||
* *WLR_HEADLESS_OUTPUTS*: when using the headless backend specifies the number
|
||||
of outputs
|
||||
|
||||
# RDP backend
|
||||
|
||||
* *WLR_RDP_TLS_CERT_PATH*: required when using `wlr_backend_autocreate`,
|
||||
specifies the path to the TLS certificate to use for encrypting connections
|
||||
* *WLR_RDP_TLS_KEY_PATH*: required when using `wlr_backend_autocreate`,
|
||||
specifies the path to the TLS private key to use for encrypting connections
|
||||
* *WLR_RDP_ADDRESS*: the IP address to bind to, defaults to `127.0.0.1`
|
||||
* *WLR_RDP_PORT*: the port to bind to, defaults to 3389.
|
||||
|
||||
Note: a TLS certificate and key can be generated like so:
|
||||
|
||||
```
|
||||
$ openssl genrsa -out tls.key 2048
|
||||
$ openssl req -new -key tls.key -out tls.csr
|
||||
$ openssl x509 -req -days 365 -signkey tls.key -in tls.csr -out tls.crt
|
||||
```
|
||||
|
||||
`tls.csr` can be discarded. Connecting to the RDP backend with xfreedrp can be
|
||||
done like so:
|
||||
|
||||
xfreerdp -v localhost --bpp 32 --size 1920x1080 --rfx
|
||||
|
||||
# Wayland backend
|
||||
|
||||
* *WLR_WL_OUTPUTS*: when using the wayland backend specifies the number of outputs
|
||||
|
||||
# X11 backend
|
||||
|
||||
* *WLR_X11_OUTPUTS*: when using the X11 backend specifies the number of outputs
|
||||
|
||||
# Rootston specific
|
||||
|
||||
* *XKB_DEFAULT_RULES*, *XKB_DEFAULT_MODEL*, *XKB_DEFAULT_LAYOUT*,
|
||||
*XKB_DEFAULT_VARIANT*, *XKB_DEFAULT_OPTIONS*: xkb setup
|
||||
|
||||
generic
|
||||
-------
|
||||
# Generic
|
||||
|
||||
* *DISPLAY*: if set probe X11 backend in *wlr_backend_autocreate*
|
||||
* *WAYLAND_DISPLAY*, *_WAYLAND_DISPLAY*, *WAYLAND_SOCKET*: if set probe Wayland
|
||||
backend in *wlr_backend_autocreate*
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
#ifndef BACKEND_RDP_H
|
||||
#define BACKEND_RDP_H
|
||||
#include <freerdp/codec/color.h>
|
||||
#include <freerdp/codec/nsc.h>
|
||||
#include <freerdp/codec/rfx.h>
|
||||
#include <freerdp/freerdp.h>
|
||||
#include <freerdp/input.h>
|
||||
#include <freerdp/listener.h>
|
||||
#include <freerdp/locale/keyboard.h>
|
||||
#include <freerdp/update.h>
|
||||
#include <pixman.h>
|
||||
#include <wlr/backend/interface.h>
|
||||
#include <wlr/backend/rdp.h>
|
||||
#include <wlr/types/wlr_input_device.h>
|
||||
#include <wlr/types/wlr_keyboard.h>
|
||||
#include <wlr/types/wlr_output.h>
|
||||
#include <wlr/types/wlr_pointer.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
|
||||
#define MAX_FREERDP_FDS 64
|
||||
|
||||
struct wlr_rdp_peer_context;
|
||||
|
||||
struct wlr_rdp_output {
|
||||
struct wlr_output wlr_output;
|
||||
struct wlr_rdp_backend *backend;
|
||||
struct wlr_rdp_peer_context *context;
|
||||
|
||||
void *egl_surface;
|
||||
pixman_image_t *shadow_surface;
|
||||
struct wl_event_source *frame_timer;
|
||||
int frame_delay; // ms
|
||||
};
|
||||
|
||||
struct wlr_rdp_input_device {
|
||||
struct wlr_input_device wlr_input_device;
|
||||
};
|
||||
|
||||
struct wlr_rdp_keyboard {
|
||||
struct wlr_keyboard keyboard;
|
||||
struct xkb_keymap *keymap;
|
||||
};
|
||||
|
||||
enum wlr_rdp_peer_flags {
|
||||
RDP_PEER_ACTIVATED = 1 << 0,
|
||||
RDP_PEER_OUTPUT_ENABLED = 1 << 1,
|
||||
};
|
||||
|
||||
struct wlr_rdp_peer_context {
|
||||
rdpContext _p;
|
||||
|
||||
struct wlr_rdp_backend *backend;
|
||||
struct wl_event_source *events[MAX_FREERDP_FDS];
|
||||
freerdp_peer *peer;
|
||||
uint32_t flags;
|
||||
RFX_CONTEXT *rfx_context;
|
||||
wStream *encode_stream;
|
||||
RFX_RECT *rfx_rects;
|
||||
NSC_CONTEXT *nsc_context;
|
||||
|
||||
struct wlr_rdp_output *output;
|
||||
struct wlr_rdp_input_device *pointer;
|
||||
struct wlr_rdp_input_device *keyboard;
|
||||
|
||||
struct wl_list link;
|
||||
};
|
||||
|
||||
struct wlr_rdp_backend {
|
||||
struct wlr_backend backend;
|
||||
struct wlr_egl egl;
|
||||
struct wlr_renderer *renderer;
|
||||
struct wl_display *display;
|
||||
struct wl_listener display_destroy;
|
||||
|
||||
const char *tls_cert_path;
|
||||
const char *tls_key_path;
|
||||
char *address;
|
||||
int port;
|
||||
|
||||
freerdp_listener *listener;
|
||||
struct wl_event_source *listener_events[MAX_FREERDP_FDS];
|
||||
|
||||
struct wl_list clients;
|
||||
};
|
||||
|
||||
struct wlr_rdp_backend *rdp_backend_from_backend(
|
||||
struct wlr_backend *wlr_backend);
|
||||
bool rdp_configure_listener(struct wlr_rdp_backend *backend);
|
||||
int rdp_peer_init(freerdp_peer *client, struct wlr_rdp_backend *backend);
|
||||
struct wlr_rdp_output *wlr_rdp_output_create(struct wlr_rdp_backend *backend,
|
||||
struct wlr_rdp_peer_context *context, unsigned int width,
|
||||
unsigned int height);
|
||||
struct wlr_rdp_input_device *wlr_rdp_pointer_create(
|
||||
struct wlr_rdp_backend *backend, struct wlr_rdp_peer_context *context);
|
||||
struct wlr_rdp_input_device *wlr_rdp_keyboard_create(
|
||||
struct wlr_rdp_backend *backend, rdpSettings *settings);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* This an unstable interface of wlroots. No guarantees are made regarding the
|
||||
* future consistency of this API.
|
||||
*/
|
||||
#ifndef WLR_USE_UNSTABLE
|
||||
#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features"
|
||||
#endif
|
||||
|
||||
#ifndef WLR_BACKEND_RDP_H
|
||||
#define WLR_BACKEND_RDP_H
|
||||
#include <wlr/backend.h>
|
||||
#include <wlr/types/wlr_input_device.h>
|
||||
#include <wlr/types/wlr_output.h>
|
||||
|
||||
/**
|
||||
* Creates an RDP backend. An RDP backend will create one output, keyboard, and
|
||||
* pointer for each client that connects.
|
||||
*/
|
||||
struct wlr_backend *wlr_rdp_backend_create(struct wl_display *display,
|
||||
wlr_renderer_create_func_t create_renderer_func,
|
||||
const char *tls_cert_path, const char *tls_key_path);
|
||||
|
||||
void wlr_rdp_backend_set_address(struct wlr_backend *wlr_backend,
|
||||
const char *address);
|
||||
void wlr_rdp_backend_set_port(struct wlr_backend *wlr_backend, int port);
|
||||
|
||||
bool wlr_backend_is_rdp(struct wlr_backend *backend);
|
||||
bool wlr_input_device_is_rdp(struct wlr_input_device *device);
|
||||
bool wlr_output_is_rdp(struct wlr_output *output);
|
||||
|
||||
#endif
|
|
@ -7,6 +7,7 @@
|
|||
#mesondefine WLR_HAS_ELOGIND
|
||||
|
||||
#mesondefine WLR_HAS_X11_BACKEND
|
||||
#mesondefine WLR_HAS_RDP_BACKEND
|
||||
|
||||
#mesondefine WLR_HAS_XWAYLAND
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ conf_data = configuration_data()
|
|||
conf_data.set10('WLR_HAS_LIBCAP', false)
|
||||
conf_data.set10('WLR_HAS_SYSTEMD', false)
|
||||
conf_data.set10('WLR_HAS_ELOGIND', false)
|
||||
conf_data.set10('WLR_HAS_RDP_BACKEND', false)
|
||||
conf_data.set10('WLR_HAS_X11_BACKEND', false)
|
||||
conf_data.set10('WLR_HAS_XWAYLAND', false)
|
||||
conf_data.set10('WLR_HAS_XCB_ERRORS', false)
|
||||
|
@ -67,6 +68,8 @@ wayland_client = dependency('wayland-client')
|
|||
wayland_egl = dependency('wayland-egl')
|
||||
wayland_protos = dependency('wayland-protocols', version: '>=1.17')
|
||||
egl = dependency('egl')
|
||||
freerdp = dependency('freerdp2', required: get_option('freerdp'))
|
||||
winpr2 = dependency('winpr2', required: get_option('freerdp'))
|
||||
glesv2 = dependency('glesv2')
|
||||
drm = dependency('libdrm', version: '>=2.4.95')
|
||||
gbm = dependency('gbm', version: '>=17.1.0')
|
||||
|
@ -156,6 +159,7 @@ summary = [
|
|||
' systemd: @0@'.format(conf_data.get('WLR_HAS_SYSTEMD', false)),
|
||||
' elogind: @0@'.format(conf_data.get('WLR_HAS_ELOGIND', false)),
|
||||
' xwayland: @0@'.format(conf_data.get('WLR_HAS_XWAYLAND', false)),
|
||||
' rdp_backend: @0@'.format(conf_data.get('WLR_HAS_RDP_BACKEND', false)),
|
||||
' x11_backend: @0@'.format(conf_data.get('WLR_HAS_X11_BACKEND', false)),
|
||||
' xcb-icccm: @0@'.format(conf_data.get('WLR_HAS_XCB_ICCCM', false)),
|
||||
' xcb-errors: @0@'.format(conf_data.get('WLR_HAS_XCB_ERRORS', false)),
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
option('freerdp', type: 'feature', value: 'auto', description: 'Enable support for the RDP backend with freerdp')
|
||||
option('libcap', type: 'feature', value: 'auto', description: 'Enable support for rootless session via capabilities (cap_sys_admin)')
|
||||
option('logind', type: 'feature', value: 'auto', description: 'Enable support for rootless session via logind')
|
||||
option('logind-provider', type: 'combo', choices: ['systemd', 'elogind'], value: 'systemd', description: 'Provider of logind support library')
|
||||
|
|
Loading…
Reference in New Issue