From d48adecd6c00546483be60f3bc75342bdf878dd7 Mon Sep 17 00:00:00 2001 From: Manuel Bachmann Date: Mon, 13 Oct 2014 11:43:16 +0200 Subject: [PATCH] wlfreerdp: initial Wayland client Implement an initial Wayland client, which will build if the wayland-client development libraries are detected (or if -DWITH_WAYLAND:BOOL=ON is set). It is currently view-only, but inputs will be implemented soon. It uses the software SHM interface, which means it does not require GL acceleration to run. It should be compatible with any compositor Signed-off-by: Manuel Bachmann --- CMakeLists.txt | 9 + client/CMakeLists.txt | 4 + client/Wayland/CMakeLists.txt | 34 +++ client/Wayland/wlfreerdp.c | 420 ++++++++++++++++++++++++++++++++++ cmake/FindWayland.cmake | 46 ++++ 5 files changed, 513 insertions(+) create mode 100644 client/Wayland/CMakeLists.txt create mode 100644 client/Wayland/wlfreerdp.c create mode 100644 cmake/FindWayland.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 8cf5416b2..2b71fd2b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -389,8 +389,10 @@ if(UNIX OR CYGWIN) check_include_files(sys/timerfd.h HAVE_TIMERFD_H) check_include_files(poll.h HAVE_POLL_H) set(X11_FEATURE_TYPE "RECOMMENDED") + set(WAYLAND_FEATURE_TYPE "RECOMMENDED") else() set(X11_FEATURE_TYPE "DISABLED") + set(WAYLAND_FEATURE_TYPE "DISABLED") endif() if(WITH_PCSC_WINPR) @@ -400,6 +402,9 @@ endif() set(X11_FEATURE_PURPOSE "X11") set(X11_FEATURE_DESCRIPTION "X11 client and server") +set(WAYLAND_FEATURE_PURPOSE "Wayland") +set(WAYLAND_FEATURE_DESCRIPTION "Wayland client") + set(DIRECTFB_FEATURE_TYPE "OPTIONAL") set(DIRECTFB_FEATURE_PURPOSE "DirectFB") set(DIRECTFB_FEATURE_DESCRIPTION "DirectFB client") @@ -462,6 +467,7 @@ set(GSM_FEATURE_DESCRIPTION "GSM audio codec library") if(WIN32) set(X11_FEATURE_TYPE "DISABLED") + set(WAYLAND_FEATURE_TYPE "DISABLED") set(ZLIB_FEATURE_TYPE "DISABLED") set(DIRECTFB_FEATURE_TYPE "DISABLED") set(ALSA_FEATURE_TYPE "DISABLED") @@ -479,6 +485,7 @@ if(APPLE) set(FFMPEG_FEATURE_TYPE "OPTIONAL") set(GSTREAMER_1_0_FEATURE_TYPE "OPTIONAL") set(X11_FEATURE_TYPE "OPTIONAL") + set(WAYLAND_FEATURE_TYPE "DISABLED") if(IOS) set(X11_FEATURE_TYPE "DISABLED") set(ALSA_FEATURE_TYPE "DISABLED") @@ -493,6 +500,7 @@ endif() if(ANDROID) set(X11_FEATURE_TYPE "DISABLED") + set(WAYLAND_FEATURE_TYPE "DISABLED") set(DIRECTFB_FEATURE_TYPE "DISABLED") set(ALSA_FEATURE_TYPE "DISABLED") set(PULSE_FEATURE_TYPE "DISABLED") @@ -506,6 +514,7 @@ endif() find_feature(X11 ${X11_FEATURE_TYPE} ${X11_FEATURE_PURPOSE} ${X11_FEATURE_DESCRIPTION}) +find_feature(Wayland ${WAYLAND_FEATURE_TYPE} ${WAYLAND_FEATURE_PURPOSE} ${WAYLAND_FEATURE_DESCRIPTION}) find_feature(DirectFB ${DIRECTFB_FEATURE_TYPE} ${DIRECTFB_FEATURE_PURPOSE} ${DIRECTFB_FEATURE_DESCRIPTION}) if (${WITH_DIRECTFB}) message(WARNING "DIRECTFB is orphaned and not maintained see docs/README.directfb for details") diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index c171c0dfe..14ed3633f 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -36,6 +36,10 @@ if(FREERDP_VENDOR) add_subdirectory(X11) endif() + if(WITH_WAYLAND) + add_subdirectory(Wayland) + endif() + if(APPLE) if(IOS) if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/iOS") diff --git a/client/Wayland/CMakeLists.txt b/client/Wayland/CMakeLists.txt new file mode 100644 index 000000000..182fb2bea --- /dev/null +++ b/client/Wayland/CMakeLists.txt @@ -0,0 +1,34 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP Wayland Client cmake build script +# +# Copyright 2014 Manuel Bachmann +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(MODULE_NAME "wlfreerdp") +set(MODULE_PREFIX "FREERDP_CLIENT_WAYLAND") + +include_directories(${WAYLAND_INCLUDE_DIRS}) + +set(${MODULE_PREFIX}_SRCS + wlfreerdp.c) + +add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS}) + +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${CMAKE_DL_LIBS}) +set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${WAYLAND_LIBRARIES} freerdp-client freerdp) +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + +install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT client) + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Client/Wayland") diff --git a/client/Wayland/wlfreerdp.c b/client/Wayland/wlfreerdp.c new file mode 100644 index 000000000..65f8b3226 --- /dev/null +++ b/client/Wayland/wlfreerdp.c @@ -0,0 +1,420 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Wayland Client Interface + * + * Copyright 2014 Manuel Bachmann + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +struct display +{ + struct wl_display* display; + struct wl_registry* registry; + struct wl_compositor* compositor; + struct wl_shell* shell; + struct wl_shm* shm; +}; + +struct buffer +{ + struct wl_buffer* buffer; + void* shm_data; + int busy; +}; + +struct window +{ + int width, height; + struct wl_surface* surface; + struct wl_shell_surface* shell_surface; + struct wl_callback* callback; + struct buffer buffers[2]; + struct display* display; + void* data; +}; + +struct wl_context +{ + rdpContext _p; + struct display* display; + struct window* window; +}; + +static void wl_buffer_release(void* data, struct wl_buffer* wl_buffer) +{ + struct buffer* buffer = data; + + buffer->busy = 0; +} + +static const struct wl_buffer_listener wl_buffer_listener = +{ + wl_buffer_release +}; + +static void window_redraw(void* data, struct wl_callback* callback, uint32_t time); +static const struct wl_callback_listener wl_callback_listener = +{ + window_redraw +}; + +static void window_redraw(void* data, struct wl_callback* callback, uint32_t time) +{ + struct window* window = data; + struct wl_shm_pool* shm_pool; + struct buffer* buffer; + int fd; + int fdt; + + if (!window->buffers[0].busy) + buffer = &window->buffers[0]; + else if (!window->buffers[1].busy) + buffer = &window->buffers[1]; + else + return; + + fd = shm_open("wlfreerdp_shm", O_CREAT | O_TRUNC | O_RDWR, 0666); + fdt = ftruncate(fd, window->width * window->height * 4); + if (fdt != 0) + { + fprintf(stderr, "window_redraw: could not allocate memory\n"); + close(fd); + return; + } + + buffer->shm_data = mmap(0, window->width * window->height * 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (buffer->shm_data == MAP_FAILED) + { + fprintf(stderr, "window_redraw: failed to memory map buffer\n"); + close(fd); + return; + } + + shm_pool = wl_shm_create_pool(window->display->shm, fd, window->width * window->height * 4); + buffer->buffer = wl_shm_pool_create_buffer(shm_pool, 0, window->width, window->height, window->width* 4, WL_SHM_FORMAT_XRGB8888); + wl_buffer_add_listener(buffer->buffer, &wl_buffer_listener, buffer); + wl_shm_pool_destroy(shm_pool); + shm_unlink("wlfreerdp_shm"); + close(fd); + + /* this is the real surface data */ + memcpy(buffer->shm_data, (void*) window->data, window->width * window->height * 4); + wl_surface_attach(window->surface, buffer->buffer, 0, 0); + wl_surface_damage(window->surface, 0, 0, window->width, window->height); + + if (callback) wl_callback_destroy(callback); + window->callback = wl_surface_frame(window->surface); + wl_callback_add_listener(window->callback, &wl_callback_listener, window); + wl_surface_commit(window->surface); + + buffer->busy = 1; + munmap(buffer->shm_data, window->width * window->height * 4); +} + +static void wl_shell_surface_handle_ping(void* data, struct wl_shell_surface* shell_surface, uint32_t serial) +{ + wl_shell_surface_pong(shell_surface, serial); +} + +static const struct wl_shell_surface_listener wl_shell_surface_listener = +{ + wl_shell_surface_handle_ping +}; + +static void wl_registry_handle_global(void* data, struct wl_registry* registry, uint32_t id, const char *interface, uint32_t version) +{ + struct display* display = data; + + if (strcmp(interface, "wl_compositor") == 0) + display->compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 1); + else if (strcmp(interface, "wl_shell") == 0) + display->shell = wl_registry_bind(registry, id, &wl_shell_interface, 1); + else if (strcmp(interface, "wl_shm") == 0) + display->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1); +} + +static void wl_registry_handle_global_remove(void* data, struct wl_registry* registry, uint32_t name) +{ + +} + +static const struct wl_registry_listener wl_registry_listener = +{ + wl_registry_handle_global, + wl_registry_handle_global_remove +}; + +int wl_context_new(freerdp* instance, rdpContext* context) +{ + context->channels = freerdp_channels_new(); + + return 0; +} + +void wl_context_free(freerdp* instance, rdpContext* context) +{ + +} + +void wl_begin_paint(rdpContext* context) +{ + rdpGdi* gdi; + + gdi = context->gdi; + gdi->primary->hdc->hwnd->invalid->null = 1; +} + +void wl_end_paint(rdpContext* context) +{ + rdpGdi* gdi; + struct display* display; + struct window* window; + struct wl_context* context_w; + + gdi = context->gdi; + if (gdi->primary->hdc->hwnd->invalid->null) + return; + + context_w = (struct wl_context*) context; + display = context_w->display; + window = context_w->window; + + realloc(window->data, gdi->width * gdi->height * 4); + memcpy(window->data, (void*) gdi->primary_buffer, gdi->width * gdi->height * 4); + wl_display_dispatch(display->display); +} + +BOOL wl_pre_connect(freerdp* instance) +{ + struct display* display; + struct wl_context* context; + + freerdp_channels_pre_connect(instance->context->channels, instance); + + display = malloc(sizeof(*display)); + display->display = wl_display_connect(NULL); + + if (!display->display) + { + fprintf(stderr, "wl_pre_connect: failed to connect to Wayland compositor\n"); + fprintf(stderr, "Please check that the XDG_RUNTIME_DIR environment variable is properly set.\n"); + free(display); + return FALSE; + } + + display->registry = wl_display_get_registry(display->display); + wl_registry_add_listener(display->registry, &wl_registry_listener, display); + wl_display_roundtrip(display->display); + + if (!display->compositor || !display->shell || !display->shm) + { + fprintf(stderr, "wl_pre_connect: failed to find needed compositor interfaces\n"); + free(display); + return FALSE; + } + + /* put Wayland data in the context here */ + context = (struct wl_context*) instance->context; + context->display = display; + + return TRUE; +} + +BOOL wl_post_connect(freerdp* instance) +{ + struct window* window; + struct wl_context* context; + + context = (struct wl_context*) instance->context; + + window = malloc(sizeof(*window)); + window->width = instance->settings->DesktopWidth; + window->height = instance->settings->DesktopHeight; + window->buffers[0].busy = 0; + window->buffers[1].busy = 0; + window->callback = NULL; + window->display = context->display; + window->surface = wl_compositor_create_surface(window->display->compositor); + window->shell_surface = wl_shell_get_shell_surface(window->display->shell, window->surface); + + wl_shell_surface_add_listener(window->shell_surface, &wl_shell_surface_listener, NULL); + wl_shell_surface_set_title(window->shell_surface, "FreeRDP"); + wl_shell_surface_set_toplevel(window->shell_surface); + wl_surface_damage(window->surface, 0, 0, window->width, window->height); + + /* GC/GDI logic here */ + rdpGdi* gdi; + + gdi_init(instance, CLRCONV_ALPHA | CLRCONV_INVERT | CLRBUF_32BPP, NULL); + gdi = instance->context->gdi; + + /* fill buffer with first image here */ + window->data = malloc (gdi->width * gdi->height *4); + memcpy(window->data, (void*) gdi->primary_buffer, gdi->width * gdi->height * 4); + instance->update->BeginPaint = wl_begin_paint; + instance->update->EndPaint = wl_end_paint; + + /* put Wayland data in the context here */ + context->window = window; + + freerdp_channels_post_connect(instance->context->channels, instance); + + window_redraw(window, NULL, 0); + + return TRUE; +} + +int wlfreerdp_run(freerdp* instance) +{ + int i; + int fds; + int max_fds; + int rcount; + int wcount; + void* rfds[32]; + void* wfds[32]; + fd_set rfds_set; + fd_set wfds_set; + + ZeroMemory(rfds, sizeof(rfds)); + ZeroMemory(wfds, sizeof(wfds)); + + freerdp_connect(instance); + + while (1) + { + rcount = 0; + wcount = 0; + if (freerdp_get_fds(instance, rfds, &rcount, wfds, &wcount) != TRUE) + { + printf("Failed to get FreeRDP file descriptor"); + break; + } + if (freerdp_channels_get_fds(instance->context->channels, instance, rfds, &rcount, wfds, &wcount) != TRUE) + { + printf("Failed to get FreeRDP file descriptor"); + break; + } + + max_fds = 0; + FD_ZERO(&rfds_set); + FD_ZERO(&wfds_set); + + for (i = 0; i < rcount; i++) + { + fds = (int)(long)(rfds[i]); + + if (fds > max_fds) + max_fds = fds; + + FD_SET(fds, &rfds_set); + } + + if (max_fds == 0) + break; + + if (select(max_fds + 1, &rfds_set, &wfds_set, NULL, NULL) == -1) + { + if (!((errno == EAGAIN) || + (errno == EWOULDBLOCK) || + (errno == EINPROGRESS) || + (errno == EINTR))) + { + printf("wlfreerdp_run: select failed\n"); + break; + } + } + + if (freerdp_check_fds(instance) != TRUE) + { + printf("Failed to check FreeRDP file descriptor\n"); + break; + } + if (freerdp_channels_check_fds(instance->context->channels, instance) != TRUE) + { + printf("Failed to check channel manager file descriptor\n"); + break; + } + } + + struct display* display; + struct window* window; + struct wl_context* context; + + context = (struct wl_context*) instance->context; + display = context->display; + window = context->window; + free(window->buffers[0].shm_data); + free(window->buffers[1].shm_data); + free(window->data); + + wl_buffer_destroy(window->buffers[0].buffer); + wl_buffer_destroy(window->buffers[1].buffer); + wl_shell_surface_destroy(window->shell_surface); + wl_surface_destroy(window->surface); + wl_shm_destroy(display->shm); + wl_shell_destroy(display->shell); + wl_compositor_destroy(display->compositor); + wl_registry_destroy(display->registry); + wl_display_disconnect(display->display); + + freerdp_channels_close(instance->context->channels, instance); + freerdp_channels_free(instance->context->channels); + freerdp_free(instance); + + return 0; +} + +int main(int argc, char* argv[]) +{ + int status; + freerdp* instance; + + freerdp_channels_global_init(); + + instance = freerdp_new(); + instance->PreConnect = wl_pre_connect; + instance->PostConnect = wl_post_connect; + + instance->ContextSize = sizeof(struct wl_context); + instance->ContextNew = wl_context_new; + instance->ContextFree = wl_context_free; + freerdp_context_new(instance); + + status = freerdp_client_parse_command_line_arguments(argc, argv, instance->settings); + + if (status < 0) + exit(0); + + freerdp_client_load_addins(instance->context->channels, instance->settings); + + wlfreerdp_run(instance); + + freerdp_channels_global_uninit(); + + return 0; +} diff --git a/cmake/FindWayland.cmake b/cmake/FindWayland.cmake new file mode 100644 index 000000000..131544465 --- /dev/null +++ b/cmake/FindWayland.cmake @@ -0,0 +1,46 @@ +# - Find Wayland +# Find the Wayland libraries +# +# This module defines the following variables: +# WAYLAND_FOUND - true if WAYLAND_INCLUDE_DIR & WAYLAND_LIBRARY are found +# WAYLAND_LIBRARIES - Set when WAYLAND_LIBRARY is found +# WAYLAND_INCLUDE_DIRS - Set when WAYLAND_INCLUDE_DIR is found +# +# WAYLAND_INCLUDE_DIR - where to find wayland-client.h, etc. +# WAYLAND_LIBRARY - the Wayland client library +# + +#============================================================================= +# Copyright 2014 Manuel Bachmann +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#============================================================================= + +find_path(WAYLAND_INCLUDE_DIR NAMES wayland-client.h + DOC "The Wayland include directory" +) + +find_library(WAYLAND_LIBRARY NAMES wayland-client + DOC "The Wayland client library" +) + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(WAYLAND DEFAULT_MSG WAYLAND_LIBRARY WAYLAND_INCLUDE_DIR) + +if(WAYLAND_FOUND) + set( WAYLAND_LIBRARIES ${WAYLAND_LIBRARY} ) + set( WAYLAND_INCLUDE_DIRS ${WAYLAND_INCLUDE_DIR} ) +endif() + +mark_as_advanced(WAYLAND_INCLUDE_DIR WAYLAND_LIBRARY) +