From fd0d7d0907fcb8783c4bd2fbbd8c8812f3ba1494 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Fri, 22 Feb 2019 13:58:52 -0500 Subject: [PATCH] Add FreeRDP backend for remote desktop support --- .builds/alpine.yml | 1 + .builds/archlinux.yml | 1 + backend/backend.c | 39 +++++ backend/meson.build | 16 ++ backend/rdp/backend.c | 134 ++++++++++++++ backend/rdp/keyboard.c | 219 +++++++++++++++++++++++ backend/rdp/listener.c | 61 +++++++ backend/rdp/output.c | 288 ++++++++++++++++++++++++++++++ backend/rdp/peer.c | 360 ++++++++++++++++++++++++++++++++++++++ backend/rdp/pointer.c | 40 +++++ docs/env_vars.md | 51 ++++-- include/backend/rdp.h | 98 +++++++++++ include/wlr/backend/rdp.h | 31 ++++ include/wlr/config.h.in | 1 + meson.build | 4 + meson_options.txt | 1 + 16 files changed, 1335 insertions(+), 10 deletions(-) create mode 100644 backend/rdp/backend.c create mode 100644 backend/rdp/keyboard.c create mode 100644 backend/rdp/listener.c create mode 100644 backend/rdp/output.c create mode 100644 backend/rdp/peer.c create mode 100644 backend/rdp/pointer.c create mode 100644 include/backend/rdp.h create mode 100644 include/wlr/backend/rdp.h diff --git a/.builds/alpine.yml b/.builds/alpine.yml index 9751dc8a..3a56af6a 100644 --- a/.builds/alpine.yml +++ b/.builds/alpine.yml @@ -1,6 +1,7 @@ image: alpine/edge packages: - eudev-dev + - freerdp-dev - ffmpeg-dev - libcap-dev - libinput-dev diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml index 2a4b5fc0..198ed4ee 100644 --- a/.builds/archlinux.yml +++ b/.builds/archlinux.yml @@ -1,6 +1,7 @@ image: archlinux packages: - clang + - freerdp - ffmpeg - libcap - libinput diff --git a/backend/backend.c b/backend/backend.c index 33ad20f2..2c76d989 100644 --- a/backend/backend.c +++ b/backend/backend.c @@ -21,6 +21,9 @@ #if WLR_HAS_X11_BACKEND #include #endif +#if WLR_HAS_RDP_BACKEND +#include +#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) { diff --git a/backend/meson.build b/backend/meson.build index 67bc1874..ca85ad31 100644 --- a/backend/meson.build +++ b/backend/meson.build @@ -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( diff --git a/backend/rdp/backend.c b/backend/rdp/backend.c new file mode 100644 index 00000000..ef0fca85 --- /dev/null +++ b/backend/rdp/backend.c @@ -0,0 +1,134 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/backend/rdp/keyboard.c b/backend/rdp/keyboard.c new file mode 100644 index 00000000..b3c5869d --- /dev/null +++ b/backend/rdp/keyboard.c @@ -0,0 +1,219 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/backend/rdp/listener.c b/backend/rdp/listener.c new file mode 100644 index 00000000..cfbdc537 --- /dev/null +++ b/backend/rdp/listener.c @@ -0,0 +1,61 @@ +#include +#include +#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; +} diff --git a/backend/rdp/output.c b/backend/rdp/output.c new file mode 100644 index 00000000..4d5d259d --- /dev/null +++ b/backend/rdp/output.c @@ -0,0 +1,288 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/backend/rdp/peer.c b/backend/rdp/peer.c new file mode 100644 index 00000000..350a4721 --- /dev/null +++ b/backend/rdp/peer.c @@ -0,0 +1,360 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/backend/rdp/pointer.c b/backend/rdp/pointer.c new file mode 100644 index 00000000..31186669 --- /dev/null +++ b/backend/rdp/pointer.c @@ -0,0 +1,40 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/docs/env_vars.md b/docs/env_vars.md index 04c5aa85..81f69b90 100644 --- a/docs/env_vars.md +++ b/docs/env_vars.md @@ -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* diff --git a/include/backend/rdp.h b/include/backend/rdp.h new file mode 100644 index 00000000..f8faa8fd --- /dev/null +++ b/include/backend/rdp.h @@ -0,0 +1,98 @@ +#ifndef BACKEND_RDP_H +#define BACKEND_RDP_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 diff --git a/include/wlr/backend/rdp.h b/include/wlr/backend/rdp.h new file mode 100644 index 00000000..3ba60c66 --- /dev/null +++ b/include/wlr/backend/rdp.h @@ -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 +#include +#include + +/** + * 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 diff --git a/include/wlr/config.h.in b/include/wlr/config.h.in index 94273fac..8d86b78d 100644 --- a/include/wlr/config.h.in +++ b/include/wlr/config.h.in @@ -7,6 +7,7 @@ #mesondefine WLR_HAS_ELOGIND #mesondefine WLR_HAS_X11_BACKEND +#mesondefine WLR_HAS_RDP_BACKEND #mesondefine WLR_HAS_XWAYLAND diff --git a/meson.build b/meson.build index 0f1268ca..18afd9bc 100644 --- a/meson.build +++ b/meson.build @@ -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)), diff --git a/meson_options.txt b/meson_options.txt index 9dd1695c..5d322b0c 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -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')