diff --git a/builder b/builder index af9c3fa..d647437 100755 --- a/builder +++ b/builder @@ -147,7 +147,8 @@ function x() { ## Runs Xephyr for testing purposes } function run() { ## Runs feynman inside Xephyr - EMACS_DEV=$EMACS_DEV DISPLAY=:1 "$BUILD_DIR/src/feynman-wm" "$@" + # + DISPLAY=:1 EMACS_DEV=$EMACS_DEV "$BUILD_DIR/src/feynman-wm" "$@" } function d() { ## Runs the debugger diff --git a/libfeynman/CMakeLists.txt b/libfeynman/CMakeLists.txt new file mode 100644 index 0000000..ddf1a83 --- /dev/null +++ b/libfeynman/CMakeLists.txt @@ -0,0 +1,17 @@ +# Feynman -- Wayland compositor for GNU Emacs +# +# Copyright (c) 2022 Sameer Rahmani +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 2. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +add_subdirectory(lib) diff --git a/libfeynman/include/feynman/feynman.h b/libfeynman/include/feynman/feynman.h new file mode 100644 index 0000000..5ab16fd --- /dev/null +++ b/libfeynman/include/feynman/feynman.h @@ -0,0 +1,23 @@ +/* + * Feynman -- Wayland compositor for GNU Emacs + * + * Copyright (c) 2022 Sameer Rahmani + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef FEYNMAN_H +#define FEYNMAN_H + + +#endif diff --git a/libfeynman/lib/CMakeLists.txt b/libfeynman/lib/CMakeLists.txt new file mode 100644 index 0000000..0e88d9f --- /dev/null +++ b/libfeynman/lib/CMakeLists.txt @@ -0,0 +1,71 @@ +# Feynman -- Wayland compositor for GNU Emacs +# +# Copyright (c) 2022 Sameer Rahmani +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 2. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +add_library(feynman SHARED + feynman.c + queue.c + compositor.c + utils.c + xdg-shell-protocol.c + ) + +set_target_properties(feynman PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} + # Warn on unused libs + LINK_WHAT_YOU_USE TRUE + C_INCLUDE_WHAT_YOU_USE "${iwyu_path}" + + # LTO support + INTERPROCEDURAL_OPTIMIZATION TRUE) + +if(FEYNMAN_ENABLE_TIDY) + set_target_properties(feynman PROPERTIES CXX_CLANG_TIDY ${CLANG_TIDY_PATH}) +endif() + +# Generate the export.h +include(GenerateExportHeader) + +generate_export_header(feynman EXPORT_FILE_NAME ${PROJECT_BINARY_DIR}/src/export.h) + +target_link_libraries(feynman PRIVATE PkgConfig::WLROOTS) + +find_package(PkgConfig REQUIRED) +pkg_check_modules(WLROOTS REQUIRED IMPORTED_TARGET wlroots) +include_directories(SYSTEM ${WLROOTS_INCLUDE_DIRS}) + +# find Wayland protocols +pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir) + +# find 'wayland-scanner' executable +pkg_get_variable(WAYLAND_SCANNER wayland-scanner wayland_scanner) + +set(XDG_PROT_DEF "${WAYLAND_PROTOCOLS_DIR}/stable/xdg-shell/xdg-shell.xml") +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-shell-protocol.h + COMMAND ${WAYLAND_SCANNER} client-header ${XDG_PROT_DEF} xdg-shell-protocol.h) + +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-shell-protocol.c + COMMAND ${WAYLAND_SCANNER} private-code ${XDG_PROT_DEF} xdg-shell-protocol.c + DEPENDS xdg-shell-protocol.h) + +target_include_directories(feynman PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +target_sources(feynman PRIVATE xdg-shell-protocol.c) + +find_package(Threads REQUIRED) +target_link_libraries(feynman PRIVATE Threads::Threads) diff --git a/libfeynman/lib/compositor.c b/libfeynman/lib/compositor.c new file mode 100644 index 0000000..cce0146 --- /dev/null +++ b/libfeynman/lib/compositor.c @@ -0,0 +1,915 @@ +/* + * Feynman -- Wayland compositor for GNU Emacs + * + * Copyright (c) 2022 Sameer Rahmani + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "compositor.h" + +#include "utils.h" + +#include +#include +#include +#include +#include +#include + +static void +/* cppcheck-suppress constParameter */ +focus_view (struct feynman_view *view, struct wlr_surface *surface) +{ + /* Note: this function only deals with keyboard focus. */ + if (view == NULL) + { + return; + } + struct feynman_server *server = view->server; + struct wlr_seat *seat = server->seat; + struct wlr_surface *prev_surface = seat->keyboard_state.focused_surface; + if (prev_surface == surface) + { + /* Don't re-focus an already focused surface. */ + return; + } + if (prev_surface) + { + /* + * Deactivate the previously focused surface. This lets the client know + * it no longer has focus and the client will repaint accordingly, e.g. + * stop displaying a caret. + */ + struct wlr_xdg_surface *previous = wlr_xdg_surface_from_wlr_surface ( + seat->keyboard_state.focused_surface); + wlr_xdg_toplevel_set_activated (previous, false); + } + struct wlr_keyboard *keyboard = wlr_seat_get_keyboard (seat); + /* Move the view to the front */ + wlr_scene_node_raise_to_top (view->scene_node); + wl_list_remove (&view->link); + wl_list_insert (&server->views, &view->link); + /* Activate the new surface */ + wlr_xdg_toplevel_set_activated (view->xdg_surface, true); + /* + * Tell the seat to have the keyboard enter this surface. wlroots will keep + * track of this and automatically send key events to the appropriate + * clients without additional work on your part. + */ + wlr_seat_keyboard_notify_enter (seat, view->xdg_surface->surface, + keyboard->keycodes, keyboard->num_keycodes, + &keyboard->modifiers); +} + +static void +keyboard_handle_modifiers (struct wl_listener *listener, void *data) +{ + (void)data; + /* This event is raised when a modifier key, such as shift or alt, is + * pressed. We simply communicate this to the client. */ + struct feynman_keyboard *keyboard + = wl_container_of (listener, keyboard, modifiers); + /* + * A seat can only have one keyboard, but this is a limitation of the + * Wayland protocol - not wlroots. We assign all connected keyboards to the + * same seat. You can swap out the underlying wlr_keyboard like this and + * wlr_seat handles this transparently. + */ + wlr_seat_set_keyboard (keyboard->server->seat, keyboard->device); + /* Send modifiers to the client. */ + wlr_seat_keyboard_notify_modifiers (keyboard->server->seat, + &keyboard->device->keyboard->modifiers); +} + +static bool +handle_keybinding (struct feynman_server *server, xkb_keysym_t sym) +{ + /* + * Here we handle compositor keybindings. This is when the compositor is + * processing keys, rather than passing them on to the client for its own + * processing. + * + * This function assumes Alt is held down. + */ + switch (sym) + { + case XKB_KEY_Escape: + wl_display_terminate (server->wl_display); + break; + case XKB_KEY_F1: + /* Cycle to the next view */ + if (wl_list_length (&server->views) < 2) + { + break; + } + struct feynman_view *next_view + = wl_container_of (server->views.prev, next_view, link); + focus_view (next_view, next_view->xdg_surface->surface); + break; + default: + return false; + } + return true; +} + +static void +keyboard_handle_key (struct wl_listener *listener, void *data) +{ + /* This event is raised when a key is pressed or released. */ + struct feynman_keyboard *keyboard + = wl_container_of (listener, keyboard, key); + struct feynman_server *server = keyboard->server; + struct wlr_event_keyboard_key *event = data; + struct wlr_seat *seat = server->seat; + + /* Translate libinput keycode -> xkbcommon */ + uint32_t keycode = event->keycode + 8; + /* Get a list of keysyms based on the keymap for this keyboard */ + const xkb_keysym_t *syms; + int nsyms = xkb_state_key_get_syms (keyboard->device->keyboard->xkb_state, + keycode, &syms); + + bool handled = false; + uint32_t modifiers = wlr_keyboard_get_modifiers (keyboard->device->keyboard); + if ((modifiers & WLR_MODIFIER_ALT) + && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) + { + /* If alt is held down and this button was _pressed_, we attempt to + * process it as a compositor keybinding. */ + for (int i = 0; i < nsyms; i++) + { + handled = handle_keybinding (server, syms[i]); + } + } + + if (!handled) + { + /* Otherwise, we pass it along to the client. */ + wlr_seat_set_keyboard (seat, keyboard->device); + wlr_seat_keyboard_notify_key (seat, event->time_msec, event->keycode, + event->state); + } +} + +static void +server_new_keyboard (struct feynman_server *server, + struct wlr_input_device *device) +{ + struct feynman_keyboard *keyboard + = calloc (1, sizeof (struct feynman_keyboard)); + keyboard->server = server; + keyboard->device = device; + + /* We need to prepare an XKB keymap and assign it to the keyboard. This + * assumes the defaults (e.g. layout = "us"). */ + struct xkb_context *context = xkb_context_new (XKB_CONTEXT_NO_FLAGS); + struct xkb_keymap *keymap + = xkb_keymap_new_from_names (context, NULL, XKB_KEYMAP_COMPILE_NO_FLAGS); + + wlr_keyboard_set_keymap (device->keyboard, keymap); + xkb_keymap_unref (keymap); + xkb_context_unref (context); + wlr_keyboard_set_repeat_info (device->keyboard, 25, 600); + + /* Here we set up listeners for keyboard events. */ + keyboard->modifiers.notify = keyboard_handle_modifiers; + wl_signal_add (&device->keyboard->events.modifiers, &keyboard->modifiers); + keyboard->key.notify = keyboard_handle_key; + wl_signal_add (&device->keyboard->events.key, &keyboard->key); + + wlr_seat_set_keyboard (server->seat, device); + + /* And add the keyboard to our list of keyboards */ + wl_list_insert (&server->keyboards, &keyboard->link); +} + +static void +server_new_pointer (struct feynman_server *server, + struct wlr_input_device *device) +{ + /* We don't do anything special with pointers. All of our pointer handling + * is proxied through wlr_cursor. On another compositor, you might take this + * opportunity to do libinput configuration on the device to set + * acceleration, etc. */ + wlr_cursor_attach_input_device (server->cursor, device); +} + +static void +server_new_input (struct wl_listener *listener, void *data) +{ + /* This event is raised by the backend when a new input device becomes + * available. */ + struct feynman_server *server + = wl_container_of (listener, server, new_input); + struct wlr_input_device *device = data; + switch (device->type) + { + case WLR_INPUT_DEVICE_KEYBOARD: + server_new_keyboard (server, device); + break; + case WLR_INPUT_DEVICE_POINTER: + server_new_pointer (server, device); + break; + default: + break; + } + /* We need to let the wlr_seat know what our capabilities are, which is + * communiciated to the client. In Feynman we always have a cursor, even if + * there are no pointer devices, so we always include that capability. */ + uint32_t caps = WL_SEAT_CAPABILITY_POINTER; + if (!wl_list_empty (&server->keyboards)) + { + caps |= WL_SEAT_CAPABILITY_KEYBOARD; + } + wlr_seat_set_capabilities (server->seat, caps); +} + +static void +seat_request_cursor (struct wl_listener *listener, void *data) +{ + struct feynman_server *server + = wl_container_of (listener, server, request_cursor); + /* This event is raised by the seat when a client provides a cursor image */ + struct wlr_seat_pointer_request_set_cursor_event *event = data; + struct wlr_seat_client *focused_client + = server->seat->pointer_state.focused_client; + /* This can be sent by any client, so we check to make sure this one is + * actually has pointer focus first. */ + if (focused_client == event->seat_client) + { + /* Once we've vetted the client, we can tell the cursor to use the + * provided surface as the cursor image. It will set the hardware cursor + * on the output that it's currently on and continue to do so as the + * cursor moves between outputs. */ + wlr_cursor_set_surface (server->cursor, event->surface, event->hotspot_x, + event->hotspot_y); + } +} + +static void +seat_request_set_selection (struct wl_listener *listener, void *data) +{ + /* This event is raised by the seat when a client wants to set the selection, + * usually when the user copies something. wlroots allows compositors to + * ignore such requests if they so choose, but in feynman we always honor + */ + struct feynman_server *server + = wl_container_of (listener, server, request_set_selection); + struct wlr_seat_request_set_selection_event *event = data; + wlr_seat_set_selection (server->seat, event->source, event->serial); +} + +static struct feynman_view * +desktop_view_at (struct feynman_server *server, double lx, double ly, + struct wlr_surface **surface, double *sx, double *sy) +{ + /* This returns the topmost node in the scene at the given layout coords. + * we only care about surface nodes as we are specifically looking for a + * surface in the surface tree of a feynman_view. */ + struct wlr_scene_node *node + = wlr_scene_node_at (&server->scene->node, lx, ly, sx, sy); + if (node == NULL || node->type != WLR_SCENE_NODE_SURFACE) + { + return NULL; + } + *surface = wlr_scene_surface_from_node (node)->surface; + /* Find the node corresponding to the feynman_view at the root of this + * surface tree, it is the only one for which we set the data field. */ + while (node != NULL && node->data == NULL) + { + node = node->parent; + } + /* cppcheck-suppress nullPointerRedundantCheck */ + return node->data; +} + +static void +process_cursor_move (struct feynman_server *server, uint32_t time) +{ + (void)time; + /* Move the grabbed view to the new position. */ + struct feynman_view *view = server->grabbed_view; + view->x = server->cursor->x - server->grab_x; + view->y = server->cursor->y - server->grab_y; + wlr_scene_node_set_position (view->scene_node, view->x, view->y); +} + +static void +process_cursor_resize (struct feynman_server *server, uint32_t time) +{ + (void)time; + /* + * Resizing the grabbed view can be a little bit complicated, because we + * could be resizing from any corner or edge. This not only resizes the view + * on one or two axes, but can also move the view if you resize from the top + * or left edges (or top-left corner). + * + * Note that I took some shortcuts here. In a more fleshed-out compositor, + * you'd wait for the client to prepare a buffer at the new size, then + * commit any movement that was prepared. + */ + struct feynman_view *view = server->grabbed_view; + double border_x = server->cursor->x - server->grab_x; + double border_y = server->cursor->y - server->grab_y; + int new_left = server->grab_geobox.x; + int new_right = server->grab_geobox.x + server->grab_geobox.width; + int new_top = server->grab_geobox.y; + int new_bottom = server->grab_geobox.y + server->grab_geobox.height; + + if (server->resize_edges & WLR_EDGE_TOP) + { + new_top = border_y; + if (new_top >= new_bottom) + { + new_top = new_bottom - 1; + } + } + else if (server->resize_edges & WLR_EDGE_BOTTOM) + { + new_bottom = border_y; + if (new_bottom <= new_top) + { + new_bottom = new_top + 1; + } + } + if (server->resize_edges & WLR_EDGE_LEFT) + { + new_left = border_x; + if (new_left >= new_right) + { + new_left = new_right - 1; + } + } + else if (server->resize_edges & WLR_EDGE_RIGHT) + { + new_right = border_x; + if (new_right <= new_left) + { + new_right = new_left + 1; + } + } + + struct wlr_box geo_box; + wlr_xdg_surface_get_geometry (view->xdg_surface, &geo_box); + view->x = new_left - geo_box.x; + view->y = new_top - geo_box.y; + wlr_scene_node_set_position (view->scene_node, view->x, view->y); + + int new_width = new_right - new_left; + int new_height = new_bottom - new_top; + wlr_xdg_toplevel_set_size (view->xdg_surface, new_width, new_height); +} + +static void +process_cursor_motion (struct feynman_server *server, uint32_t time) +{ + /* If the mode is non-passthrough, delegate to those functions. */ + if (server->cursor_mode == FEYNMAN_CURSOR_MOVE) + { + process_cursor_move (server, time); + return; + } + else if (server->cursor_mode == FEYNMAN_CURSOR_RESIZE) + { + process_cursor_resize (server, time); + return; + } + + /* Otherwise, find the view under the pointer and send the event along. */ + double sx, sy; + struct wlr_seat *seat = server->seat; + struct wlr_surface *surface = NULL; + struct feynman_view *view = desktop_view_at ( + server, server->cursor->x, server->cursor->y, &surface, &sx, &sy); + if (!view) + { + /* If there's no view under the cursor, set the cursor image to a + * default. This is what makes the cursor image appear when you move it + * around the screen, not over any views. */ + wlr_xcursor_manager_set_cursor_image (server->cursor_mgr, "left_ptr", + server->cursor); + } + if (surface) + { + /* + * Send pointer enter and motion events. + * + * The enter event gives the surface "pointer focus", which is distinct + * from keyboard focus. You get pointer focus by moving the pointer over + * a window. + * + * Note that wlroots will avoid sending duplicate enter/motion events if + * the surface has already has pointer focus or if the client is already + * aware of the coordinates passed. + */ + wlr_seat_pointer_notify_enter (seat, surface, sx, sy); + wlr_seat_pointer_notify_motion (seat, time, sx, sy); + } + else + { + /* Clear pointer focus so future button events and such are not sent to + * the last client to have the cursor over it. */ + wlr_seat_pointer_clear_focus (seat); + } +} + +static void +server_cursor_motion (struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits a _relative_ + * pointer motion event (i.e. a delta) */ + struct feynman_server *server + = wl_container_of (listener, server, cursor_motion); + struct wlr_event_pointer_motion *event = data; + /* The cursor doesn't move unless we tell it to. The cursor automatically + * handles constraining the motion to the output layout, as well as any + * special configuration applied for the specific input device which + * generated the event. You can pass NULL for the device if you want to move + * the cursor around without any input. */ + wlr_cursor_move (server->cursor, event->device, event->delta_x, + event->delta_y); + process_cursor_motion (server, event->time_msec); +} + +static void +server_cursor_motion_absolute (struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits an _absolute_ + * motion event, from 0..1 on each axis. This happens, for example, when + * wlroots is running under a Wayland window rather than KMS+DRM, and you + * move the mouse over the window. You could enter the window from any edge, + * so we have to warp the mouse there. There is also some hardware which + * emits these events. */ + struct feynman_server *server + = wl_container_of (listener, server, cursor_motion_absolute); + struct wlr_event_pointer_motion_absolute *event = data; + wlr_cursor_warp_absolute (server->cursor, event->device, event->x, event->y); + process_cursor_motion (server, event->time_msec); +} + +static void +server_cursor_button (struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits a button + * event. */ + struct feynman_server *server + = wl_container_of (listener, server, cursor_button); + struct wlr_event_pointer_button *event = data; + /* Notify the client with pointer focus that a button press has occurred */ + wlr_seat_pointer_notify_button (server->seat, event->time_msec, + event->button, event->state); + double sx, sy; + struct wlr_surface *surface = NULL; + struct feynman_view *view = desktop_view_at ( + server, server->cursor->x, server->cursor->y, &surface, &sx, &sy); + if (event->state == WLR_BUTTON_RELEASED) + { + /* If you released any buttons, we exit interactive move/resize mode. */ + server->cursor_mode = FEYNMAN_CURSOR_PASSTHROUGH; + } + else + { + /* Focus that client if the button was _pressed_ */ + focus_view (view, surface); + } +} + +static void +server_cursor_axis (struct wl_listener *listener, void *data) +{ + /* This event is forwarded by the cursor when a pointer emits an axis event, + * for example when you move the scroll wheel. */ + struct feynman_server *server + = wl_container_of (listener, server, cursor_axis); + struct wlr_event_pointer_axis *event = data; + /* Notify the client with pointer focus of the axis event. */ + wlr_seat_pointer_notify_axis (server->seat, event->time_msec, + event->orientation, event->delta, + event->delta_discrete, event->source); +} + +static void +server_cursor_frame (struct wl_listener *listener, void *data) +{ + (void)data; + /* This event is forwarded by the cursor when a pointer emits an frame + * event. Frame events are sent after regular pointer events to group + * multiple events together. For instance, two axis events may happen at the + * same time, in which case a frame event won't be sent in between. */ + struct feynman_server *server + = wl_container_of (listener, server, cursor_frame); + /* Notify the client with pointer focus of the frame event. */ + wlr_seat_pointer_notify_frame (server->seat); +} + +static void +output_frame (struct wl_listener *listener, void *data) +{ + (void)data; + /* This function is called every time an output is ready to display a frame, + * generally at the output's refresh rate (e.g. 60Hz). */ + struct feynman_output *output = wl_container_of (listener, output, frame); + struct wlr_scene *scene = output->server->scene; + + struct wlr_scene_output *scene_output + = wlr_scene_get_scene_output (scene, output->wlr_output); + + /* Render the scene if needed and commit the output */ + wlr_scene_output_commit (scene_output); + + struct timespec now; + clock_gettime (CLOCK_MONOTONIC, &now); + wlr_scene_output_send_frame_done (scene_output, &now); +} + +static void +server_new_output (struct wl_listener *listener, void *data) +{ + /* This event is raised by the backend when a new output (aka a display or + * monitor) becomes available. */ + struct feynman_server *server + = wl_container_of (listener, server, new_output); + struct wlr_output *wlr_output = data; + + /* Configures the output created by the backend to use our allocator + * and our renderer. Must be done once, before commiting the output */ + wlr_output_init_render (wlr_output, server->allocator, server->renderer); + + /* Some backends don't have modes. DRM+KMS does, and we need to set a mode + * before we can use the output. The mode is a tuple of (width, height, + * refresh rate), and each monitor supports only a specific set of modes. We + * just pick the monitor's preferred mode, a more sophisticated compositor + * would let the user configure it. */ + if (!wl_list_empty (&wlr_output->modes)) + { + struct wlr_output_mode *mode = wlr_output_preferred_mode (wlr_output); + wlr_output_set_mode (wlr_output, mode); + wlr_output_enable (wlr_output, true); + if (!wlr_output_commit (wlr_output)) + { + return; + } + } + + /* Allocates and configures our state for this output */ + struct feynman_output *output = calloc (1, sizeof (struct feynman_output)); + output->wlr_output = wlr_output; + output->server = server; + /* Sets up a listener for the frame notify event. */ + output->frame.notify = output_frame; + wl_signal_add (&wlr_output->events.frame, &output->frame); + wl_list_insert (&server->outputs, &output->link); + + /* Adds this to the output layout. The add_auto function arranges outputs + * from left-to-right in the order they appear. A more sophisticated + * compositor would let the user configure the arrangement of outputs in the + * layout. + * + * The output layout utility automatically adds a wl_output global to the + * display, which Wayland clients can see to find out information about the + * output (such as DPI, scale factor, manufacturer, etc). + */ + wlr_output_layout_add_auto (server->output_layout, wlr_output); +} + +static void +xdg_toplevel_map (struct wl_listener *listener, void *data) +{ + (void)data; + /* Called when the surface is mapped, or ready to display on-screen. */ + struct feynman_view *view = wl_container_of (listener, view, map); + + wl_list_insert (&view->server->views, &view->link); + + focus_view (view, view->xdg_surface->surface); +} + +static void +xdg_toplevel_unmap (struct wl_listener *listener, void *data) +{ + (void)data; + /* Called when the surface is unmapped, and should no longer be shown. */ + struct feynman_view *view = wl_container_of (listener, view, unmap); + + wl_list_remove (&view->link); +} + +static void +xdg_toplevel_handler (struct wl_listener *listener, void *data) +{ + (void)data; + /* Called when the surface is destroyed and should never be shown again. */ + struct feynman_view *view = wl_container_of (listener, view, destroy); + + wl_list_remove (&view->map.link); + wl_list_remove (&view->unmap.link); + wl_list_remove (&view->destroy.link); + wl_list_remove (&view->request_move.link); + wl_list_remove (&view->request_resize.link); + + free (view); +} + +static void +begin_interactive (struct feynman_view *view, enum feynman_cursor_mode mode, + uint32_t edges) +{ + /* This function sets up an interactive move or resize operation, where the + * compositor stops propegating pointer events to clients and instead + * consumes them itself, to move or resize windows. */ + struct feynman_server *server = view->server; + struct wlr_surface *focused_surface + = server->seat->pointer_state.focused_surface; + if (view->xdg_surface->surface + != wlr_surface_get_root_surface (focused_surface)) + { + /* Deny move/resize requests from unfocused clients. */ + return; + } + server->grabbed_view = view; + server->cursor_mode = mode; + + if (mode == FEYNMAN_CURSOR_MOVE) + { + server->grab_x = server->cursor->x - view->x; + server->grab_y = server->cursor->y - view->y; + } + else + { + struct wlr_box geo_box; + wlr_xdg_surface_get_geometry (view->xdg_surface, &geo_box); + + double border_x = (view->x + geo_box.x) + + ((edges & WLR_EDGE_RIGHT) ? geo_box.width : 0); + double border_y = (view->y + geo_box.y) + + ((edges & WLR_EDGE_BOTTOM) ? geo_box.height : 0); + server->grab_x = server->cursor->x - border_x; + server->grab_y = server->cursor->y - border_y; + + server->grab_geobox = geo_box; + server->grab_geobox.x += view->x; + server->grab_geobox.y += view->y; + + server->resize_edges = edges; + } +} + +static void +xdg_toplevel_request_move (struct wl_listener *listener, void *data) +{ + (void)data; + /* This event is raised when a client would like to begin an interactive + * move, typically because the user clicked on their client-side + * decorations. Note that a more sophisticated compositor should check the + * provided serial against a list of button press serials sent to this + * client, to prevent the client from requesting this whenever they want. */ + struct feynman_view *view = wl_container_of (listener, view, request_move); + begin_interactive (view, FEYNMAN_CURSOR_MOVE, 0); +} + +static void +xdg_toplevel_request_resize (struct wl_listener *listener, void *data) +{ + /* This event is raised when a client would like to begin an interactive + * resize, typically because the user clicked on their client-side + * decorations. Note that a more sophisticated compositor should check the + * provided serial against a list of button press serials sent to this + * client, to prevent the client from requesting this whenever they want. */ + struct wlr_xdg_toplevel_resize_event *event = data; + struct feynman_view *view = wl_container_of (listener, view, request_resize); + begin_interactive (view, FEYNMAN_CURSOR_RESIZE, event->edges); +} + +static void +server_new_xdg_surface (struct wl_listener *listener, void *data) +{ + /* This event is raised when wlr_xdg_shell receives a new xdg surface from a + * client, either a toplevel (application window) or popup. */ + struct feynman_server *server + = wl_container_of (listener, server, new_xdg_surface); + struct wlr_xdg_surface *xdg_surface = data; + + /* We must add xdg popups to the scene graph so they get rendered. The + * wlroots scene graph provides a helper for this, but to use it we must + * provide the proper parent scene node of the xdg popup. To enable this, + * we always set the user data field of xdg_surfaces to the corresponding + * scene node. */ + if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP) + { + struct wlr_xdg_surface *parent + = wlr_xdg_surface_from_wlr_surface (xdg_surface->popup->parent); + struct wlr_scene_node *parent_node = parent->data; + xdg_surface->data + = wlr_scene_xdg_surface_create (parent_node, xdg_surface); + return; + } + assert (xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL); + + /* Allocate a feynman_view for this surface */ + struct feynman_view *view = calloc (1, sizeof (struct feynman_view)); + view->server = server; + view->xdg_surface = xdg_surface; + view->scene_node = wlr_scene_xdg_surface_create (&view->server->scene->node, + view->xdg_surface); + view->scene_node->data = view; + xdg_surface->data = view->scene_node; + + /* Listen to the various events it can emit */ + view->map.notify = xdg_toplevel_map; + wl_signal_add (&xdg_surface->events.map, &view->map); + view->unmap.notify = xdg_toplevel_unmap; + wl_signal_add (&xdg_surface->events.unmap, &view->unmap); + view->destroy.notify = xdg_toplevel_handler; + wl_signal_add (&xdg_surface->events.destroy, &view->destroy); + + /* cotd */ + struct wlr_xdg_toplevel *toplevel = xdg_surface->toplevel; + view->request_move.notify = xdg_toplevel_request_move; + wl_signal_add (&toplevel->events.request_move, &view->request_move); + view->request_resize.notify = xdg_toplevel_request_resize; + wl_signal_add (&toplevel->events.request_resize, &view->request_resize); +} + +int +init_feynman_server (emacs_env *env, struct feynman_server *server) +{ + wlr_log_init (WLR_DEBUG, NULL); + + /* The Wayland display is managed by libwayland. It handles accepting + * clients from the Unix socket, manging Wayland globals, and so on. */ + server->wl_display = wl_display_create (); + /* The backend is a wlroots feature which abstracts the underlying input and + * output hardware. The autocreate option will choose the most suitable + * backend based on the current environment, such as opening an X11 window + * if an X11 server is running. */ + server->backend = wlr_backend_autocreate (server->wl_display); + + /* Autocreates a renderer, either Pixman, GLES2 or Vulkan for us. The user + * can also specify a renderer using the WLR_RENDERER env var. + * The renderer is responsible for defining the various pixel formats it + * supports for shared memory, this configures that for clients. */ + server->renderer = wlr_renderer_autocreate (server->backend); + wlr_renderer_init_wl_display (server->renderer, server->wl_display); + + /* Autocreates an allocator for us. + * The allocator is the bridge between the renderer and the backend. It + * handles the buffer creation, allowing wlroots to render onto the + * screen */ + server->allocator + = wlr_allocator_autocreate (server->backend, server->renderer); + + /* This creates some hands-off wlroots interfaces. The compositor is + * necessary for clients to allocate surfaces and the data device manager + * handles the clipboard. Each of these wlroots interfaces has room for you + * to dig your fingers in and play with their behavior if you want. Note that + * the clients cannot set the selection directly without compositor approval, + * see the handling of the request_set_selection event below.*/ + wlr_compositor_create (server->wl_display, server->renderer); + wlr_data_device_manager_create (server->wl_display); + + /* Creates an output layout, which a wlroots utility for working with an + * arrangement of screens in a physical layout. */ + server->output_layout = wlr_output_layout_create (); + + /* Configure a listener to be notified when new outputs are available on the + * backend. */ + wl_list_init (&server->outputs); + server->new_output.notify = server_new_output; + wl_signal_add (&server->backend->events.new_output, &server->new_output); + + /* Create a scene graph. This is a wlroots abstraction that handles all + * rendering and damage tracking. All the compositor author needs to do + * is add things that should be rendered to the scene graph at the proper + * positions and then call wlr_scene_output_commit() to render a frame if + * necessary. + */ + server->scene = wlr_scene_create (); + wlr_scene_attach_output_layout (server->scene, server->output_layout); + + /* Set up the xdg-shell. The xdg-shell is a Wayland protocol which is used + * for application windows. For more detail on shells, refer to my article: + * + * https://drewdevault.com/2018/07/29/Wayland-shells.html + */ + wl_list_init (&server->views); + server->xdg_shell = wlr_xdg_shell_create (server->wl_display); + server->new_xdg_surface.notify = server_new_xdg_surface; + wl_signal_add (&server->xdg_shell->events.new_surface, + &server->new_xdg_surface); + + /* + * Creates a cursor, which is a wlroots utility for tracking the cursor + * image shown on screen. + */ + server->cursor = wlr_cursor_create (); + wlr_cursor_attach_output_layout (server->cursor, server->output_layout); + + /* Creates an xcursor manager, another wlroots utility which loads up + * Xcursor themes to source cursor images from and makes sure that cursor + * images are available at all scale factors on the screen (necessary for + * HiDPI support). We add a cursor theme at scale factor 1 to begin with. */ + server->cursor_mgr = wlr_xcursor_manager_create (NULL, 24); + wlr_xcursor_manager_load (server->cursor_mgr, 1); + + /* + * wlr_cursor *only* displays an image on screen. It does not move around + * when the pointer moves. However, we can attach input devices to it, and + * it will generate aggregate events for all of them. In these events, we + * can choose how we want to process them, forwarding them to clients and + * moving the cursor around. More detail on this process is described in my + * input handling blog post: + * + * https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html + * + * And more comments are sprinkled throughout the notify functions above. + */ + server->cursor_motion.notify = server_cursor_motion; + wl_signal_add (&server->cursor->events.motion, &server->cursor_motion); + server->cursor_motion_absolute.notify = server_cursor_motion_absolute; + wl_signal_add (&server->cursor->events.motion_absolute, + &server->cursor_motion_absolute); + server->cursor_button.notify = server_cursor_button; + wl_signal_add (&server->cursor->events.button, &server->cursor_button); + server->cursor_axis.notify = server_cursor_axis; + wl_signal_add (&server->cursor->events.axis, &server->cursor_axis); + server->cursor_frame.notify = server_cursor_frame; + wl_signal_add (&server->cursor->events.frame, &server->cursor_frame); + + /* + * Configures a seat, which is a single "seat" at which a user sits and + * operates the computer. This conceptually includes up to one keyboard, + * pointer, touch, and drawing tablet device. We also rig up a listener to + * let us know when new input devices are available on the backend. + */ + wl_list_init (&server->keyboards); + server->new_input.notify = server_new_input; + wl_signal_add (&server->backend->events.new_input, &server->new_input); + server->seat = wlr_seat_create (server->wl_display, "seat0"); + server->request_cursor.notify = seat_request_cursor; + wl_signal_add (&server->seat->events.request_set_cursor, + &server->request_cursor); + server->request_set_selection.notify = seat_request_set_selection; + wl_signal_add (&server->seat->events.request_set_selection, + &server->request_set_selection); + + /* Add a Unix socket to the Wayland display. */ + const char *socket = wl_display_add_socket_auto (server->wl_display); + if (!socket) + { + wlr_backend_destroy (server->backend); + em_error (env, "Failed to create a unix socket!"); + return 1; + } + + /* Start the backend. This will enumerate outputs and inputs, become the DRM + * master, etc */ + if (!wlr_backend_start (server->backend)) + { + wlr_backend_destroy (server->backend); + wl_display_destroy (server->wl_display); + em_error (env, "Couldn't start the backend"); + return 2; + } + + /* Set the WAYLAND_DISPLAY environment variable to our socket */ + setenv ("WAYLAND_DISPLAY", socket, true); + + /* Run the Wayland event loop. This does not return until you exit the + * compositor. Starting the backend rigged up all of the necessary event + * loop configuration to listen to libinput events, DRM events, generate + * frame events at the refresh rate, and so on. */ + em_message (env, "Running Wayland compositor on WAYLAND_DISPLAY=%s", socket); + wlr_log (WLR_INFO, "Running Wayland compositor on WAYLAND_DISPLAY=%s", + socket); + + return 0; +} + +void +start_feynman (struct feynman_server *server) +{ + assert (server); + wl_display_run (server->wl_display); +} + +void +stop_feynman (emacs_env *env, struct feynman_server *server) +{ + (void)env; + + wl_display_terminate (server->wl_display); + /* Once wl_display_run returns, we shut down the server-> */ + wl_display_destroy_clients (server->wl_display); + wl_display_destroy (server->wl_display); +} diff --git a/libfeynman/lib/compositor.h b/libfeynman/lib/compositor.h new file mode 100644 index 0000000..823be90 --- /dev/null +++ b/libfeynman/lib/compositor.h @@ -0,0 +1,125 @@ +/* + * Feynman -- Wayland compositor for GNU Emacs + * + * Copyright (c) 2022 Sameer Rahmani + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef FEYNMAN_COMPOSITOR_H +#define FEYNMAN_COMPOSITOR_H + +#include "utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum feynman_cursor_mode +{ + FEYNMAN_CURSOR_PASSTHROUGH, + FEYNMAN_CURSOR_MOVE, + FEYNMAN_CURSOR_RESIZE, +}; + +// This struct holds the state of the WM +struct feynman_server +{ + struct wl_display *wl_display; + struct wlr_backend *backend; + struct wlr_renderer *renderer; + struct wlr_allocator *allocator; + struct wlr_scene *scene; + + struct wlr_xdg_shell *xdg_shell; + struct wl_listener new_xdg_surface; + struct wl_list views; + + struct wlr_cursor *cursor; + struct wlr_xcursor_manager *cursor_mgr; + struct wl_listener cursor_motion; + struct wl_listener cursor_motion_absolute; + struct wl_listener cursor_button; + struct wl_listener cursor_axis; + struct wl_listener cursor_frame; + + struct wlr_seat *seat; + struct wl_listener new_input; + struct wl_listener request_cursor; + struct wl_listener request_set_selection; + struct wl_list keyboards; + enum feynman_cursor_mode cursor_mode; + struct feynman_view *grabbed_view; + double grab_x, grab_y; + struct wlr_box grab_geobox; + uint32_t resize_edges; + + struct wlr_output_layout *output_layout; + struct wl_list outputs; + struct wl_listener new_output; + + pthread_t server_thread_id; +}; + +struct feynman_output +{ + struct wl_list link; + struct feynman_server *server; + struct wlr_output *wlr_output; + struct wl_listener frame; +}; + +struct feynman_view +{ + struct wl_list link; + struct feynman_server *server; + struct wlr_xdg_surface *xdg_surface; + struct wlr_scene_node *scene_node; + struct wl_listener map; + struct wl_listener unmap; + struct wl_listener destroy; + struct wl_listener request_move; + struct wl_listener request_resize; + int x, y; +}; + +struct feynman_keyboard +{ + struct wl_list link; + struct feynman_server *server; + struct wlr_input_device *device; + + struct wl_listener modifiers; + struct wl_listener key; +}; +int init_feynman_server (emacs_env *env, struct feynman_server *server); +void start_feynman (struct feynman_server *server); +void stop_feynman (emacs_env *env, struct feynman_server *server); +#endif diff --git a/libfeynman/lib/feynman.c b/libfeynman/lib/feynman.c new file mode 100644 index 0000000..2169a34 --- /dev/null +++ b/libfeynman/lib/feynman.c @@ -0,0 +1,157 @@ +/* + * Feynman -- Wayland compositor for GNU Emacs + * + * Copyright (c) 2022 Sameer Rahmani + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "compositor.h" +#include "export.h" +#include "queue.h" +#include "utils.h" + +#include +#include + +/* Declare mandatory GPL symbol. */ +FEYNMAN_EXPORT int plugin_is_GPL_compatible = 0; + +// static queue_t *feynman_events_q; + +// This value will hold a global reference to a `feynman_server` struct +emacs_value server_state; + +static void +free_value (void *ptr) +{ + free (ptr); +}; + +void * +start_event_loop (void *server_ptr) +{ + assert (server_ptr); + struct feynman_server *server = (struct feynman_server *)server_ptr; + + start_feynman (server); + return NULL; +} + +static emacs_value +feynman_start (emacs_env *env, ptrdiff_t nargs, emacs_value args[], void *data) +{ + (void)nargs; + (void)args; + (void)data; + emacs_value nil = env->intern (env, "nil"); + + struct feynman_server *server = malloc (sizeof (struct feynman_server)); + int err = init_feynman_server (env, server); + if (err != 0) + { + em_error (env, "Start process failed.Received none zero error code: %d", + err); + return nil; + } + // feynman_events_q = (queue_t *)init_queue(100); + + pthread_create (&server->server_thread_id, NULL, start_event_loop, + (void *)server); + server_state = env->make_global_ref ( + env, env->make_user_ptr (env, free_value, (void *)server)); + + return server_state; +} + +static emacs_value +/* cppcheck-suppress constParameter */ +feynman_stop (emacs_env *env, ptrdiff_t nargs, emacs_value args[], void *data) +{ + (void)nargs; + (void)data; + emacs_value nil = env->intern (env, "nil"); + emacs_value first_arg = args[0]; + + if (env->eq (env, first_arg, nil)) + { + em_message (env, "server parameter is nil"); + return nil; + } + + struct feynman_server *server + = (struct feynman_server *)env->get_user_ptr (env, first_arg); + + stop_feynman (env, server); + // deinit_queue(feynman_events_q); + + emacs_value t = env->intern (env, "t"); + return t; +} + +/* Bind NAME to FUN. */ +static void +bind_function (emacs_env *env, const char *name, emacs_value Sfun) +{ + /* Set the function cell of the symbol named NAME to SFUN using + the 'fset' function. */ + + /* Convert the strings to symbols by interning them */ + emacs_value Qfset = env->intern (env, "fset"); + emacs_value Qsym = env->intern (env, name); + + /* Prepare the arguments array */ + emacs_value args[] = { Qsym, Sfun }; + + /* Make the call (2 == nb of arguments) */ + env->funcall (env, Qfset, 2, args); +} + +/* Provide FEATURE to Emacs. */ +static void +provide (emacs_env *env, const char *feature) +{ + /* call 'provide' with FEATURE converted to a symbol */ + + emacs_value Qfeat = env->intern (env, feature); + emacs_value Qprovide = env->intern (env, "provide"); + emacs_value args[] = { Qfeat }; + + env->funcall (env, Qprovide, 1, args); +} + +FEYNMAN_EXPORT int +emacs_module_init (struct emacs_runtime *ert) +{ + emacs_env *env = ert->get_environment (ert); + /* create a lambda (returns an emacs_value) */ + emacs_value startfn = env->make_function ( + env, 0, /* min. number of arguments */ + 0, /* max. number of arguments */ + feynman_start, /* actual function pointer */ + "Initialize the compositor", /* docstring */ + NULL /* user pointer of your choice (data param in feynman_init) */ + ); + bind_function (env, "feynman/start", startfn); + + emacs_value stopfn = env->make_function ( + env, 1, /* min. number of arguments */ + 1, /* max. number of arguments */ + feynman_stop, /* actual function pointer */ + "Stop the compositor", /* docstring */ + NULL /* user pointer of your choice (data param in feynman_init) */ + ); + bind_function (env, "feynman/stop", stopfn); + + provide (env, "feynman"); + return 0; +} diff --git a/libfeynman/lib/queue.c b/libfeynman/lib/queue.c new file mode 100644 index 0000000..964ef86 --- /dev/null +++ b/libfeynman/lib/queue.c @@ -0,0 +1,42 @@ +/* + * Feynman -- Wayland compositor for GNU Emacs + * + * Copyright (c) 2022 Sameer Rahmani + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "queue.h" + +#include "utils.h" + +queue_t * +init_queue (unsigned size) +{ + event_t *data = (event_t *)malloc (sizeof (event_t) * size); + queue_t *e = (queue_t *)malloc (sizeof (queue_t)); + + e->size = size; + e->events = data; + e->head = NULL; + e->tail = NULL; + + return e; +}; + +void +deinit_queue (queue_t *q) +{ + free (q->events); + free (q); +}; diff --git a/libfeynman/lib/queue.h b/libfeynman/lib/queue.h new file mode 100644 index 0000000..571bf81 --- /dev/null +++ b/libfeynman/lib/queue.h @@ -0,0 +1,59 @@ +/* + * Feynman -- Wayland compositor for GNU Emacs + * + * Copyright (c) 2022 Sameer Rahmani + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef FEYNMAN_QUEUE_H +#define FEYNMAN_QUEUE_H + +#include +#include + +typedef enum event_enum +{ + echo = 0, + exit = 1, +} event_type_t; + +typedef struct arguments +{ + unsigned count; + void **args; +} arguments_t; + +typedef struct event +{ + unsigned id; + event_type_t event_type; + arguments_t *args; +} event_t; + +typedef struct queue +{ + unsigned *head; + unsigned *tail; + unsigned size; + event_t *events; +} queue_t; + +queue_t *init_queue (unsigned size); +void deinit_queue (queue_t *q); + +int enqueue_event (queue_t *q, event_t *event); +event_t *pop_event (queue_t *q); +bool is_queue_empty (queue_t *q); + +#endif diff --git a/libfeynman/lib/utils.c b/libfeynman/lib/utils.c new file mode 100644 index 0000000..5787009 --- /dev/null +++ b/libfeynman/lib/utils.c @@ -0,0 +1,74 @@ +/* + * Feynman -- Wayland compositor for GNU Emacs + * + * Copyright (c) 2022 Sameer Rahmani + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "utils.h" + +#include + +void +em_message (emacs_env *env, const char *fmt, ...) +{ + char *internalfmt = NULL; + char buffer[4096]; + va_list args; + va_start (args, fmt); + int rc = vsnprintf (buffer, sizeof (buffer), fmt, args); + va_end (args); + + if (rc < 0) + { + em_error (env, "Variadic params failed on 'em_message'"); + return; + } + + sprintf (internalfmt, "[Feynman]: %s", fmt); + emacs_value message = env->intern (env, "message"); + emacs_value msg = env->make_string (env, internalfmt, strlen (internalfmt)); + // emacs_value msgstr = env->make_string (env, , strlen (msg)); + + emacs_value message_args[] = { msg }; + env->funcall (env, message, 2, message_args); +}; + +void +em_error (emacs_env *env, const char *fmt, ...) +{ + char *internalfmt = NULL; + emacs_value error = env->intern (env, "error"); + + char buffer[4096]; + va_list args; + va_start (args, fmt); + int rc = vsnprintf (buffer, sizeof (buffer), fmt, args); + va_end (args); + + if (rc < 0) + { + char *err_msg = "Variadic params failed on 'em_error'"; + emacs_value msg = env->make_string (env, err_msg, strlen (err_msg)); + emacs_value err_args[] = { msg }; + env->funcall (env, error, 1, err_args); + return; + } + + sprintf (internalfmt, "[Feynman][Error]: %s", fmt); + emacs_value msg = env->make_string (env, internalfmt, strlen (internalfmt)); + + emacs_value error_args[] = { msg }; + env->funcall (env, error, 2, error_args); +}; diff --git a/libfeynman/lib/utils.h b/libfeynman/lib/utils.h new file mode 100644 index 0000000..68841c5 --- /dev/null +++ b/libfeynman/lib/utils.h @@ -0,0 +1,35 @@ +/* + * Feynman -- Wayland compositor for GNU Emacs + * + * Copyright (c) 2022 Sameer Rahmani + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef FEYNMAN_UTILS_H +#define FEYNMAN_UTILS_H + +#include +#include +#include + +// Since free and malloc are provided by emacs already +void free (void *); +void *malloc (unsigned long); +void *calloc (unsigned long, unsigned long); +int setenv (const char *envname, const char *envval, int overwrite); + +void em_message (emacs_env *env, const char *fmt, ...); +void em_error (emacs_env *env, const char *fmt, ...); + +#endif diff --git a/scripts/debugger.py b/scripts/debugger.py new file mode 100644 index 0000000..b90b9ae --- /dev/null +++ b/scripts/debugger.py @@ -0,0 +1,8 @@ +import lldb + + +def __lldb_init_module(debugger, internal_dict): + # debugger.HandleCommand("command script add -f " + __name__ + ".GetBundleIdentifier bundle_id") + env = lldb.SBEnvironment() + env.Set("DISPLAY", ":1", True) + print("here") diff --git a/scripts/debugger_init b/scripts/debugger_init new file mode 100644 index 0000000..d2db907 --- /dev/null +++ b/scripts/debugger_init @@ -0,0 +1,6 @@ +set env DISPLAY=:1 +set env EMACS_DEV=/home/lxsameer/builds/emacs/2022-08-05/ +set follow-fork-mode child +set substitute-path ./build/./ ./ +b compositor.c:116 +r \ No newline at end of file diff --git a/src/compositor.c b/src/compositor.c index 0ef373c..4922fa5 100644 --- a/src/compositor.c +++ b/src/compositor.c @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include static void @@ -139,8 +141,10 @@ handle_keybinding (struct feynman_server *server, xkb_keysym_t sym) char *const args[] = { emacs, NULL }; /* TODO: Set these vars accordingly on the prod build */ - char *const env[] - = { "WAYLAND_DISPLAY=wayland-0", "DISPLAY=:1", NULL }; + char *const env[] = { "WAYLAND_DISPLAY=wayland-0", "DISPLAY=:1", + "WAYLAND_DEBUG=1", NULL }; + char *const xargs[] = { "/usr/bin/xterm", NULL }; + execve ("/usr/bin/xterm", xargs, env); execve (emacs, args, env); } else @@ -733,6 +737,28 @@ xdg_toplevel_request_resize (struct wl_listener *listener, void *data) begin_interactive (view, FEYNMAN_CURSOR_RESIZE, event->edges); } +static void +xdg_configure_top_level (struct wl_listener *listener, void *data) +{ + + struct feynman_server *server + = wl_container_of (listener, server, new_xdg_surface); + + struct wlr_xdg_surface *xdg_surface = data; + wlr_log (WLR_INFO, "Configure method for '%d:%d'", + wl_resource_get_id(xdg_surface->resource), + wl_resource_get_id(xdg_surface->surface->resource)); + if (xdg_surface->surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) + { + wlr_log (WLR_INFO, "Surface '%d' role is '%d:%d' and not '%d'", + wl_resource_get_id(xdg_surface->resource), + xdg_surface->role, + WLR_XDG_SURFACE_ROLE_TOPLEVEL); + return; + } + wlr_xdg_toplevel_set_size (xdg_surface, 400, 400); +}; + static void server_new_xdg_surface (struct wl_listener *listener, void *data) { @@ -741,7 +767,8 @@ server_new_xdg_surface (struct wl_listener *listener, void *data) struct feynman_server *server = wl_container_of (listener, server, new_xdg_surface); struct wlr_xdg_surface *xdg_surface = data; - + wlr_log (WLR_INFO, "New surface '%d', role '%d'", + xdg_surface->resource->object.id, xdg_surface->role); /* We must add xdg popups to the scene graph so they get rendered. The * wlroots scene graph provides a helper for this, but to use it we must * provide the proper parent scene node of the xdg popup. To enable this, @@ -760,14 +787,25 @@ server_new_xdg_surface (struct wl_listener *listener, void *data) /* Allocate a feynman_view for this surface */ struct feynman_view *view = calloc (1, sizeof (struct feynman_view)); - view->server = server; - view->xdg_surface = xdg_surface; - view->scene_node = wlr_scene_xdg_surface_create (&view->server->scene->node, - view->xdg_surface); + + view->server = server; + view->xdg_surface = xdg_surface; + view->scene_node = wlr_scene_xdg_surface_create (&view->server->scene->node, + view->xdg_surface); + + view->scene_node->data = view; xdg_surface->data = view->scene_node; + + wlr_log (WLR_INFO, "Setup the listeners for '%d:%d'", + wl_resource_get_id(xdg_surface->resource), + wl_resource_get_id(xdg_surface->surface->resource)); + /* Listen to the various events it can emit */ + view->configure.notify = xdg_configure_top_level; + wl_signal_add (&xdg_surface->events.configure, &view->configure); + view->map.notify = xdg_toplevel_map; wl_signal_add (&xdg_surface->events.map, &view->map); view->unmap.notify = xdg_toplevel_unmap; diff --git a/src/compositor.h b/src/compositor.h index 58c9833..ab58c2f 100644 --- a/src/compositor.h +++ b/src/compositor.h @@ -39,6 +39,7 @@ #include #include #include +#include #include enum feynman_cursor_mode @@ -59,6 +60,7 @@ struct feynman_server struct wlr_xdg_shell *xdg_shell; struct wl_listener new_xdg_surface; + // struct xdg_surface_listener struct wl_list views; struct wlr_cursor *cursor; @@ -107,6 +109,7 @@ struct feynman_view struct wl_listener destroy; struct wl_listener request_move; struct wl_listener request_resize; + struct wl_listener configure; int x, y; }; diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..48791f3 --- /dev/null +++ b/src/main.c @@ -0,0 +1,34 @@ +/* + * Feynman -- Wayland compositor for GNU Emacs + * + * Copyright (c) 2022 Sameer Rahmani + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "feynman/feynman.h" +#include "compositor.h" + +int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + + struct feynman_server server; + int err = init_feynman_server(&server); + if (err != 0) { + return err; + } + + start_feynman(&server); + stop_feynman(&server); +}