diff options
31 files changed, 1814 insertions, 82 deletions
diff --git a/CMake/FindGBM.cmake b/CMake/FindGBM.cmake new file mode 100644 index 00000000..85df1f7c --- /dev/null +++ b/CMake/FindGBM.cmake @@ -0,0 +1,39 @@ +#.rst: +# FindGBM +# ------- +# +# Find GBM library +# +# Try to find GBM library on UNIX systems. The following values are defined +# +# :: +# +# GBM_FOUND - True if gbm is available +# GBM_INCLUDE_DIRS - Include directories for gbm +# GBM_LIBRARIES - List of libraries for gbm +# GBM_DEFINITIONS - List of definitions for gbm +# +#============================================================================= +# Copyright (c) 2015 Jari Vetoniemi +# +# Distributed under the OSI-approved BSD License (the "License"); +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= + +set_package_properties(GBM PROPERTIES + URL "http://www.mesa3d.org/" + DESCRIPTION "Generic buffer manager") + +find_package(PkgConfig) +pkg_check_modules(PC_GBM QUIET gbm) +find_library(GBM_LIBRARIES NAMES gbm HINTS ${PC_GBM_LIBRARY_DIRS}) +find_path(GBM_INCLUDE_DIRS gbm.h HINTS ${PC_GBM_INCLUDE_DIRS}) + +set(GBM_DEFINITIONS ${PC_GBM_CFLAGS_OTHER}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(GBM DEFAULT_MSG GBM_INCLUDE_DIRS GBM_LIBRARIES) +mark_as_advanced(GBM_INCLUDE_DIRS GBM_LIBRARIES GBM_DEFINITIONS) diff --git a/CMake/FindSystemd.cmake b/CMake/FindSystemd.cmake new file mode 100644 index 00000000..09d60115 --- /dev/null +++ b/CMake/FindSystemd.cmake @@ -0,0 +1,40 @@ +#.rst: +# FindSystemd +# ------- +# +# Find Systemd library +# +# Try to find Systemd library on UNIX systems. The following values are defined +# +# :: +# +# SYSTEMD_FOUND - True if Systemd is available +# SYSTEMD_INCLUDE_DIRS - Include directories for Systemd +# SYSTEMD_LIBRARIES - List of libraries for Systemd +# SYSTEMD_DEFINITIONS - List of definitions for Systemd +# +#============================================================================= +# Copyright (c) 2015 Jari Vetoniemi +# +# Distributed under the OSI-approved BSD License (the "License"); +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= + +include(FeatureSummary) +set_package_properties(Systemd PROPERTIES + URL "http://freedesktop.org/wiki/Software/systemd/" + DESCRIPTION "System and Service Manager") + +find_package(PkgConfig) +pkg_check_modules(PC_SYSTEMD QUIET libsystemd) +find_library(SYSTEMD_LIBRARIES NAMES systemd ${PC_SYSTEMD_LIBRARY_DIRS}) +find_path(SYSTEMD_INCLUDE_DIRS systemd/sd-login.h HINTS ${PC_SYSTEMD_INCLUDE_DIRS}) + +set(SYSTEMD_DEFINITIONS ${PC_SYSTEMD_CFLAGS_OTHER}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(SYSTEMD DEFAULT_MSG SYSTEMD_INCLUDE_DIRS SYSTEMD_LIBRARIES) +mark_as_advanced(SYSTEMD_INCLUDE_DIRS SYSTEMD_LIBRARIES SYSTEMD_DEFINITIONS) diff --git a/CMakeLists.txt b/CMakeLists.txt index dd4d9969..616a8911 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,9 +48,10 @@ find_package(WaylandProtocols REQUIRED) find_package(EGL REQUIRED) find_package(GLESv2 REQUIRED) find_package(DRM REQUIRED) +find_package(GBM REQUIRED) find_package(LibInput REQUIRED) -find_package(Udev) -find_package(Dbus) +find_package(Udev REQUIRED) +find_package(Systemd) include(Wayland) include(Manpage) @@ -60,5 +61,6 @@ include_directories(include) add_subdirectory(backend) add_subdirectory(common) add_subdirectory(wayland) +add_subdirectory(session) add_subdirectory(example) diff --git a/backend/CMakeLists.txt b/backend/CMakeLists.txt index 830a2158..fc1793cf 100644 --- a/backend/CMakeLists.txt +++ b/backend/CMakeLists.txt @@ -1,17 +1,30 @@ include_directories( ${PROTOCOLS_INCLUDE_DIRS} ${WAYLAND_INCLUDE_DIR} + ${DRM_INCLUDE_DIRS} ) add_library(wlr-backend - wayland/backend.c - wayland/registry.c - wayland/wl_seat.c - wayland/wl_output.c + #wayland/backend.c + #wayland/registry.c + #wayland/wl_seat.c + #wayland/wl_output.c + drm/backend.c + drm/drm.c + drm/udev.c + backend.c + egl.c ) target_link_libraries(wlr-backend wlr-common wlr-wayland ${WAYLAND_LIBRARIES} + ${DRM_LIBRARIES} + ${GBM_LIBRARIES} + ${GLESv2_LIBRARIES} + ${EGL_LIBRARIES} + ${SYSTEMD_LIBRARIES} + ${UDEV_LIBRARIES} + ${GBM_LIBRARIES} ) diff --git a/backend/backend.c b/backend/backend.c new file mode 100644 index 00000000..753bb3ae --- /dev/null +++ b/backend/backend.c @@ -0,0 +1,36 @@ +#include <wayland-server.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include "common/log.h" +#include "backend.h" + +struct wlr_backend *wlr_backend_create(const struct wlr_backend_impl *impl, + struct wlr_backend_state *state) { + struct wlr_backend *backend = calloc(1, sizeof(struct wlr_backend)); + if (!backend) { + wlr_log(L_ERROR, "Allocation failed: %s", strerror(errno)); + return NULL; + } + backend->state = state; + backend->impl = impl; + wl_signal_init(&backend->events.output_add); + wl_signal_init(&backend->events.output_remove); + wl_signal_init(&backend->events.keyboard_add); + wl_signal_init(&backend->events.keyboard_remove); + wl_signal_init(&backend->events.pointer_add); + wl_signal_init(&backend->events.pointer_remove); + wl_signal_init(&backend->events.touch_add); + wl_signal_init(&backend->events.touch_remove); + return backend; +} + +bool wlr_backend_init(struct wlr_backend *backend) { + return backend->impl->init(backend->state); +} + +void wlr_backend_destroy(struct wlr_backend *backend) { + backend->impl->destroy(backend->state); + // TODO: free outputs + free(backend); +} diff --git a/backend/drm/backend.c b/backend/drm/backend.c new file mode 100644 index 00000000..0da84745 --- /dev/null +++ b/backend/drm/backend.c @@ -0,0 +1,103 @@ +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <wayland-server.h> + +#include <wlr/session.h> +#include <wlr/common/list.h> + +#include "backend.h" +#include "backend/drm/backend.h" +#include "backend/drm/drm.h" +#include "backend/drm/udev.h" +#include "common/log.h" + +static bool wlr_drm_backend_init(struct wlr_backend_state *state) { + wlr_drm_scan_connectors(state); + return true; +} + +static void wlr_drm_backend_destroy(struct wlr_backend_state *state) { + if (!state) { + return; + } + // TODO: free outputs in shared backend code + wlr_drm_renderer_free(&state->renderer); + wlr_udev_free(&state->udev); + wlr_session_close_file(state->session, state->fd); + wlr_session_finish(state->session); + wl_event_source_remove(state->drm_event); + free(state); +} + +static struct wlr_backend_impl backend_impl = { + .init = wlr_drm_backend_init, + .destroy = wlr_drm_backend_destroy +}; + +struct wlr_backend *wlr_drm_backend_create(struct wl_display *display, + struct wlr_session *session) { + struct wlr_backend_state *state = calloc(1, sizeof(struct wlr_backend_state)); + if (!state) { + wlr_log(L_ERROR, "Allocation failed: %s", strerror(errno)); + return NULL; + } + + struct wlr_backend *backend = wlr_backend_create(&backend_impl, state); + if (!backend) { + wlr_log(L_ERROR, "Allocation failed: %s", strerror(errno)); + return NULL; + } + + state->backend = backend; + state->session = session; + state->outputs = list_create(); + if (!state->outputs) { + wlr_log(L_ERROR, "Failed to allocate list"); + goto error_backend; + } + + if (!wlr_udev_init(display, &state->udev)) { + wlr_log(L_ERROR, "Failed to start udev"); + goto error_list; + } + + state->fd = wlr_udev_find_gpu(&state->udev, state->session); + if (state->fd == -1) { + wlr_log(L_ERROR, "Failed to open DRM device"); + goto error_udev; + } + + struct wl_event_loop *event_loop = wl_display_get_event_loop(display); + + state->drm_event = wl_event_loop_add_fd(event_loop, state->fd, + WL_EVENT_READABLE, wlr_drm_event, NULL); + if (!state->drm_event) { + wlr_log(L_ERROR, "Failed to create DRM event source"); + goto error_fd; + } + + // TODO: what is the difference between the per-output renderer and this + // one? + if (!wlr_drm_renderer_init(&state->renderer, state->fd)) { + wlr_log(L_ERROR, "Failed to initialize renderer"); + goto error_event; + } + + return backend; + +error_event: + wl_event_source_remove(state->drm_event); +error_fd: + wlr_session_close_file(state->session, state->fd); +error_udev: + wlr_udev_free(&state->udev); +error_list: + list_free(state->outputs); +error_backend: + free(state); + free(backend); + return NULL; +} diff --git a/backend/drm/drm.c b/backend/drm/drm.c new file mode 100644 index 00000000..7cbd3e4a --- /dev/null +++ b/backend/drm/drm.c @@ -0,0 +1,418 @@ +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include <errno.h> +#include <xf86drm.h> +#include <xf86drmMode.h> +#include <drm_mode.h> +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <gbm.h> +#include <GLES3/gl3.h> +#include <wayland-server.h> + +#include "wayland.h" +#include "backend.h" +#include "backend/drm/backend.h" +#include "backend/drm/drm.h" +#include "common/log.h" + +static const char *conn_name[] = { + [DRM_MODE_CONNECTOR_Unknown] = "Unknown", + [DRM_MODE_CONNECTOR_VGA] = "VGA", + [DRM_MODE_CONNECTOR_DVII] = "DVI-I", + [DRM_MODE_CONNECTOR_DVID] = "DVI-D", + [DRM_MODE_CONNECTOR_DVIA] = "DVI-A", + [DRM_MODE_CONNECTOR_Composite] = "Composite", + [DRM_MODE_CONNECTOR_SVIDEO] = "SVIDEO", + [DRM_MODE_CONNECTOR_LVDS] = "LVDS", + [DRM_MODE_CONNECTOR_Component] = "Component", + [DRM_MODE_CONNECTOR_9PinDIN] = "DIN", + [DRM_MODE_CONNECTOR_DisplayPort] = "DP", + [DRM_MODE_CONNECTOR_HDMIA] = "HDMI-A", + [DRM_MODE_CONNECTOR_HDMIB] = "HDMI-B", + [DRM_MODE_CONNECTOR_TV] = "TV", + [DRM_MODE_CONNECTOR_eDP] = "eDP", + [DRM_MODE_CONNECTOR_VIRTUAL] = "Virtual", + [DRM_MODE_CONNECTOR_DSI] = "DSI", +}; + +bool wlr_drm_renderer_init(struct wlr_drm_renderer *renderer, int fd) { + renderer->gbm = gbm_create_device(fd); + if (!renderer->gbm) { + wlr_log(L_ERROR, "Failed to create GBM device: %s", strerror(errno)); + return false; + } + + if (!wlr_egl_init(&renderer->egl, EGL_PLATFORM_GBM_MESA, renderer->gbm)) { + gbm_device_destroy(renderer->gbm); + return false; + } + + renderer->fd = fd; + return true; +} + +void wlr_drm_renderer_free(struct wlr_drm_renderer *renderer) { + if (!renderer) { + return; + } + wlr_egl_free(&renderer->egl); + gbm_device_destroy(renderer->gbm); +} + +static void free_fb(struct gbm_bo *bo, void *data) { + uint32_t *id = data; + + if (id && *id) { + drmModeRmFB(gbm_bo_get_fd(bo), *id); + } + + free(id); +} + +static uint32_t get_fb_for_bo(int fd, struct gbm_bo *bo) { + uint32_t *id = gbm_bo_get_user_data(bo); + + if (id) { + return *id; + } + + id = calloc(1, sizeof *id); + if (!id) { + wlr_log(L_ERROR, "Allocation failed: %s", strerror(errno)); + return 0; + } + + drmModeAddFB(fd, gbm_bo_get_width(bo), gbm_bo_get_height(bo), 24, 32, + gbm_bo_get_stride(bo), gbm_bo_get_handle(bo).u32, id); + + gbm_bo_set_user_data(bo, id, free_fb); + + return *id; +} + +void wlr_drm_output_begin(struct wlr_output *output) { + struct wlr_output_state *_output = output->state; + struct wlr_drm_renderer *renderer = _output->renderer; + eglMakeCurrent(renderer->egl.display, _output->egl, + _output->egl, renderer->egl.context); +} + +void wlr_drm_output_end(struct wlr_output *output) { + struct wlr_output_state *_output = output->state; + struct wlr_drm_renderer *renderer = _output->renderer; + + eglSwapBuffers(renderer->egl.display, _output->egl); + struct gbm_bo *bo = gbm_surface_lock_front_buffer(_output->gbm); + uint32_t fb_id = get_fb_for_bo(renderer->fd, bo); + drmModePageFlip(renderer->fd, _output->crtc, fb_id, DRM_MODE_PAGE_FLIP_EVENT, _output); + gbm_surface_release_buffer(_output->gbm, bo); + _output->pageflip_pending = true; +} + +static bool display_init_renderer(struct wlr_drm_renderer *renderer, + struct wlr_output_state *output) { + struct wlr_output_mode *mode = output->wlr_output->current_mode; + output->renderer = renderer; + output->gbm = gbm_surface_create(renderer->gbm, mode->width, + mode->height, GBM_FORMAT_XRGB8888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); + if (!output->gbm) { + wlr_log(L_ERROR, "Failed to create GBM surface for %s: %s", output->name, + strerror(errno)); + return false; + } + + output->egl = wlr_egl_create_surface(&renderer->egl, output->gbm); + if (output->egl == EGL_NO_SURFACE) { + wlr_log(L_ERROR, "Failed to create EGL surface for %s", output->name); + return false; + } + + // Render black frame + eglMakeCurrent(renderer->egl.display, output->egl, output->egl, renderer->egl.context); + + glViewport(0, 0, output->width, output->height); + glClearColor(0.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + eglSwapBuffers(renderer->egl.display, output->egl); + + struct gbm_bo *bo = gbm_surface_lock_front_buffer(output->gbm); + uint32_t fb_id = get_fb_for_bo(renderer->fd, bo); + + drmModeSetCrtc(renderer->fd, output->crtc, fb_id, 0, 0, + &output->connector, 1, &mode->state->mode); + drmModePageFlip(renderer->fd, output->crtc, fb_id, + DRM_MODE_PAGE_FLIP_EVENT, output); + + gbm_surface_release_buffer(output->gbm, bo); + return true; +} + +static int find_id(const void *item, const void *cmp_to) { + const struct wlr_output_state *output = item; + const uint32_t *id = cmp_to; + + if (output->connector < *id) { + return -1; + } else if (output->connector > *id) { + return 1; + } else { + return 0; + } +} + +static bool wlr_drm_output_set_mode(struct wlr_output_state *output, + struct wlr_output_mode *mode) { + struct wlr_backend_state *state = + wl_container_of(output->renderer, state, renderer); + + wlr_log(L_INFO, "Modesetting '%s' with '%ux%u@%u mHz'", output->name, + mode->width, mode->height, mode->refresh); + + drmModeConnector *conn = drmModeGetConnector(state->fd, output->connector); + if (!conn) { + wlr_log(L_ERROR, "Failed to get DRM connector"); + goto error; + } + + if (conn->connection != DRM_MODE_CONNECTED || conn->count_modes == 0) { + wlr_log(L_ERROR, "%s is not connected", output->name); + goto error; + } + + drmModeRes *res = drmModeGetResources(state->fd); + if (!res) { + wlr_log(L_ERROR, "Failed to get DRM resources"); + goto error; + } + + bool success = false; + for (int i = 0; !success && i < conn->count_encoders; ++i) { + drmModeEncoder *enc = drmModeGetEncoder(state->fd, conn->encoders[i]); + if (!enc) { + continue; + } + + for (int j = 0; j < res->count_crtcs; ++j) { + if ((enc->possible_crtcs & (1 << j)) == 0) { + continue; + } + + if ((state->taken_crtcs & (1 << j)) == 0) { + state->taken_crtcs |= 1 << j; + output->crtc = res->crtcs[j]; + success = true; + break; + } + } + drmModeFreeEncoder(enc); + } + + drmModeFreeResources(res); + + if (!success) { + wlr_log(L_ERROR, "Failed to find CRTC for %s", output->name); + goto error; + } + + output->state = DRM_OUTPUT_CONNECTED; + output->width = mode->width; + output->height = mode->height; + output->wlr_output->current_mode = mode; + + if (!display_init_renderer(&state->renderer, output)) { + wlr_log(L_ERROR, "Failed to initalise renderer for %s", output->name); + goto error; + } + + drmModeFreeConnector(conn); + return true; + +error: + wlr_drm_output_cleanup(output, false); + drmModeFreeConnector(conn); + return false; +} + +static void wlr_drm_output_destroy(struct wlr_output_state *output) { + wlr_drm_output_cleanup(output, true); + wlr_drm_renderer_free(output->renderer); + free(output); +} + +static struct wlr_output_impl output_impl = { + .set_mode = wlr_drm_output_set_mode, + .destroy = wlr_drm_output_destroy, +}; + +void wlr_drm_scan_connectors(struct wlr_backend_state *state) { + wlr_log(L_INFO, "Scanning DRM connectors"); + + drmModeRes *res = drmModeGetResources(state->fd); + if (!res) { + wlr_log(L_ERROR, "Failed to get DRM resources"); + return; + } + + for (int i = 0; i < res->count_connectors; ++i) { + uint32_t id = res->connectors[i]; + + drmModeConnector *conn = drmModeGetConnector(state->fd, id); + if (!conn) { + wlr_log(L_ERROR, "Failed to get DRM connector"); + continue; + } + + struct wlr_output_state *output; + struct wlr_output *wlr_output; + int index = list_seq_find(state->outputs, find_id, &id); + + if (index == -1) { + output = calloc(1, sizeof(struct wlr_output_state)); + if (!state) { + drmModeFreeConnector(conn); + wlr_log(L_ERROR, "Allocation failed: %s", strerror(errno)); + return; + } + wlr_output = output->wlr_output = wlr_output_create(&output_impl, output); + if (!wlr_output) { + drmModeFreeConnector(conn); + wlr_log(L_ERROR, "Allocation failed: %s", strerror(errno)); + return; + } + + output->renderer = &state->renderer; + output->state = DRM_OUTPUT_DISCONNECTED; + output->connector = id; + // TODO: Populate more wlr_output fields + // TODO: Move this to wlr_output->name + snprintf(output->name, sizeof(output->name), "%s-%"PRIu32, + conn_name[conn->connector_type], + conn->connector_type_id); + + drmModeEncoder *curr_enc = drmModeGetEncoder(state->fd, conn->encoder_id); + if (curr_enc) { + output->old_crtc = drmModeGetCrtc(state->fd, curr_enc->crtc_id); + free(curr_enc); + } + + list_add(state->outputs, output); + wlr_log(L_INFO, "Found display '%s'", output->name); + } else { + output = state->outputs->items[index]; + wlr_output = output->wlr_output; + } + + // TODO: move state into wlr_output + if (output->state == DRM_OUTPUT_DISCONNECTED && + conn->connection == DRM_MODE_CONNECTED) { + + wlr_log(L_INFO, "'%s' connected", output->name); + wlr_log(L_INFO, "Detected modes:"); + + for (int i = 0; i < conn->count_modes; ++i) { + struct wlr_output_mode_state *_state = calloc(1, + sizeof(struct wlr_output_mode_state)); + _state->mode = conn->modes[i]; + struct wlr_output_mode *mode = calloc(1, + sizeof(struct wlr_output_mode)); + mode->width = _state->mode.hdisplay; + // TODO: Calculate more accurate refresh rate + // TODO: Check that this refresh rate is mHz + mode->height = _state->mode.vdisplay; + mode->state = _state; + + wlr_log(L_INFO, " %"PRIu16"@%"PRIu16"@%"PRIu32, + _state->mode.hdisplay, _state->mode.vdisplay, + _state->mode.vrefresh); + + list_add(wlr_output->modes, mode); + } + + output->state = DRM_OUTPUT_NEEDS_MODESET; + wlr_log(L_INFO, "Sending modesetting signal for '%s'", output->name); + wl_signal_emit(&state->backend->events.output_add, wlr_output); + } else if (output->state == DRM_OUTPUT_CONNECTED && + conn->connection != DRM_MODE_CONNECTED) { + + wlr_log(L_INFO, "'%s' disconnected", output->name); + // TODO: Destroy + wlr_drm_output_cleanup(output, false); + } + + drmModeFreeConnector(conn); + } + + drmModeFreeResources(res); +} + +static void page_flip_handler(int fd, unsigned seq, + unsigned tv_sec, unsigned tv_usec, void *user) { + struct wlr_output_state *output = user; + struct wlr_backend_state *state = + wl_container_of(output->renderer, state, renderer); + + output->pageflip_pending = false; + if (output->state == DRM_OUTPUT_CONNECTED) { + wl_signal_emit(&output->wlr_output->events.frame, output->wlr_output); + } +} + +int wlr_drm_event(int fd, uint32_t mask, void *data) { + drmEventContext event = { + .version = DRM_EVENT_CONTEXT_VERSION, + .page_flip_handler = page_flip_handler, + }; + + drmHandleEvent(fd, &event); + return 1; +} + +static void restore_output(struct wlr_output_state *output, int fd) { + // Wait for any pending pageflips to finish + while (output->pageflip_pending) { + wlr_drm_event(fd, 0, NULL); + } + + drmModeCrtc *crtc = output->old_crtc; + if (!crtc) { + return; + } + + drmModeSetCrtc(fd, crtc->crtc_id, crtc->buffer_id, crtc->x, crtc->y, + &output->connector, 1, &crtc->mode); + drmModeFreeCrtc(crtc); +} + +void wlr_drm_output_cleanup(struct wlr_output_state *output, bool restore) { + if (!output) { + return; + } + + struct wlr_drm_renderer *renderer = output->renderer; + struct wlr_backend_state *state = wl_container_of(renderer, state, renderer); + + switch (output->state) { + case DRM_OUTPUT_CONNECTED: + eglDestroySurface(renderer->egl.display, output->egl); + gbm_surface_destroy(output->gbm); + output->egl = EGL_NO_SURFACE; + output->gbm = NULL; + /* Fallthrough */ + case DRM_OUTPUT_NEEDS_MODESET: + output->state = DRM_OUTPUT_DISCONNECTED; + if (restore) { + restore_output(output, renderer->fd); + } + wlr_log(L_INFO, "Emmiting destruction signal for '%s'", output->name); + wl_signal_emit(&state->backend->events.output_remove, output->wlr_output); + break; + case DRM_OUTPUT_DISCONNECTED: + break; + } + // TODO: free wlr_output +} diff --git a/backend/drm/udev.c b/backend/drm/udev.c new file mode 100644 index 00000000..e5470157 --- /dev/null +++ b/backend/drm/udev.c @@ -0,0 +1,214 @@ +#define _POSIX_C_SOURCE 200809L + +#include <libudev.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <xf86drm.h> +#include <xf86drmMode.h> +#include <wayland-server.h> + +#include <wlr/session.h> + +#include "backend.h" +#include "backend/drm/backend.h" +#include "backend/drm/udev.h" +#include "backend/drm/drm.h" +#include "common/log.h" + +/* Tests if 'path' is KMS compatible by trying to open it. + * It leaves the open device in *fd_out it it succeeds. + */ +static bool device_is_kms(struct wlr_session *restrict session, + const char *restrict path, int *restrict fd_out) { + + int fd; + + if (!path) { + return false; + } + + fd = wlr_session_open_file(session, path); + if (fd < 0) { + return false; + } + + drmModeRes *res = drmModeGetResources(fd); + if (!res) { + goto out_fd; + } + + if (res->count_crtcs <= 0 || res->count_connectors <= 0 || + res->count_encoders <= 0) { + + goto out_res; + } + + if (*fd_out >= 0) { + wlr_session_close_file(session, *fd_out); + } + + *fd_out = fd; + + drmModeFreeResources(res); + return true; + +out_res: + drmModeFreeResources(res); +out_fd: + wlr_session_close_file(session, fd); + return false; +} + +/* Tries to find the primary GPU by checking for the "boot_vga" attribute. + * If it's not found, it returns the first valid GPU it finds. + */ +int wlr_udev_find_gpu(struct wlr_udev *udev, struct wlr_session *session) { + struct udev_enumerate *en = udev_enumerate_new(udev->udev); + if (!en) { + wlr_log(L_ERROR, "Failed to create udev enumeration"); + return -1; + } + + udev_enumerate_add_match_subsystem(en, "drm"); + udev_enumerate_add_match_sysname(en, "card[0-9]*"); + udev_enumerate_scan_devices(en); + + struct udev_list_entry *entry; + int fd = -1; + char *drm_path = NULL; + + udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(en)) { + bool is_boot_vga = false; + + const char *path = udev_list_entry_get_name(entry); + struct udev_device *dev = udev_device_new_from_syspath(udev->udev, path); + if (!dev) { + continue; + } + + /* + const char *seat = udev_device_get_property_value(dev, "ID_SEAT"); + if (!seat) + seat = "seat0"; + if (strcmp(session->seat, seat) != 0) { + udev_device_unref(dev); + continue; + } + */ + + // This is owned by 'dev', so we don't need to free it + struct udev_device *pci = + udev_device_get_parent_with_subsystem_devtype(dev, "pci", NULL); + + if (pci) { + const char *id = udev_device_get_sysattr_value(pci, "boot_vga"); + if (id && strcmp(id, "1") == 0) { + is_boot_vga = true; + } + } + + // We already have a valid GPU + if (!is_boot_vga && fd >= 0) { + udev_device_unref(dev); + continue; + } + + path = udev_device_get_devnode(dev); + if (!device_is_kms(session, path, &fd)) { + udev_device_unref(dev); + continue; + } + + free(drm_path); + drm_path = strdup(path); + + udev_device_unref(dev); + + // We've found the primary GPU + if (is_boot_vga) { + break; + } + } + + udev_enumerate_unref(en); + + udev->drm_path = drm_path; + return fd; +} + +static int udev_event(int fd, uint32_t mask, void *data) { + struct wlr_udev *udev = data; + struct wlr_backend_state *state = wl_container_of(udev, state, udev); + + struct udev_device *dev = udev_monitor_receive_device(udev->mon); + if (!dev) { + return 1; + } + + const char *path = udev_device_get_devnode(dev); + if (!path || strcmp(path, udev->drm_path) != 0) { + goto out; + } + + const char *action = udev_device_get_action(dev); + if (!action || strcmp(action, "change") != 0) { + goto out; + } + + wlr_drm_scan_connectors(state); + +out: + udev_device_unref(dev); + return 1; +} + +bool wlr_udev_init(struct wl_display *display, struct wlr_udev *udev) { + udev->udev = udev_new(); + if (!udev->udev) { + wlr_log(L_ERROR, "Failed to create udev context"); + return false; + } + + udev->mon = udev_monitor_new_from_netlink(udev->udev, "udev"); + if (!udev->mon) { + wlr_log(L_ERROR, "Failed to create udev monitor"); + goto error_udev; + } + + udev_monitor_filter_add_match_subsystem_devtype(udev->mon, "drm", NULL); + udev_monitor_enable_receiving(udev->mon); + + struct wl_event_loop *event_loop = wl_display_get_event_loop(display); + int fd = udev_monitor_get_fd(udev->mon); + + udev->event = wl_event_loop_add_fd(event_loop, fd, WL_EVENT_READABLE, + udev_event, udev); + if (!udev->event) { + wlr_log(L_ERROR, "Failed to create udev event source"); + goto error_mon; + } + + udev->drm_path = NULL; + + return true; + +error_mon: + udev_monitor_unref(udev->mon); +error_udev: + udev_unref(udev->udev); + return false; +} + +void wlr_udev_free(struct wlr_udev *udev) { + if (!udev) { + return; + } + + wl_event_source_remove(udev->event); + + udev_monitor_unref(udev->mon); + udev_unref(udev->udev); + free(udev->drm_path); +} diff --git a/backend/egl.c b/backend/egl.c new file mode 100644 index 00000000..2aac25b7 --- /dev/null +++ b/backend/egl.c @@ -0,0 +1,178 @@ +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <GLES3/gl3.h> +#include <gbm.h> // GBM_FORMAT_XRGB8888 + +#include "backend/egl.h" +#include "common/log.h" + +static const char *egl_error(void) { + switch (eglGetError()) { + case EGL_SUCCESS: + return "Success"; + case EGL_NOT_INITIALIZED: + return "Not initialized"; + case EGL_BAD_ACCESS: + return "Bad access"; + case EGL_BAD_ALLOC: + return "Bad alloc"; + case EGL_BAD_ATTRIBUTE: + return "Bad attribute"; + case EGL_BAD_CONTEXT: + return "Bad Context"; + case EGL_BAD_CONFIG: + return "Bad Config"; + case EGL_BAD_CURRENT_SURFACE: + return "Bad current surface"; + case EGL_BAD_DISPLAY: + return "Bad display"; + case EGL_BAD_SURFACE: + return "Bad surface"; + case EGL_BAD_MATCH: + return "Bad match"; + case EGL_BAD_PARAMETER: + return "Bad parameter"; + case EGL_BAD_NATIVE_PIXMAP: + return "Bad native pixmap"; + case EGL_BAD_NATIVE_WINDOW: + return "Bad native window"; + case EGL_CONTEXT_LOST: + return "Context lost"; + default: + return "Unknown"; + } +} + +// EGL extensions +PFNEGLGETPLATFORMDISPLAYEXTPROC get_platform_display; +PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC create_platform_window_surface; + +static bool egl_exts() { + get_platform_display = (PFNEGLGETPLATFORMDISPLAYEXTPROC) + eglGetProcAddress("eglGetPlatformDisplayEXT"); + + if (!get_platform_display) { + wlr_log(L_ERROR, "Failed to load EGL extension 'eglGetPlatformDisplayEXT'"); + return false; + } + + create_platform_window_surface = (PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC) + eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT"); + + if (!get_platform_display) { + wlr_log(L_ERROR, + "Failed to load EGL extension 'eglCreatePlatformWindowSurfaceEXT'"); + return false; + } + + return true; +} + +static bool egl_get_config(EGLDisplay disp, EGLConfig *out) { + EGLint count = 0, matched = 0, ret; + + ret = eglGetConfigs(disp, NULL, 0, &count); + if (ret == EGL_FALSE || count == 0) { + return false; + } + + EGLConfig configs[count]; + + ret = eglChooseConfig(disp, NULL, configs, count, &matched); + if (ret == EGL_FALSE) { + return false; + } + + for (int i = 0; i < matched; ++i) { + EGLint gbm_format; + + if (!eglGetConfigAttrib(disp, + configs[i], + EGL_NATIVE_VISUAL_ID, + &gbm_format)) { + continue; + } + + // XXX: Is GBM_FORMAT_XRGB8888 what we want? + // I don't know if this works for wl_displays. + if (gbm_format == GBM_FORMAT_XRGB8888) { + *out = configs[i]; + return true; + } + } + + return false; +} + + +bool wlr_egl_init(struct wlr_egl *egl, EGLenum platform, void *display) { + if (!egl_exts()) { + return false; + } + + if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) { + wlr_log(L_ERROR, "Failed to bind to the OpenGL ES API: %s", egl_error()); + goto error; + } + + egl->display = get_platform_display(platform, display, NULL); + if (egl->display == EGL_NO_DISPLAY) { + wlr_log(L_ERROR, "Failed to create EGL display: %s", egl_error()); + goto error; + } + + EGLint major, minor; + if (eglInitialize(egl->display, &major, &minor) == EGL_FALSE) { + wlr_log(L_ERROR, "Failed to initialize EGL: %s", egl_error()); + goto error; + } + + if (!egl_get_config(egl->display, &egl->config)) { + wlr_log(L_ERROR, "Failed to get EGL config"); + goto error; + } + + static const EGLint attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE}; + + egl->context = eglCreateContext(egl->display, egl->config, + EGL_NO_CONTEXT, attribs); + + if (egl->context == EGL_NO_CONTEXT) { + wlr_log(L_ERROR, "Failed to create EGL context: %s", egl_error()); + goto error; + } + + eglMakeCurrent(egl->display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl->context); + wlr_log(L_INFO, "Using EGL %d.%d", (int)major, (int)minor); + wlr_log(L_INFO, "Supported EGL extensions: %s", eglQueryString(egl->display, + EGL_EXTENSIONS)); + wlr_log(L_INFO, "Using %s", glGetString(GL_VERSION)); + wlr_log(L_INFO, "Supported OpenGL ES extensions: %s", glGetString(GL_EXTENSIONS)); + + return true; + +error: + eglTerminate(egl->display); + eglReleaseThread(); + eglMakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + + return false; +} + +void wlr_egl_free(struct wlr_egl *egl) { + eglDestroyContext(egl->display, egl->context); + eglTerminate(egl->display); + eglReleaseThread(); + eglMakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); +} + +EGLSurface wlr_egl_create_surface(struct wlr_egl *egl, void *window) { + EGLSurface surf = create_platform_window_surface(egl->display, egl->config, + window, NULL); + if (surf == EGL_NO_SURFACE) { + wlr_log(L_ERROR, "Failed to create EGL surface: %s", egl_error()); + return EGL_NO_SURFACE; + } + + return surf; +} diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 73768393..f619b97f 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -1,7 +1,12 @@ +include_directories( + ${DRM_INCLUDE_DIRS} +) + add_executable(example main.c ) target_link_libraries(example wlr-backend + wlr-session ) diff --git a/example/main.c b/example/main.c index 4240b9e3..2c168d98 100644 --- a/example/main.c +++ b/example/main.c @@ -1,19 +1,133 @@ -#define _POSIX_C_SOURCE 200112L +#define _POSIX_C_SOURCE 199309L +#include <stdio.h> #include <stdlib.h> -#include <wlr/backend.h> -#include <wlr/backend/wayland.h> +#include <time.h> #include <wayland-server.h> +#include <GLES3/gl3.h> +#include <wlr/backend/drm.h> +#include <wlr/session.h> +#include <wlr/common/list.h> -int main(int argc, char **argv) { - // TODO: Move this stuff to a wlr backend selector function - char *_wl_display = getenv("WAYLAND_DISPLAY"); - if (_wl_display) { - unsetenv("WAYLAND_DISPLAY"); - setenv("_WAYLAND_DISPLAY", _wl_display, 1); +struct state { + float color[3]; + int dec; + struct timespec last_frame; + struct wl_listener output_add; + struct wl_listener output_remove; + list_t *outputs; +}; + +struct output_state { + struct wlr_output *output; + struct state *state; + struct wl_listener frame; +}; + +void output_frame(struct wl_listener *listener, void *data) { + struct wlr_output *output = data; + struct output_state *ostate = wl_container_of( + listener, ostate, frame); + struct state *s = ostate->state; + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + long ms = (now.tv_sec - s->last_frame.tv_sec) * 1000 + + (now.tv_nsec - s->last_frame.tv_nsec) / 1000000; + int inc = (s->dec + 1) % 3; + + s->color[inc] += ms / 2000.0f; + s->color[s->dec] -= ms / 2000.0f; + + if (s->color[s->dec] < 0.0f) { + s->color[inc] = 1.0f; + s->color[s->dec] = 0.0f; + + s->dec = inc; + } + + s->last_frame = now; + + wlr_drm_output_begin(output); + + glClearColor(s->color[0], s->color[1], s->color[2], 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + wlr_drm_output_end(output); +} + +void output_add(struct wl_listener *listener, void *data) { + struct wlr_output *output = data; + struct state *state = wl_container_of(listener, state, output_add); + fprintf(stderr, "Output '%s' added\n", output->name); + wlr_output_set_mode(output, output->modes->items[0]); + struct output_state *ostate = calloc(1, sizeof(struct output_state)); + ostate->output = output; + ostate->state = state; + ostate->frame.notify = output_frame; + wl_list_init(&ostate->frame.link); + wl_signal_add(&output->events.frame, &ostate->frame); + list_add(state->outputs, ostate); +} + +void output_remove(struct wl_listener *listener, void *data) { + struct wlr_output *output = data; + fprintf(stderr, "Output '%s' removed\n", output->name); + // TODO: remove signal from state->output_frame +} + +int timer_done(void *data) { + *(bool *)data = true; + return 1; +} + +int main() { + if (getenv("DISPLAY")) { + fprintf(stderr, "Detected that X is running. Run this in its own virtual terminal.\n"); + return 1; + } else if (getenv("WAYLAND_DISPLAY")) { + fprintf(stderr, "Detected that Wayland is running. Run this in its own virtual terminal.\n"); + return 1; + } + + struct state state = { + .color = { 1.0, 0.0, 0.0 }, + .dec = 0, + .output_add = { .notify = output_add }, + .output_remove = { .notify = output_remove }, + .outputs = list_create(), + }; + + wl_list_init(&state.output_add.link); + wl_list_init(&state.output_remove.link); + clock_gettime(CLOCK_MONOTONIC, &state.last_frame); + + struct wl_display *display = wl_display_create(); + struct wl_event_loop *event_loop = wl_display_get_event_loop(display); + + struct wlr_session *session = wlr_session_start(); + if (!session) { + return 1; + } + + struct wlr_backend *wlr = wlr_drm_backend_create(display, session); + wl_signal_add(&wlr->events.output_add, &state.output_add); + wl_signal_add(&wlr->events.output_remove, &state.output_remove); + if (!wlr || !wlr_backend_init(wlr)) { + return 1; + } + + bool done = false; + struct wl_event_source *timer = wl_event_loop_add_timer(event_loop, + timer_done, &done); + + wl_event_source_timer_update(timer, 5000); + + while (!done) { + wl_event_loop_dispatch(event_loop, 0); } - struct wl_display *wl_display = wl_display_create(); - struct wlr_wl_backend *backend = wlr_wl_backend_init(wl_display, 1); - wlr_wl_backend_free(backend); - wl_display_destroy(wl_display); - return 0; + + wl_event_source_remove(timer); + wlr_backend_destroy(wlr); + wl_display_destroy(display); } diff --git a/include/backend.h b/include/backend.h new file mode 100644 index 00000000..d42c6f17 --- /dev/null +++ b/include/backend.h @@ -0,0 +1,17 @@ +#ifndef _WLR_BACKEND_INTERNAL_H +#define _WLR_BACKEND_INTERNAL_H + +#include <stdbool.h> +#include <wlr/backend.h> + +struct wlr_backend_state; + +struct wlr_backend_impl { + bool (*init)(struct wlr_backend_state *state); + void (*destroy)(struct wlr_backend_state *state); +}; + +struct wlr_backend *wlr_backend_create(const struct wlr_backend_impl *impl, + struct wlr_backend_state *state); + +#endif diff --git a/include/backend/drm/backend.h b/include/backend/drm/backend.h new file mode 100644 index 00000000..0c725ccb --- /dev/null +++ b/include/backend/drm/backend.h @@ -0,0 +1,34 @@ +#ifndef DRM_BACKEND_H +#define DRM_BACKEND_H + +#include <stdbool.h> +#include <stddef.h> +#include <EGL/egl.h> +#include <gbm.h> +#include <libudev.h> +#include <wayland-server.h> + +#include <wlr/session.h> +#include <wlr/common/list.h> +#include <wlr/backend/drm.h> + +#include "backend.h" +#include "udev.h" +#include "event.h" +#include "drm.h" + +struct wlr_backend_state { + int fd; + + struct wlr_backend *backend; + struct wl_event_source *drm_event; + + uint32_t taken_crtcs; + list_t *outputs; + + struct wlr_drm_renderer renderer; + struct wlr_session *session; + struct wlr_udev udev; +}; + +#endif diff --git a/include/backend/drm/drm.h b/include/backend/drm/drm.h new file mode 100644 index 00000000..4b42aa68 --- /dev/null +++ b/include/backend/drm/drm.h @@ -0,0 +1,58 @@ +#ifndef DRM_H +#define DRM_H + +#include <stdbool.h> +#include <stdint.h> +#include <xf86drmMode.h> +#include <EGL/egl.h> +#include <gbm.h> + +#include "backend/egl.h" +#include "backend.h" + +struct wlr_drm_renderer { + int fd; + struct gbm_device *gbm; + struct wlr_egl egl; +}; + +bool wlr_drm_renderer_init(struct wlr_drm_renderer *renderer, int fd); +void wlr_drm_renderer_free(struct wlr_drm_renderer *renderer); + +enum wlr_drm_output_state { + DRM_OUTPUT_DISCONNECTED, + DRM_OUTPUT_NEEDS_MODESET, + DRM_OUTPUT_CONNECTED, +}; + +struct wlr_output_mode_state { + struct wlr_wl_output_mode *wlr_mode; + drmModeModeInfo mode; +}; + +struct wlr_output_state { + struct wlr_output *wlr_output; + enum wlr_drm_output_state state; + uint32_t connector; + char name[16]; + + uint32_t width; + uint32_t height; + + uint32_t crtc; + drmModeCrtc *old_crtc; + + struct wlr_drm_renderer *renderer; + struct gbm_surface *gbm; + EGLSurface *egl; + + bool pageflip_pending; + bool cleanup; +}; + +void wlr_drm_output_cleanup(struct wlr_output_state *output, bool restore); + +void wlr_drm_scan_connectors(struct wlr_backend_state *state); +int wlr_drm_event(int fd, uint32_t mask, void *data); + +#endif diff --git a/include/backend/drm/udev.h b/include/backend/drm/udev.h new file mode 100644 index 00000000..99c2c403 --- /dev/null +++ b/include/backend/drm/udev.h @@ -0,0 +1,24 @@ +#ifndef UDEV_H +#define UDEV_H + +#include <libudev.h> + +#include <wlr/session.h> +#include <wayland-server.h> + +struct wlr_udev { + struct udev *udev; + struct udev_monitor *mon; + char *drm_path; + + struct wl_event_source *event; +}; + +struct wlr_drm_backend; +bool wlr_udev_init(struct wl_display *display, struct wlr_udev *udev); +void wlr_udev_free(struct wlr_udev *udev); +int wlr_udev_find_gpu(struct wlr_udev *udev, struct wlr_session *session); + +void wlr_udev_event(struct wlr_drm_backend *backend); + +#endif diff --git a/include/backend/egl.h b/include/backend/egl.h new file mode 100644 index 00000000..8cef36b7 --- /dev/null +++ b/include/backend/egl.h @@ -0,0 +1,17 @@ +#ifndef WLR_BACKEND_EGL_H +#define WLR_BACKEND_EGL_H + +#include <EGL/egl.h> +#include <stdbool.h> + +struct wlr_egl { + EGLDisplay display; + EGLConfig config; + EGLContext context; +}; + +bool wlr_egl_init(struct wlr_egl *egl, EGLenum platform, void *display); +void wlr_egl_free(struct wlr_egl *egl); +EGLSurface wlr_egl_create_surface(struct wlr_egl *egl, void *window); + +#endif diff --git a/include/session/interface.h b/include/session/interface.h new file mode 100644 index 00000000..a815718f --- /dev/null +++ b/include/session/interface.h @@ -0,0 +1,21 @@ +#ifndef WLR_SESSION_INTERFACE_H +#define WLR_SESSION_INTERFACE_H + +struct wlr_session; + +struct session_interface { + struct wlr_session *(*start)(void); + void (*finish)(struct wlr_session *session); + int (*open)(struct wlr_session *restrict session, + const char *restrict path); + void (*close)(struct wlr_session *session, int fd); +}; + +struct wlr_session { + struct session_interface iface; +}; + +extern const struct session_interface session_logind_iface; +extern const struct session_interface session_direct_iface; + +#endif diff --git a/include/wayland.h b/include/wayland.h new file mode 100644 index 00000000..68817936 --- /dev/null +++ b/include/wayland.h @@ -0,0 +1,18 @@ +#ifndef _WLR_WAYLAND_INTERNAL_H +#define _WLR_WAYLAND_INTERNAL_H + +#include <wayland-server.h> +#include <wlr/wayland.h> +#include <stdbool.h> + +struct wlr_output_impl { + bool (*set_mode)(struct wlr_output_state *state, struct wlr_output_mode *mode); + void (*destroy)(struct wlr_output_state *state); +}; + +struct wlr_output *wlr_output_create(struct wlr_output_impl *impl, + struct wlr_output_state *state); + +void wlr_output_free(struct wlr_output *output); + +#endif diff --git a/include/wlr/backend.h b/include/wlr/backend.h index 527efa05..b424c29f 100644 --- a/include/wlr/backend.h +++ b/include/wlr/backend.h @@ -1,7 +1,29 @@ #ifndef _WLR_BACKEND_H #define _WLR_BACKEND_H -struct wlr_backend *wlr_backend_init(); -void wlr_backend_free(struct wlr_backend *backend); +#include <wayland-server.h> + +struct wlr_backend_impl; +struct wlr_backend_state; + +struct wlr_backend { + const struct wlr_backend_impl *impl; + struct wlr_backend_state *state; + + struct { + struct wl_signal output_add; + struct wl_signal output_remove; + struct wl_signal keyboard_add; + struct wl_signal keyboard_remove; + struct wl_signal pointer_add; + struct wl_signal pointer_remove; + struct wl_signal touch_add; + struct wl_signal touch_remove; + } events; +}; + +struct wlr_backend *wlr_backend_autocreate(); +bool wlr_backend_init(struct wlr_backend *backend); +void wlr_backend_destroy(struct wlr_backend *backend); #endif diff --git a/include/wlr/backend/drm.h b/include/wlr/backend/drm.h new file mode 100644 index 00000000..2d9bf879 --- /dev/null +++ b/include/wlr/backend/drm.h @@ -0,0 +1,16 @@ +#ifndef WLR_BACKEND_DRM_H +#define WLR_BACKEND_DRM_H + +#include <wayland-server.h> +#include <wlr/session.h> +#include <wlr/backend.h> +#include <xf86drmMode.h> // drmModeModeInfo +#include <wlr/wayland.h> + +struct wlr_backend *wlr_drm_backend_create(struct wl_display *display, + struct wlr_session *session); + +void wlr_drm_output_begin(struct wlr_output *out); +void wlr_drm_output_end(struct wlr_output *out); + +#endif diff --git a/include/wlr/common/log.h b/include/wlr/common/log.h index 5b4d5a53..079f989a 100644 --- a/include/wlr/common/log.h +++ b/include/wlr/common/log.h @@ -1,6 +1,7 @@ #ifndef _WLR_COMMON_LOG_H #define _WLR_COMMON_LOG_H #include <stdbool.h> +#include <stdarg.h> typedef enum { L_SILENT = 0, diff --git a/include/wlr/session.h b/include/wlr/session.h new file mode 100644 index 00000000..cf04f1da --- /dev/null +++ b/include/wlr/session.h @@ -0,0 +1,12 @@ +#ifndef WLR_SESSION_H +#define WLR_SESSION_H + +struct wlr_session; + +struct wlr_session *wlr_session_start(void); +void wlr_session_finish(struct wlr_session *session); +int wlr_session_open_file(struct wlr_session *restrict session, + const char *restrict path); +void wlr_session_close_file(struct wlr_session *session, int fd); + +#endif diff --git a/include/wlr/wayland.h b/include/wlr/wayland.h index bbbd2457..158acc33 100644 --- a/include/wlr/wayland.h +++ b/include/wlr/wayland.h @@ -3,26 +3,26 @@ #include <wayland-server.h> #include <wlr/common/list.h> +#include <stdbool.h> -struct wlr_wl_seat { - struct wl_seat *wl_seat; - uint32_t capabilities; - char *name; - list_t *keyboards; - list_t *pointers; -}; - -void wlr_wl_seat_free(struct wlr_wl_seat *seat); +struct wlr_output_mode_state; -struct wlr_wl_output_mode { +struct wlr_output_mode { + struct wlr_output_mode_state *state; uint32_t flags; // enum wl_output_mode int32_t width, height; int32_t refresh; // mHz }; -struct wlr_wl_output { - struct wl_output *wl_output; +struct wlr_output_impl; +struct wlr_output_state; + +struct wlr_output { + const struct wlr_output_impl *impl; + struct wlr_output_state *state; + uint32_t flags; + char *name; char *make; char *model; uint32_t scale; @@ -30,20 +30,15 @@ struct wlr_wl_output { int32_t phys_width, phys_height; // mm int32_t subpixel; // enum wl_output_subpixel int32_t transform; // enum wl_output_transform - list_t *modes; - struct wlr_wl_output_mode *current_mode; -}; -void wlr_wl_output_free(struct wlr_wl_output *output); + list_t *modes; + struct wlr_output_mode *current_mode; -struct wlr_wl_keyboard { - struct wl_keyboard *wl_keyboard; + struct { + struct wl_signal frame; + } events; }; -struct wlr_wl_pointer { - struct wl_pointer *wl_pointer; - struct wl_surface *current_surface; - wl_fixed_t x, y; -}; +bool wlr_output_set_mode(struct wlr_output *output, struct wlr_output_mode *mode); #endif diff --git a/session/CMakeLists.txt b/session/CMakeLists.txt new file mode 100644 index 00000000..bd83068b --- /dev/null +++ b/session/CMakeLists.txt @@ -0,0 +1,25 @@ +include_directories( + ${WAYLAND_INCLUDE_DIR} +) + +set(sources + session.c + direct.c +) + +set(libs + wlr-common + ${WAYLAND_LIBRARIES} +) + +if (SYSTEMD_FOUND) + add_definitions(${SYSTEMD_DEFINITIONS}) + include_directories(${SYSTEMD_INCLUDE_DIRS}) + + add_definitions(-DHAS_SYSTEMD) + list(APPEND sources logind.c) + list(APPEND libs ${SYSTEMD_LIBRARIES}) +endif () + +add_library(wlr-session ${sources}) +target_link_libraries(wlr-session ${libs}) diff --git a/session/direct.c b/session/direct.c new file mode 100644 index 00000000..08a9b617 --- /dev/null +++ b/session/direct.c @@ -0,0 +1,50 @@ +#define _POSIX_C_SOURCE 200809L + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <wayland-server.h> + +#include "session/interface.h" +#include "common/log.h" + +struct direct_session { + struct wlr_session base; +}; + +static int direct_session_open(struct wlr_session *restrict base, + const char *restrict path) { + return open(path, O_RDWR | O_CLOEXEC); +} + +static void direct_session_close(struct wlr_session *base, int fd) { + close(fd); +} + +static void direct_session_finish(struct wlr_session *base) { + struct direct_session *session = wl_container_of(base, session, base); + + free(session); +} + +static struct wlr_session *direct_session_start(void) { + struct direct_session *session = calloc(1, sizeof(*session)); + if (!session) { + wlr_log(L_ERROR, "Allocation failed: %s", strerror(errno)); + return NULL; + } + + wlr_log(L_INFO, "Successfully loaded direct session"); + + session->base.iface = session_direct_iface; + return &session->base; +} + +const struct session_interface session_direct_iface = { + .start = direct_session_start, + .finish = direct_session_finish, + .open = direct_session_open, + .close = direct_session_close, +}; diff --git a/session/logind.c b/session/logind.c new file mode 100644 index 00000000..052c7454 --- /dev/null +++ b/session/logind.c @@ -0,0 +1,223 @@ +#define _POSIX_C_SOURCE 200809L + +#include <stdio.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <systemd/sd-bus.h> +#include <systemd/sd-login.h> +#include <unistd.h> +#include <sys/sysmacros.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <wayland-server.h> + +#include "session/interface.h" +#include "common/log.h" + +struct logind_session { + struct wlr_session base; + + sd_bus *bus; + + char *id; + char *path; + char *seat; +}; + +static int logind_take_device(struct wlr_session *restrict base, + const char *restrict path) { + + struct logind_session *session = wl_container_of(base, session, base); + + int ret; + int fd = -1; + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + + struct stat st; + if (stat(path, &st) < 0) { + wlr_log(L_ERROR, "Failed to stat '%s'", path); + return -1; + } + + ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", + session->path, "org.freedesktop.login1.Session", "TakeDevice", + &error, &msg, "uu", major(st.st_rdev), minor(st.st_rdev)); + if (ret < 0) { + wlr_log(L_ERROR, "Failed to take device '%s': %s", path, error.message); + goto error; + } + + int paused = 0; + ret = sd_bus_message_read(msg, "hb", &fd, &paused); + if (ret < 0) { + wlr_log(L_ERROR, "Failed to parse DBus response for '%s': %s", + path, strerror(-ret)); + goto error; + } + + // The original fd seem to be closed when the message is freed + // so we just clone it. + fd = fcntl(fd, F_DUPFD_CLOEXEC, 0); + if (fd == -1) { + wlr_log(L_ERROR, "Failed to clone file descriptor for '%s': %s", + path, strerror(errno)); + goto error; + } + +error: + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + return fd; +} + +static void logind_release_device(struct wlr_session *base, int fd) { + struct logind_session *session = wl_container_of(base, session, base); + + int ret; + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + + struct stat st; + if (fstat(fd, &st) < 0) { + wlr_log(L_ERROR, "Failed to stat device '%d'", fd); + return; + } + + ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", + session->path, "org.freedesktop.login1.Session", "ReleaseDevice", + &error, &msg, "uu", major(st.st_rdev), minor(st.st_rdev)); + if (ret < 0) { + wlr_log(L_ERROR, "Failed to release device '%d'", fd); + } + + sd_bus_error_free(&error); + sd_bus_message_unref(msg); +} + +static bool session_activate(struct logind_session *session) { + int ret; + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + + ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", + session->path, "org.freedesktop.login1.Session", "Activate", + &error, &msg, ""); + if (ret < 0) { + wlr_log(L_ERROR, "Failed to activate session"); + } + + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + return ret >= 0; +} + +static bool take_control(struct logind_session *session) { + int ret; + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + + ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", + session->path, "org.freedesktop.login1.Session", "TakeControl", + &error, &msg, "b", false); + if (ret < 0) { + wlr_log(L_ERROR, "Failed to take control of session"); + } + + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + return ret >= 0; +} + +static void release_control(struct logind_session *session) { + int ret; + sd_bus_message *msg = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + + ret = sd_bus_call_method(session->bus, "org.freedesktop.login1", + session->path, "org.freedesktop.login1.Session", "ReleaseControl", + &error, &msg, ""); + if (ret < 0) { + wlr_log(L_ERROR, "Failed to release control of session"); + } + + sd_bus_error_free(&error); + sd_bus_message_unref(msg); +} + +static void logind_session_finish(struct wlr_session *base) { + struct logind_session *session = wl_container_of(base, session, base); + + release_control(session); + + sd_bus_unref(session->bus); + free(session->id); + free(session->path); + free(session->seat); + free(session); +} + +static struct wlr_session *logind_session_start(void) { + int ret; + struct logind_session *session = calloc(1, sizeof(*session)); + if (!session) { + wlr_log(L_ERROR, "Allocation failed: %s", strerror(errno)); + return NULL; + } + + ret = sd_pid_get_session(getpid(), &session->id); + if (ret < 0) { + wlr_log(L_ERROR, "Failed to get session id: %s", strerror(-ret)); + goto error; + } + + ret = sd_session_get_seat(session->id, &session->seat); + if (ret < 0) { + wlr_log(L_ERROR, "Failed to get seat id: %s", strerror(-ret)); + goto error; + } + + const char *fmt = "/org/freedesktop/login1/session/%s"; + int len = snprintf(NULL, 0, fmt, session->id); + + session->path = malloc(len + 1); + if (!session->path) + goto error; + + sprintf(session->path, fmt, session->id); + + ret = sd_bus_default_system(&session->bus); + if (ret < 0) { + wlr_log(L_ERROR, "Failed to open DBus connection: %s", strerror(-ret)); + goto error; + } + + if (!session_activate(session)) + goto error_bus; + + if (!take_control(session)) + goto error_bus; + + wlr_log(L_INFO, "Successfully loaded logind session"); + + session->base.iface = session_logind_iface; + return &session->base; + +error_bus: + sd_bus_unref(session->bus); + +error: + free(session->path); + free(session->id); + free(session->seat); + return NULL; +} + +const struct session_interface session_logind_iface = { + .start = logind_session_start, + .finish = logind_session_finish, + .open = logind_take_device, + .close = logind_release_device, +}; diff --git a/session/session.c b/session/session.c new file mode 100644 index 00000000..0562dbf7 --- /dev/null +++ b/session/session.c @@ -0,0 +1,42 @@ +#include <stddef.h> + +#include <wlr/session.h> +#include <stdarg.h> +#include "common/log.h" +#include "session/interface.h" + +static const struct session_interface *ifaces[] = { +#ifdef HAS_SYSTEMD + &session_logind_iface, +#endif + &session_direct_iface, + NULL, +}; + +struct wlr_session *wlr_session_start(void) { + const struct session_interface **iter; + + for (iter = ifaces; *iter; ++iter) { + struct wlr_session *session = (*iter)->start(); + if (session) { + return session; + } + } + + wlr_log(L_ERROR, "Failed to load session backend"); + return NULL; +} + +void wlr_session_finish(struct wlr_session *session) { + session->iface.finish(session); +}; + +int wlr_session_open_file(struct wlr_session *restrict session, + const char *restrict path) { + + return session->iface.open(session, path); +} + +void wlr_session_close_file(struct wlr_session *session, int fd) { + session->iface.close(session, fd); +} diff --git a/wayland/CMakeLists.txt b/wayland/CMakeLists.txt index be4a00de..675a7bd2 100644 --- a/wayland/CMakeLists.txt +++ b/wayland/CMakeLists.txt @@ -4,8 +4,7 @@ include_directories( ) add_library(wlr-wayland - types/wlr_wl_seat.c - types/wlr_wl_output.c + types/wlr_output.c ) target_link_libraries(wlr-wayland diff --git a/wayland/types/wlr_output.c b/wayland/types/wlr_output.c new file mode 100644 index 00000000..bc093c00 --- /dev/null +++ b/wayland/types/wlr_output.c @@ -0,0 +1,31 @@ +#include <stdlib.h> +#include <wayland-server.h> +#include "wlr/wayland.h" +#include "wlr/common/list.h" +#include "wayland.h" + +struct wlr_output *wlr_output_create(struct wlr_output_impl *impl, + struct wlr_output_state *state) { + struct wlr_output *output = calloc(1, sizeof(struct wlr_output)); + output->impl = impl; + output->state = state; + output->modes = list_create(); + wl_signal_init(&output->events.frame); + return output; +} + +void wlr_output_free(struct wlr_output *output) { + if (!output) return; + if (output->make) free(output->make); + if (output->model) free(output->model); + for (size_t i = 0; output->modes && i < output->modes->length; ++i) { + free(output->modes->items[i]); + } + list_free(output->modes); + output->impl->destroy(output->state); + free(output); +} + +bool wlr_output_set_mode(struct wlr_output *output, struct wlr_output_mode *mode) { + return output->impl->set_mode(output->state, mode); +} diff --git a/wayland/types/wlr_wl_output.c b/wayland/types/wlr_wl_output.c deleted file mode 100644 index 32aaf070..00000000 --- a/wayland/types/wlr_wl_output.c +++ /dev/null @@ -1,16 +0,0 @@ -#include <stdlib.h> -#include <wayland-client.h> -#include "wlr/wayland.h" -#include "wlr/common/list.h" - -void wlr_wl_output_free(struct wlr_wl_output *output) { - if (!output) return; - if (output->wl_output) wl_output_destroy(output->wl_output); - if (output->make) free(output->make); - if (output->model) free(output->model); - for (size_t i = 0; output->modes && i < output->modes->length; ++i) { - free(output->modes->items[i]); - } - list_free(output->modes); - free(output); -} diff --git a/wayland/types/wlr_wl_seat.c b/wayland/types/wlr_wl_seat.c deleted file mode 100644 index a5ef3853..00000000 --- a/wayland/types/wlr_wl_seat.c +++ /dev/null @@ -1,19 +0,0 @@ -#include <stdlib.h> -#include <wayland-client.h> -#include "wlr/wayland.h" -#include "wlr/common/list.h" - -void wlr_wl_seat_free(struct wlr_wl_seat *seat) { - if (!seat) return; - if (seat->wl_seat) wl_seat_destroy(seat->wl_seat); - if (seat->name) free(seat->name); - if (seat->keyboards) { - // TODO: free children - list_free(seat->keyboards); - } - if (seat->pointers) { - // TODO: free children - list_free(seat->keyboards); - } - free(seat); -} |