aboutsummaryrefslogtreecommitdiff
path: root/src/main.zig
blob: ae41e6b61c95e071c38c4576bef46deafbe53237 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
const std = @import("std");

const c = @cImport({
    @cDefine("WLR_USE_UNSTABLE", {});
    @cInclude("wayland-server-core.h");
    @cInclude("wlr/render/wlr_renderer.h");
    @cInclude("wlr/types/wlr_cursor.h");
    @cInclude("wlr/types/wlr_compositor.h");
    @cInclude("wlr/types/wlr_data_device.h");
    @cInclude("wlr/types/wlr_input_device.h");
    @cInclude("wlr/types/wlr_keyboard.h");
    @cInclude("wlr/types/wlr_matrix.h");
    @cInclude("wlr/types/wlr_output.h");
    @cInclude("wlr/types/wlr_output_layout.h");
    @cInclude("wlr/types/wlr_pointer.h");
    @cInclude("wlr/types/wlr_seat.h");
    @cInclude("wlr/types/wlr_xcursor_manager.h");
    @cInclude("wlr/types/wlr_xdg_shell.h");
    @cInclude("wlr/util/log.h");
    @cInclude("xkbcommon/xkbcommon.h");
});

const CursorMode = enum {
    Passthrough,
    Move,
    Resize,
};

fn create_list() c.wl_list {
    return c.wl_list{
        .prev = null,
        .next = null,
    };
}

fn create_listener() c.wl_listener {
    return c.wl_listener{
        .link = create_list(),
        .notify = null,
    };
}

const RenderData = struct {
    output: *c.wlr_output,
    renderer: *c.wlr_renderer,
    view: *View,
    when: *std.os.timespec,
};

fn output_frame(listener: *c.wl_listener, data: *c_void) void {
    // This function is called every time an output is ready to display a frame,
    // generally at the output's refresh rate (e.g. 60Hz). */
    var output = @fieldParentPtr(Output, "frame", listener);
    var renderer = output.*.server.*.renderer;

    var now = undefined;
    std.os.linux.clock_gettime(std.os.CLOCK_MONOTONIC, &now);

    // wlr_output_attach_render makes the OpenGL context current.
    if (!c.wlr_output_attach_render(output.*.wlr_output, null)) {
        return;
    }
    // The "effective" resolution can change if you rotate your outputs.
    var width = undefined;
    var height = undefined;
    c.wlr_output_effective_resolution(output.*.wlr_output, &width, &height);
    // Begin the renderer (calls glViewport and some other GL sanity checks)
    c.wlr_renderer_begin(renderer, width, height);

    const color = [_]f32{ 0.3, 0.3, 0.3, 1.0 };
    c.wlr_renderer_clear(renderer, color);

    // Each subsequent window we render is rendered on top of the last. Because
    //  our view list is ordered front-to-back, we iterate over it backwards.
    // wl_list_for_each_reverse(view, &output.*.server.*.views, link) {

    var view = @fieldParentPtr(View, "link", &output.*.server.*.views.*.prev);

    while (&view.*.link != &output.*.server.*.views) {
        if (!view.*.mapped) {
            // An unmapped view should not be rendered.
            continue;
        }
        var rdata = RenderData{
            .output = output.*.wlr_output,
            .view = view,
            .renderer = renderer,
            .when = &now,
        };
        // This calls our render_surface function for each surface among the
        // xdg_surface's toplevel and popups.
        c.wlr_xdg_surface_for_each_surface(view.*.xdg_surface, render_surface, &rdata);

        // Move to next item in list
        view = @fieldParentPtr(View, "link", view.*.link.*.prev);
    }

    // Hardware cursors are rendered by the GPU on a separate plane, and can be
    // moved around without re-rendering what's beneath them - which is more
    // efficient. However, not all hardware supports hardware cursors. For this
    // reason, wlroots provides a software fallback, which we ask it to render
    // here. wlr_cursor handles configuring hardware vs software cursors for you,
    // and this function is a no-op when hardware cursors are in use.
    c.wlr_output_render_software_cursors(output.*.wlr_output, null);

    // Conclude rendering and swap the buffers, showing the final frame
    // on-screen.
    c.wlr_renderer_end(renderer);
    c.wlr_output_commit(output.*.wlr_output);
}

fn server_new_output(listener: *c.wl_listener, data: *c_void) void {
    var server = @fieldParentPtr(Server, "new_output", listener);
    var wlr_output = @ptrCast(c.wlr_output, data);

    // 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 (!c.wl_list_empty(&wlr_output.*.modes)) {
        var mode = wlr_output_preferred_mode(wlr_output);
        c.wlr_output_set_mode(wlr_output, mode);
        c.wlr_output_enable(wlr_output, true);
        if (!c.wlr_output_commit(wlr_output)) {
            return;
        }
    }

    // Allocates and configures our state for this output
    var output = std.heap.c_allocator.create(Output) catch unreachable;
    output.*.wlr_output = wlr_output;
    output.*.server = server;
    // Sets up a listener for the frame notify event.
    output.*.frame.notify = output_frame;
    c.wl_signal_add(&wlr_output.*.events.frame, &output.*.frame);
    c.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.
    c.wlr_output_layout_add_auto(server.*.output_layout, wlr_output);

    // Creating the global 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).
    c.wlr_output_create_global(wlr_output);
}

const Server = struct {
    wl_display: *c.wl_display,
    backend: *c.wlr_backend,
    renderer: *c.wlr_renderer,

    xdg_shell: ?*c.wlr_xdg_shell,
    new_xdg_surface: c.wl_listener,
    views: c.wl_list,

    cursor: ?*c.wlr_cursor,
    cursor_mgr: ?*c.wlr_xcursor_manager,
    cursor_motion: c.wl_listener,
    cursor_motion_absolute: c.wl_listener,
    cursor_button: c.wl_listener,
    cursor_axis: c.wl_listener,
    cursor_frame: c.wl_listener,

    seat: ?*c.wlr_seat,
    new_input: c.wl_listener,
    request_cursor: c.wl_listener,
    keyboards: c.wl_list,
    cursor_mode: CursorMode,
    grabbed_view: ?*View,
    grab_x: f64,
    grab_y: f64,
    grab_width: c_int,
    grab_height: c_int,
    resize_edges: u32,

    output_layout: ?*c.wlr_output_layout,
    outputs: c.wl_list,
    new_output: c.wl_listener,

    fn create() Server {
        const wl_display = c.wl_display_create();
        const backend = c.wlr_backend_autocreate(wl_display, null);
        const renderer = c.wlr_backend_get_renderer(server.backend);
        wlr_renderer_init_wl_display(renderer, wl_display);

        wlr_compositor_create(wl_display, renderer);
        wlr_data_device_manager_create(wl_display);

        const output_layout = wlr_output_layout_create();
        var outputs = create_list();
        wl_list_init(&outputs);

        new_output = create_listener();
        server.new_output.notify = server_new_output;
        wl_signal_add(&server.backend.*.events.new_output, &server.new_output);

        return Server{
            .wl_display = wl_display,
            .backend = backend,
            .renderer = null,

            .xdg_shell = null,
            .new_xdg_surface = create_listener(),
            .views = create_list(),

            .cursor = null,
            .cursor_mgr = null,
            .cursor_motion = create_listener(),
            .cursor_motion_absolute = create_listener(),
            .cursor_button = create_listener(),
            .cursor_axis = create_listener(),
            .cursor_frame = create_listener(),

            .seat = null,
            .new_input = create_listener(),
            .request_cursor = create_listener(),
            .keyboards = create_list(),
            .cursor_mode = CursorMode.Passthrough,
            .grabbed_view = null,
            .grab_x = 0.0,
            .grab_y = 0.0,
            .grab_width = 0,
            .grab_height = 0,
            .resize_edges = 0,

            .output_layout = null,
            .outputs = c.wl_list{ .prev = null, .next = null },
            .new_output = create_listener(),
        };
    }
};

const Output = struct {
    link: c.wl_list,
    server: *c.tinywl_server,
    xdg_surface: ?*c.wlr_xdg_surface,
    map: c.wl_listener,
    unmap: c.wl_listener,
    destroy: c.wl_listener,
    request_move: c.wl_listener,
    request_resize: c.wl_listener,
    mapped: bool,
    x: c_int,
    y: c_int,
};

const View = struct {
    link: c.wl_list,
    server: *Server,
    xdg_surface: ?*c.wlr_xdg_surface,
    map: c.wl_listener,
    unmap: c.wl_listener,
    destroy: c.wl_listener,
    request_move: c.wl_listener,
    request_resize: c.wl_listener,
    mapped: bool,
    x: c_int,
    y: c_int,
};

const Keyboard = struct {
    link: c.wl_list,
    server: *Server,
    device: ?*c.wlr_input_device,

    modifiers: c.wl_listener,
    key: c.wl_listener,
};

pub fn main() !void {
    std.debug.warn("Starting up.\n", .{});

    c.wlr_log_init(c.enum_wlr_log_importance.WLR_DEBUG, null);

    var server = Server.create();
}