Some checks failed
CI / markdown-lint (push) Failing after 14s
- Updated .gitignore with comprehensive exclusions for build artifacts, IDE files, and OS-specific files - Created BlackBerry-inspired website with Heroicons and Gitea integration - Added complete project structure with all 7 phases implemented - Included kernel drivers, UI components, telephony stack, and packaging tools - Added emulation scripts for testing and development - Comprehensive documentation for all development phases - Security analysis and hardware testing guides - SDK and application framework for third-party development
361 lines
12 KiB
C
361 lines
12 KiB
C
/*
|
|
* Q20 Wayland Compositor
|
|
* BlackBerry Classic Q20 Display Server
|
|
*
|
|
* A lightweight Wayland compositor optimized for the Q20's
|
|
* 720x720 square display and keyboard navigation.
|
|
*/
|
|
|
|
#include <wayland-server.h>
|
|
#include <wlr/backend.h>
|
|
#include <wlr/render/wlr_renderer.h>
|
|
#include <wlr/types/wlr_compositor.h>
|
|
#include <wlr/types/wlr_data_device.h>
|
|
#include <wlr/types/wlr_input_device.h>
|
|
#include <wlr/types/wlr_keyboard.h>
|
|
#include <wlr/types/wlr_output.h>
|
|
#include <wlr/types/wlr_seat.h>
|
|
#include <wlr/types/wlr_xdg_shell.h>
|
|
#include <wlr/util/log.h>
|
|
#include <xkbcommon/xkbcommon.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <signal.h>
|
|
|
|
struct q20_server {
|
|
struct wl_display *wl_display;
|
|
struct wlr_backend *backend;
|
|
struct wlr_renderer *renderer;
|
|
struct wlr_compositor *compositor;
|
|
struct wlr_seat *seat;
|
|
struct wlr_output *output;
|
|
struct wlr_xdg_shell *xdg_shell;
|
|
|
|
struct wl_listener new_output;
|
|
struct wl_listener new_input;
|
|
struct wl_listener new_xdg_surface;
|
|
|
|
struct wl_list views;
|
|
struct wl_list keyboards;
|
|
|
|
struct wlr_view *focused_view;
|
|
struct wlr_keyboard *focused_keyboard;
|
|
};
|
|
|
|
struct q20_view {
|
|
struct wl_list link;
|
|
struct q20_server *server;
|
|
struct wlr_xdg_surface *xdg_surface;
|
|
struct wl_listener map;
|
|
struct wl_listener unmap;
|
|
struct wl_listener destroy;
|
|
struct wl_listener request_move;
|
|
struct wl_listener request_resize;
|
|
struct wl_listener request_maximize;
|
|
struct wl_listener request_fullscreen;
|
|
|
|
int x, y;
|
|
bool maximized;
|
|
bool fullscreen;
|
|
};
|
|
|
|
struct q20_keyboard {
|
|
struct wl_list link;
|
|
struct q20_server *server;
|
|
struct wlr_input_device *device;
|
|
struct wl_listener key;
|
|
struct wl_listener modifiers;
|
|
struct wl_listener destroy;
|
|
|
|
struct xkb_state *xkb_state;
|
|
xkb_keycode_t repeat_keycode;
|
|
uint32_t repeat_time;
|
|
struct wl_event_source *repeat_source;
|
|
};
|
|
|
|
static void focus_view(struct q20_server *server, struct q20_view *view) {
|
|
if (server->focused_view == view) {
|
|
return;
|
|
}
|
|
|
|
if (server->focused_view) {
|
|
struct wlr_xdg_surface *previous_surface = server->focused_view->xdg_surface;
|
|
wlr_xdg_toplevel_set_activated(previous_surface, false);
|
|
}
|
|
|
|
server->focused_view = view;
|
|
|
|
if (view) {
|
|
wlr_xdg_toplevel_set_activated(view->xdg_surface, true);
|
|
wlr_seat_keyboard_notify_enter(server->seat, view->xdg_surface->surface,
|
|
NULL, 0, NULL);
|
|
}
|
|
}
|
|
|
|
static void keyboard_key_notify(struct wl_listener *listener, void *data) {
|
|
struct q20_keyboard *keyboard = wl_container_of(listener, keyboard, key);
|
|
struct q20_server *server = keyboard->server;
|
|
struct wlr_event_keyboard_key *event = data;
|
|
struct wlr_seat *seat = server->seat;
|
|
|
|
// Get the keycode and translate it to a keysym
|
|
xkb_keycode_t keycode = event->keycode + 8;
|
|
xkb_keysym_t sym = xkb_state_key_get_one_sym(keyboard->xkb_state, keycode);
|
|
|
|
// Handle Q20-specific key combinations
|
|
if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) {
|
|
switch (sym) {
|
|
case XKB_KEY_Escape:
|
|
// Exit compositor
|
|
wl_display_terminate(server->wl_display);
|
|
break;
|
|
case XKB_KEY_F1:
|
|
// Switch to next view
|
|
// TODO: Implement view switching
|
|
break;
|
|
case XKB_KEY_F2:
|
|
// Toggle maximize
|
|
if (server->focused_view) {
|
|
server->focused_view->maximized = !server->focused_view->maximized;
|
|
if (server->focused_view->maximized) {
|
|
wlr_xdg_toplevel_set_maximized(server->focused_view->xdg_surface, true);
|
|
} else {
|
|
wlr_xdg_toplevel_set_maximized(server->focused_view->xdg_surface, false);
|
|
}
|
|
}
|
|
break;
|
|
case XKB_KEY_F3:
|
|
// Toggle fullscreen
|
|
if (server->focused_view) {
|
|
server->focused_view->fullscreen = !server->focused_view->fullscreen;
|
|
if (server->focused_view->fullscreen) {
|
|
wlr_xdg_toplevel_set_fullscreen(server->focused_view->xdg_surface, true);
|
|
} else {
|
|
wlr_xdg_toplevel_set_fullscreen(server->focused_view->xdg_surface, false);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
wlr_seat_keyboard_notify_key(seat, event->time_msec, event->keycode, event->state);
|
|
}
|
|
|
|
static void keyboard_modifiers_notify(struct wl_listener *listener, void *data) {
|
|
struct q20_keyboard *keyboard = wl_container_of(listener, keyboard, modifiers);
|
|
struct q20_server *server = keyboard->server;
|
|
|
|
wlr_seat_keyboard_notify_modifiers(server->seat, &keyboard->device->keyboard->modifiers);
|
|
}
|
|
|
|
static void keyboard_destroy_notify(struct wl_listener *listener, void *data) {
|
|
struct q20_keyboard *keyboard = wl_container_of(listener, keyboard, destroy);
|
|
|
|
wl_list_remove(&keyboard->link);
|
|
free(keyboard);
|
|
}
|
|
|
|
static void server_new_keyboard(struct q20_server *server, struct wlr_input_device *device) {
|
|
struct q20_keyboard *keyboard = calloc(1, sizeof(struct q20_keyboard));
|
|
keyboard->server = server;
|
|
keyboard->device = device;
|
|
|
|
// Get the keymap
|
|
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);
|
|
|
|
keyboard->xkb_state = xkb_state_new(keymap);
|
|
xkb_keymap_unref(keymap);
|
|
xkb_context_unref(context);
|
|
|
|
wlr_keyboard_set_keymap(device->keyboard, keymap);
|
|
wlr_keyboard_set_repeat_info(device->keyboard, 25, 600);
|
|
|
|
keyboard->key.notify = keyboard_key_notify;
|
|
wl_signal_add(&device->keyboard->events.key, &keyboard->key);
|
|
|
|
keyboard->modifiers.notify = keyboard_modifiers_notify;
|
|
wl_signal_add(&device->keyboard->events.modifiers, &keyboard->modifiers);
|
|
|
|
keyboard->destroy.notify = keyboard_destroy_notify;
|
|
wl_signal_add(&device->events.destroy, &keyboard->destroy);
|
|
|
|
wl_list_insert(&server->keyboards, &keyboard->link);
|
|
|
|
// Set this keyboard as the current one
|
|
wlr_seat_set_keyboard(server->seat, device);
|
|
}
|
|
|
|
static void server_new_input_notify(struct wl_listener *listener, void *data) {
|
|
struct q20_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;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void view_map_notify(struct wl_listener *listener, void *data) {
|
|
struct q20_view *view = wl_container_of(listener, view, map);
|
|
struct q20_server *server = view->server;
|
|
|
|
// Center the view on the 720x720 display
|
|
view->x = (720 - view->xdg_surface->surface->current.width) / 2;
|
|
view->y = (720 - view->xdg_surface->surface->current.height) / 2;
|
|
|
|
focus_view(server, view);
|
|
}
|
|
|
|
static void view_unmap_notify(struct wl_listener *listener, void *data) {
|
|
struct q20_view *view = wl_container_of(listener, view, unmap);
|
|
struct q20_server *server = view->server;
|
|
|
|
if (server->focused_view == view) {
|
|
focus_view(server, NULL);
|
|
}
|
|
}
|
|
|
|
static void view_destroy_notify(struct wl_listener *listener, void *data) {
|
|
struct q20_view *view = wl_container_of(listener, view, destroy);
|
|
|
|
wl_list_remove(&view->link);
|
|
free(view);
|
|
}
|
|
|
|
static void server_new_xdg_surface_notify(struct wl_listener *listener, void *data) {
|
|
struct q20_server *server = wl_container_of(listener, server, new_xdg_surface);
|
|
struct wlr_xdg_surface *xdg_surface = data;
|
|
|
|
if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) {
|
|
return;
|
|
}
|
|
|
|
struct q20_view *view = calloc(1, sizeof(struct q20_view));
|
|
view->server = server;
|
|
view->xdg_surface = xdg_surface;
|
|
|
|
view->map.notify = view_map_notify;
|
|
wl_signal_add(&xdg_surface->events.map, &view->map);
|
|
|
|
view->unmap.notify = view_unmap_notify;
|
|
wl_signal_add(&xdg_surface->events.unmap, &view->unmap);
|
|
|
|
view->destroy.notify = view_destroy_notify;
|
|
wl_signal_add(&xdg_surface->events.destroy, &view->destroy);
|
|
|
|
wl_list_insert(&server->views, &view->link);
|
|
}
|
|
|
|
static void output_frame_notify(struct wl_listener *listener, void *data) {
|
|
struct q20_server *server = wl_container_of(listener, server, output_frame);
|
|
struct wlr_output *output = data;
|
|
struct wlr_renderer *renderer = server->renderer;
|
|
|
|
struct timespec now;
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
|
|
wlr_output_make_current(output, NULL);
|
|
wlr_renderer_begin(renderer, output->width, output->height);
|
|
|
|
float color[4] = {0.1f, 0.1f, 0.1f, 1.0f};
|
|
wlr_renderer_clear(renderer, color);
|
|
|
|
// Render all views
|
|
struct q20_view *view;
|
|
wl_list_for_each_reverse(view, &server->views, link) {
|
|
if (!view->xdg_surface->mapped) {
|
|
continue;
|
|
}
|
|
|
|
struct wlr_box box = {
|
|
.x = view->x,
|
|
.y = view->y,
|
|
.width = view->xdg_surface->surface->current.width,
|
|
.height = view->xdg_surface->surface->current.height,
|
|
};
|
|
|
|
wlr_renderer_scissor(renderer, &box);
|
|
wlr_render_texture(renderer, view->xdg_surface->surface->texture, output->transform_matrix, box.x, box.y, 1.0f);
|
|
}
|
|
|
|
wlr_renderer_scissor(renderer, NULL);
|
|
wlr_renderer_end(renderer);
|
|
|
|
wlr_output_swap_buffers(output, NULL, NULL);
|
|
}
|
|
|
|
static void server_new_output_notify(struct wl_listener *listener, void *data) {
|
|
struct q20_server *server = wl_container_of(listener, server, new_output);
|
|
struct wlr_output *output = data;
|
|
|
|
if (!wl_list_empty(&output->modes)) {
|
|
struct wlr_output_mode *mode = wl_container_of(output->modes.prev, mode, link);
|
|
wlr_output_set_mode(output, mode);
|
|
}
|
|
|
|
// Configure for Q20's 720x720 display
|
|
wlr_output_set_custom_mode(output, 720, 720, 60000);
|
|
|
|
wlr_output_create_global(output);
|
|
|
|
server->output = output;
|
|
|
|
output->frame.notify = output_frame_notify;
|
|
wl_signal_add(&output->events.frame, &output->frame);
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
wlr_log_init(WLR_DEBUG, NULL);
|
|
|
|
struct q20_server server = {0};
|
|
|
|
server.wl_display = wl_display_create();
|
|
server.backend = wlr_backend_autocreate(server.wl_display);
|
|
server.renderer = wlr_renderer_autocreate(server.backend);
|
|
wlr_renderer_init_wl_display(server.renderer, server.wl_display);
|
|
|
|
server.compositor = wlr_compositor_create(server.wl_display, server.renderer);
|
|
wlr_export_dmabuf_manager_v1_create(server.wl_display);
|
|
wlr_screencopy_manager_v1_create(server.wl_display);
|
|
wlr_data_device_manager_create(server.wl_display);
|
|
|
|
server.seat = wlr_seat_create(server.wl_display, "seat0");
|
|
|
|
server.xdg_shell = wlr_xdg_shell_create(server.wl_display);
|
|
server.new_xdg_surface.notify = server_new_xdg_surface_notify;
|
|
wl_signal_add(&server.xdg_shell->events.new_surface, &server.new_xdg_surface);
|
|
|
|
wl_list_init(&server.views);
|
|
wl_list_init(&server.keyboards);
|
|
|
|
server.new_output.notify = server_new_output_notify;
|
|
wl_signal_add(&server.backend->events.new_output, &server.new_output);
|
|
|
|
server.new_input.notify = server_new_input_notify;
|
|
wl_signal_add(&server.backend->events.new_input, &server.new_input);
|
|
|
|
const char *socket = wl_display_add_socket_auto(server.wl_display);
|
|
if (!socket) {
|
|
wlr_backend_destroy(server.backend);
|
|
return 1;
|
|
}
|
|
|
|
if (!wlr_backend_start(server.backend)) {
|
|
wlr_backend_destroy(server.backend);
|
|
wl_display_destroy(server.wl_display);
|
|
return 1;
|
|
}
|
|
|
|
setenv("WAYLAND_DISPLAY", socket, true);
|
|
wlr_log(WLR_INFO, "Running Q20 compositor on WAYLAND_DISPLAY=%s", socket);
|
|
|
|
wl_display_run(server.wl_display);
|
|
|
|
wl_display_destroy(server.wl_display);
|
|
return 0;
|
|
}
|