commit 2d04210e1cce62eccd2d89eec19bab9a26c1c33c
parent c8f59414fcf97183c94188eeab7af21393f0dba6
Author: MikoĊaj Lenczewski <mblenczewski@gmail.com>
Date: Thu, 9 May 2024 22:48:49 +0000
Initial linux wayland window system
Diffstat:
8 files changed, 192 insertions(+), 161 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1,4 +1,5 @@
bin/
+dep/
opt/
**/.*.swp
diff --git a/README b/README
@@ -11,6 +11,9 @@ Our expected build environment is linux, with the following dependencies:
When building for linux, our dependency list is:
```text
+- wayland-protocols
+- wayland-client
+- alsa-libs
```
When building for windows, our dependency list is:
diff --git a/api/starfield_api.h b/api/starfield_api.h
@@ -61,7 +61,6 @@ struct str_t {
#define ASSERT(cond)
#endif
-#define global extern
#define internal static
#define func_local static
diff --git a/build_linux.sh b/build_linux.sh
@@ -1,6 +1,9 @@
#!/bin/sh
CC="clang"
+PKGCONF="pkg-config"
+
+WL_PROTO="/usr/share/wayland-protocols"
WARNINGS="-Wall -Wextra -Wpedantic -Werror"
@@ -8,13 +11,18 @@ CFLAGS="-std=c11 -O0 -g"
CPPFLAGS="-DSTARFIELD_DEBUG -Iapi"
LDFLAGS="-fuse-ld=lld"
+LINUX_LIBS="$($PKGCONF --cflags --libs alsa wayland-client wayland-egl)"
+
set -ex
-mkdir -p bin
+mkdir -p bin dep
$CC -shared -o bin/starfield.so starfield/starfield.c \
$WARNINGS $CFLAGS $CPPFLAGS $LDFLAGS -nostdlib
+wayland-scanner client-header $WL_PROTO/stable/xdg-shell/xdg-shell.xml dep/xdg-shell.h
+wayland-scanner private-code $WL_PROTO/stable/xdg-shell/xdg-shell.xml dep/xdg-shell.c
+
# TODO: how to build a static binary that supports loading starfield.so?
-$CC -o bin/linux_starfield platform/linux_starfield.c \
- $WARNINGS $CFLAGS $CPPFLAGS $LDFLAGS -lc -ldl
+$CC -o bin/linux_starfield platform/linux_starfield.c dep/xdg-shell.c \
+ $WARNINGS $CFLAGS $CPPFLAGS $LDFLAGS -lc -ldl $LINUX_LIBS -Idep
diff --git a/clean.sh b/clean.sh
@@ -2,4 +2,4 @@
set -ex
-rm -rf bin
+rm -rf bin dep
diff --git a/platform/linux_starfield.c b/platform/linux_starfield.c
@@ -50,7 +50,8 @@ main(int argc, char **argv, char **envp)
fprintf(stderr, "Failed to initialise platform audio\n");
}
- if (!init_video(&state.video)) {
+ char *display_name = NULL;
+ if (!init_video(&state.video, display_name)) {
fprintf(stderr, "Failed to initialise platform video\n");
_exit(1);
}
@@ -71,12 +72,8 @@ main(int argc, char **argv, char **envp)
state.starfield_api.update(&thread, &state.starfield_memory,
&state.platform_api, &input, dt);
- struct starfield_video_buffer video = {
- 0,
- };
-
state.starfield_api.render(&thread, &state.starfield_memory,
- &state.platform_api, &video, dt);
+ &state.platform_api, &state.video.buffer, dt);
if (state.audio.enabled) {
state.starfield_api.sample(&thread, &state.starfield_memory,
@@ -108,6 +105,8 @@ main(int argc, char **argv, char **envp)
play_audio(&state.audio);
}
+ blit_video(&state.video);
+
clock_gettime(CLOCK_MONOTONIC, &end_nanos);
u64 elapsed_us = linux_elapsed_ns(&start_nanos, &end_nanos) / 1000;
@@ -210,85 +209,25 @@ init_audio(struct linux_audio *audio, u32 period_us, u32 buffered_periods)
{
audio->enabled = false;
- void *library = dlopen("libasound.so", RTLD_NOW);
- if (!library) return false;
-
- if (!LOAD_SYMBOL(audio->api.snd_strerror, alsa_snd_strerror_t, "snd_strerror"))
- return false;
-
- if (!LOAD_SYMBOL(audio->api.snd_pcm_open, alsa_snd_pcm_open_t, "snd_pcm_open"))
- return false;
-
- if (!LOAD_SYMBOL(audio->api.snd_pcm_hw_params_sizeof,
- alsa_snd_pcm_hw_params_sizeof_t,
- "snd_pcm_hw_params_sizeof"))
- return false;
-
- if (!LOAD_SYMBOL(audio->api.snd_pcm_hw_params_any,
- alsa_snd_pcm_hw_params_any_t,
- "snd_pcm_hw_params_any"))
- return false;
-
- if (!LOAD_SYMBOL(audio->api.snd_pcm_hw_params_set_access,
- alsa_snd_pcm_hw_params_set_access_t,
- "snd_pcm_hw_params_set_access"))
- return false;
-
- if (!LOAD_SYMBOL(audio->api.snd_pcm_hw_params_set_format,
- alsa_snd_pcm_hw_params_set_format_t,
- "snd_pcm_hw_params_set_format"))
- return false;
-
- if (!LOAD_SYMBOL(audio->api.snd_pcm_hw_params_set_channels,
- alsa_snd_pcm_hw_params_set_channels_t,
- "snd_pcm_hw_params_set_channels"))
- return false;
-
- if (!LOAD_SYMBOL(audio->api.snd_pcm_hw_params_set_rate,
- alsa_snd_pcm_hw_params_set_rate_t,
- "snd_pcm_hw_params_set_rate"))
- return false;
-
- if (!LOAD_SYMBOL(audio->api.snd_pcm_hw_params_set_periods,
- alsa_snd_pcm_hw_params_set_periods_t,
- "snd_pcm_hw_params_set_periods"))
- return false;
-
- if (!LOAD_SYMBOL(audio->api.snd_pcm_hw_params_set_period_time,
- alsa_snd_pcm_hw_params_set_period_time_t,
- "snd_pcm_hw_params_set_period_time"))
- return false;
-
- if (!LOAD_SYMBOL(audio->api.snd_pcm_hw_params,
- alsa_snd_pcm_hw_params_t,
- "snd_pcm_hw_params"))
- return false;
-
- if (!LOAD_SYMBOL(audio->api.snd_pcm_writei,
- alsa_snd_pcm_writei_t,
- "snd_pcm_writei"))
- return false;
-
- /* initialise audio */
int err;
- if ((err = audio->api.snd_pcm_open(&audio->handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
- fprintf(stderr, "snd_pcm_open: %s\n", audio->api.snd_strerror(err));
+ if ((err = snd_pcm_open(&audio->handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
+ fprintf(stderr, "snd_pcm_open: %s\n", snd_strerror(err));
return false;
}
- snd_pcm_hw_params_t *hw_params = alloca(audio->api.snd_pcm_hw_params_sizeof());
+ snd_pcm_hw_params_t *hw_params = alloca(snd_pcm_hw_params_sizeof());
assert(hw_params);
- audio->api.snd_pcm_hw_params_any(audio->handle, hw_params);
- audio->api.snd_pcm_hw_params_set_access(audio->handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
- audio->api.snd_pcm_hw_params_set_format(audio->handle, hw_params, SND_PCM_FORMAT_S32_LE);
- audio->api.snd_pcm_hw_params_set_channels(audio->handle, hw_params, STARFIELD_AUDIO_CHANNELS);
- audio->api.snd_pcm_hw_params_set_rate(audio->handle, hw_params, STARFIELD_AUDIO_SAMPLE_RATE, 0);
- audio->api.snd_pcm_hw_params_set_periods(audio->handle, hw_params, buffered_periods, 0);
- audio->api.snd_pcm_hw_params_set_period_time(audio->handle, hw_params, period_us, 0);
+ snd_pcm_hw_params_any(audio->handle, hw_params);
+ snd_pcm_hw_params_set_access(audio->handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
+ snd_pcm_hw_params_set_format(audio->handle, hw_params, SND_PCM_FORMAT_S32_LE);
+ snd_pcm_hw_params_set_channels(audio->handle, hw_params, STARFIELD_AUDIO_CHANNELS);
+ snd_pcm_hw_params_set_rate(audio->handle, hw_params, STARFIELD_AUDIO_SAMPLE_RATE, 0);
+ snd_pcm_hw_params_set_periods(audio->handle, hw_params, buffered_periods, 0);
+ snd_pcm_hw_params_set_period_time(audio->handle, hw_params, period_us, 0);
- if ((err = audio->api.snd_pcm_hw_params(audio->handle, hw_params)) < 0) {
- fprintf(stderr, "and_pcm_hw_params: %s\n", audio->api.snd_strerror(err));
+ if ((err = snd_pcm_hw_params(audio->handle, hw_params)) < 0) {
+ fprintf(stderr, "and_pcm_hw_params: %s\n", snd_strerror(err));
return false;
}
@@ -319,19 +258,157 @@ play_audio(struct linux_audio *audio)
u32 frames = audio->buffer.expected_frames;
int err;
- if ((err = audio->api.snd_pcm_writei(audio->handle, buf, frames)) < 0) {
- fprintf(stderr, "snd_pcm_write: %s\n", audio->api.snd_strerror(err));
+ if ((err = snd_pcm_writei(audio->handle, buf, frames)) < 0) {
+ fprintf(stderr, "snd_pcm_write: %s\n", snd_strerror(err));
+ }
+}
+
+internal void
+xdg_wm_base_handle_ping(void *data, struct xdg_wm_base *wm_base, u32 serial)
+{
+ (void) data;
+
+ xdg_wm_base_pong(wm_base, serial);
+}
+
+internal const struct xdg_wm_base_listener xdg_wm_base_listener = {
+ .ping = xdg_wm_base_handle_ping,
+};
+
+internal void
+xdg_surface_handle_configure(void *data, struct xdg_surface *xdg_surface, u32 serial)
+{
+ (void) data;
+
+ xdg_surface_ack_configure(xdg_surface, serial);
+}
+
+internal const struct xdg_surface_listener xdg_surface_listener = {
+ .configure = xdg_surface_handle_configure,
+};
+
+internal void
+xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *toplevel, s32 width, s32 height, struct wl_array *states)
+{
+ (void) data;
+ (void) toplevel;
+ (void) width;
+ (void) height;
+ (void) states;
+
+ // TODO: handle me, pass through a resize event to starfield
+}
+
+internal void
+xdg_toplevel_handle_close(void *data, struct xdg_toplevel *toplevel)
+{
+ (void) data;
+ (void) toplevel;
+
+ state.running = false;
+}
+
+internal void
+xdg_toplevel_handle_configure_bounds(void *data, struct xdg_toplevel *toplevel, s32 width, s32 height)
+{
+ (void) data;
+ (void) toplevel;
+ (void) width;
+ (void) height;
+
+ // TODO: handle me, pass through a resize event to starfield
+}
+
+internal void
+xdg_toplevel_handle_wm_capabilities(void *data, struct xdg_toplevel *toplevel, struct wl_array *caps)
+{
+ (void) data;
+ (void) toplevel;
+ (void) caps;
+
+ // TODO: handle me, do we care about any special capabilities?
+}
+
+internal const struct xdg_toplevel_listener xdg_toplevel_listener = {
+ .configure = xdg_toplevel_handle_configure,
+ .close = xdg_toplevel_handle_close,
+ .configure_bounds = xdg_toplevel_handle_configure_bounds,
+ .wm_capabilities = xdg_toplevel_handle_wm_capabilities,
+};
+
+internal void
+wl_registry_handle_global(void *data, struct wl_registry *registry, u32 name, char const *interface, u32 version)
+{
+ struct linux_video *video = data;
+
+ fprintf(stderr, "wl: interface: %u, name: %s, version: %u\n", name, interface, version);
+
+ if (strcmp(interface, wl_compositor_interface.name) == 0) {
+ video->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 6);
+ } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
+ video->wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 6);
+ xdg_wm_base_add_listener(video->wm_base, &xdg_wm_base_listener, data);
}
}
+internal void
+wl_registry_handle_global_remove(void *data, struct wl_registry *registry, u32 name)
+{
+ (void) data;
+ (void) registry;
+ (void) name;
+}
+
+internal const struct wl_registry_listener wl_registry_listener = {
+ .global = wl_registry_handle_global,
+ .global_remove = wl_registry_handle_global_remove,
+};
+
internal b32
-init_video(struct linux_video *video)
+init_video(struct linux_video *video, char const *display_name)
{
- (void) video;
+ if (!(video->display = wl_display_connect(display_name))) {
+ fprintf(stderr, "wl_display_connect: could not connect to %s\n", display_name);
+ return false;
+ }
+
+ video->registry = wl_display_get_registry(video->display);
+
+ wl_registry_add_listener(video->registry, &wl_registry_listener, video);
+
+ wl_display_roundtrip(video->display);
+
+ assert(video->compositor);
+ assert(video->wm_base);
+
+ video->surface = wl_compositor_create_surface(video->compositor);
+
+ video->xdg_surface = xdg_wm_base_get_xdg_surface(video->wm_base, video->surface);
+ xdg_surface_add_listener(video->xdg_surface, &xdg_surface_listener, video);
+
+ video->toplevel = xdg_surface_get_toplevel(video->xdg_surface);
+ xdg_toplevel_add_listener(video->toplevel, &xdg_toplevel_listener, video);
+
+ xdg_toplevel_set_app_id(video->toplevel, "starfield");
+ xdg_toplevel_set_title(video->toplevel, "starfield");
+
+ wl_surface_commit(video->surface);
+
+ wl_display_roundtrip(video->display);
return true;
}
+internal void
+blit_video(struct linux_video *video)
+{
+ int res = wl_display_dispatch_pending(video->display);
+ if (res < 0) {
+ fprintf(stderr, "wl_display_dispatch_pending error\n");
+ return;
+ }
+}
+
/* platform implementation
* ===========================================================================
*/
diff --git a/platform/linux_starfield.h b/platform/linux_starfield.h
@@ -16,6 +16,8 @@
#include <alsa/asoundlib.h>
#include <wayland-client.h>
+#include <wayland-egl.h>
+#include "xdg-shell.h"
#include "starfield_api.h"
@@ -24,74 +26,7 @@ struct starfield_thread {
};
/* alsa state */
-#define ALSA_SND_STRERROR(name) \
- char const *name(int)
-
-#define ALSA_SND_PCM_OPEN(name) \
- int name(snd_pcm_t **, char const *, snd_pcm_stream_t, int)
-
-#define ALSA_SND_PCM_HW_PARAMS_SIZEOF(name) \
- size_t name(void)
-
-#define ALSA_SND_PCM_HW_PARAMS_ANY(name) \
- int name(snd_pcm_t *, snd_pcm_hw_params_t *)
-
-#define ALSA_SND_PCM_HW_PARAMS_SET_ACCESS(name) \
- int name(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_access_t)
-
-#define ALSA_SND_PCM_HW_PARAMS_SET_FORMAT(name) \
- int name(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_format_t)
-
-#define ALSA_SND_PCM_HW_PARAMS_SET_CHANNELS(name) \
- int name(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int)
-
-#define ALSA_SND_PCM_HW_PARAMS_SET_RATE(name) \
- int name(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int, int)
-
-#define ALSA_SND_PCM_HW_PARAMS_SET_PERIODS(name) \
- int name(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int, int)
-
-#define ALSA_SND_PCM_HW_PARAMS_SET_PERIOD_TIME(name) \
- int name(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int, int)
-
-#define ALSA_SND_PCM_HW_PARAMS(name) \
- int name(snd_pcm_t *, snd_pcm_hw_params_t *)
-
-#define ALSA_SND_PCM_WRITEI(name) \
- int name(snd_pcm_t *, void const *, snd_pcm_uframes_t)
-
-typedef ALSA_SND_STRERROR(alsa_snd_strerror_t);
-typedef ALSA_SND_PCM_OPEN(alsa_snd_pcm_open_t);
-typedef ALSA_SND_PCM_HW_PARAMS_SIZEOF(alsa_snd_pcm_hw_params_sizeof_t);
-typedef ALSA_SND_PCM_HW_PARAMS_ANY(alsa_snd_pcm_hw_params_any_t);
-typedef ALSA_SND_PCM_HW_PARAMS_SET_ACCESS(alsa_snd_pcm_hw_params_set_access_t);
-typedef ALSA_SND_PCM_HW_PARAMS_SET_FORMAT(alsa_snd_pcm_hw_params_set_format_t);
-typedef ALSA_SND_PCM_HW_PARAMS_SET_CHANNELS(alsa_snd_pcm_hw_params_set_channels_t);
-typedef ALSA_SND_PCM_HW_PARAMS_SET_RATE(alsa_snd_pcm_hw_params_set_rate_t);
-typedef ALSA_SND_PCM_HW_PARAMS_SET_PERIODS(alsa_snd_pcm_hw_params_set_periods_t);
-typedef ALSA_SND_PCM_HW_PARAMS_SET_PERIOD_TIME(alsa_snd_pcm_hw_params_set_period_time_t);
-typedef ALSA_SND_PCM_HW_PARAMS(alsa_snd_pcm_hw_params_t);
-typedef ALSA_SND_PCM_WRITEI(alsa_snd_pcm_writei_t);
-
struct linux_audio {
- /* api reference:
- * https://www.alsa-project.org/alsa-doc/alsa-lib/modules.html
- */
- struct {
- alsa_snd_strerror_t *snd_strerror;
- alsa_snd_pcm_open_t *snd_pcm_open;
- alsa_snd_pcm_hw_params_sizeof_t *snd_pcm_hw_params_sizeof;
- alsa_snd_pcm_hw_params_any_t *snd_pcm_hw_params_any;
- alsa_snd_pcm_hw_params_set_access_t *snd_pcm_hw_params_set_access;
- alsa_snd_pcm_hw_params_set_format_t *snd_pcm_hw_params_set_format;
- alsa_snd_pcm_hw_params_set_channels_t *snd_pcm_hw_params_set_channels;
- alsa_snd_pcm_hw_params_set_rate_t *snd_pcm_hw_params_set_rate;
- alsa_snd_pcm_hw_params_set_periods_t *snd_pcm_hw_params_set_periods;
- alsa_snd_pcm_hw_params_set_period_time_t *snd_pcm_hw_params_set_period_time;
- alsa_snd_pcm_hw_params_t *snd_pcm_hw_params;
- alsa_snd_pcm_writei_t *snd_pcm_writei;
- } api;
-
snd_pcm_t *handle;
struct starfield_audio_buffer buffer;
@@ -101,7 +36,17 @@ struct linux_audio {
/* wayland state */
struct linux_video {
- u8 pad;
+ struct wl_display *display;
+ struct wl_registry *registry;
+
+ struct wl_compositor *compositor;
+ struct wl_surface *surface;
+
+ struct xdg_wm_base *wm_base;
+ struct xdg_surface *xdg_surface;
+ struct xdg_toplevel *toplevel;
+
+ struct starfield_video_buffer buffer;
};
struct linux_state {
@@ -135,6 +80,9 @@ internal void
play_audio(struct linux_audio *audio);
internal b32
-init_video(struct linux_video *video);
+init_video(struct linux_video *video, char const *display_name);
+
+internal void
+blit_video(struct linux_video *video);
#endif /* LINUX_STARFIELD_H */
diff --git a/starfield/starfield.c b/starfield/starfield.c
@@ -1,6 +1,5 @@
#include "starfield.h"
-global
STARFIELD_INIT(starfield_init)
{
(void) thread;
@@ -8,7 +7,6 @@ STARFIELD_INIT(starfield_init)
(void) platform;
}
-global
STARFIELD_UPDATE(starfield_update)
{
(void) thread;
@@ -18,7 +16,6 @@ STARFIELD_UPDATE(starfield_update)
(void) dt;
}
-global
STARFIELD_RENDER(starfield_render)
{
(void) thread;
@@ -28,7 +25,6 @@ STARFIELD_RENDER(starfield_render)
(void) dt;
}
-global
STARFIELD_SAMPLE(starfield_sample)
{
(void) thread;
@@ -59,7 +55,6 @@ STARFIELD_SAMPLE(starfield_sample)
}
}
-global
STARFIELD_FREE(starfield_free)
{
(void) thread;