aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--README.md13
-rw-r--r--TODO3
-rw-r--r--include/mgl/gl_macro.h2
-rw-r--r--include/mgl/graphics/backend/egl.h8
-rw-r--r--include/mgl/graphics/backend/glx.h8
-rw-r--r--include/mgl/graphics/backend/graphics.h48
-rw-r--r--include/mgl/graphics/texture.h11
-rw-r--r--include/mgl/mgl.h10
-rw-r--r--include/mgl/window/key.h2
-rw-r--r--include/mgl/window/wayland.h8
-rw-r--r--include/mgl/window/window.h119
-rw-r--r--include/mgl/window/x11.h8
-rw-r--r--meson.build39
-rw-r--r--project.conf4
-rw-r--r--protocol/meson.build25
-rw-r--r--protocol/xdg-shell.xml1415
-rw-r--r--src/graphics/backend/egl.c248
-rw-r--r--src/graphics/backend/glx.c173
-rw-r--r--src/graphics/backend/graphics.c68
-rw-r--r--src/graphics/font.c2
-rw-r--r--src/graphics/texture.c41
-rw-r--r--src/mgl.c200
-rw-r--r--src/window/key.c4
-rw-r--r--src/window/wayland.c781
-rw-r--r--src/window/window.c1969
-rw-r--r--src/window/x11.c1600
-rw-r--r--tests/main.c18
28 files changed, 4761 insertions, 2070 deletions
diff --git a/.gitignore b/.gitignore
index 636c6b9..4c85824 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,7 @@ sibs-build/
compile_commands.json
tests/sibs-build/
tests/compile_commands.json
+build/
+
+src/window/xdg-shell-client-protocol.h
+src/window/xdg-shell-protocol.c
diff --git a/README.md b/README.md
index 91e07c9..0367e11 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,15 @@
# Minimal Graphics Library
Written in C and uses OpenGL 2.1 to support as many platforms as possible.\
-Right now mgl only supports x11. Mgl allows you to choose glx or egl at runtime.
+Mgl supports both x11 and wayland and allows you to choose either glx or egl at runtime.
# Dependencies
## Build
-`x11, libxrender, libxrandr`
+`libx11, libxrender, libxrandr`\
+`wayland-client, wayland-egl, wayland-scanner`
## Runtime
-`libglvnd (libGL.so, libEGL.so)`
+`libglvnd (libGL.so, libGLX.so, libEGL.so)`
# Notes
-Every window _get_ function is cached from the last event poll, no calls to x11 is made.\
+Every window _get_ function is cached from the last event poll, no calls to x11/wayland is made.\
Only one window can be created and used at once.\
-mgl needs to be initialized first and then a window created, before other functions are called.
+mgl needs to be initialized first and then a window has to be created before other functions are called.
+# TODO
+Wayland support is not finished yet. Use MGL_WINDOW_SYSTEM_X11 for now in mgl_init. \ No newline at end of file
diff --git a/TODO b/TODO
index 86d4bce..5c9191c 100644
--- a/TODO
+++ b/TODO
@@ -8,4 +8,5 @@ Only render one glyph for missing symbols.\
High precision mouse wheel event by using xi2, which also allows us to get which scroll wheel was used and scrolling in y direction.\
Use XPresent (PresentNotifyMSC, glSwapBuffers, wait for PresentCompleteNotify before doing presentNotifyMsc and glSwapBuffers again), see https://invent.kde.org/plasma/kwin/-/issues/27. This doesn't work on nvidia because nvidia never sends the PresentCompleteNotify event.
Support drag and drop.\
-Use _NET_WM_SYNC_REQUEST.
+Use _NET_WM_SYNC_REQUEST.\
+Finish wayland support. \ No newline at end of file
diff --git a/include/mgl/gl_macro.h b/include/mgl/gl_macro.h
index 07ce1c4..6057bbb 100644
--- a/include/mgl/gl_macro.h
+++ b/include/mgl/gl_macro.h
@@ -48,6 +48,7 @@
#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3
#define GL_CLAMP_TO_EDGE 0x812F
+#define GL_NEAREST 0x2600
#define GL_LINEAR 0x2601
#define GL_LINEAR_MIPMAP_LINEAR 0x2703
@@ -109,6 +110,7 @@
#define EGL_COLOR_BUFFER_TYPE 0x303F
#define EGL_RGB_BUFFER 0x308E
#define EGL_SURFACE_TYPE 0x3033
+#define EGL_OPENGL_ES2_BIT 0x0004
#define EGL_WINDOW_BIT 0x0004
#endif /* MGL_GL_MACRO_H */
diff --git a/include/mgl/graphics/backend/egl.h b/include/mgl/graphics/backend/egl.h
new file mode 100644
index 0000000..5dede61
--- /dev/null
+++ b/include/mgl/graphics/backend/egl.h
@@ -0,0 +1,8 @@
+#ifndef MGL_GRAPHICS_EGL
+#define MGL_GRAPHICS_EGL
+
+#include "graphics.h"
+
+bool mgl_graphics_egl_init(mgl_graphics *self);
+
+#endif /* MGL_GRAPHICS_EGL */
diff --git a/include/mgl/graphics/backend/glx.h b/include/mgl/graphics/backend/glx.h
new file mode 100644
index 0000000..e2a5758
--- /dev/null
+++ b/include/mgl/graphics/backend/glx.h
@@ -0,0 +1,8 @@
+#ifndef MGL_GRAPHICS_GLX
+#define MGL_GRAPHICS_GLX
+
+#include "graphics.h"
+
+bool mgl_graphics_glx_init(mgl_graphics *self);
+
+#endif /* MGL_GRAPHICS_GLX */
diff --git a/include/mgl/graphics/backend/graphics.h b/include/mgl/graphics/backend/graphics.h
new file mode 100644
index 0000000..1553364
--- /dev/null
+++ b/include/mgl/graphics/backend/graphics.h
@@ -0,0 +1,48 @@
+#ifndef MGL_GRAPHICS_H
+#define MGL_GRAPHICS_H
+
+#include <stdbool.h>
+
+/* Each window should have its own mgl_graphics */
+
+typedef struct mgl_graphics mgl_graphics;
+typedef void* mgl_window_handle;
+
+typedef enum {
+ MGL_GRAPHICS_API_EGL,
+ MGL_GRAPHICS_API_GLX /* Only available when using X11 (or XWayland) */
+} mgl_graphics_api;
+
+struct mgl_graphics {
+ void (*deinit)(mgl_graphics *self);
+ bool (*make_context_current)(mgl_graphics *self, mgl_window_handle window);
+ void (*swap_buffers)(mgl_graphics *self, mgl_window_handle window);
+ bool (*set_swap_interval)(mgl_graphics *self, mgl_window_handle window, bool enabled);
+ void* (*get_xvisual_info)(mgl_graphics *self);
+
+ /* Optional */
+ void* (*get_display)(mgl_graphics *self);
+ void* (*get_context)(mgl_graphics *self);
+
+ void *impl;
+
+ mgl_graphics_api graphics_api;
+ bool alpha;
+};
+
+typedef struct {
+ mgl_graphics_api graphics_api; /* The graphics api to use. MGL_GRAPHICS_API_EGL by default */
+ bool alpha; /* Support window alpha transparency. False by default */
+} mgl_graphics_create_params;
+
+bool mgl_graphics_init(mgl_graphics *self, const mgl_graphics_create_params *params);
+void mgl_graphics_deinit(mgl_graphics *self);
+
+bool mgl_graphics_make_context_current(mgl_graphics *self, mgl_window_handle window);
+void mgl_graphics_swap_buffers(mgl_graphics *self, mgl_window_handle window);
+bool mgl_graphics_set_swap_interval(mgl_graphics *self, mgl_window_handle window, bool enabled);
+void* mgl_graphics_get_xvisual_info(mgl_graphics *self);
+void* mgl_graphics_get_display(mgl_graphics *self);
+void* mgl_graphics_get_context(mgl_graphics *self);
+
+#endif /* MGL_GRAPHICS_H */
diff --git a/include/mgl/graphics/texture.h b/include/mgl/graphics/texture.h
index d65adee..b658daf 100644
--- a/include/mgl/graphics/texture.h
+++ b/include/mgl/graphics/texture.h
@@ -14,6 +14,12 @@ typedef enum {
MGL_TEXTURE_FORMAT_RGBA
} mgl_texture_format;
+typedef enum {
+ MGL_TEXTURE_SCALE_NEAREST,
+ MGL_TEXTURE_SCALE_LINEAR,
+ MGL_TEXTURE_SCALE_LINEAR_MIPMAP /* generate mipmaps (available since opengl 3.0) */
+} mgl_texture_scale_type;
+
struct mgl_texture {
unsigned int id;
int width;
@@ -22,18 +28,19 @@ struct mgl_texture {
int max_width;
int max_height;
bool pixel_coordinates;
- bool mipmap;
+ mgl_texture_scale_type scale_type;
bool owned;
};
typedef struct {
bool compressed; /* false by default */
bool pixel_coordinates; /* false by default, texture coordinates are normalized */
- bool mipmap; /* false by default, generate mipmaps (available since opengl 3.0) */
+ mgl_texture_scale_type scale_type; /* MGL_TEXTURE_SCALE_LINEAR by default */
} mgl_texture_load_options;
typedef struct {
bool pixel_coordinates; /* false by default, texture coordinates are normalized */
+ mgl_texture_scale_type scale_type; /* MGL_TEXTURE_SCALE_LINEAR by default */
} mgl_texture_reference_options;
int mgl_texture_init(mgl_texture *self);
diff --git a/include/mgl/mgl.h b/include/mgl/mgl.h
index c5d1f2d..fd3367e 100644
--- a/include/mgl/mgl.h
+++ b/include/mgl/mgl.h
@@ -9,7 +9,15 @@ typedef void* mgl_connection;
typedef struct mgl_context mgl_context;
typedef struct mgl_window mgl_window;
+typedef enum {
+ MGL_WINDOW_SYSTEM_NATIVE, /* Use X11 on X11 and Wayland on Wayland */
+ MGL_WINDOW_SYSTEM_X11, /* Use X11 on X11 and XWayland on Wayland */
+ MGL_WINDOW_SYSTEM_WAYLAND, /* Use Wayland. If user runs on X11 then it fails to connect */
+} mgl_window_system;
+
struct mgl_context {
+ bool display_server_is_wayland;
+ mgl_window_system window_system; /* Window system requested with mgl_init */
mgl_connection connection;
mgl_gl gl;
mgl_window *current_window;
@@ -30,7 +38,7 @@ struct mgl_context {
Returns non-0 value on failure.
Note: not thread safe.
*/
-int mgl_init(void);
+int mgl_init(mgl_window_system window_system);
/*
Safe to call multiple times, but will only be deinitialized the last time called.
diff --git a/include/mgl/window/key.h b/include/mgl/window/key.h
index 722a80a..befd65a 100644
--- a/include/mgl/window/key.h
+++ b/include/mgl/window/key.h
@@ -129,6 +129,8 @@ typedef enum {
MGL_KEY_F20,
MGL_KEY_F21,
MGL_KEY_F22,
+ MGL_KEY_F23,
+ MGL_KEY_F24,
/* This should always be the last key */
__MGL_NUM_KEYS__
diff --git a/include/mgl/window/wayland.h b/include/mgl/window/wayland.h
new file mode 100644
index 0000000..081f87c
--- /dev/null
+++ b/include/mgl/window/wayland.h
@@ -0,0 +1,8 @@
+#ifndef MGL_WINDOW_WAYLAND_H
+#define MGL_WINDOW_WAYLAND_H
+
+#include "window.h"
+
+bool mgl_window_wayland_init(mgl_window *self, const char *title, const mgl_window_create_params *params, mgl_window_handle existing_window);
+
+#endif /* MGL_WINDOW_WAYLAND_H */
diff --git a/include/mgl/window/window.h b/include/mgl/window/window.h
index 748e728..3a190f5 100644
--- a/include/mgl/window/window.h
+++ b/include/mgl/window/window.h
@@ -2,6 +2,7 @@
#define MGL_WINDOW_H
#include "../graphics/color.h"
+#include "../graphics/backend/graphics.h"
#include "../system/vec.h"
#include "../system/clock.h"
#include "key.h"
@@ -9,13 +10,16 @@
#include <stdbool.h>
#include <stddef.h>
+#define MGL_MAX_MONITORS 12
+
/* Vsync is automatically set for created windows, if supported by the system */
typedef union _XEvent XEvent;
typedef struct mgl_event mgl_event;
-/* x11 window handle. TODO: Add others when wayland, etc is added */
-typedef unsigned long mgl_window_handle;
+/* x11/wayland window handle */
+typedef void* mgl_window_handle;
+typedef void* mgl_connection;
typedef struct mgl_window mgl_window;
typedef struct {
@@ -37,41 +41,77 @@ typedef struct {
int refresh_rate;
} mgl_monitor;
+typedef enum {
+ MGL_WINDOW_TYPE_NORMAL,
+ MGL_WINDOW_TYPE_DIALOG, /* Also sets the window as always on top */
+ MGL_WINDOW_TYPE_NOTIFICATION /* Also sets the window as always on top */
+} mgl_window_type;
+
+typedef enum {
+ MGL_CLIPBOARD_TYPE_STRING = 1 << 0,
+ MGL_CLIPBOARD_TYPE_IMAGE_PNG = 1 << 1,
+ MGL_CLIPBOARD_TYPE_IMAGE_JPG = 1 << 2,
+ MGL_CLIPBOARD_TYPE_IMAGE_GIF = 1 << 3,
+} mgl_clipboard_type;
+
+#define MGL_CLIPBOARD_TYPE_ALL 0xFFFFFFFF
+#define MGL_CLIPBOARD_TYPE_IMAGE (MGL_CLIPBOARD_TYPE_IMAGE_PNG | MGL_CLIPBOARD_TYPE_IMAGE_JPG | MGL_CLIPBOARD_TYPE_IMAGE_GIF)
+
+/*
+ Return true to continue. |mgl_window_get_clipboard| returns false if this returns false.
+ Note: |size| is the size of the current data, not the total data (if the callback only contains a part of the data).
+*/
+typedef bool (*mgl_clipboard_callback)(const unsigned char *data, size_t size, mgl_clipboard_type clipboard_type, void *userdata);
+typedef void (*mgl_active_monitor_callback)(const mgl_monitor *monitor, void *userdata);
+
struct mgl_window {
- mgl_window_handle window;
- void *context;
+ mgl_window_handle (*get_system_handle)(const mgl_window *self);
+ void (*deinit)(mgl_window *self);
+ void (*close)(mgl_window *self);
+ bool (*poll_event)(mgl_window *self, mgl_event *event);
+ bool (*inject_x11_event)(mgl_window *self, XEvent *xev, mgl_event *event); /* Optional */
+ void (*swap_buffers)(mgl_window *self);
+ void (*set_visible)(mgl_window *self, bool visible);
+ bool (*is_key_pressed)(const mgl_window *self, mgl_key key);
+ bool (*is_mouse_button_pressed)(const mgl_window *self, mgl_mouse_button button);
+ void (*set_title)(mgl_window *self, const char *title);
+ void (*set_cursor_visible)(mgl_window *self, bool visible);
+ void (*set_vsync_enabled)(mgl_window *self, bool enabled);
+ bool (*is_vsync_enabled)(const mgl_window *self);
+ void (*set_fullscreen)(mgl_window *self, bool fullscreen);
+ bool (*is_fullscreen)(const mgl_window *self);
+ void (*set_position)(mgl_window *self, mgl_vec2i position);
+ void (*set_size)(mgl_window *self, mgl_vec2i size);
+ void (*set_size_limits)(mgl_window *self, mgl_vec2i minimum, mgl_vec2i maximum);
+ void (*set_clipboard)(mgl_window *self, const char *str, size_t size);
+ bool (*get_clipboard)(mgl_window *self, mgl_clipboard_callback callback, void *userdata, uint32_t clipboard_types);
+ void (*set_key_repeat_enabled)(mgl_window *self, bool enabled);
+ void (*flush)(mgl_window *self);
+ void* (*get_egl_display)(mgl_window *self);
+ void* (*get_egl_context)(mgl_window *self);
+ void (*for_each_active_monitor_output)(mgl_window *self, mgl_active_monitor_callback callback, void *userdata);
+
+ void *impl;
+
+ bool vsync_enabled; /* true by default */
+ bool low_latency; /* false by default */
+ bool open;
+ bool focused;
+ bool key_repeat_enabled; /* true by default */
+ double frame_time_limit;
+ double frame_time_limit_monitor;
+ mgl_clock frame_timer;
mgl_vec2i pos;
mgl_vec2i size;
/* relative to the top left of the window. only updates when the cursor is inside the window */
mgl_vec2i cursor_position;
mgl_view view;
mgl_scissor scissor;
- bool open;
- bool focused;
- bool key_repeat_enabled;
- bool vsync_enabled; /* true by default */
- double frame_time_limit;
- double frame_time_limit_monitor;
- mgl_clock frame_timer;
- char *clipboard_string;
- size_t clipboard_size;
- bool low_latency; /* false by default */
-
- mgl_monitor *monitors; /* TODO: Move these to mgl file */
+ /* This only contains connected and active monitors */
+ mgl_monitor monitors[MGL_MAX_MONITORS];
int num_monitors;
};
-typedef enum {
- MGL_WINDOW_TYPE_NORMAL,
- MGL_WINDOW_TYPE_DIALOG, /* Also sets the window as always on top */
- MGL_WINDOW_TYPE_NOTIFICATION /* Also sets the window as always on top */
-} mgl_window_type;
-
-typedef enum {
- MGL_RENDER_API_GLX,
- MGL_RENDER_API_EGL
-} mgl_render_api;
-
/* TODO: Some of these parameters only apply to new window */
typedef struct {
mgl_vec2i position;
@@ -84,28 +124,12 @@ typedef struct {
bool support_alpha; /* support alpha for the window, false by default */
bool hide_decorations; /* this is a hint, it may be ignored by the window manager, false by default */
mgl_color background_color; /* default: black */
- const char *class_name;
+ const char *class_name; /* Class name on X11, App ID on Wayland */
mgl_window_type window_type; /* default: normal */
mgl_window_handle transient_for_window; /* 0 = none */
- mgl_render_api render_api; /* default: MGL_RENDER_API_GLX */
+ mgl_graphics_api graphics_api; /* Can only be MGL_GRAPHICS_API_GLX in an X11 window. default: MGL_GRAPHICS_API_EGL */
} mgl_window_create_params;
-typedef enum {
- MGL_CLIPBOARD_TYPE_STRING = 1 << 0,
- MGL_CLIPBOARD_TYPE_IMAGE_PNG = 1 << 1,
- MGL_CLIPBOARD_TYPE_IMAGE_JPG = 1 << 2,
- MGL_CLIPBOARD_TYPE_IMAGE_GIF = 1 << 3,
-} mgl_clipboard_type;
-
-#define MGL_CLIPBOARD_TYPE_ALL 0xFFFFFFFF
-#define MGL_CLIPBOARD_TYPE_IMAGE (MGL_CLIPBOARD_TYPE_IMAGE_PNG | MGL_CLIPBOARD_TYPE_IMAGE_JPG | MGL_CLIPBOARD_TYPE_IMAGE_GIF)
-
-/*
- Return true to continue. |mgl_window_get_clipboard| returns false if this returns false.
- Note: |size| is the size of the current data, not the total data (if the callback only contains a part of the data).
-*/
-typedef bool (*mgl_clipboard_callback)(const unsigned char *data, size_t size, mgl_clipboard_type clipboard_type, void *userdata);
-
/* |params| can be NULL. Note: vsync is enabled by default */
int mgl_window_create(mgl_window *self, const char *title, const mgl_window_create_params *params);
int mgl_window_init_from_existing_window(mgl_window *self, mgl_window_handle existing_window);
@@ -142,6 +166,8 @@ bool mgl_window_has_focus(const mgl_window *self);
bool mgl_window_is_key_pressed(const mgl_window *self, mgl_key key);
bool mgl_window_is_mouse_button_pressed(const mgl_window *self, mgl_mouse_button button);
+/* Returns 0 if none is available */
+mgl_window_handle mgl_window_get_system_handle(const mgl_window *self);
void mgl_window_close(mgl_window *self);
void mgl_window_set_title(mgl_window *self, const char *title);
void mgl_window_set_cursor_visible(mgl_window *self, bool visible);
@@ -187,7 +213,6 @@ void mgl_window_flush(mgl_window *self);
void* mgl_window_get_egl_display(mgl_window *self);
void* mgl_window_get_egl_context(mgl_window *self);
-typedef void (*mgl_active_monitor_callback)(const mgl_monitor *monitor, void *userdata);
-void mgl_for_each_active_monitor_output(void *display, mgl_active_monitor_callback callback, void *userdata);
+void mgl_window_for_each_active_monitor_output(mgl_window *self, mgl_active_monitor_callback callback, void *userdata);
#endif /* MGL_WINDOW_H */
diff --git a/include/mgl/window/x11.h b/include/mgl/window/x11.h
new file mode 100644
index 0000000..6c4aa93
--- /dev/null
+++ b/include/mgl/window/x11.h
@@ -0,0 +1,8 @@
+#ifndef MGL_WINDOW_X11_H
+#define MGL_WINDOW_X11_H
+
+#include "window.h"
+
+bool mgl_window_x11_init(mgl_window *self, const char *title, const mgl_window_create_params *params, mgl_window_handle existing_window);
+
+#endif /* MGL_WINDOW_X11_H */
diff --git a/meson.build b/meson.build
index 7369b48..58a280a 100644
--- a/meson.build
+++ b/meson.build
@@ -18,42 +18,26 @@ src = [
'src/graphics/text.c',
'src/graphics/shader.c',
'src/graphics/rectangle.c',
+ 'src/graphics/backend/graphics.c',
+ 'src/graphics/backend/glx.c',
+ 'src/graphics/backend/egl.c',
'src/system/fileutils.c',
'src/system/utf8.c',
'src/system/clock.c',
'src/mgl.c',
+ 'src/window/x11.c',
'src/window/window.c',
'src/window/key.c',
'src/gl.c',
]
-project_headers = [
- 'include/mgl/graphics/rectangle.h',
- 'include/mgl/graphics/sprite.h',
- 'include/mgl/graphics/texture.h',
- 'include/mgl/graphics/font_char_map.h',
- 'include/mgl/graphics/font.h',
- 'include/mgl/graphics/image.h',
- 'include/mgl/graphics/shader.h',
- 'include/mgl/graphics/primitive_type.h',
- 'include/mgl/graphics/vertex.h',
- 'include/mgl/graphics/vertex_buffer.h',
- 'include/mgl/graphics/font_glyph.h',
- 'include/mgl/graphics/color.h',
- 'include/mgl/graphics/text.h',
- 'include/mgl/system/fileutils.h',
- 'include/mgl/system/clock.h',
- 'include/mgl/system/vec.h',
- 'include/mgl/system/utf8.h',
- 'include/mgl/gl.h',
- 'include/mgl/window/event.h',
- 'include/mgl/window/mouse_button.h',
- 'include/mgl/window/window.h',
- 'include/mgl/window/key.h',
- 'include/mgl/gl_macro.h',
- 'include/mgl/mgl.h',
+src += [
+ 'src/window/wayland.c',
]
+subdir('protocol')
+src += protocol_src
+
cc = meson.get_compiler('c')
dep = [
dependency('x11'),
@@ -63,6 +47,11 @@ dep = [
cc.find_library('m'),
]
+dep += [
+ dependency('wayland-client'),
+ dependency('wayland-egl'),
+]
+
public_headers = include_directories('include')
project_target = static_library(
diff --git a/project.conf b/project.conf
index 8977367..e4ea76a 100644
--- a/project.conf
+++ b/project.conf
@@ -11,4 +11,6 @@ expose_include_dirs = ["include"]
[dependencies]
x11 = ">=0.5"
xrender = ">=0.5"
-xrandr = ">=0.5" \ No newline at end of file
+xrandr = ">=0.5"
+wayland-client = ">=1"
+wayland-egl = ">=15" \ No newline at end of file
diff --git a/protocol/meson.build b/protocol/meson.build
new file mode 100644
index 0000000..adaaefc
--- /dev/null
+++ b/protocol/meson.build
@@ -0,0 +1,25 @@
+wayland_scanner = dependency('wayland-scanner', native: true)
+wayland_scanner_path = wayland_scanner.get_variable(pkgconfig: 'wayland_scanner')
+wayland_scanner_prog = find_program(wayland_scanner_path, native: true)
+
+wayland_scanner_code = generator(
+ wayland_scanner_prog,
+ output: '@BASENAME@-protocol.c',
+ arguments: ['private-code', '@INPUT@', '@OUTPUT@'],
+)
+
+wayland_scanner_client = generator(
+ wayland_scanner_prog,
+ output: '@BASENAME@-client-protocol.h',
+ arguments: ['client-header', '@INPUT@', '@OUTPUT@'],
+)
+
+protocols = [
+ 'xdg-shell.xml',
+]
+
+protocol_src = []
+foreach xml : protocols
+ protocol_src += wayland_scanner_code.process(xml)
+ protocol_src += wayland_scanner_client.process(xml)
+endforeach
diff --git a/protocol/xdg-shell.xml b/protocol/xdg-shell.xml
new file mode 100644
index 0000000..c4d4685
--- /dev/null
+++ b/protocol/xdg-shell.xml
@@ -0,0 +1,1415 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="xdg_shell">
+
+ <copyright>
+ Copyright © 2008-2013 Kristian Høgsberg
+ Copyright © 2013 Rafael Antognolli
+ Copyright © 2013 Jasper St. Pierre
+ Copyright © 2010-2013 Intel Corporation
+ Copyright © 2015-2017 Samsung Electronics Co., Ltd
+ Copyright © 2015-2017 Red Hat Inc.
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+ </copyright>
+
+ <interface name="xdg_wm_base" version="7">
+ <description summary="create desktop-style surfaces">
+ The xdg_wm_base interface is exposed as a global object enabling clients
+ to turn their wl_surfaces into windows in a desktop environment. It
+ defines the basic functionality needed for clients and the compositor to
+ create windows that can be dragged, resized, maximized, etc, as well as
+ creating transient windows such as popup menus.
+ </description>
+
+ <enum name="error">
+ <entry name="role" value="0" summary="given wl_surface has another role"/>
+ <entry name="defunct_surfaces" value="1"
+ summary="xdg_wm_base was destroyed before children"/>
+ <entry name="not_the_topmost_popup" value="2"
+ summary="the client tried to map or destroy a non-topmost popup"/>
+ <entry name="invalid_popup_parent" value="3"
+ summary="the client specified an invalid popup parent surface"/>
+ <entry name="invalid_surface_state" value="4"
+ summary="the client provided an invalid surface state"/>
+ <entry name="invalid_positioner" value="5"
+ summary="the client provided an invalid positioner"/>
+ <entry name="unresponsive" value="6"
+ summary="the client didn’t respond to a ping event in time"/>
+ </enum>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy xdg_wm_base">
+ Destroy this xdg_wm_base object.
+
+ Destroying a bound xdg_wm_base object while there are surfaces
+ still alive created by this xdg_wm_base object instance is illegal
+ and will result in a defunct_surfaces error.
+ </description>
+ </request>
+
+ <request name="create_positioner">
+ <description summary="create a positioner object">
+ Create a positioner object. A positioner object is used to position
+ surfaces relative to some parent surface. See the interface description
+ and xdg_surface.get_popup for details.
+ </description>
+ <arg name="id" type="new_id" interface="xdg_positioner"/>
+ </request>
+
+ <request name="get_xdg_surface">
+ <description summary="create a shell surface from a surface">
+ This creates an xdg_surface for the given surface. While xdg_surface
+ itself is not a role, the corresponding surface may only be assigned
+ a role extending xdg_surface, such as xdg_toplevel or xdg_popup. It is
+ illegal to create an xdg_surface for a wl_surface which already has an
+ assigned role and this will result in a role error.
+
+ This creates an xdg_surface for the given surface. An xdg_surface is
+ used as basis to define a role to a given surface, such as xdg_toplevel
+ or xdg_popup. It also manages functionality shared between xdg_surface
+ based surface roles.
+
+ See the documentation of xdg_surface for more details about what an
+ xdg_surface is and how it is used.
+ </description>
+ <arg name="id" type="new_id" interface="xdg_surface"/>
+ <arg name="surface" type="object" interface="wl_surface"/>
+ </request>
+
+ <request name="pong">
+ <description summary="respond to a ping event">
+ A client must respond to a ping event with a pong request or
+ the client may be deemed unresponsive. See xdg_wm_base.ping
+ and xdg_wm_base.error.unresponsive.
+ </description>
+ <arg name="serial" type="uint" summary="serial of the ping event"/>
+ </request>
+
+ <event name="ping">
+ <description summary="check if the client is alive">
+ The ping event asks the client if it's still alive. Pass the
+ serial specified in the event back to the compositor by sending
+ a "pong" request back with the specified serial. See xdg_wm_base.pong.
+
+ Compositors can use this to determine if the client is still
+ alive. It's unspecified what will happen if the client doesn't
+ respond to the ping request, or in what timeframe. Clients should
+ try to respond in a reasonable amount of time. The “unresponsive”
+ error is provided for compositors that wish to disconnect unresponsive
+ clients.
+
+ A compositor is free to ping in any way it wants, but a client must
+ always respond to any xdg_wm_base object it created.
+ </description>
+ <arg name="serial" type="uint" summary="pass this to the pong request"/>
+ </event>
+ </interface>
+
+ <interface name="xdg_positioner" version="7">
+ <description summary="child surface positioner">
+ The xdg_positioner provides a collection of rules for the placement of a
+ child surface relative to a parent surface. Rules can be defined to ensure
+ the child surface remains within the visible area's borders, and to
+ specify how the child surface changes its position, such as sliding along
+ an axis, or flipping around a rectangle. These positioner-created rules are
+ constrained by the requirement that a child surface must intersect with or
+ be at least partially adjacent to its parent surface.
+
+ See the various requests for details about possible rules.
+
+ At the time of the request, the compositor makes a copy of the rules
+ specified by the xdg_positioner. Thus, after the request is complete the
+ xdg_positioner object can be destroyed or reused; further changes to the
+ object will have no effect on previous usages.
+
+ For an xdg_positioner object to be considered complete, it must have a
+ non-zero size set by set_size, and a non-zero anchor rectangle set by
+ set_anchor_rect. Passing an incomplete xdg_positioner object when
+ positioning a surface raises an invalid_positioner error.
+ </description>
+
+ <enum name="error">
+ <entry name="invalid_input" value="0" summary="invalid input provided"/>
+ </enum>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the xdg_positioner object">
+ Notify the compositor that the xdg_positioner will no longer be used.
+ </description>
+ </request>
+
+ <request name="set_size">
+ <description summary="set the size of the to-be positioned rectangle">
+ Set the size of the surface that is to be positioned with the positioner
+ object. The size is in surface-local coordinates and corresponds to the
+ window geometry. See xdg_surface.set_window_geometry.
+
+ If a zero or negative size is set the invalid_input error is raised.
+ </description>
+ <arg name="width" type="int" summary="width of positioned rectangle"/>
+ <arg name="height" type="int" summary="height of positioned rectangle"/>
+ </request>
+
+ <request name="set_anchor_rect">
+ <description summary="set the anchor rectangle within the parent surface">
+ Specify the anchor rectangle within the parent surface that the child
+ surface will be placed relative to. The rectangle is relative to the
+ window geometry as defined by xdg_surface.set_window_geometry of the
+ parent surface.
+
+ When the xdg_positioner object is used to position a child surface, the
+ anchor rectangle may not extend outside the window geometry of the
+ positioned child's parent surface.
+
+ If a negative size is set the invalid_input error is raised.
+ </description>
+ <arg name="x" type="int" summary="x position of anchor rectangle"/>
+ <arg name="y" type="int" summary="y position of anchor rectangle"/>
+ <arg name="width" type="int" summary="width of anchor rectangle"/>
+ <arg name="height" type="int" summary="height of anchor rectangle"/>
+ </request>
+
+ <enum name="anchor">
+ <entry name="none" value="0"/>
+ <entry name="top" value="1"/>
+ <entry name="bottom" value="2"/>
+ <entry name="left" value="3"/>
+ <entry name="right" value="4"/>
+ <entry name="top_left" value="5"/>
+ <entry name="bottom_left" value="6"/>
+ <entry name="top_right" value="7"/>
+ <entry name="bottom_right" value="8"/>
+ </enum>
+
+ <request name="set_anchor">
+ <description summary="set anchor rectangle anchor">
+ Defines the anchor point for the anchor rectangle. The specified anchor
+ is used derive an anchor point that the child surface will be
+ positioned relative to. If a corner anchor is set (e.g. 'top_left' or
+ 'bottom_right'), the anchor point will be at the specified corner;
+ otherwise, the derived anchor point will be centered on the specified
+ edge, or in the center of the anchor rectangle if no edge is specified.
+ </description>
+ <arg name="anchor" type="uint" enum="anchor"
+ summary="anchor"/>
+ </request>
+
+ <enum name="gravity">
+ <entry name="none" value="0"/>
+ <entry name="top" value="1"/>
+ <entry name="bottom" value="2"/>
+ <entry name="left" value="3"/>
+ <entry name="right" value="4"/>
+ <entry name="top_left" value="5"/>
+ <entry name="bottom_left" value="6"/>
+ <entry name="top_right" value="7"/>
+ <entry name="bottom_right" value="8"/>
+ </enum>
+
+ <request name="set_gravity">
+ <description summary="set child surface gravity">
+ Defines in what direction a surface should be positioned, relative to
+ the anchor point of the parent surface. If a corner gravity is
+ specified (e.g. 'bottom_right' or 'top_left'), then the child surface
+ will be placed towards the specified gravity; otherwise, the child
+ surface will be centered over the anchor point on any axis that had no
+ gravity specified. If the gravity is not in the ‘gravity’ enum, an
+ invalid_input error is raised.
+ </description>
+ <arg name="gravity" type="uint" enum="gravity"
+ summary="gravity direction"/>
+ </request>
+
+ <enum name="constraint_adjustment" bitfield="true">
+ <description summary="constraint adjustments">
+ The constraint adjustment value define ways the compositor will adjust
+ the position of the surface, if the unadjusted position would result
+ in the surface being partly constrained.
+
+ Whether a surface is considered 'constrained' is left to the compositor
+ to determine. For example, the surface may be partly outside the
+ compositor's defined 'work area', thus necessitating the child surface's
+ position be adjusted until it is entirely inside the work area.
+
+ The adjustments can be combined, according to a defined precedence: 1)
+ Flip, 2) Slide, 3) Resize.
+ </description>
+ <entry name="none" value="0">
+ <description summary="don't move the child surface when constrained">
+ Don't alter the surface position even if it is constrained on some
+ axis, for example partially outside the edge of an output.
+ </description>
+ </entry>
+ <entry name="slide_x" value="1">
+ <description summary="move along the x axis until unconstrained">
+ Slide the surface along the x axis until it is no longer constrained.
+
+ First try to slide towards the direction of the gravity on the x axis
+ until either the edge in the opposite direction of the gravity is
+ unconstrained or the edge in the direction of the gravity is
+ constrained.
+
+ Then try to slide towards the opposite direction of the gravity on the
+ x axis until either the edge in the direction of the gravity is
+ unconstrained or the edge in the opposite direction of the gravity is
+ constrained.
+ </description>
+ </entry>
+ <entry name="slide_y" value="2">
+ <description summary="move along the y axis until unconstrained">
+ Slide the surface along the y axis until it is no longer constrained.
+
+ First try to slide towards the direction of the gravity on the y axis
+ until either the edge in the opposite direction of the gravity is
+ unconstrained or the edge in the direction of the gravity is
+ constrained.
+
+ Then try to slide towards the opposite direction of the gravity on the
+ y axis until either the edge in the direction of the gravity is
+ unconstrained or the edge in the opposite direction of the gravity is
+ constrained.
+ </description>
+ </entry>
+ <entry name="flip_x" value="4">
+ <description summary="invert the anchor and gravity on the x axis">
+ Invert the anchor and gravity on the x axis if the surface is
+ constrained on the x axis. For example, if the left edge of the
+ surface is constrained, the gravity is 'left' and the anchor is
+ 'left', change the gravity to 'right' and the anchor to 'right'.
+
+ If the adjusted position also ends up being constrained, the resulting
+ position of the flip_x adjustment will be the one before the
+ adjustment.
+ </description>
+ </entry>
+ <entry name="flip_y" value="8">
+ <description summary="invert the anchor and gravity on the y axis">
+ Invert the anchor and gravity on the y axis if the surface is
+ constrained on the y axis. For example, if the bottom edge of the
+ surface is constrained, the gravity is 'bottom' and the anchor is
+ 'bottom', change the gravity to 'top' and the anchor to 'top'.
+
+ The adjusted position is calculated given the original anchor
+ rectangle and offset, but with the new flipped anchor and gravity
+ values.
+
+ If the adjusted position also ends up being constrained, the resulting
+ position of the flip_y adjustment will be the one before the
+ adjustment.
+ </description>
+ </entry>
+ <entry name="resize_x" value="16">
+ <description summary="horizontally resize the surface">
+ Resize the surface horizontally so that it is completely
+ unconstrained.
+ </description>
+ </entry>
+ <entry name="resize_y" value="32">
+ <description summary="vertically resize the surface">
+ Resize the surface vertically so that it is completely unconstrained.
+ </description>
+ </entry>
+ </enum>
+
+ <request name="set_constraint_adjustment">
+ <description summary="set the adjustment to be done when constrained">
+ Specify how the window should be positioned if the originally intended
+ position caused the surface to be constrained, meaning at least
+ partially outside positioning boundaries set by the compositor. The
+ adjustment is set by constructing a bitmask describing the adjustment to
+ be made when the surface is constrained on that axis.
+
+ If no bit for one axis is set, the compositor will assume that the child
+ surface should not change its position on that axis when constrained.
+
+ If more than one bit for one axis is set, the order of how adjustments
+ are applied is specified in the corresponding adjustment descriptions.
+
+ The default adjustment is none.
+ </description>
+ <arg name="constraint_adjustment" type="uint" enum="constraint_adjustment"
+ summary="bit mask of constraint adjustments"/>
+ </request>
+
+ <request name="set_offset">
+ <description summary="set surface position offset">
+ Specify the surface position offset relative to the position of the
+ anchor on the anchor rectangle and the anchor on the surface. For
+ example if the anchor of the anchor rectangle is at (x, y), the surface
+ has the gravity bottom|right, and the offset is (ox, oy), the calculated
+ surface position will be (x + ox, y + oy). The offset position of the
+ surface is the one used for constraint testing. See
+ set_constraint_adjustment.
+
+ An example use case is placing a popup menu on top of a user interface
+ element, while aligning the user interface element of the parent surface
+ with some user interface element placed somewhere in the popup surface.
+ </description>
+ <arg name="x" type="int" summary="surface position x offset"/>
+ <arg name="y" type="int" summary="surface position y offset"/>
+ </request>
+
+ <!-- Version 3 additions -->
+
+ <request name="set_reactive" since="3">
+ <description summary="continuously reconstrain the surface">
+ When set reactive, the surface is reconstrained if the conditions used
+ for constraining changed, e.g. the parent window moved.
+
+ If the conditions changed and the popup was reconstrained, an
+ xdg_popup.configure event is sent with updated geometry, followed by an
+ xdg_surface.configure event.
+ </description>
+ </request>
+
+ <request name="set_parent_size" since="3">
+ <description summary="">
+ Set the parent window geometry the compositor should use when
+ positioning the popup. The compositor may use this information to
+ determine the future state the popup should be constrained using. If
+ this doesn't match the dimension of the parent the popup is eventually
+ positioned against, the behavior is undefined.
+
+ The arguments are given in the surface-local coordinate space.
+ </description>
+ <arg name="parent_width" type="int"
+ summary="future window geometry width of parent"/>
+ <arg name="parent_height" type="int"
+ summary="future window geometry height of parent"/>
+ </request>
+
+ <request name="set_parent_configure" since="3">
+ <description summary="set parent configure this is a response to">
+ Set the serial of an xdg_surface.configure event this positioner will be
+ used in response to. The compositor may use this information together
+ with set_parent_size to determine what future state the popup should be
+ constrained using.
+ </description>
+ <arg name="serial" type="uint"
+ summary="serial of parent configure event"/>
+ </request>
+ </interface>
+
+ <interface name="xdg_surface" version="7">
+ <description summary="desktop user interface surface base interface">
+ An interface that may be implemented by a wl_surface, for
+ implementations that provide a desktop-style user interface.
+
+ It provides a base set of functionality required to construct user
+ interface elements requiring management by the compositor, such as
+ toplevel windows, menus, etc. The types of functionality are split into
+ xdg_surface roles.
+
+ Creating an xdg_surface does not set the role for a wl_surface. In order
+ to map an xdg_surface, the client must create a role-specific object
+ using, e.g., get_toplevel, get_popup. The wl_surface for any given
+ xdg_surface can have at most one role, and may not be assigned any role
+ not based on xdg_surface.
+
+ A role must be assigned before any other requests are made to the
+ xdg_surface object.
+
+ The client must call wl_surface.commit on the corresponding wl_surface
+ for the xdg_surface state to take effect.
+
+ Creating an xdg_surface from a wl_surface which has a buffer attached or
+ committed is a client error, and any attempts by a client to attach or
+ manipulate a buffer prior to the first xdg_surface.configure call must
+ also be treated as errors.
+
+ After creating a role-specific object and setting it up (e.g. by sending
+ the title, app ID, size constraints, parent, etc), the client must
+ perform an initial commit without any buffer attached. The compositor
+ will reply with initial wl_surface state such as
+ wl_surface.preferred_buffer_scale followed by an xdg_surface.configure
+ event. The client must acknowledge it and is then allowed to attach a
+ buffer to map the surface.
+
+ Mapping an xdg_surface-based role surface is defined as making it
+ possible for the surface to be shown by the compositor. Note that
+ a mapped surface is not guaranteed to be visible once it is mapped.
+
+ For an xdg_surface to be mapped by the compositor, the following
+ conditions must be met:
+ (1) the client has assigned an xdg_surface-based role to the surface
+ (2) the client has set and committed the xdg_surface state and the
+ role-dependent state to the surface
+ (3) the client has committed a buffer to the surface
+
+ A newly-unmapped surface is considered to have met condition (1) out
+ of the 3 required conditions for mapping a surface if its role surface
+ has not been destroyed, i.e. the client must perform the initial commit
+ again before attaching a buffer.
+ </description>
+
+ <enum name="error">
+ <entry name="not_constructed" value="1"
+ summary="Surface was not fully constructed"/>
+ <entry name="already_constructed" value="2"
+ summary="Surface was already constructed"/>
+ <entry name="unconfigured_buffer" value="3"
+ summary="Attaching a buffer to an unconfigured surface"/>
+ <entry name="invalid_serial" value="4"
+ summary="Invalid serial number when acking a configure event"/>
+ <entry name="invalid_size" value="5"
+ summary="Width or height was zero or negative"/>
+ <entry name="defunct_role_object" value="6"
+ summary="Surface was destroyed before its role object"/>
+ </enum>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the xdg_surface">
+ Destroy the xdg_surface object. An xdg_surface must only be destroyed
+ after its role object has been destroyed, otherwise
+ a defunct_role_object error is raised.
+ </description>
+ </request>
+
+ <request name="get_toplevel">
+ <description summary="assign the xdg_toplevel surface role">
+ This creates an xdg_toplevel object for the given xdg_surface and gives
+ the associated wl_surface the xdg_toplevel role.
+
+ See the documentation of xdg_toplevel for more details about what an
+ xdg_toplevel is and how it is used.
+ </description>
+ <arg name="id" type="new_id" interface="xdg_toplevel"/>
+ </request>
+
+ <request name="get_popup">
+ <description summary="assign the xdg_popup surface role">
+ This creates an xdg_popup object for the given xdg_surface and gives
+ the associated wl_surface the xdg_popup role.
+
+ If null is passed as a parent, a parent surface must be specified using
+ some other protocol, before committing the initial state.
+
+ See the documentation of xdg_popup for more details about what an
+ xdg_popup is and how it is used.
+ </description>
+ <arg name="id" type="new_id" interface="xdg_popup"/>
+ <arg name="parent" type="object" interface="xdg_surface" allow-null="true"/>
+ <arg name="positioner" type="object" interface="xdg_positioner"/>
+ </request>
+
+ <request name="set_window_geometry">
+ <description summary="set the new window geometry">
+ The window geometry of a surface is its "visible bounds" from the
+ user's perspective. Client-side decorations often have invisible
+ portions like drop-shadows which should be ignored for the
+ purposes of aligning, placing and constraining windows.
+
+ The window geometry is double-buffered state, see wl_surface.commit.
+
+ When maintaining a position, the compositor should treat the (x, y)
+ coordinate of the window geometry as the top left corner of the window.
+ A client changing the (x, y) window geometry coordinate should in
+ general not alter the position of the window.
+
+ Once the window geometry of the surface is set, it is not possible to
+ unset it, and it will remain the same until set_window_geometry is
+ called again, even if a new subsurface or buffer is attached.
+
+ If never set, the value is the full bounds of the surface,
+ including any subsurfaces. This updates dynamically on every
+ commit. This unset is meant for extremely simple clients.
+
+ The arguments are given in the surface-local coordinate space of
+ the wl_surface associated with this xdg_surface, and may extend outside
+ of the wl_surface itself to mark parts of the subsurface tree as part of
+ the window geometry.
+
+ When applied, the effective window geometry will be the set window
+ geometry clamped to the bounding rectangle of the combined
+ geometry of the surface of the xdg_surface and the associated
+ subsurfaces.
+
+ The effective geometry will not be recalculated unless a new call to
+ set_window_geometry is done and the new pending surface state is
+ subsequently applied.
+
+ The width and height of the effective window geometry must be
+ greater than zero. Setting an invalid size will raise an
+ invalid_size error.
+ </description>
+ <arg name="x" type="int"/>
+ <arg name="y" type="int"/>
+ <arg name="width" type="int"/>
+ <arg name="height" type="int"/>
+ </request>
+
+ <request name="ack_configure">
+ <description summary="ack a configure event">
+ When a configure event is received, if a client commits the
+ surface in response to the configure event, then the client
+ must make an ack_configure request sometime before the commit
+ request, passing along the serial of the configure event.
+
+ For instance, for toplevel surfaces the compositor might use this
+ information to move a surface to the top left only when the client has
+ drawn itself for the maximized or fullscreen state.
+
+ If the client receives multiple configure events before it
+ can respond to one, it only has to ack the last configure event.
+ Acking a configure event that was never sent raises an invalid_serial
+ error.
+
+ A client is not required to commit immediately after sending
+ an ack_configure request - it may even ack_configure several times
+ before its next surface commit.
+
+ A client may send multiple ack_configure requests before committing, but
+ only the last request sent before a commit indicates which configure
+ event the client really is responding to.
+
+ Sending an ack_configure request consumes the serial number sent with
+ the request, as well as serial numbers sent by all configure events
+ sent on this xdg_surface prior to the configure event referenced by
+ the committed serial.
+
+ It is an error to issue multiple ack_configure requests referencing a
+ serial from the same configure event, or to issue an ack_configure
+ request referencing a serial from a configure event issued before the
+ event identified by the last ack_configure request for the same
+ xdg_surface. Doing so will raise an invalid_serial error.
+ </description>
+ <arg name="serial" type="uint" summary="the serial from the configure event"/>
+ </request>
+
+ <event name="configure">
+ <description summary="suggest a surface change">
+ The configure event marks the end of a configure sequence. A configure
+ sequence is a set of one or more events configuring the state of the
+ xdg_surface, including the final xdg_surface.configure event.
+
+ Where applicable, xdg_surface surface roles will during a configure
+ sequence extend this event as a latched state sent as events before the
+ xdg_surface.configure event. Such events should be considered to make up
+ a set of atomically applied configuration states, where the
+ xdg_surface.configure commits the accumulated state.
+
+ Clients should arrange their surface for the new states, and then send
+ an ack_configure request with the serial sent in this configure event at
+ some point before committing the new surface.
+
+ If the client receives multiple configure events before it can respond
+ to one, it is free to discard all but the last event it received.
+ </description>
+ <arg name="serial" type="uint" summary="serial of the configure event"/>
+ </event>
+
+ </interface>
+
+ <interface name="xdg_toplevel" version="7">
+ <description summary="toplevel surface">
+ This interface defines an xdg_surface role which allows a surface to,
+ among other things, set window-like properties such as maximize,
+ fullscreen, and minimize, set application-specific metadata like title and
+ id, and well as trigger user interactive operations such as interactive
+ resize and move.
+
+ A xdg_toplevel by default is responsible for providing the full intended
+ visual representation of the toplevel, which depending on the window
+ state, may mean things like a title bar, window controls and drop shadow.
+
+ Unmapping an xdg_toplevel means that the surface cannot be shown
+ by the compositor until it is explicitly mapped again.
+ All active operations (e.g., move, resize) are canceled and all
+ attributes (e.g. title, state, stacking, ...) are discarded for
+ an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to
+ the state it had right after xdg_surface.get_toplevel. The client
+ can re-map the toplevel by performing a commit without any buffer
+ attached, waiting for a configure event and handling it as usual (see
+ xdg_surface description).
+
+ Attaching a null buffer to a toplevel unmaps the surface.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroy the xdg_toplevel">
+ This request destroys the role surface and unmaps the surface;
+ see "Unmapping" behavior in interface section for details.
+ </description>
+ </request>
+
+ <enum name="error">
+ <entry name="invalid_resize_edge" value="0" summary="provided value is
+ not a valid variant of the resize_edge enum"/>
+ <entry name="invalid_parent" value="1"
+ summary="invalid parent toplevel"/>
+ <entry name="invalid_size" value="2"
+ summary="client provided an invalid min or max size"/>
+ </enum>
+
+ <request name="set_parent">
+ <description summary="set the parent of this surface">
+ Set the "parent" of this surface. This surface should be stacked
+ above the parent surface and all other ancestor surfaces.
+
+ Parent surfaces should be set on dialogs, toolboxes, or other
+ "auxiliary" surfaces, so that the parent is raised when the dialog
+ is raised.
+
+ Setting a null parent for a child surface unsets its parent. Setting
+ a null parent for a surface which currently has no parent is a no-op.
+
+ Only mapped surfaces can have child surfaces. Setting a parent which
+ is not mapped is equivalent to setting a null parent. If a surface
+ becomes unmapped, its children's parent is set to the parent of
+ the now-unmapped surface. If the now-unmapped surface has no parent,
+ its children's parent is unset. If the now-unmapped surface becomes
+ mapped again, its parent-child relationship is not restored.
+
+ The parent toplevel must not be one of the child toplevel's
+ descendants, and the parent must be different from the child toplevel,
+ otherwise the invalid_parent protocol error is raised.
+ </description>
+ <arg name="parent" type="object" interface="xdg_toplevel" allow-null="true"/>
+ </request>
+
+ <request name="set_title">
+ <description summary="set surface title">
+ Set a short title for the surface.
+
+ This string may be used to identify the surface in a task bar,
+ window list, or other user interface elements provided by the
+ compositor.
+
+ The string must be encoded in UTF-8.
+ </description>
+ <arg name="title" type="string"/>
+ </request>
+
+ <request name="set_app_id">
+ <description summary="set application ID">
+ Set an application identifier for the surface.
+
+ The app ID identifies the general class of applications to which
+ the surface belongs. The compositor can use this to group multiple
+ surfaces together, or to determine how to launch a new application.
+
+ For D-Bus activatable applications, the app ID is used as the D-Bus
+ service name.
+
+ The compositor shell will try to group application surfaces together
+ by their app ID. As a best practice, it is suggested to select app
+ ID's that match the basename of the application's .desktop file.
+ For example, "org.freedesktop.FooViewer" where the .desktop file is
+ "org.freedesktop.FooViewer.desktop".
+
+ Like other properties, a set_app_id request can be sent after the
+ xdg_toplevel has been mapped to update the property.
+
+ See the desktop-entry specification [0] for more details on
+ application identifiers and how they relate to well-known D-Bus
+ names and .desktop files.
+
+ [0] https://standards.freedesktop.org/desktop-entry-spec/
+ </description>
+ <arg name="app_id" type="string"/>
+ </request>
+
+ <request name="show_window_menu">
+ <description summary="show the window menu">
+ Clients implementing client-side decorations might want to show
+ a context menu when right-clicking on the decorations, giving the
+ user a menu that they can use to maximize or minimize the window.
+
+ This request asks the compositor to pop up such a window menu at
+ the given position, relative to the local surface coordinates of
+ the parent surface. There are no guarantees as to what menu items
+ the window menu contains, or even if a window menu will be drawn
+ at all.
+
+ This request must be used in response to some sort of user action
+ like a button press, key press, or touch down event.
+ </description>
+ <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/>
+ <arg name="serial" type="uint" summary="the serial of the user event"/>
+ <arg name="x" type="int" summary="the x position to pop up the window menu at"/>
+ <arg name="y" type="int" summary="the y position to pop up the window menu at"/>
+ </request>
+
+ <request name="move">
+ <description summary="start an interactive move">
+ Start an interactive, user-driven move of the surface.
+
+ This request must be used in response to some sort of user action
+ like a button press, key press, or touch down event. The passed
+ serial is used to determine the type of interactive move (touch,
+ pointer, etc).
+
+ The server may ignore move requests depending on the state of
+ the surface (e.g. fullscreen or maximized), or if the passed serial
+ is no longer valid.
+
+ If triggered, the surface will lose the focus of the device
+ (wl_pointer, wl_touch, etc) used for the move. It is up to the
+ compositor to visually indicate that the move is taking place, such as
+ updating a pointer cursor, during the move. There is no guarantee
+ that the device focus will return when the move is completed.
+ </description>
+ <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/>
+ <arg name="serial" type="uint" summary="the serial of the user event"/>
+ </request>
+
+ <enum name="resize_edge">
+ <description summary="edge values for resizing">
+ These values are used to indicate which edge of a surface
+ is being dragged in a resize operation.
+ </description>
+ <entry name="none" value="0"/>
+ <entry name="top" value="1"/>
+ <entry name="bottom" value="2"/>
+ <entry name="left" value="4"/>
+ <entry name="top_left" value="5"/>
+ <entry name="bottom_left" value="6"/>
+ <entry name="right" value="8"/>
+ <entry name="top_right" value="9"/>
+ <entry name="bottom_right" value="10"/>
+ </enum>
+
+ <request name="resize">
+ <description summary="start an interactive resize">
+ Start a user-driven, interactive resize of the surface.
+
+ This request must be used in response to some sort of user action
+ like a button press, key press, or touch down event. The passed
+ serial is used to determine the type of interactive resize (touch,
+ pointer, etc).
+
+ The server may ignore resize requests depending on the state of
+ the surface (e.g. fullscreen or maximized).
+
+ If triggered, the client will receive configure events with the
+ "resize" state enum value and the expected sizes. See the "resize"
+ enum value for more details about what is required. The client
+ must also acknowledge configure events using "ack_configure". After
+ the resize is completed, the client will receive another "configure"
+ event without the resize state.
+
+ If triggered, the surface also will lose the focus of the device
+ (wl_pointer, wl_touch, etc) used for the resize. It is up to the
+ compositor to visually indicate that the resize is taking place,
+ such as updating a pointer cursor, during the resize. There is no
+ guarantee that the device focus will return when the resize is
+ completed.
+
+ The edges parameter specifies how the surface should be resized, and
+ is one of the values of the resize_edge enum. Values not matching
+ a variant of the enum will cause the invalid_resize_edge protocol error.
+ The compositor may use this information to update the surface position
+ for example when dragging the top left corner. The compositor may also
+ use this information to adapt its behavior, e.g. choose an appropriate
+ cursor image.
+ </description>
+ <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/>
+ <arg name="serial" type="uint" summary="the serial of the user event"/>
+ <arg name="edges" type="uint" enum="resize_edge" summary="which edge or corner is being dragged"/>
+ </request>
+
+ <enum name="state">
+ <description summary="types of state on the surface">
+ The different state values used on the surface. This is designed for
+ state values like maximized, fullscreen. It is paired with the
+ configure event to ensure that both the client and the compositor
+ setting the state can be synchronized.
+
+ States set in this way are double-buffered, see wl_surface.commit.
+ </description>
+ <entry name="maximized" value="1" summary="the surface is maximized">
+ <description summary="the surface is maximized">
+ The surface is maximized. The window geometry specified in the configure
+ event must be obeyed by the client, or the xdg_wm_base.invalid_surface_state
+ error is raised.
+
+ The client should draw without shadow or other
+ decoration outside of the window geometry.
+ </description>
+ </entry>
+ <entry name="fullscreen" value="2" summary="the surface is fullscreen">
+ <description summary="the surface is fullscreen">
+ The surface is fullscreen. The window geometry specified in the
+ configure event is a maximum; the client cannot resize beyond it. For
+ a surface to cover the whole fullscreened area, the geometry
+ dimensions must be obeyed by the client. For more details, see
+ xdg_toplevel.set_fullscreen.
+ </description>
+ </entry>
+ <entry name="resizing" value="3" summary="the surface is being resized">
+ <description summary="the surface is being resized">
+ The surface is being resized. The window geometry specified in the
+ configure event is a maximum; the client cannot resize beyond it.
+ Clients that have aspect ratio or cell sizing configuration can use
+ a smaller size, however.
+ </description>
+ </entry>
+ <entry name="activated" value="4" summary="the surface is now activated">
+ <description summary="the surface is now activated">
+ Client window decorations should be painted as if the window is
+ active. Do not assume this means that the window actually has
+ keyboard or pointer focus.
+ </description>
+ </entry>
+ <entry name="tiled_left" value="5" since="2">
+ <description summary="the surface’s left edge is tiled">
+ The window is currently in a tiled layout and the left edge is
+ considered to be adjacent to another part of the tiling grid.
+
+ The client should draw without shadow or other decoration outside of
+ the window geometry on the left edge.
+ </description>
+ </entry>
+ <entry name="tiled_right" value="6" since="2">
+ <description summary="the surface’s right edge is tiled">
+ The window is currently in a tiled layout and the right edge is
+ considered to be adjacent to another part of the tiling grid.
+
+ The client should draw without shadow or other decoration outside of
+ the window geometry on the right edge.
+ </description>
+ </entry>
+ <entry name="tiled_top" value="7" since="2">
+ <description summary="the surface’s top edge is tiled">
+ The window is currently in a tiled layout and the top edge is
+ considered to be adjacent to another part of the tiling grid.
+
+ The client should draw without shadow or other decoration outside of
+ the window geometry on the top edge.
+ </description>
+ </entry>
+ <entry name="tiled_bottom" value="8" since="2">
+ <description summary="the surface’s bottom edge is tiled">
+ The window is currently in a tiled layout and the bottom edge is
+ considered to be adjacent to another part of the tiling grid.
+
+ The client should draw without shadow or other decoration outside of
+ the window geometry on the bottom edge.
+ </description>
+ </entry>
+ <entry name="suspended" value="9" since="6">
+ <description summary="surface repaint is suspended">
+ The surface is currently not ordinarily being repainted; for
+ example because its content is occluded by another window, or its
+ outputs are switched off due to screen locking.
+ </description>
+ </entry>
+ <entry name="constrained_left" value="10" since="7">
+ <description summary="the surface’s left edge is constrained">
+ The left edge of the window is currently constrained, meaning it
+ shouldn't attempt to resize from that edge. It can for example mean
+ it's tiled next to a monitor edge on the constrained side of the
+ window.
+ </description>
+ </entry>
+ <entry name="constrained_right" value="11" since="7">
+ <description summary="the surface’s right edge is constrained">
+ The right edge of the window is currently constrained, meaning it
+ shouldn't attempt to resize from that edge. It can for example mean
+ it's tiled next to a monitor edge on the constrained side of the
+ window.
+ </description>
+ </entry>
+ <entry name="constrained_top" value="12" since="7">
+ <description summary="the surface’s top edge is constrained">
+ The top edge of the window is currently constrained, meaning it
+ shouldn't attempt to resize from that edge. It can for example mean
+ it's tiled next to a monitor edge on the constrained side of the
+ window.
+ </description>
+ </entry>
+ <entry name="constrained_bottom" value="13" since="7">
+ <description summary="the surface’s bottom edge is tiled">
+ The bottom edge of the window is currently constrained, meaning it
+ shouldn't attempt to resize from that edge. It can for example mean
+ it's tiled next to a monitor edge on the constrained side of the
+ window.
+ </description>
+ </entry>
+ </enum>
+
+ <request name="set_max_size">
+ <description summary="set the maximum size">
+ Set a maximum size for the window.
+
+ The client can specify a maximum size so that the compositor does
+ not try to configure the window beyond this size.
+
+ The width and height arguments are in window geometry coordinates.
+ See xdg_surface.set_window_geometry.
+
+ Values set in this way are double-buffered, see wl_surface.commit.
+
+ The compositor can use this information to allow or disallow
+ different states like maximize or fullscreen and draw accurate
+ animations.
+
+ Similarly, a tiling window manager may use this information to
+ place and resize client windows in a more effective way.
+
+ The client should not rely on the compositor to obey the maximum
+ size. The compositor may decide to ignore the values set by the
+ client and request a larger size.
+
+ If never set, or a value of zero in the request, means that the
+ client has no expected maximum size in the given dimension.
+ As a result, a client wishing to reset the maximum size
+ to an unspecified state can use zero for width and height in the
+ request.
+
+ Requesting a maximum size to be smaller than the minimum size of
+ a surface is illegal and will result in an invalid_size error.
+
+ The width and height must be greater than or equal to zero. Using
+ strictly negative values for width or height will result in a
+ invalid_size error.
+ </description>
+ <arg name="width" type="int"/>
+ <arg name="height" type="int"/>
+ </request>
+
+ <request name="set_min_size">
+ <description summary="set the minimum size">
+ Set a minimum size for the window.
+
+ The client can specify a minimum size so that the compositor does
+ not try to configure the window below this size.
+
+ The width and height arguments are in window geometry coordinates.
+ See xdg_surface.set_window_geometry.
+
+ Values set in this way are double-buffered, see wl_surface.commit.
+
+ The compositor can use this information to allow or disallow
+ different states like maximize or fullscreen and draw accurate
+ animations.
+
+ Similarly, a tiling window manager may use this information to
+ place and resize client windows in a more effective way.
+
+ The client should not rely on the compositor to obey the minimum
+ size. The compositor may decide to ignore the values set by the
+ client and request a smaller size.
+
+ If never set, or a value of zero in the request, means that the
+ client has no expected minimum size in the given dimension.
+ As a result, a client wishing to reset the minimum size
+ to an unspecified state can use zero for width and height in the
+ request.
+
+ Requesting a minimum size to be larger than the maximum size of
+ a surface is illegal and will result in an invalid_size error.
+
+ The width and height must be greater than or equal to zero. Using
+ strictly negative values for width and height will result in a
+ invalid_size error.
+ </description>
+ <arg name="width" type="int"/>
+ <arg name="height" type="int"/>
+ </request>
+
+ <request name="set_maximized">
+ <description summary="maximize the window">
+ Maximize the surface.
+
+ After requesting that the surface should be maximized, the compositor
+ will respond by emitting a configure event. Whether this configure
+ actually sets the window maximized is subject to compositor policies.
+ The client must then update its content, drawing in the configured
+ state. The client must also acknowledge the configure when committing
+ the new content (see ack_configure).
+
+ It is up to the compositor to decide how and where to maximize the
+ surface, for example which output and what region of the screen should
+ be used.
+
+ If the surface was already maximized, the compositor will still emit
+ a configure event with the "maximized" state.
+
+ If the surface is in a fullscreen state, this request has no direct
+ effect. It may alter the state the surface is returned to when
+ unmaximized unless overridden by the compositor.
+ </description>
+ </request>
+
+ <request name="unset_maximized">
+ <description summary="unmaximize the window">
+ Unmaximize the surface.
+
+ After requesting that the surface should be unmaximized, the compositor
+ will respond by emitting a configure event. Whether this actually
+ un-maximizes the window is subject to compositor policies.
+ If available and applicable, the compositor will include the window
+ geometry dimensions the window had prior to being maximized in the
+ configure event. The client must then update its content, drawing it in
+ the configured state. The client must also acknowledge the configure
+ when committing the new content (see ack_configure).
+
+ It is up to the compositor to position the surface after it was
+ unmaximized; usually the position the surface had before maximizing, if
+ applicable.
+
+ If the surface was already not maximized, the compositor will still
+ emit a configure event without the "maximized" state.
+
+ If the surface is in a fullscreen state, this request has no direct
+ effect. It may alter the state the surface is returned to when
+ unmaximized unless overridden by the compositor.
+ </description>
+ </request>
+
+ <request name="set_fullscreen">
+ <description summary="set the window as fullscreen on an output">
+ Make the surface fullscreen.
+
+ After requesting that the surface should be fullscreened, the
+ compositor will respond by emitting a configure event. Whether the
+ client is actually put into a fullscreen state is subject to compositor
+ policies. The client must also acknowledge the configure when
+ committing the new content (see ack_configure).
+
+ The output passed by the request indicates the client's preference as
+ to which display it should be set fullscreen on. If this value is NULL,
+ it's up to the compositor to choose which display will be used to map
+ this surface.
+
+ If the surface doesn't cover the whole output, the compositor will
+ position the surface in the center of the output and compensate with
+ with border fill covering the rest of the output. The content of the
+ border fill is undefined, but should be assumed to be in some way that
+ attempts to blend into the surrounding area (e.g. solid black).
+
+ If the fullscreened surface is not opaque, the compositor must make
+ sure that other screen content not part of the same surface tree (made
+ up of subsurfaces, popups or similarly coupled surfaces) are not
+ visible below the fullscreened surface.
+ </description>
+ <arg name="output" type="object" interface="wl_output" allow-null="true"/>
+ </request>
+
+ <request name="unset_fullscreen">
+ <description summary="unset the window as fullscreen">
+ Make the surface no longer fullscreen.
+
+ After requesting that the surface should be unfullscreened, the
+ compositor will respond by emitting a configure event.
+ Whether this actually removes the fullscreen state of the client is
+ subject to compositor policies.
+
+ Making a surface unfullscreen sets states for the surface based on the following:
+ * the state(s) it may have had before becoming fullscreen
+ * any state(s) decided by the compositor
+ * any state(s) requested by the client while the surface was fullscreen
+
+ The compositor may include the previous window geometry dimensions in
+ the configure event, if applicable.
+
+ The client must also acknowledge the configure when committing the new
+ content (see ack_configure).
+ </description>
+ </request>
+
+ <request name="set_minimized">
+ <description summary="set the window as minimized">
+ Request that the compositor minimize your surface. There is no
+ way to know if the surface is currently minimized, nor is there
+ any way to unset minimization on this surface.
+
+ If you are looking to throttle redrawing when minimized, please
+ instead use the wl_surface.frame event for this, as this will
+ also work with live previews on windows in Alt-Tab, Expose or
+ similar compositor features.
+ </description>
+ </request>
+
+ <event name="configure">
+ <description summary="suggest a surface change">
+ This configure event asks the client to resize its toplevel surface or
+ to change its state. The configured state should not be applied
+ immediately. See xdg_surface.configure for details.
+
+ The width and height arguments specify a hint to the window
+ about how its surface should be resized in window geometry
+ coordinates. See set_window_geometry.
+
+ If the width or height arguments are zero, it means the client
+ should decide its own window dimension. This may happen when the
+ compositor needs to configure the state of the surface but doesn't
+ have any information about any previous or expected dimension.
+
+ The states listed in the event specify how the width/height
+ arguments should be interpreted, and possibly how it should be
+ drawn.
+
+ Clients must send an ack_configure in response to this event. See
+ xdg_surface.configure and xdg_surface.ack_configure for details.
+ </description>
+ <arg name="width" type="int"/>
+ <arg name="height" type="int"/>
+ <arg name="states" type="array"/>
+ </event>
+
+ <event name="close">
+ <description summary="surface wants to be closed">
+ The close event is sent by the compositor when the user
+ wants the surface to be closed. This should be equivalent to
+ the user clicking the close button in client-side decorations,
+ if your application has any.
+
+ This is only a request that the user intends to close the
+ window. The client may choose to ignore this request, or show
+ a dialog to ask the user to save their data, etc.
+ </description>
+ </event>
+
+ <!-- Version 4 additions -->
+
+ <event name="configure_bounds" since="4">
+ <description summary="recommended window geometry bounds">
+ The configure_bounds event may be sent prior to a xdg_toplevel.configure
+ event to communicate the bounds a window geometry size is recommended
+ to constrain to.
+
+ The passed width and height are in surface coordinate space. If width
+ and height are 0, it means bounds is unknown and equivalent to as if no
+ configure_bounds event was ever sent for this surface.
+
+ The bounds can for example correspond to the size of a monitor excluding
+ any panels or other shell components, so that a surface isn't created in
+ a way that it cannot fit.
+
+ The bounds may change at any point, and in such a case, a new
+ xdg_toplevel.configure_bounds will be sent, followed by
+ xdg_toplevel.configure and xdg_surface.configure.
+ </description>
+ <arg name="width" type="int"/>
+ <arg name="height" type="int"/>
+ </event>
+
+ <!-- Version 5 additions -->
+
+ <enum name="wm_capabilities" since="5">
+ <entry name="window_menu" value="1" summary="show_window_menu is available"/>
+ <entry name="maximize" value="2" summary="set_maximized and unset_maximized are available"/>
+ <entry name="fullscreen" value="3" summary="set_fullscreen and unset_fullscreen are available"/>
+ <entry name="minimize" value="4" summary="set_minimized is available"/>
+ </enum>
+
+ <event name="wm_capabilities" since="5">
+ <description summary="compositor capabilities">
+ This event advertises the capabilities supported by the compositor. If
+ a capability isn't supported, clients should hide or disable the UI
+ elements that expose this functionality. For instance, if the
+ compositor doesn't advertise support for minimized toplevels, a button
+ triggering the set_minimized request should not be displayed.
+
+ The compositor will ignore requests it doesn't support. For instance,
+ a compositor which doesn't advertise support for minimized will ignore
+ set_minimized requests.
+
+ Compositors must send this event once before the first
+ xdg_surface.configure event. When the capabilities change, compositors
+ must send this event again and then send an xdg_surface.configure
+ event.
+
+ The configured state should not be applied immediately. See
+ xdg_surface.configure for details.
+
+ The capabilities are sent as an array of 32-bit unsigned integers in
+ native endianness.
+ </description>
+ <arg name="capabilities" type="array" summary="array of 32-bit capabilities"/>
+ </event>
+ </interface>
+
+ <interface name="xdg_popup" version="7">
+ <description summary="short-lived, popup surfaces for menus">
+ A popup surface is a short-lived, temporary surface. It can be used to
+ implement for example menus, popovers, tooltips and other similar user
+ interface concepts.
+
+ A popup can be made to take an explicit grab. See xdg_popup.grab for
+ details.
+
+ When the popup is dismissed, a popup_done event will be sent out, and at
+ the same time the surface will be unmapped. See the xdg_popup.popup_done
+ event for details.
+
+ Explicitly destroying the xdg_popup object will also dismiss the popup and
+ unmap the surface. Clients that want to dismiss the popup when another
+ surface of their own is clicked should dismiss the popup using the destroy
+ request.
+
+ A newly created xdg_popup will be stacked on top of all previously created
+ xdg_popup surfaces associated with the same xdg_toplevel.
+
+ The parent of an xdg_popup must be mapped (see the xdg_surface
+ description) before the xdg_popup itself.
+
+ The client must call wl_surface.commit on the corresponding wl_surface
+ for the xdg_popup state to take effect.
+ </description>
+
+ <enum name="error">
+ <entry name="invalid_grab" value="0"
+ summary="tried to grab after being mapped"/>
+ </enum>
+
+ <request name="destroy" type="destructor">
+ <description summary="remove xdg_popup interface">
+ This destroys the popup. Explicitly destroying the xdg_popup
+ object will also dismiss the popup, and unmap the surface.
+
+ If this xdg_popup is not the "topmost" popup, the
+ xdg_wm_base.not_the_topmost_popup protocol error will be sent.
+ </description>
+ </request>
+
+ <request name="grab">
+ <description summary="make the popup take an explicit grab">
+ This request makes the created popup take an explicit grab. An explicit
+ grab will be dismissed when the user dismisses the popup, or when the
+ client destroys the xdg_popup. This can be done by the user clicking
+ outside the surface, using the keyboard, or even locking the screen
+ through closing the lid or a timeout.
+
+ If the compositor denies the grab, the popup will be immediately
+ dismissed.
+
+ This request must be used in response to some sort of user action like a
+ button press, key press, or touch down event. The serial number of the
+ event should be passed as 'serial'.
+
+ The parent of a grabbing popup must either be an xdg_toplevel surface or
+ another xdg_popup with an explicit grab. If the parent is another
+ xdg_popup it means that the popups are nested, with this popup now being
+ the topmost popup.
+
+ Nested popups must be destroyed in the reverse order they were created
+ in, e.g. the only popup you are allowed to destroy at all times is the
+ topmost one.
+
+ When compositors choose to dismiss a popup, they may dismiss every
+ nested grabbing popup as well. When a compositor dismisses popups, it
+ will follow the same dismissing order as required from the client.
+
+ If the topmost grabbing popup is destroyed, the grab will be returned to
+ the parent of the popup, if that parent previously had an explicit grab.
+
+ If the parent is a grabbing popup which has already been dismissed, this
+ popup will be immediately dismissed. If the parent is a popup that did
+ not take an explicit grab, an error will be raised.
+
+ During a popup grab, the client owning the grab will receive pointer
+ and touch events for all their surfaces as normal (similar to an
+ "owner-events" grab in X11 parlance), while the top most grabbing popup
+ will always have keyboard focus.
+ </description>
+ <arg name="seat" type="object" interface="wl_seat"
+ summary="the wl_seat of the user event"/>
+ <arg name="serial" type="uint" summary="the serial of the user event"/>
+ </request>
+
+ <event name="configure">
+ <description summary="configure the popup surface">
+ This event asks the popup surface to configure itself given the
+ configuration. The configured state should not be applied immediately.
+ See xdg_surface.configure for details.
+
+ The x and y arguments represent the position the popup was placed at
+ given the xdg_positioner rule, relative to the upper left corner of the
+ window geometry of the parent surface.
+
+ For version 2 or older, the configure event for an xdg_popup is only
+ ever sent once for the initial configuration. Starting with version 3,
+ it may be sent again if the popup is setup with an xdg_positioner with
+ set_reactive requested, or in response to xdg_popup.reposition requests.
+ </description>
+ <arg name="x" type="int"
+ summary="x position relative to parent surface window geometry"/>
+ <arg name="y" type="int"
+ summary="y position relative to parent surface window geometry"/>
+ <arg name="width" type="int" summary="window geometry width"/>
+ <arg name="height" type="int" summary="window geometry height"/>
+ </event>
+
+ <event name="popup_done">
+ <description summary="popup interaction is done">
+ The popup_done event is sent out when a popup is dismissed by the
+ compositor. The client should destroy the xdg_popup object at this
+ point.
+ </description>
+ </event>
+
+ <!-- Version 3 additions -->
+
+ <request name="reposition" since="3">
+ <description summary="recalculate the popup's location">
+ Reposition an already-mapped popup. The popup will be placed given the
+ details in the passed xdg_positioner object, and a
+ xdg_popup.repositioned followed by xdg_popup.configure and
+ xdg_surface.configure will be emitted in response. Any parameters set
+ by the previous positioner will be discarded.
+
+ The passed token will be sent in the corresponding
+ xdg_popup.repositioned event. The new popup position will not take
+ effect until the corresponding configure event is acknowledged by the
+ client. See xdg_popup.repositioned for details. The token itself is
+ opaque, and has no other special meaning.
+
+ If multiple reposition requests are sent, the compositor may skip all
+ but the last one.
+
+ If the popup is repositioned in response to a configure event for its
+ parent, the client should send an xdg_positioner.set_parent_configure
+ and possibly an xdg_positioner.set_parent_size request to allow the
+ compositor to properly constrain the popup.
+
+ If the popup is repositioned together with a parent that is being
+ resized, but not in response to a configure event, the client should
+ send an xdg_positioner.set_parent_size request.
+ </description>
+ <arg name="positioner" type="object" interface="xdg_positioner"/>
+ <arg name="token" type="uint" summary="reposition request token"/>
+ </request>
+
+ <event name="repositioned" since="3">
+ <description summary="signal the completion of a repositioned request">
+ The repositioned event is sent as part of a popup configuration
+ sequence, together with xdg_popup.configure and lastly
+ xdg_surface.configure to notify the completion of a reposition request.
+
+ The repositioned event is to notify about the completion of a
+ xdg_popup.reposition request. The token argument is the token passed
+ in the xdg_popup.reposition request.
+
+ Immediately after this event is emitted, xdg_popup.configure and
+ xdg_surface.configure will be sent with the updated size and position,
+ as well as a new configure serial.
+
+ The client should optionally update the content of the popup, but must
+ acknowledge the new popup configuration for the new position to take
+ effect. See xdg_surface.ack_configure for details.
+ </description>
+ <arg name="token" type="uint" summary="reposition request token"/>
+ </event>
+
+ </interface>
+</protocol>
diff --git a/src/graphics/backend/egl.c b/src/graphics/backend/egl.c
new file mode 100644
index 0000000..cabbf04
--- /dev/null
+++ b/src/graphics/backend/egl.c
@@ -0,0 +1,248 @@
+#include "../../../include/mgl/graphics/backend/egl.h"
+#include "../../../include/mgl/mgl.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <X11/Xutil.h>
+#include <X11/extensions/Xrender.h>
+
+static void mgl_graphics_egl_deinit(mgl_graphics *self);
+
+typedef struct {
+ EGLDisplay display;
+ EGLSurface surface;
+ EGLContext context;
+ EGLConfig *configs;
+ EGLConfig ecfg;
+ XVisualInfo *visual_info;
+} mgl_graphics_egl;
+
+static int32_t mgl_graphics_egl_get_config_attrib(mgl_graphics_egl *self, EGLConfig ecfg, int32_t attribute_name) {
+ mgl_context *context = mgl_get_context();
+ int32_t value = 0;
+ context->gl.eglGetConfigAttrib(self->display, ecfg, attribute_name, &value);
+ return value;
+}
+
+static bool xvisual_match_alpha(Display *dpy, XVisualInfo *visual_info, bool alpha) {
+ XRenderPictFormat *pict_format = XRenderFindVisualFormat(dpy, visual_info->visual);
+ if(!pict_format)
+ return false;
+ return (alpha && pict_format->direct.alphaMask > 0) || (!alpha && pict_format->direct.alphaMask == 0);
+}
+
+static bool mgl_graphics_egl_choose_config(mgl_graphics_egl *self, mgl_context *context, bool alpha) {
+ self->configs = NULL;
+ self->ecfg = NULL;
+ self->visual_info = NULL;
+
+ int32_t num_configs = 0;
+ context->gl.eglGetConfigs(self->display, NULL, 0, &num_configs);
+ if(num_configs == 0) {
+ fprintf(stderr, "mgl error: mgl_graphics_egl_choose_config: no configs found\n");
+ return false;
+ }
+
+ self->configs = (EGLConfig*)calloc(num_configs, sizeof(EGLConfig));
+ if(!self->configs) {
+ fprintf(stderr, "mgl error: mgl_graphics_egl_choose_config: failed to allocate %d configs\n", (int)num_configs);
+ return false;
+ }
+
+ context->gl.eglGetConfigs(self->display, self->configs, num_configs, &num_configs);
+ for(int i = 0; i < num_configs; i++) {
+ self->ecfg = self->configs[i];
+
+ //if(mgl_graphics_egl_get_config_attrib(self, self->ecfg, EGL_RENDERABLE_TYPE) != EGL_OPENGL_ES2_BIT)
+ // continue;
+
+ if(mgl_graphics_egl_get_config_attrib(self, self->ecfg, EGL_COLOR_BUFFER_TYPE) != EGL_RGB_BUFFER)
+ continue;
+
+ if(!(mgl_graphics_egl_get_config_attrib(self, self->ecfg, EGL_SURFACE_TYPE) & EGL_WINDOW_BIT))
+ continue;
+
+ if(mgl_graphics_egl_get_config_attrib(self, self->ecfg, EGL_RED_SIZE) != 8)
+ continue;
+
+ if(mgl_graphics_egl_get_config_attrib(self, self->ecfg, EGL_GREEN_SIZE) != 8)
+ continue;
+
+ if(mgl_graphics_egl_get_config_attrib(self, self->ecfg, EGL_BLUE_SIZE) != 8)
+ continue;
+
+ if(mgl_graphics_egl_get_config_attrib(self, self->ecfg, EGL_ALPHA_SIZE) != (alpha ? 8 : 0))
+ continue;
+
+ if(context->window_system == MGL_WINDOW_SYSTEM_WAYLAND)
+ break;
+
+ XVisualInfo vi = {0};
+ vi.visualid = mgl_graphics_egl_get_config_attrib(self, self->ecfg, EGL_NATIVE_VISUAL_ID);
+ if(!vi.visualid)
+ continue;
+
+ int vis_count = 0;
+ self->visual_info = XGetVisualInfo(context->connection, VisualIDMask, &vi, &vis_count);
+ if(!self->visual_info)
+ continue;
+
+ if(xvisual_match_alpha(context->connection, self->visual_info, alpha)) {
+ break;
+ } else {
+ XFree(self->visual_info);
+ self->visual_info = NULL;
+ self->ecfg = NULL;
+ }
+ }
+
+ if(!self->ecfg) {
+ fprintf(stderr, "mgl error: mgl_graphics_egl_choose_config: no appropriate glx config found\n");
+ return false;
+ }
+
+ if(context->window_system == MGL_WINDOW_SYSTEM_X11 && !self->visual_info) {
+ fprintf(stderr, "mgl error: mgl_graphics_egl_choose_config: no appropriate visual found\n");
+ return false;
+ }
+
+ return true;
+}
+
+static bool mgl_graphics_egl_make_context_current(mgl_graphics *self, mgl_window_handle window) {
+ (void)window;
+ mgl_graphics_egl *impl = self->impl;
+ mgl_context *context = mgl_get_context();
+
+ if(!impl->surface) {
+ impl->surface = context->gl.eglCreateWindowSurface(impl->display, impl->ecfg, (EGLNativeWindowType)window, NULL);
+ if(!impl->surface) {
+ fprintf(stderr, "mgl error: mgl_graphics_egl_make_context_current: failed to create window surface\n");
+ return false;
+ }
+ }
+
+ return context->gl.eglMakeCurrent(impl->display, impl->surface, impl->surface, impl->context) != 0;
+}
+
+static void mgl_graphics_egl_swap_buffers(mgl_graphics *self, mgl_window_handle window) {
+ (void)window;
+ mgl_graphics_egl *impl = self->impl;
+ mgl_context *context = mgl_get_context();
+ context->gl.eglSwapBuffers(impl->display, impl->surface);
+}
+
+static bool mgl_graphics_egl_set_swap_interval(mgl_graphics *self, mgl_window_handle window, bool enabled) {
+ (void)window;
+ mgl_graphics_egl *impl = self->impl;
+ mgl_context *context = mgl_get_context();
+ return context->gl.eglSwapInterval(impl->display, (int32_t)enabled) == 1;
+}
+
+static void* mgl_graphics_egl_get_xvisual_info(mgl_graphics *self) {
+ mgl_graphics_egl *impl = self->impl;
+ return impl->visual_info;
+}
+
+static void* mgl_graphics_egl_get_display(mgl_graphics *self) {
+ mgl_graphics_egl *impl = self->impl;
+ return impl->display;
+}
+
+static void* mgl_graphics_egl_get_context(mgl_graphics *self) {
+ mgl_graphics_egl *impl = self->impl;
+ return impl->context;
+}
+
+bool mgl_graphics_egl_init(mgl_graphics *self) {
+ mgl_graphics_egl *impl = calloc(1, sizeof(mgl_graphics_egl));
+ if(!impl)
+ return false;
+
+ self->deinit = mgl_graphics_egl_deinit;
+ self->make_context_current = mgl_graphics_egl_make_context_current;
+ self->swap_buffers = mgl_graphics_egl_swap_buffers;
+ self->set_swap_interval = mgl_graphics_egl_set_swap_interval;
+ self->get_xvisual_info = mgl_graphics_egl_get_xvisual_info;
+ self->get_display = mgl_graphics_egl_get_display;
+ self->get_context = mgl_graphics_egl_get_context;
+ self->impl = impl;
+
+ const int32_t ctxattr[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 2,
+ EGL_NONE, EGL_NONE
+ };
+
+ mgl_context *context = mgl_get_context();
+ context->gl.eglBindAPI(EGL_OPENGL_API);
+
+ impl->display = context->gl.eglGetDisplay((EGLNativeDisplayType)context->connection);
+ if(!impl->display) {
+ fprintf(stderr, "mgl error: mgl_graphics_egl_init: eglGetDisplay failed\n");
+ mgl_graphics_egl_deinit(self);
+ return false;
+ }
+
+ if(!context->gl.eglInitialize(impl->display, NULL, NULL)) {
+ fprintf(stderr, "mgl error: mgl_graphics_egl_init: eglInitialize failed\n");
+ mgl_graphics_egl_deinit(self);
+ return false;
+ }
+
+ if(!mgl_graphics_egl_choose_config(impl, context, self->alpha)) {
+ mgl_graphics_egl_deinit(self);
+ return false;
+ }
+
+ impl->context = context->gl.eglCreateContext(impl->display, impl->ecfg, NULL, ctxattr);
+ if(!impl->context) {
+ fprintf(stderr, "mgl error: mgl_graphics_egl_init: failed to create egl context\n");
+ mgl_graphics_egl_deinit(self);
+ return false;
+ }
+
+ return true;
+}
+
+void mgl_graphics_egl_deinit(mgl_graphics *self) {
+ mgl_graphics_egl *impl = self->impl;
+ if(!impl)
+ return;
+
+ mgl_context *context = mgl_get_context();
+
+ if(impl->visual_info) {
+ XFree(impl->visual_info);
+ impl->visual_info = NULL;
+ }
+
+ if(impl->configs) {
+ free(impl->configs);
+ impl->configs = NULL;
+ }
+
+ if(impl->context) {
+ context->gl.eglMakeCurrent(impl->display, NULL, NULL, NULL);
+ context->gl.eglDestroyContext(impl->display, impl->context);
+ impl->context = NULL;
+ }
+
+ if(impl->surface) {
+ context->gl.eglDestroySurface(impl->display, impl->surface);
+ impl->surface = NULL;
+ }
+
+ if(impl->display) {
+ context->gl.eglTerminate(impl->display);
+ impl->display = NULL;
+ }
+
+ if(impl->visual_info) {
+ XFree(impl->visual_info);
+ impl->visual_info = NULL;
+ }
+
+ free(self->impl);
+ self->impl = NULL;
+}
diff --git a/src/graphics/backend/glx.c b/src/graphics/backend/glx.c
new file mode 100644
index 0000000..70836e3
--- /dev/null
+++ b/src/graphics/backend/glx.c
@@ -0,0 +1,173 @@
+#include "../../../include/mgl/graphics/backend/glx.h"
+#include "../../../include/mgl/mgl.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <X11/Xutil.h>
+#include <X11/extensions/Xrender.h>
+
+static void mgl_graphics_glx_deinit(mgl_graphics *self);
+
+typedef struct {
+ GLXContext glx_context;
+ GLXFBConfig *fbconfigs;
+ GLXFBConfig fbconfig;
+ XVisualInfo *visual_info;
+} mgl_graphics_glx;
+
+static bool xvisual_match_alpha(Display *dpy, XVisualInfo *visual_info, bool alpha) {
+ XRenderPictFormat *pict_format = XRenderFindVisualFormat(dpy, visual_info->visual);
+ if(!pict_format)
+ return false;
+ return (alpha && pict_format->direct.alphaMask > 0) || (!alpha && pict_format->direct.alphaMask == 0);
+}
+
+static bool mgl_graphics_glx_choose_config(mgl_graphics_glx *self, mgl_context *context, bool alpha) {
+ const int attr[] = {
+ GLX_RENDER_TYPE, GLX_RGBA_BIT,
+ GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
+ GLX_DOUBLEBUFFER, True,
+ GLX_RED_SIZE, 8,
+ GLX_GREEN_SIZE, 8,
+ GLX_BLUE_SIZE, 8,
+ GLX_ALPHA_SIZE, alpha ? 8 : 0,
+ // TODO:
+ //GLX_DEPTH_SIZE, 0,
+ None
+ };
+
+ self->fbconfigs = NULL;
+ self->visual_info = NULL;
+ self->fbconfig = NULL;
+
+ int numfbconfigs = 0;
+ self->fbconfigs = context->gl.glXChooseFBConfig(context->connection, DefaultScreen(context->connection), attr, &numfbconfigs);
+ for(int i = 0; i < numfbconfigs; i++) {
+ self->visual_info = (XVisualInfo*)context->gl.glXGetVisualFromFBConfig(context->connection, self->fbconfigs[i]);
+ if(!self->visual_info)
+ continue;
+
+ if(xvisual_match_alpha(context->connection, self->visual_info, alpha)) {
+ self->fbconfig = self->fbconfigs[i];
+ break;
+ } else {
+ XFree(self->visual_info);
+ self->visual_info = NULL;
+ self->fbconfig = NULL;
+ }
+ }
+
+ if(!self->fbconfig) {
+ fprintf(stderr, "mgl error: mgl_graphics_glx_choose_config: no appropriate glx config found\n");
+ return false;
+ }
+
+ if(!self->visual_info) {
+ fprintf(stderr, "mgl error: mgl_graphics_glx_choose_config: no appropriate visual found\n");
+ return false;
+ }
+
+ return true;
+}
+
+static bool mgl_graphics_glx_make_context_current(mgl_graphics *self, mgl_window_handle window) {
+ mgl_graphics_glx *impl = self->impl;
+ mgl_context *context = mgl_get_context();
+ return context->gl.glXMakeContextCurrent(context->connection, (GLXDrawable)window, (GLXDrawable)window, impl->glx_context) != 0;
+}
+
+static void mgl_graphics_glx_swap_buffers(mgl_graphics *self, mgl_window_handle window) {
+ (void)self;
+ mgl_context *context = mgl_get_context();
+ context->gl.glXSwapBuffers(context->connection, (GLXDrawable)window);
+}
+
+/* TODO: Use gl OML present for other platforms than nvidia? nvidia doesn't support present yet */
+/* TODO: check for glx swap control extension string (GLX_EXT_swap_control, etc) */
+static bool mgl_graphics_glx_set_swap_interval(mgl_graphics *self, mgl_window_handle window, bool enabled) {
+ (void)self;
+ mgl_context *context = mgl_get_context();
+
+ int result = 0;
+ if(context->gl.glXSwapIntervalEXT) {
+ context->gl.glXSwapIntervalEXT(context->connection, (GLXDrawable)window, enabled);
+ } else if(context->gl.glXSwapIntervalMESA) {
+ result = context->gl.glXSwapIntervalMESA(enabled);
+ } else if(context->gl.glXSwapIntervalSGI) {
+ result = context->gl.glXSwapIntervalSGI(enabled);
+ } else {
+ static int warned = 0;
+ if (!warned) {
+ warned = 1;
+ fprintf(stderr, "mgl warning: setting vertical sync not supported\n");
+ }
+ }
+
+ if(result != 0)
+ fprintf(stderr, "mgl warning: setting vertical sync failed\n");
+
+ return result == 0;
+}
+
+static void* mgl_graphics_glx_get_xvisual_info(mgl_graphics *self) {
+ mgl_graphics_glx *impl = self->impl;
+ return impl->visual_info;
+}
+
+bool mgl_graphics_glx_init(mgl_graphics *self) {
+ mgl_graphics_glx *impl = calloc(1, sizeof(mgl_graphics_glx));
+ if(!impl)
+ return false;
+
+ self->deinit = mgl_graphics_glx_deinit;
+ self->make_context_current = mgl_graphics_glx_make_context_current;
+ self->swap_buffers = mgl_graphics_glx_swap_buffers;
+ self->set_swap_interval = mgl_graphics_glx_set_swap_interval;
+ self->get_xvisual_info = mgl_graphics_glx_get_xvisual_info;
+ self->impl = impl;
+
+ mgl_context *context = mgl_get_context();
+ if(!mgl_graphics_glx_choose_config(impl, context, self->alpha)) {
+ mgl_graphics_glx_deinit(self);
+ return false;
+ }
+
+ impl->glx_context = context->gl.glXCreateNewContext(context->connection, impl->fbconfig, GLX_RGBA_TYPE, 0, True);
+ if(!impl->glx_context) {
+ fprintf(stderr, "mgl error: mgl_graphics_glx_init: glXCreateContext failed\n");
+ mgl_graphics_glx_deinit(self);
+ return false;
+ }
+
+ return true;
+}
+
+void mgl_graphics_glx_deinit(mgl_graphics *self) {
+ mgl_graphics_glx *impl = self->impl;
+ if(!impl)
+ return;
+
+ mgl_context *context = mgl_get_context();
+
+ if(impl->glx_context) {
+ context->gl.glXMakeContextCurrent(context->connection, None, None, NULL);
+ context->gl.glXDestroyContext(context->connection, impl->glx_context);
+ impl->glx_context = NULL;
+ }
+
+ if(impl->visual_info) {
+ XFree(impl->visual_info);
+ impl->visual_info = NULL;
+ }
+
+ if(impl->fbconfigs) {
+ XFree(impl->fbconfigs);
+ impl->fbconfigs = NULL;
+ }
+
+ impl->fbconfig = NULL;
+
+ free(self->impl);
+ self->impl = NULL;
+}
diff --git a/src/graphics/backend/graphics.c b/src/graphics/backend/graphics.c
new file mode 100644
index 0000000..201b9e3
--- /dev/null
+++ b/src/graphics/backend/graphics.c
@@ -0,0 +1,68 @@
+#include "../../../include/mgl/graphics/backend/graphics.h"
+#include "../../../include/mgl/graphics/backend/glx.h"
+#include "../../../include/mgl/graphics/backend/egl.h"
+#include "../../../include/mgl/mgl.h"
+
+#include <string.h>
+
+bool mgl_graphics_init(mgl_graphics *self, const mgl_graphics_create_params *params) {
+ memset(self, 0, sizeof(*self));
+ self->graphics_api = params ? params->graphics_api : MGL_GRAPHICS_API_EGL;
+ self->alpha = params && params->alpha;
+
+ switch(self->graphics_api) {
+ case MGL_GRAPHICS_API_GLX:
+ return mgl_graphics_glx_init(self);
+ case MGL_GRAPHICS_API_EGL:
+ return mgl_graphics_egl_init(self);
+ }
+ return false;
+}
+
+void mgl_graphics_deinit(mgl_graphics *self) {
+ if(self->deinit)
+ self->deinit(self);
+}
+
+bool mgl_graphics_make_context_current(mgl_graphics *self, mgl_window_handle window) {
+ const bool result = self->make_context_current(self, window);
+ if(result) {
+ mgl_context *context = mgl_get_context();
+ context->gl.glEnable(GL_TEXTURE_2D);
+ context->gl.glEnable(GL_BLEND);
+ context->gl.glEnable(GL_SCISSOR_TEST);
+ context->gl.glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+ context->gl.glEnableClientState(GL_VERTEX_ARRAY);
+ context->gl.glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+ context->gl.glEnableClientState(GL_COLOR_ARRAY);
+ context->gl.glPixelStorei(GL_PACK_ALIGNMENT, 1);
+ context->gl.glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ }
+ return result;
+}
+
+void mgl_graphics_swap_buffers(mgl_graphics *self, mgl_window_handle window) {
+ self->swap_buffers(self, window);
+}
+
+bool mgl_graphics_set_swap_interval(mgl_graphics *self, mgl_window_handle window, bool enabled) {
+ return self->set_swap_interval(self, window, enabled);
+}
+
+void* mgl_graphics_get_xvisual_info(mgl_graphics *self) {
+ return self->get_xvisual_info(self);
+}
+
+void* mgl_graphics_get_display(mgl_graphics *self) {
+ if(self->get_display)
+ return self->get_display(self);
+ else
+ return NULL;
+}
+
+void* mgl_graphics_get_context(mgl_graphics *self) {
+ if(self->get_context)
+ return self->get_context(self);
+ else
+ return NULL;
+}
diff --git a/src/graphics/font.c b/src/graphics/font.c
index 12235f6..671d8a1 100644
--- a/src/graphics/font.c
+++ b/src/graphics/font.c
@@ -210,7 +210,7 @@ int mgl_font_get_glyph(mgl_font *self, uint32_t codepoint, mgl_font_glyph *glyph
/* TODO: Use stbtt_MakeGlyphBitmapSubpixelPrefilter instead for better text quality */
const size_t pixels_width = (width + GLYPH_PADDING * 2);
const size_t pixels_height = (height + GLYPH_PADDING * 2);
- const size_t pixels_size = pixels_width * pixels_height;
+ const size_t pixels_size = pixels_width * pixels_height * 2; // *2 required for opengl glTexSubImage2D
unsigned char *pixels = calloc(pixels_size, 1);
if(pixels) {
const int top_padding = GLYPH_PADDING;
diff --git a/src/graphics/texture.c b/src/graphics/texture.c
index 9a13314..d33bf58 100644
--- a/src/graphics/texture.c
+++ b/src/graphics/texture.c
@@ -70,7 +70,7 @@ int mgl_texture_init(mgl_texture *self) {
self->max_width = 0;
self->max_height = 0;
self->pixel_coordinates = false;
- self->mipmap = false;
+ self->scale_type = MGL_TEXTURE_SCALE_LINEAR;
self->owned = true;
mgl_context *context = mgl_get_context();
@@ -96,7 +96,7 @@ int mgl_texture_init_reference_existing_gl_texture(mgl_texture *self, unsigned i
self->max_width = 0;
self->max_height = 0;
self->pixel_coordinates = reference_options && reference_options->pixel_coordinates;
- self->mipmap = false;
+ self->scale_type = reference_options ? reference_options->scale_type : MGL_TEXTURE_SCALE_LINEAR;
self->owned = false;
gl_get_texture_size(self->id, &self->width, &self->height);
@@ -137,7 +137,7 @@ int mgl_texture_load_from_memory(mgl_texture *self, const unsigned char *data, i
self->height = height;
self->format = mgl_image_format_to_mgl_texture_format(format);
self->pixel_coordinates = load_options && load_options->pixel_coordinates;
- self->mipmap = load_options && load_options->mipmap && context->gl.glGenerateMipmap; /* TODO: Check if glGenerateMipmap is actually available */
+ self->scale_type = load_options ? load_options->scale_type : MGL_TEXTURE_SCALE_LINEAR; /* TODO: Check if glGenerateMipmap is actually available */
int opengl_texture_format = mgl_texture_format_to_opengl_format(self->format);
if(load_options && load_options->compressed)
@@ -147,13 +147,28 @@ int mgl_texture_load_from_memory(mgl_texture *self, const unsigned char *data, i
context->gl.glTexImage2D(GL_TEXTURE_2D, 0, opengl_texture_format, self->width, self->height, 0, mgl_texture_format_to_source_opengl_format(self->format), GL_UNSIGNED_BYTE, data);
context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- if(self->mipmap) {
- context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
- context->gl.glGenerateMipmap(GL_TEXTURE_2D);
- } else {
- context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ switch(self->scale_type) {
+ default:
+ case MGL_TEXTURE_SCALE_LINEAR: {
+ context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ break;
+ }
+ case MGL_TEXTURE_SCALE_NEAREST: {
+ context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ break;
+ }
+ case MGL_TEXTURE_SCALE_LINEAR_MIPMAP: {
+ context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ if(context->gl.glGenerateMipmap) {
+ context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ context->gl.glGenerateMipmap(GL_TEXTURE_2D);
+ } else {
+ context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ }
+ break;
+ }
}
context->gl.glBindTexture(GL_TEXTURE_2D, 0);
@@ -168,9 +183,8 @@ int mgl_texture_update(mgl_texture *self, const unsigned char *data, int offset_
context->gl.glBindTexture(GL_TEXTURE_2D, self->id);
const mgl_texture_format texture_format = mgl_image_format_to_mgl_texture_format(format);
context->gl.glTexSubImage2D(GL_TEXTURE_2D, 0, offset_x, offset_y, width, height, mgl_texture_format_to_source_opengl_format(texture_format), GL_UNSIGNED_BYTE, data);
- if(self->mipmap) {
+ if(self->scale_type == MGL_TEXTURE_SCALE_LINEAR_MIPMAP && context->gl.glGenerateMipmap)
context->gl.glGenerateMipmap(GL_TEXTURE_2D);
- }
context->gl.glBindTexture(GL_TEXTURE_2D, 0);
return 0;
}
@@ -193,9 +207,8 @@ int mgl_texture_resize(mgl_texture *self, int new_width, int new_height, const m
mgl_context *context = mgl_get_context();
context->gl.glBindTexture(GL_TEXTURE_2D, self->id);
context->gl.glTexImage2D(GL_TEXTURE_2D, 0, opengl_texture_format, self->width, self->height, 0, mgl_texture_format_to_source_opengl_format(self->format), GL_UNSIGNED_BYTE, NULL);
- if(self->mipmap) {
+ if(self->scale_type == MGL_TEXTURE_SCALE_LINEAR_MIPMAP && context->gl.glGenerateMipmap)
context->gl.glGenerateMipmap(GL_TEXTURE_2D);
- }
context->gl.glBindTexture(GL_TEXTURE_2D, 0);
return 0;
}
diff --git a/src/mgl.c b/src/mgl.c
index 3b5adf2..fdf5c5b 100644
--- a/src/mgl.c
+++ b/src/mgl.c
@@ -1,18 +1,21 @@
#include "../include/mgl/mgl.h"
-#include <X11/Xutil.h>
-#include <X11/XKBlib.h>
-#include <X11/extensions/Xrender.h>
-#include <X11/extensions/Xrandr.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
+#include <assert.h>
+
+#include <X11/Xutil.h>
+#include <X11/XKBlib.h>
+#include <X11/extensions/Xrender.h>
+#include <X11/extensions/Xrandr.h>
+#include <wayland-client.h>
static mgl_context context;
static int init_count = 0;
static XErrorHandler prev_xerror = NULL;
static XIOErrorHandler prev_xioerror = NULL;
-static bool connected_to_x_server = false;
+static bool connected_to_display_server = false;
static int mgl_x_error_handler(Display *display, XErrorEvent *ee) {
(void)display;
@@ -22,7 +25,8 @@ static int mgl_x_error_handler(Display *display, XErrorEvent *ee) {
static int mgl_x_io_error_handler(Display *display) {
(void)display;
- connected_to_x_server = false;
+ /* TODO: Do something equivalent for wayland */
+ connected_to_display_server = false;
return 0;
}
@@ -54,45 +58,112 @@ static bool xrandr_is_supported(Display *display, int *event_base, int *error_ba
return major_version > 1 || (major_version == 1 && minor_version >= 2);
}
-int mgl_init(void) {
- ++init_count;
- if(init_count == 1) {
- setenv("__GL_MaxFramesAllowed", "1", true);
- memset(&context, 0, sizeof(context));
+static bool is_xwayland(Display *dpy) {
+ int opcode, event, error;
+ return XQueryExtension(dpy, "XWAYLAND", &opcode, &event, &error);
+}
+static int mgl_init_x11(void) {
+ if(!context.connection) {
context.connection = XOpenDisplay(NULL);
if(!context.connection) {
- fprintf(stderr, "mgl error: XOpenDisplay failed\n");
+ fprintf(stderr, "mgl error: mgl_init_x11: failed to connect to the X11 server\n");
mgl_deinit();
return -1;
}
- connected_to_x_server = true;
- /* If we dont call we will never get a MappingNotify until a key has been pressed */
- XKeysymToKeycode(context.connection, XK_F1);
+ }
+ connected_to_display_server = true;
+ /* If we dont call we will never get a MappingNotify until a key has been pressed */
+ XKeysymToKeycode(context.connection, XK_F1);
- prev_xerror = XSetErrorHandler(mgl_x_error_handler);
- prev_xioerror = XSetIOErrorHandler(mgl_x_io_error_handler);
+ prev_xerror = XSetErrorHandler(mgl_x_error_handler);
+ prev_xioerror = XSetIOErrorHandler(mgl_x_io_error_handler);
- if(!xrender_is_supported(context.connection, &context.render_event_base, &context.render_error_base)) {
- fprintf(stderr, "mgl error: x11 render extension is not supported by your X server\n");
- mgl_deinit();
- return -1;
- }
+ context.display_server_is_wayland = is_xwayland(context.connection);
- if(!xrandr_is_supported(context.connection, &context.randr_event_base, &context.randr_error_base)) {
- fprintf(stderr, "mgl error: x11 randr extension is not supported by your X server\n");
- mgl_deinit();
- return -1;
+ if(!xrender_is_supported(context.connection, &context.render_event_base, &context.render_error_base)) {
+ fprintf(stderr, "mgl error: mgl_init_x11: x11 render extension is not supported by your X server\n");
+ mgl_deinit();
+ return -1;
+ }
+
+ if(!xrandr_is_supported(context.connection, &context.randr_event_base, &context.randr_error_base)) {
+ fprintf(stderr, "mgl error: mgl_init_x11: x11 randr extension is not supported by your X server\n");
+ mgl_deinit();
+ return -1;
+ }
+
+ XRRSelectInput(context.connection, DefaultRootWindow(context.connection), RRScreenChangeNotifyMask | RRCrtcChangeNotifyMask | RROutputChangeNotifyMask);
+
+ XInitThreads();
+ XkbSetDetectableAutoRepeat(context.connection, True, NULL);
+
+ context.wm_delete_window_atom = XInternAtom(context.connection, "WM_DELETE_WINDOW", False);
+ context.net_wm_ping_atom = XInternAtom(context.connection, "_NET_WM_PING", False);
+ context.net_wm_pid_atom = XInternAtom(context.connection, "_NET_WM_PID", False);
+ return 0;
+}
+
+static int mgl_init_wayland(void) {
+ context.connection = wl_display_connect(NULL);
+ if(!context.connection) {
+ fprintf(stderr, "mgl error: mgl_init_wayland: failed to connect to the Wayland server\n");
+ mgl_deinit();
+ return -1;
+ }
+ connected_to_display_server = true;
+ return 0;
+}
+
+static int mgl_init_native(void) {
+ context.connection = XOpenDisplay(NULL);
+ if(context.connection) {
+ context.display_server_is_wayland = is_xwayland(context.connection);
+ if(context.display_server_is_wayland) {
+ XCloseDisplay(context.connection);
+ context.connection = NULL;
}
+ } else {
+ context.display_server_is_wayland = true;
+ }
- XRRSelectInput(context.connection, DefaultRootWindow(context.connection), RRScreenChangeNotifyMask | RRCrtcChangeNotifyMask | RROutputChangeNotifyMask);
+ if(context.display_server_is_wayland) {
+ context.window_system = MGL_WINDOW_SYSTEM_WAYLAND;
+ if(mgl_init_wayland() != 0)
+ return -1;
+ } else {
+ context.window_system = MGL_WINDOW_SYSTEM_X11;
+ if(mgl_init_x11() != 0)
+ return -1;
+ }
- XInitThreads();
- XkbSetDetectableAutoRepeat(context.connection, True, NULL);
+ return 0;
+}
- context.wm_delete_window_atom = XInternAtom(context.connection, "WM_DELETE_WINDOW", False);
- context.net_wm_ping_atom = XInternAtom(context.connection, "_NET_WM_PING", False);
- context.net_wm_pid_atom = XInternAtom(context.connection, "_NET_WM_PID", False);
+int mgl_init(mgl_window_system window_system) {
+ ++init_count;
+ if(init_count == 1) {
+ setenv("__GL_MaxFramesAllowed", "1", true);
+ memset(&context, 0, sizeof(context));
+ context.window_system = window_system;
+
+ switch(window_system) {
+ case MGL_WINDOW_SYSTEM_NATIVE: {
+ if(mgl_init_native() != 0)
+ return -1;
+ break;
+ }
+ case MGL_WINDOW_SYSTEM_X11: {
+ if(mgl_init_x11() != 0)
+ return -1;
+ break;
+ }
+ case MGL_WINDOW_SYSTEM_WAYLAND: {
+ if(mgl_init_wayland() != 0)
+ return -1;
+ break;
+ }
+ }
if(mgl_gl_load(&context.gl) != 0) {
mgl_deinit();
@@ -102,30 +173,49 @@ int mgl_init(void) {
return 0;
}
-void mgl_deinit(void) {
- if(init_count == 1) {
- if(context.connection) {
- XCloseDisplay(context.connection);
- context.connection = NULL;
- connected_to_x_server = false;
+static void mgl_deinit_x11(void) {
+ if(context.connection) {
+ XCloseDisplay(context.connection);
+ context.connection = NULL;
+ connected_to_display_server = false;
+ }
- /*
- GLX needs to be unloaded after closing the display on nvidia because
- nvidia registers cleanup callbacks on exit, that uses the x11 display.
- */
- mgl_gl_unload(&context.gl);
- }
+ if(prev_xioerror) {
+ XSetIOErrorHandler(prev_xioerror);
+ prev_xioerror = NULL;
+ }
- if(prev_xioerror) {
- XSetIOErrorHandler(prev_xioerror);
- prev_xioerror = NULL;
- }
+ if(prev_xerror) {
+ XSetErrorHandler(prev_xerror);
+ prev_xerror = NULL;
+ }
+}
- if(prev_xerror) {
- XSetErrorHandler(prev_xerror);
- prev_xerror = NULL;
+static void mgl_deinit_wayland(void) {
+ if(context.connection) {
+ wl_display_disconnect(context.connection);
+ context.connection = NULL;
+ connected_to_display_server = false;
+ }
+}
+
+void mgl_deinit(void) {
+ if(init_count == 1) {
+ switch(context.window_system) {
+ case MGL_WINDOW_SYSTEM_NATIVE:
+ assert(false);
+ break;
+ case MGL_WINDOW_SYSTEM_X11: {
+ mgl_deinit_x11();
+ break;
+ }
+ case MGL_WINDOW_SYSTEM_WAYLAND: {
+ mgl_deinit_wayland();
+ break;
+ }
}
+ mgl_gl_unload(&context.gl);
context.current_window = NULL;
}
@@ -144,11 +234,15 @@ mgl_context* mgl_get_context(void) {
}
bool mgl_is_connected_to_display_server(void) {
- return connected_to_x_server;
+ return connected_to_display_server;
}
void mgl_ping_display_server(void) {
- if(context.connection) {
+ if(!context.connection)
+ return;
+
+ // TODO: Do something equivalent for wayland, maybe wl_display_roundtrip and if it returns -1 then the connection to the server died
+ if(context.window_system == MGL_WINDOW_SYSTEM_X11) {
XNoOp(context.connection);
XFlush(context.connection);
}
diff --git a/src/window/key.c b/src/window/key.c
index b68270a..ffef2b4 100644
--- a/src/window/key.c
+++ b/src/window/key.c
@@ -127,6 +127,8 @@ const char* mgl_key_to_string(mgl_key key) {
case MGL_KEY_F20: return "F20";
case MGL_KEY_F21: return "F21";
case MGL_KEY_F22: return "F22";
+ case MGL_KEY_F23: return "F23";
+ case MGL_KEY_F24: return "F24";
case __MGL_NUM_KEYS__: return "";
}
return "";
@@ -206,6 +208,8 @@ uint64_t mgl_key_to_x11_keysym(mgl_key key) {
case MGL_KEY_F20: return XK_F20;
case MGL_KEY_F21: return XK_F21;
case MGL_KEY_F22: return XK_F22;
+ case MGL_KEY_F23: return XK_F23;
+ case MGL_KEY_F24: return XK_F24;
default: return XK_VoidSymbol;
}
return XK_VoidSymbol;
diff --git a/src/window/wayland.c b/src/window/wayland.c
new file mode 100644
index 0000000..f889126
--- /dev/null
+++ b/src/window/wayland.c
@@ -0,0 +1,781 @@
+#include "../../include/mgl/window/wayland.h"
+#include "../../include/mgl/mgl.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include <wayland-client.h>
+#include <wayland-egl.h>
+#include "xdg-shell-client-protocol.h"
+
+static void mgl_window_wayland_deinit(mgl_window *self);
+
+typedef struct {
+ struct wl_egl_window *window;
+ struct wl_registry *registry;
+ struct wl_surface *surface;
+ struct wl_compositor *compositor;
+ struct wl_shell *shell;
+ struct wl_shell_surface *shell_surface;
+ struct wl_seat *seat;
+ struct wl_pointer *pointer;
+ struct wl_keyboard *keyboard;
+
+ struct xdg_wm_base *xdg_wm_base;
+ struct xdg_surface *xdg_surface;
+ struct xdg_toplevel *xdg_toplevel;
+
+ bool mouse_moved;
+ mgl_vec2i mouse_position;
+
+ mgl_graphics graphics;
+} mgl_window_wayland;
+
+static void mgl_window_wayland_on_resize(mgl_window *self, int width, int height) {
+ mgl_window_wayland *impl = self->impl;
+ self->size.x = width;
+ self->size.y = height;
+ wl_egl_window_resize(impl->window, self->size.x, self->size.y, 0, 0);
+
+ mgl_view view;
+ view.position = (mgl_vec2i){ 0, 0 };
+ view.size = self->size;
+ mgl_window_set_view(self, &view);
+ mgl_window_set_scissor(self, &(mgl_scissor){ .position = { 0, 0 }, .size = self->size });
+}
+
+static void registry_add_object(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) {
+ (void)version;
+ mgl_window *self = data;
+ mgl_window_wayland *impl = self->impl;
+ if(strcmp(interface, wl_compositor_interface.name) == 0) {
+ if(impl->compositor)
+ return;
+
+ impl->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 1);
+ } else if(strcmp(interface, wl_output_interface.name) == 0) {
+ if(version < 4) {
+ fprintf(stderr, "mgl warning: wl output interface version is < 4, expected >= 4\n");
+ return;
+ }
+
+ if(self->num_monitors == MGL_MAX_MONITORS) {
+ fprintf(stderr, "mgl warning: reached maximum outputs (%d), ignoring output %u\n", MGL_MAX_MONITORS, name);
+ return;
+ }
+
+ // gsr_wayland_output *gsr_output = &window_wayland->outputs[window_wayland->num_outputs];
+ // ++self->num_monitors;
+ // *gsr_output = (gsr_wayland_output) {
+ // .wl_name = name,
+ // .output = wl_registry_bind(registry, name, &wl_output_interface, 4),
+ // .pos = { .x = 0, .y = 0 },
+ // .size = { .x = 0, .y = 0 },
+ // .transform = 0,
+ // .name = NULL,
+ // };
+ // wl_output_add_listener(gsr_output->output, &output_listener, gsr_output);
+ } else if(strcmp(interface, wl_shell_interface.name) == 0) {
+ if(impl->shell)
+ return;
+
+ impl->shell = wl_registry_bind(registry, name, &wl_shell_interface, 1);
+ } else if(strcmp(interface, xdg_wm_base_interface.name) == 0) {
+ if(impl->xdg_wm_base)
+ return;
+
+ impl->xdg_wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
+ } else if(strcmp(interface, wl_seat_interface.name) == 0) {
+ if(version < 5) {
+ fprintf(stderr, "mgl warning: wl seat interface version is < 5, expected >= 5\n");
+ return;
+ }
+
+ if(impl->seat)
+ return;
+
+ impl->seat = wl_registry_bind(registry, name, &wl_seat_interface, 5);
+ }
+}
+
+static void registry_remove_object(void *data, struct wl_registry *registry, uint32_t name) {
+ (void)data;
+ (void)registry;
+ (void)name;
+ // TODO: Remove output
+}
+
+static struct wl_registry_listener registry_listener = {
+ .global = registry_add_object,
+ .global_remove = registry_remove_object,
+};
+
+static void shell_surface_ping(void *data, struct wl_shell_surface *shell_surface, uint32_t serial) {
+ wl_shell_surface_pong(shell_surface, serial);
+}
+
+static void shell_surface_configure(void *data, struct wl_shell_surface *shell_surface, uint32_t edges, int32_t width, int32_t height) {
+ mgl_window *self = data;
+ mgl_window_wayland *impl = self->impl;
+ wl_egl_window_resize(impl->window, width, height, 0, 0);
+}
+
+void shell_surface_popup_done(void *data, struct wl_shell_surface *shell_surface) {
+
+}
+
+static struct wl_shell_surface_listener shell_surface_listener = {
+ .ping = shell_surface_ping,
+ .configure = shell_surface_configure,
+ .popup_done = shell_surface_popup_done,
+};
+
+static void xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial) {
+ xdg_wm_base_pong(shell, serial);
+}
+
+static const struct xdg_wm_base_listener wm_base_listener = {
+ .ping = xdg_wm_base_ping,
+};
+
+static void xdg_surface_configure(void *data, struct xdg_surface *surface, uint32_t serial) {
+ xdg_surface_ack_configure(surface, serial);
+ // TODO:
+ //window->wait_for_configure = false;
+}
+
+static const struct xdg_surface_listener xdg_surface_listener = {
+ .configure = xdg_surface_configure,
+};
+
+static void xdg_toplevel_configure(void *data, struct xdg_toplevel *toplevel, int32_t width, int32_t height, struct wl_array *states) {
+ mgl_window *self = data;
+ mgl_window_wayland *impl = self->impl;
+ // TODO:
+ mgl_window_wayland_on_resize(self, width, height);
+ // struct window *window = data;
+ // uint32_t *p;
+
+ // window->fullscreen = 0;
+ // window->maximized = 0;
+ // wl_array_for_each(p, states) {
+ // uint32_t state = *p;
+ // switch (state) {
+ // case XDG_TOPLEVEL_STATE_FULLSCREEN:
+ // window->fullscreen = 1;
+ // break;
+ // case XDG_TOPLEVEL_STATE_MAXIMIZED:
+ // window->maximized = 1;
+ // break;
+ // }
+ // }
+
+ // if (width > 0 && height > 0) {
+ // if (!window->fullscreen && !window->maximized) {
+ // window->window_size.width = width;
+ // window->window_size.height = height;
+ // }
+ // window->geometry.width = width;
+ // window->geometry.height = height;
+ // } else if (!window->fullscreen && !window->maximized) {
+ // window->geometry = window->window_size;
+ // }
+
+ // if (window->native)
+ // wl_egl_window_resize(window->native,
+ // window->geometry.width,
+ // window->geometry.height, 0, 0);
+}
+
+static void xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) {
+ // TODO:
+ //running = 0;
+}
+
+static const struct xdg_toplevel_listener xdg_toplevel_listener = {
+ .configure = xdg_toplevel_configure,
+ .close = xdg_toplevel_close,
+};
+
+static void
+wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
+ uint32_t serial, struct wl_surface *surface,
+ wl_fixed_t surface_x, wl_fixed_t surface_y)
+{
+ // // Set our pointer
+ // wl_pointer_set_cursor(wl_pointer, serial, cursor_surface,
+ // cursor_image->hotspot_x, cursor_image->hotspot_y);
+}
+
+static void
+wl_pointer_leave(void *data, struct wl_pointer *wl_pointer,
+ uint32_t serial, struct wl_surface *surface)
+{
+}
+
+static void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
+ mgl_window *self = data;
+ mgl_window_wayland *impl = self->impl;
+ impl->mouse_moved = true;
+ impl->mouse_position = (mgl_vec2i){ .x = wl_fixed_to_int(surface_x), .y = wl_fixed_to_int(surface_y) };
+}
+
+static void
+wl_pointer_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial,
+ uint32_t time, uint32_t button, uint32_t state)
+{
+
+}
+
+static void
+wl_pointer_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time,
+ uint32_t axis, wl_fixed_t value)
+{
+
+}
+
+static void
+wl_pointer_axis_source(void *data, struct wl_pointer *wl_pointer,
+ uint32_t axis_source)
+{
+
+}
+
+static void
+wl_pointer_axis_stop(void *data, struct wl_pointer *wl_pointer,
+ uint32_t time, uint32_t axis)
+{
+
+}
+
+static void
+wl_pointer_axis_discrete(void *data, struct wl_pointer *wl_pointer,
+ uint32_t axis, int32_t discrete)
+{
+
+}
+
+static void
+wl_pointer_frame(void *data, struct wl_pointer *wl_pointer)
+{
+ mgl_window *self = data;
+ mgl_window_wayland *impl = self->impl;
+ if(impl->mouse_moved) {
+ impl->mouse_moved = false;
+ self->cursor_position = impl->mouse_position;
+ }
+}
+
+static const struct wl_pointer_listener wl_pointer_listener = {
+ .enter = wl_pointer_enter,
+ .leave = wl_pointer_leave,
+ .motion = wl_pointer_motion,
+ .button = wl_pointer_button,
+ .axis = wl_pointer_axis,
+ .frame = wl_pointer_frame,
+ .axis_source = wl_pointer_axis_source,
+ .axis_stop = wl_pointer_axis_stop,
+ .axis_discrete = wl_pointer_axis_discrete,
+};
+
+static void
+wl_keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t format, int32_t fd, uint32_t size)
+{
+ // struct client_state *client_state = data;
+ // assert(format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1);
+
+ // char *map_shm = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
+ // assert(map_shm != MAP_FAILED);
+
+ // struct xkb_keymap *xkb_keymap = xkb_keymap_new_from_string(
+ // client_state->xkb_context, map_shm,
+ // XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
+ // munmap(map_shm, size);
+ // close(fd);
+
+ // struct xkb_state *xkb_state = xkb_state_new(xkb_keymap);
+ // xkb_keymap_unref(client_state->xkb_keymap);
+ // xkb_state_unref(client_state->xkb_state);
+ // client_state->xkb_keymap = xkb_keymap;
+ // client_state->xkb_state = xkb_state;
+}
+
+static void
+wl_keyboard_enter(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t serial, struct wl_surface *surface,
+ struct wl_array *keys)
+{
+ // struct client_state *client_state = data;
+ // fprintf(stderr, "keyboard enter; keys pressed are:\n");
+ // uint32_t *key;
+ // wl_array_for_each(key, keys) {
+ // char buf[128];
+ // xkb_keysym_t sym = xkb_state_key_get_one_sym(
+ // client_state->xkb_state, *key + 8);
+ // xkb_keysym_get_name(sym, buf, sizeof(buf));
+ // fprintf(stderr, "sym: %-12s (%d), ", buf, sym);
+ // xkb_state_key_get_utf8(client_state->xkb_state,
+ // *key + 8, buf, sizeof(buf));
+ // fprintf(stderr, "utf8: '%s'\n", buf);
+ // }
+}
+
+static void
+wl_keyboard_leave(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t serial, struct wl_surface *surface)
+{
+ //fprintf(stderr, "keyboard leave\n");
+}
+
+static void
+wl_keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t serial, uint32_t mods_depressed,
+ uint32_t mods_latched, uint32_t mods_locked,
+ uint32_t group)
+{
+ // struct client_state *client_state = data;
+ // xkb_state_update_mask(client_state->xkb_state,
+ // mods_depressed, mods_latched, mods_locked, 0, 0, group);
+}
+
+static void
+wl_keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard,
+ int32_t rate, int32_t delay)
+{
+ /* Left as an exercise for the reader */
+}
+
+static void
+wl_keyboard_key(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t serial, uint32_t time, uint32_t key, uint32_t state)
+{
+ // struct client_state *client_state = data;
+ // char buf[128];
+ // uint32_t keycode = key + 8;
+ // xkb_keysym_t sym = xkb_state_key_get_one_sym(
+ // client_state->xkb_state, keycode);
+ // xkb_keysym_get_name(sym, buf, sizeof(buf));
+ // const char *action =
+ // state == WL_KEYBOARD_KEY_STATE_PRESSED ? "press" : "release";
+ // fprintf(stderr, "key %s: sym: %-12s (%d), ", action, buf, sym);
+ // xkb_state_key_get_utf8(client_state->xkb_state, keycode,
+ // buf, sizeof(buf));
+ // fprintf(stderr, "utf8: '%s'\n", buf);
+}
+
+static const struct wl_keyboard_listener wl_keyboard_listener = {
+ .keymap = wl_keyboard_keymap,
+ .enter = wl_keyboard_enter,
+ .leave = wl_keyboard_leave,
+ .key = wl_keyboard_key,
+ .modifiers = wl_keyboard_modifiers,
+ .repeat_info = wl_keyboard_repeat_info,
+};
+
+static void wl_seat_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capabilities) {
+ struct mgl_window *self = data;
+ mgl_window_wayland *impl = self->impl;
+ const bool have_pointer = capabilities & WL_SEAT_CAPABILITY_POINTER;
+ const bool have_keyboard = capabilities & WL_SEAT_CAPABILITY_KEYBOARD;
+
+ if(have_pointer && impl->pointer == NULL) {
+ impl->pointer = wl_seat_get_pointer(impl->seat);
+ wl_pointer_add_listener(impl->pointer, &wl_pointer_listener, self);
+ } else if(!have_pointer && impl->pointer != NULL) {
+ wl_pointer_release(impl->pointer);
+ impl->pointer = NULL;
+ }
+
+ if(have_keyboard && impl->keyboard == NULL) {
+ impl->keyboard = wl_seat_get_keyboard(impl->seat);
+ wl_keyboard_add_listener(impl->keyboard, &wl_keyboard_listener, self);
+ } else if(!have_keyboard && impl->keyboard != NULL) {
+ wl_keyboard_release(impl->keyboard);
+ impl->keyboard = NULL;
+ }
+}
+
+static void wl_seat_name(void *data, struct wl_seat *wl_seat, const char *name) {
+ fprintf(stderr, "seat name: %s\n", name);
+}
+
+static const struct wl_seat_listener wl_seat_listener = {
+ .capabilities = wl_seat_capabilities,
+ .name = wl_seat_name,
+};
+
+// TODO: params and existing_window
+bool mgl_wayland_setup_window(mgl_window *self, const char *title, const mgl_window_create_params *params, mgl_window_handle existing_window) {
+ mgl_window_wayland *impl = self->impl;
+ mgl_context *context = mgl_get_context();
+
+ mgl_vec2i window_size = params ? params->size : (mgl_vec2i){ 0, 0 };
+ if(window_size.x <= 0 || window_size.y <= 0) {
+ window_size.x = 640;
+ window_size.y = 480;
+ }
+ self->size = window_size;
+
+ impl->registry = wl_display_get_registry(context->connection); /* TODO: Error checking */
+ wl_registry_add_listener(impl->registry, &registry_listener, self); /* TODO: Error checking */
+
+ /* Fetch globals */
+ wl_display_roundtrip(context->connection);
+
+ /* Fetch wl_output */
+ //wl_display_roundtrip(context->connection);
+
+ if(!impl->compositor) {
+ fprintf(stderr, "mgl error: mgl_wayland_setup_window: failed to find compositor\n");
+ return false;
+ }
+
+ if(!impl->xdg_wm_base && !impl->shell) {
+ fprintf(stderr, "mgl error: mgl_wayland_setup_window: failed to find shell\n");
+ return false;
+ }
+
+ if(!impl->seat) {
+ fprintf(stderr, "mgl error: mgl_wayland_setup_window: failed to find seat\n");
+ return false;
+ }
+
+ impl->surface = wl_compositor_create_surface(impl->compositor);
+ if(!impl->surface) {
+ fprintf(stderr, "mgl error: mgl_wayland_setup_window: failed to create surface\n");
+ return false;
+ }
+
+ wl_seat_add_listener(impl->seat, &wl_seat_listener, self);
+
+ if(impl->xdg_wm_base) {
+ // TODO: Error check, cleanup
+ xdg_wm_base_add_listener(impl->xdg_wm_base, &wm_base_listener, self);
+
+ impl->xdg_surface = xdg_wm_base_get_xdg_surface(impl->xdg_wm_base, impl->surface);
+ xdg_surface_add_listener(impl->xdg_surface, &xdg_surface_listener, self);
+
+ impl->xdg_toplevel = xdg_surface_get_toplevel(impl->xdg_surface);
+ xdg_toplevel_add_listener(impl->xdg_toplevel, &xdg_toplevel_listener, self);
+
+ xdg_toplevel_set_title(impl->xdg_toplevel, title);
+ if(params && params->class_name)
+ xdg_toplevel_set_app_id(impl->xdg_toplevel, params->class_name);
+ } else {
+ // TODO: Error check, cleanup
+ impl->shell_surface = wl_shell_get_shell_surface(impl->shell, impl->surface);
+ wl_shell_surface_add_listener(impl->shell_surface, &shell_surface_listener, self);
+ wl_shell_surface_set_toplevel(impl->shell_surface);
+ wl_shell_surface_set_title(impl->shell_surface, title);
+ if(params && params->class_name)
+ wl_shell_surface_set_class(impl->shell_surface, params->class_name);
+ }
+
+ wl_surface_commit(impl->surface);
+
+ // TODO: Error check
+ struct wl_region *region = wl_compositor_create_region(impl->compositor);
+ wl_region_add(region, 0, 0, self->size.x, self->size.y);
+ wl_surface_set_opaque_region(impl->surface, region);
+ wl_region_destroy(region);
+
+ impl->window = wl_egl_window_create(impl->surface, self->size.x, self->size.y);
+ if(!impl->window) {
+ fprintf(stderr, "mgl error: mgl_wayland_setup_window: failed to create the Wayland window\n");
+ return false;
+ }
+
+ //mgl_window_x11_on_move(self, window_pos.x, window_pos.y);
+ mgl_window_wayland_on_resize(self, self->size.x, self->size.y);
+
+ self->open = true;
+ self->focused = false;
+ return true;
+}
+
+static mgl_window_handle mgl_window_wayland_get_system_handle(const mgl_window *self) {
+ const mgl_window_wayland *impl = self->impl;
+ return (mgl_window_handle)impl->window;
+}
+
+static void mgl_window_wayland_close(mgl_window *self) {
+ mgl_window_wayland *impl = self->impl;
+
+ if(impl->window) {
+ wl_egl_window_destroy(impl->window);
+ impl->window = NULL;
+ }
+
+ if(impl->surface) {
+ wl_surface_destroy(impl->surface);
+ impl->surface = NULL;
+ }
+
+ self->open = false;
+}
+
+static bool mgl_window_wayland_poll_event(mgl_window *self, mgl_event *event) {
+ mgl_context *context = mgl_get_context();
+ struct wl_display *display = context->connection;
+ const bool events_available = wl_display_dispatch_pending(display) > 0;
+ //wl_display_flush(display);
+ /* TODO: Return event here, pop from circular buffer */
+ return events_available;
+}
+
+static void mgl_window_wayland_swap_buffers(mgl_window *self) {
+ mgl_window_wayland *impl = self->impl;
+ mgl_graphics_swap_buffers(&impl->graphics, (mgl_window_handle)impl->window);
+}
+
+static void mgl_window_wayland_set_visible(mgl_window *self, bool visible) {
+ fprintf(stderr, "TODO: Implement\n");
+}
+
+static bool mgl_window_wayland_is_key_pressed(const mgl_window *self, mgl_key key) {
+ (void)self;
+ if(key < 0 || key >= __MGL_NUM_KEYS__)
+ return false;
+
+ fprintf(stderr, "TODO: Implement\n");
+ return false;
+}
+
+static bool mgl_window_wayland_is_mouse_button_pressed(const mgl_window *self, mgl_mouse_button button) {
+ (void)self;
+ if(button < 0 || button >= __MGL_NUM_MOUSE_BUTTONS__)
+ return false;
+
+ fprintf(stderr, "TODO: Implement\n");
+ return false;
+}
+
+static void mgl_window_wayland_set_title(mgl_window *self, const char *title) {
+ mgl_window_wayland *impl = self->impl;
+ // TODO: Check if wl_display_flush is needed after these calls
+ if(impl->xdg_toplevel)
+ xdg_toplevel_set_title(impl->xdg_toplevel, title);
+ else
+ wl_shell_surface_set_title(impl->shell_surface, title);
+}
+
+static void mgl_window_wayland_set_cursor_visible(mgl_window *self, bool visible) {
+ fprintf(stderr, "TODO: Implement\n");
+}
+
+static void mgl_window_wayland_set_vsync_enabled(mgl_window *self, bool enabled) {
+ mgl_window_wayland *impl = self->impl;
+ self->vsync_enabled = enabled;
+ if(!mgl_graphics_set_swap_interval(&impl->graphics, (mgl_window_handle)impl->window, self->vsync_enabled))
+ fprintf(stderr, "mgl warning: mgl_window_wayland_set_vsync_enabled: failed to enable vsync\n");
+}
+
+static bool mgl_window_wayland_is_vsync_enabled(const mgl_window *self) {
+ return self->vsync_enabled;
+}
+
+static void mgl_window_wayland_set_fullscreen(mgl_window *self, bool fullscreen) {
+ mgl_window_wayland *impl = self->impl;
+ // TODO: The last argument is the monitor we want to fullscreen on. Use this!
+ if(impl->xdg_toplevel) {
+ if(fullscreen)
+ xdg_toplevel_set_fullscreen(impl->xdg_toplevel, NULL);
+ else
+ xdg_toplevel_unset_fullscreen(impl->xdg_toplevel);
+ } else {
+ if(fullscreen)
+ wl_shell_surface_set_fullscreen(impl->shell_surface, WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT, 0, NULL);
+ else
+ fprintf(stderr, "TODO: Implement\n");
+ }
+}
+
+static bool mgl_window_wayland_is_fullscreen(const mgl_window *self) {
+ fprintf(stderr, "TODO: Implement\n");
+ return false;
+}
+
+static void mgl_window_wayland_set_position(mgl_window *self, mgl_vec2i position) {
+ fprintf(stderr, "TODO: Implement\n");
+}
+
+static void mgl_window_wayland_set_size(mgl_window *self, mgl_vec2i size) {
+ fprintf(stderr, "TODO: Implement\n");
+}
+
+static void mgl_window_wayland_set_size_limits(mgl_window *self, mgl_vec2i minimum, mgl_vec2i maximum) {
+ mgl_window_wayland *impl = self->impl;
+ if(impl->xdg_toplevel) {
+ xdg_toplevel_set_min_size(impl->xdg_toplevel, minimum.x, minimum.y);
+ xdg_toplevel_set_max_size(impl->xdg_toplevel, maximum.x, maximum.y);
+ } else {
+ fprintf(stderr, "TODO: Implement\n");
+ }
+}
+
+static void mgl_window_wayland_set_clipboard(mgl_window *self, const char *str, size_t size) {
+ fprintf(stderr, "TODO: Implement\n");
+}
+
+static bool mgl_window_wayland_get_clipboard(mgl_window *self, mgl_clipboard_callback callback, void *userdata, uint32_t clipboard_types) {
+ fprintf(stderr, "TODO: Implement\n");
+ return false;
+}
+
+static void mgl_window_wayland_set_key_repeat_enabled(mgl_window *self, bool enabled) {
+ self->key_repeat_enabled = enabled;
+ // TODO: Implement
+}
+
+static void mgl_window_wayland_flush(mgl_window *self) {
+ (void)self;
+ mgl_context *context = mgl_get_context();
+ struct wl_display *display = context->connection;
+ wl_display_flush(display);
+}
+
+static void* mgl_window_wayland_get_egl_display(mgl_window *self) {
+ mgl_window_wayland *impl = self->impl;
+ if(impl->graphics.graphics_api == MGL_GRAPHICS_API_EGL)
+ return mgl_graphics_get_display(&impl->graphics);
+ else
+ return NULL;
+}
+
+static void* mgl_window_wayland_get_egl_context(mgl_window *self) {
+ mgl_window_wayland *impl = self->impl;
+ if(impl->graphics.graphics_api == MGL_GRAPHICS_API_EGL)
+ return mgl_graphics_get_context(&impl->graphics);
+ else
+ return NULL;
+}
+
+static void mgl_window_wayland_for_each_active_monitor_output(mgl_window *self, mgl_active_monitor_callback callback, void *userdata) {
+ fprintf(stderr, "TODO: Implement\n");
+}
+
+bool mgl_window_wayland_init(mgl_window *self, const char *title, const mgl_window_create_params *params, mgl_window_handle existing_window) {
+ mgl_window_wayland *impl = calloc(1, sizeof(mgl_window_wayland));
+ if(!impl)
+ return false;
+
+ self->get_system_handle = mgl_window_wayland_get_system_handle;
+ self->deinit = mgl_window_wayland_deinit;
+ self->close = mgl_window_wayland_close;
+ self->poll_event = mgl_window_wayland_poll_event;
+ self->swap_buffers = mgl_window_wayland_swap_buffers;
+ self->set_visible = mgl_window_wayland_set_visible;
+ self->is_key_pressed = mgl_window_wayland_is_key_pressed;
+ self->is_mouse_button_pressed = mgl_window_wayland_is_mouse_button_pressed;
+ self->set_title = mgl_window_wayland_set_title;
+ self->set_cursor_visible = mgl_window_wayland_set_cursor_visible;
+ self->set_vsync_enabled = mgl_window_wayland_set_vsync_enabled;
+ self->is_vsync_enabled = mgl_window_wayland_is_vsync_enabled;
+ self->set_fullscreen = mgl_window_wayland_set_fullscreen;
+ self->is_fullscreen = mgl_window_wayland_is_fullscreen;
+ self->set_position = mgl_window_wayland_set_position;
+ self->set_size = mgl_window_wayland_set_size;
+ self->set_size_limits = mgl_window_wayland_set_size_limits;
+ self->set_clipboard = mgl_window_wayland_set_clipboard;
+ self->get_clipboard = mgl_window_wayland_get_clipboard;
+ self->set_key_repeat_enabled = mgl_window_wayland_set_key_repeat_enabled;
+ self->flush = mgl_window_wayland_flush;
+ self->get_egl_display = mgl_window_wayland_get_egl_display;
+ self->get_egl_context = mgl_window_wayland_get_egl_context;
+ self->for_each_active_monitor_output = mgl_window_wayland_for_each_active_monitor_output;
+ self->impl = impl;
+
+ if(!mgl_wayland_setup_window(self, title, params, existing_window)) {
+ mgl_window_wayland_deinit(self);
+ return false;
+ }
+
+ assert(!params || params->graphics_api == MGL_GRAPHICS_API_EGL);
+ const bool alpha = params && params->support_alpha;
+ if(!mgl_graphics_init(&impl->graphics, &(mgl_graphics_create_params){ .graphics_api = MGL_GRAPHICS_API_EGL, .alpha = alpha })) {
+ mgl_window_wayland_deinit(self);
+ return false;
+ }
+
+ if(!mgl_graphics_make_context_current(&impl->graphics, impl->window)) {
+ fprintf(stderr, "mgl error: mgl_window_wayland_init: failed to make window context current\n");
+ mgl_window_wayland_deinit(self);
+ return false;
+ }
+
+ self->vsync_enabled = true;
+ mgl_graphics_set_swap_interval(&impl->graphics, (mgl_window_handle)impl->window, self->vsync_enabled);
+
+ mgl_context *context = mgl_get_context();
+ context->current_window = self;
+ return true;
+}
+
+void mgl_window_wayland_deinit(mgl_window *self) {
+ mgl_window_wayland *impl = self->impl;
+ if(!impl)
+ return;
+
+ mgl_graphics_deinit(&impl->graphics);
+ mgl_window_wayland_close(self);
+
+ if(impl->xdg_toplevel) {
+ xdg_toplevel_destroy(impl->xdg_toplevel);
+ impl->xdg_toplevel = NULL;
+ }
+
+ if(impl->xdg_surface) {
+ xdg_surface_destroy(impl->xdg_surface);
+ impl->xdg_surface = NULL;
+ }
+
+ if(impl->shell_surface) {
+ wl_shell_surface_destroy(impl->shell_surface);
+ impl->shell_surface = NULL;
+ }
+
+ if(impl->pointer) {
+ wl_pointer_release(impl->pointer);
+ impl->pointer = NULL;
+ }
+
+ if(impl->keyboard) {
+ wl_keyboard_release(impl->keyboard);
+ impl->keyboard = NULL;
+ }
+
+ if(impl->seat) {
+ wl_seat_destroy(impl->seat);
+ impl->seat = NULL;
+ }
+
+ if(impl->shell) {
+ wl_shell_destroy(impl->shell);
+ impl->shell = NULL;
+ }
+
+ if(impl->xdg_wm_base) {
+ xdg_wm_base_destroy(impl->xdg_wm_base);
+ impl->xdg_wm_base = NULL;
+ }
+
+ if(impl->compositor) {
+ wl_compositor_destroy(impl->compositor);
+ impl->compositor = NULL;
+ }
+
+ if(impl->registry) {
+ wl_registry_destroy(impl->registry);
+ impl->registry = NULL;
+ }
+
+ mgl_context *context = mgl_get_context();
+ if(context->current_window == self)
+ context->current_window = NULL;
+
+ free(self->impl);
+ self->impl = NULL;
+}
diff --git a/src/window/window.c b/src/window/window.c
index e7f737d..a11bc15 100644
--- a/src/window/window.c
+++ b/src/window/window.c
@@ -1,1612 +1,66 @@
#include "../../include/mgl/window/window.h"
-#include "../../include/mgl/window/event.h"
+#include "../../include/mgl/window/x11.h"
+#include "../../include/mgl/window/wayland.h"
#include "../../include/mgl/mgl.h"
-#include "../../include/mgl/system/utf8.h"
-#include <X11/Xutil.h>
-#include <X11/cursorfont.h>
-#include <X11/Xatom.h>
-#include <X11/extensions/Xrender.h>
-#include <X11/extensions/Xrandr.h>
-#include <X11/XF86keysym.h>
+
#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-#include <limits.h>
-#include <assert.h>
#include <unistd.h>
+#include <assert.h>
+#include <string.h>
-/* TODO: Handle XIM better. Set XIM position to text position on screen (for text input) and reset input when selecting a new text input, etc */
-/* TODO: Separate events from windows. Especially when it comes to monitor events */
-
-/* Should be in range [2,] */
-#define MAX_STACKED_EVENTS 32
-
-typedef struct {
- mgl_event stack[MAX_STACKED_EVENTS];
- int start;
- int end;
- int size;
-} x11_events_circular_buffer;
-
-static void x11_events_circular_buffer_init(x11_events_circular_buffer *self) {
- self->start = 0;
- self->end = 0;
- self->size = 0;
-}
-
-static bool x11_events_circular_buffer_append(x11_events_circular_buffer *self, const mgl_event *event) {
- if(self->size == MAX_STACKED_EVENTS)
- return false;
-
- self->stack[self->end] = *event;
- self->end = (self->end + 1) % MAX_STACKED_EVENTS;
- ++self->size;
- return true;
-}
-
-static bool x11_events_circular_buffer_pop(x11_events_circular_buffer *self, mgl_event *event) {
- if(self->size == 0)
- return false;
-
- *event = self->stack[self->start];
- self->start = (self->start + 1) % MAX_STACKED_EVENTS;
- --self->size;
- return true;
-}
-
-static bool xvisual_match_alpha(Display *dpy, XVisualInfo *visual_info, bool alpha) {
- XRenderPictFormat *pict_format = XRenderFindVisualFormat(dpy, visual_info->visual);
- if(!pict_format)
- return false;
- return (alpha && pict_format->direct.alphaMask > 0) || (!alpha && pict_format->direct.alphaMask == 0);
-}
-
-#define MAX_MONITORS 12
-
-typedef struct {
- GLXContext glx_context;
- GLXFBConfig *fbconfigs;
- GLXFBConfig fbconfig;
- XVisualInfo *visual_info;
-} x11_context_glx;
-
-static bool glx_context_choose(mgl_context *context, x11_context_glx *glx, bool alpha) {
- const int attr[] = {
- GLX_RENDER_TYPE, GLX_RGBA_BIT,
- GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
- GLX_DOUBLEBUFFER, True,
- GLX_RED_SIZE, 8,
- GLX_GREEN_SIZE, 8,
- GLX_BLUE_SIZE, 8,
- GLX_ALPHA_SIZE, alpha ? 8 : 0,
- // TODO:
- //GLX_DEPTH_SIZE, 0,
- None
- };
-
- glx->fbconfigs = NULL;
- glx->visual_info = NULL;
- glx->fbconfig = NULL;
-
- int numfbconfigs = 0;
- glx->fbconfigs = context->gl.glXChooseFBConfig(context->connection, DefaultScreen(context->connection), attr, &numfbconfigs);
- for(int i = 0; i < numfbconfigs; i++) {
- glx->visual_info = (XVisualInfo*)context->gl.glXGetVisualFromFBConfig(context->connection, glx->fbconfigs[i]);
- if(!glx->visual_info)
- continue;
-
- if(xvisual_match_alpha(context->connection, glx->visual_info, alpha)) {
- glx->fbconfig = glx->fbconfigs[i];
- break;
- } else {
- XFree(glx->visual_info);
- glx->visual_info = NULL;
- glx->fbconfig = NULL;
- }
- }
-
- if(!glx->visual_info) {
- if(glx->fbconfigs) {
- XFree(glx->fbconfigs);
- glx->fbconfigs = NULL;
- }
- glx->fbconfig = NULL;
-
- fprintf(stderr, "mgl error: no appropriate visual found\n");
- return false;
- }
-
- return true;
-}
-
-static void x11_context_glx_deinit(x11_context_glx *self) {
- mgl_context *context = mgl_get_context();
-
- if(self->glx_context) {
- context->gl.glXMakeContextCurrent(context->connection, None, None, NULL);
- context->gl.glXDestroyContext(context->connection, self->glx_context);
- self->glx_context = NULL;
- }
-
- if(self->visual_info) {
- XFree(self->visual_info);
- self->visual_info = NULL;
- }
-
- if(self->fbconfigs) {
- XFree(self->fbconfigs);
- self->fbconfigs = NULL;
- }
-
- self->fbconfig = NULL;
-}
-
-static bool x11_context_glx_init(x11_context_glx *self, bool alpha) {
- mgl_context *context = mgl_get_context();
- memset(self, 0, sizeof(*self));
-
- if(!glx_context_choose(context, self, alpha)) {
- x11_context_glx_deinit(self);
- return false;
- }
-
- self->glx_context = context->gl.glXCreateNewContext(context->connection, self->fbconfig, GLX_RGBA_TYPE, 0, True);
- if(!self->glx_context) {
- fprintf(stderr, "mgl error: x11_context_glx_init: glXCreateContext failed\n");
- x11_context_glx_deinit(self);
- return false;
- }
-
- return true;
-}
-
-static bool x11_context_glx_make_context_current(x11_context_glx *self, Window window) {
- mgl_context *context = mgl_get_context();
- return context->gl.glXMakeContextCurrent(context->connection, window, window, self->glx_context) != 0;
-}
-
-static void x11_context_glx_swap_buffers(x11_context_glx *self, Window window) {
- (void)self;
- mgl_context *context = mgl_get_context();
- context->gl.glXSwapBuffers(context->connection, window);
-}
-
-static XVisualInfo* x11_context_glx_get_xvisual_info(x11_context_glx *self) {
- return self->visual_info;
-}
-
-static bool x11_context_glx_set_swap_interval(x11_context_glx *self, Window window, int enabled) {
- (void)self;
- mgl_context *context = mgl_get_context();
-
- int result = 0;
- if(context->gl.glXSwapIntervalEXT) {
- context->gl.glXSwapIntervalEXT(context->connection, window, enabled ? 1 : 0);
- } else if(context->gl.glXSwapIntervalMESA) {
- result = context->gl.glXSwapIntervalMESA(enabled ? 1 : 0);
- } else if(context->gl.glXSwapIntervalSGI) {
- result = context->gl.glXSwapIntervalSGI(enabled ? 1 : 0);
- } else {
- static int warned = 0;
- if (!warned) {
- warned = 1;
- fprintf(stderr, "mgl warning: setting vertical sync not supported\n");
- }
- }
-
- if(result != 0)
- fprintf(stderr, "mgl warning: setting vertical sync failed\n");
-
- return result == 0;
-}
-
-typedef struct {
- EGLDisplay egl_display;
- EGLSurface egl_surface;
- EGLContext egl_context;
- EGLConfig *configs;
- EGLConfig ecfg;
- XVisualInfo *visual_info;
-} x11_context_egl;
-
-static int32_t egl_get_config_attrib(x11_context_egl *egl, EGLConfig ecfg, int32_t attribute_name) {
- mgl_context *context = mgl_get_context();
- int32_t value = 0;
- context->gl.eglGetConfigAttrib(egl->egl_display, ecfg, attribute_name, &value);
- return value;
-}
-
-static bool egl_context_choose(mgl_context *context, x11_context_egl *egl, bool alpha) {
- egl->configs = NULL;
- egl->ecfg = NULL;
- egl->visual_info = NULL;
-
- int32_t num_configs = 0;
- context->gl.eglGetConfigs(egl->egl_display, NULL, 0, &num_configs);
- if(num_configs == 0) {
- fprintf(stderr, "mgl error: no configs found\n");
- return false;
- }
-
- egl->configs = (EGLConfig*)calloc(num_configs, sizeof(EGLConfig));
- if(!egl->configs) {
- fprintf(stderr, "mgl error: failed to allocate %d configs\n", (int)num_configs);
- return false;
- }
-
- context->gl.eglGetConfigs(egl->egl_display, egl->configs, num_configs, &num_configs);
- for(int i = 0; i < num_configs; i++) {
- egl->ecfg = egl->configs[i];
-
- if(egl_get_config_attrib(egl, egl->ecfg, EGL_COLOR_BUFFER_TYPE) != EGL_RGB_BUFFER)
- continue;
-
- if(!(egl_get_config_attrib(egl, egl->ecfg, EGL_SURFACE_TYPE) & EGL_WINDOW_BIT))
- continue;
-
- if(egl_get_config_attrib(egl, egl->ecfg, EGL_RED_SIZE) != 8)
- continue;
-
- if(egl_get_config_attrib(egl, egl->ecfg, EGL_GREEN_SIZE) != 8)
- continue;
-
- if(egl_get_config_attrib(egl, egl->ecfg, EGL_BLUE_SIZE) != 8)
- continue;
-
- if(egl_get_config_attrib(egl, egl->ecfg, EGL_ALPHA_SIZE) != (alpha ? 8 : 0))
- continue;
-
- XVisualInfo vi = {0};
- vi.visualid = egl_get_config_attrib(egl, egl->ecfg, EGL_NATIVE_VISUAL_ID);
- if(!vi.visualid)
- continue;
-
- int vis_count = 0;
- egl->visual_info = XGetVisualInfo(context->connection, VisualIDMask, &vi, &vis_count);
- if(!egl->visual_info)
- continue;
-
- if(xvisual_match_alpha(context->connection, egl->visual_info, alpha)) {
- break;
- } else {
- XFree(egl->visual_info);
- egl->visual_info = NULL;
- egl->ecfg = NULL;
- }
- }
-
- if(!egl->visual_info) {
- if(egl->configs) {
- free(egl->configs);
- egl->configs = NULL;
- }
- egl->ecfg = NULL;
-
- fprintf(stderr, "mgl error: no appropriate visual found\n");
- return false;
- }
-
- return true;
-}
-
-static void x11_context_egl_deinit(x11_context_egl *self) {
- mgl_context *context = mgl_get_context();
-
- if(self->visual_info) {
- XFree(self->visual_info);
- self->visual_info = NULL;
- }
-
- if(self->configs) {
- free(self->configs);
- self->configs = NULL;
- }
-
- if(self->egl_context) {
- context->gl.eglMakeCurrent(self->egl_display, NULL, NULL, NULL);
- context->gl.eglDestroyContext(self->egl_display, self->egl_context);
- self->egl_context = NULL;
- }
-
- if(self->egl_surface) {
- context->gl.eglDestroySurface(self->egl_display, self->egl_surface);
- self->egl_surface = NULL;
- }
-
- if(self->egl_display) {
- context->gl.eglTerminate(self->egl_display);
- self->egl_display = NULL;
- }
-
- if(self->visual_info) {
- XFree(self->visual_info);
- self->visual_info = NULL;
- }
-}
-
-static bool x11_context_egl_init(x11_context_egl *self, bool alpha) {
- mgl_context *context = mgl_get_context();
- memset(self, 0, sizeof(*self));
-
- const int32_t ctxattr[] = {
- EGL_CONTEXT_CLIENT_VERSION, 2,
- EGL_NONE, EGL_NONE
- };
-
- context->gl.eglBindAPI(EGL_OPENGL_API);
-
- self->egl_display = context->gl.eglGetDisplay((EGLNativeDisplayType)context->connection);
- if(!self->egl_display) {
- fprintf(stderr, "mgl error: x11_context_egl_init failed: eglGetDisplay failed\n");
- x11_context_egl_deinit(self);
- return false;
- }
-
- if(!context->gl.eglInitialize(self->egl_display, NULL, NULL)) {
- fprintf(stderr, "mgl error: x11_context_egl_init failed: eglInitialize failed\n");
- x11_context_egl_deinit(self);
- return false;
- }
-
- if(!egl_context_choose(context, self, alpha)) {
- x11_context_egl_deinit(self);
- return false;
- }
-
- self->egl_context = context->gl.eglCreateContext(self->egl_display, self->ecfg, NULL, ctxattr);
- if(!self->egl_context) {
- fprintf(stderr, "mgl error: x11_context_egl_init failed: failed to create egl context\n");
- x11_context_egl_deinit(self);
- return false;
- }
-
- return true;
-}
-
-static bool x11_context_egl_make_context_current(x11_context_egl *self, Window window) {
- (void)window;
- mgl_context *context = mgl_get_context();
-
- if(!self->egl_surface) {
- self->egl_surface = context->gl.eglCreateWindowSurface(self->egl_display, self->ecfg, (EGLNativeWindowType)window, NULL);
- if(!self->egl_surface) {
- fprintf(stderr, "mgl error: x11_context_egl_make_context_current: failed to create window surface\n");
- return false;
- }
- }
-
- return context->gl.eglMakeCurrent(self->egl_display, self->egl_surface, self->egl_surface, self->egl_context) != 0;
-}
-
-static void x11_context_egl_swap_buffers(x11_context_egl *self, Window window) {
- (void)window;
- mgl_context *context = mgl_get_context();
- context->gl.eglSwapBuffers(self->egl_display, self->egl_surface);
-}
-
-static XVisualInfo* x11_context_egl_get_xvisual_info(x11_context_egl *self) {
- return self->visual_info;
-}
-
-static bool x11_context_egl_set_swap_interval(x11_context_egl *self, Window window, int enabled) {
- (void)window;
- mgl_context *context = mgl_get_context();
- return context->gl.eglSwapInterval(self->egl_display, enabled) == 1;
-}
-
-typedef struct {
- x11_context_glx glx;
- x11_context_egl egl;
- mgl_render_api render_api;
- Colormap color_map;
- XIM xim;
- XIC xic;
- Atom clipboard_atom;
- Atom targets_atom;
- Atom text_atom;
- Atom utf8_string_atom;
- Atom image_png_atom;
- Atom image_jpg_atom;
- Atom image_jpeg_atom;
- Atom image_gif_atom;
- Atom incr_atom;
- Atom net_wm_state_atom;
- Atom net_wm_state_fullscreen_atom;
- Atom net_wm_state_above_atom;
- Atom net_wm_name_atom;
- Atom net_wm_window_type_atom;
- Atom net_wm_window_type_normal_atom;
- Atom net_wm_window_type_dialog_atom;
- Atom net_wm_window_type_notification_atom;
- Atom net_wm_window_type_utility;
- Atom motif_wm_hints_atom;
- Cursor default_cursor;
- Cursor invisible_cursor;
- unsigned int prev_keycode_pressed;
- bool key_was_released;
- bool support_alpha;
-
- /* This only contains connected and active monitors */
- mgl_monitor monitors[MAX_MONITORS];
- int num_monitors;
-
- /*
- Used to stack text event on top of key press/release events and other text events.
- For example pressing a key should give the user both key press and text events
- and for IM with multiple characters entered (such as chinese), we want to trigger
- an event for all of them.
- */
- x11_events_circular_buffer events;
-} x11_context;
-
-static void x11_context_deinit(x11_context *self);
-
-static int x11_context_init(x11_context *self, bool alpha, mgl_render_api render_api) {
- mgl_context *context = mgl_get_context();
-
+int mgl_window_init(mgl_window *self, const char *title, const mgl_window_create_params *params, mgl_window_handle existing_window) {
memset(self, 0, sizeof(*self));
-
- self->render_api = render_api;
-
- /* TODO: Use CLIPBOARD_MANAGER and SAVE_TARGETS to save clipboard in clipboard manager on exit */
-
- /* TODO: Create all of these with one XInternAtoms call instead */
- self->clipboard_atom = XInternAtom(context->connection, "CLIPBOARD", False);
- self->targets_atom = XInternAtom(context->connection, "TARGETS", False);
- self->text_atom = XInternAtom(context->connection, "TEXT", False);
- self->utf8_string_atom = XInternAtom(context->connection, "UTF8_STRING", False);
- self->image_png_atom = XInternAtom(context->connection, "image/png", False);
- self->image_jpg_atom = XInternAtom(context->connection, "image/jpg", False);
- self->image_jpeg_atom = XInternAtom(context->connection, "image/jpeg", False);
- self->image_gif_atom = XInternAtom(context->connection, "image/gif", False);
- self->incr_atom = XInternAtom(context->connection, "INCR", False);
- self->net_wm_state_atom = XInternAtom(context->connection, "_NET_WM_STATE", False);
- self->net_wm_state_fullscreen_atom = XInternAtom(context->connection, "_NET_WM_STATE_FULLSCREEN", False);
- self->net_wm_state_above_atom = XInternAtom(context->connection, "_NET_WM_STATE_ABOVE", False);
- self->net_wm_name_atom = XInternAtom(context->connection, "_NET_WM_NAME", False);
- self->net_wm_window_type_atom = XInternAtom(context->connection, "_NET_WM_WINDOW_TYPE", False);
- self->net_wm_window_type_normal_atom = XInternAtom(context->connection, "_NET_WM_WINDOW_TYPE_NORMAL", False);
- self->net_wm_window_type_dialog_atom = XInternAtom(context->connection, "_NET_WM_WINDOW_TYPE_DIALOG", False);
- self->net_wm_window_type_notification_atom = XInternAtom(context->connection, "_NET_WM_WINDOW_TYPE_NOTIFICATION", False);
- self->net_wm_window_type_utility = XInternAtom(context->connection, "_NET_WM_WINDOW_TYPE_UTILITY", False);
- self->motif_wm_hints_atom = XInternAtom(context->connection, "_MOTIF_WM_HINTS", False);
-
- self->support_alpha = alpha;
-
- switch(self->render_api) {
- case MGL_RENDER_API_GLX: {
- if(!x11_context_glx_init(&self->glx, alpha)) {
- x11_context_deinit(self);
- return -1;
- }
- break;
- }
- case MGL_RENDER_API_EGL: {
- if(!x11_context_egl_init(&self->egl, alpha)) {
- x11_context_deinit(self);
- return -1;
- }
- break;
- }
- }
-
- self->default_cursor = XCreateFontCursor(context->connection, XC_arrow);
- if(!self->default_cursor) {
- x11_context_deinit(self);
- return -1;
- }
-
- const char data[1] = {0};
- Pixmap blank_bitmap = XCreateBitmapFromData(context->connection, DefaultRootWindow(context->connection), data, 1, 1);
- if(!blank_bitmap) {
- x11_context_deinit(self);
- return -1;
- }
-
- XColor dummy;
- self->invisible_cursor = XCreatePixmapCursor(context->connection, blank_bitmap, blank_bitmap, &dummy, &dummy, 0, 0);
- XFreePixmap(context->connection, blank_bitmap);
- if(!self->invisible_cursor) {
- x11_context_deinit(self);
- return -1;
- }
-
- x11_events_circular_buffer_init(&self->events);
-
- return 0;
-}
-
-static void x11_context_clear_monitors(x11_context *self) {
- for(int i = 0; i < self->num_monitors; ++i) {
- mgl_monitor *monitor = &self->monitors[i];
- if(monitor->name) {
- free((char*)monitor->name);
- monitor->name = NULL;
- }
- }
- self->num_monitors = 0;
-}
-
-void x11_context_deinit(x11_context *self) {
- mgl_context *context = mgl_get_context();
-
- x11_context_clear_monitors(self);
-
- if(self->color_map) {
- XFreeColormap(context->connection, self->color_map);
- self->color_map = None;
- }
-
- if(self->invisible_cursor) {
- XFreeCursor(context->connection, self->invisible_cursor);
- self->invisible_cursor = None;
- }
-
- if(self->default_cursor) {
- XFreeCursor(context->connection, self->default_cursor);
- self->default_cursor = None;
- }
-
- if(self->xic) {
- XDestroyIC(self->xic);
- self->xic = NULL;
- }
-
- if(self->xim) {
- XCloseIM(self->xim);
- self->xim = NULL;
- }
-
- switch(self->render_api) {
- case MGL_RENDER_API_GLX: {
- x11_context_glx_deinit(&self->glx);
- break;
- }
- case MGL_RENDER_API_EGL: {
- x11_context_egl_deinit(&self->egl);
- break;
- }
- }
-}
-
-static bool x11_context_make_gl_context_current(x11_context *self, Window window) {
- switch(self->render_api) {
- case MGL_RENDER_API_GLX:
- return x11_context_glx_make_context_current(&self->glx, window);
- case MGL_RENDER_API_EGL:
- return x11_context_egl_make_context_current(&self->egl, window);
- }
- return false;
-}
-
-static void x11_context_swap_buffers(x11_context *self, Window window) {
- switch(self->render_api) {
- case MGL_RENDER_API_GLX: {
- x11_context_glx_swap_buffers(&self->glx, window);
- break;
- }
- case MGL_RENDER_API_EGL: {
- x11_context_egl_swap_buffers(&self->egl, window);
- break;
- }
- }
-}
-
-static XVisualInfo* x11_context_get_xvisual_info(x11_context *self) {
- switch(self->render_api) {
- case MGL_RENDER_API_GLX:
- return x11_context_glx_get_xvisual_info(&self->glx);
- case MGL_RENDER_API_EGL:
- return x11_context_egl_get_xvisual_info(&self->egl);
- }
- return NULL;
-}
-
-static bool x11_context_set_swap_interval(x11_context *self, Window window, int enabled) {
- switch(self->render_api) {
- case MGL_RENDER_API_GLX:
- return x11_context_glx_set_swap_interval(&self->glx, window, enabled);
- case MGL_RENDER_API_EGL:
- return x11_context_egl_set_swap_interval(&self->egl, window, enabled);
- }
- return false;
-}
-
-static bool x11_context_append_event(x11_context *self, const mgl_event *event) {
- return x11_events_circular_buffer_append(&self->events, event);
-}
-
-static bool x11_context_pop_event(x11_context *self, mgl_event *event) {
- return x11_events_circular_buffer_pop(&self->events, event);
-}
-
-static mgl_monitor* x11_context_add_monitor(x11_context *self, RROutput output_id, RRCrtc crtc_id, const char *name, mgl_vec2i pos, mgl_vec2i size, int refresh_rate) {
- if(self->num_monitors == MAX_MONITORS)
- return NULL;
-
- mgl_monitor *monitor = &self->monitors[self->num_monitors];
- monitor->id = output_id;
- monitor->crtc_id = crtc_id;
- monitor->name = strdup(name);
- if(!monitor->name)
- return NULL;
- monitor->pos = pos;
- monitor->size = size;
- monitor->refresh_rate = refresh_rate;
- self->num_monitors++;
-
- return monitor;
-}
-
-static mgl_monitor* x11_context_get_monitor_by_id(x11_context *self, RROutput output_id) {
- for(int i = 0; i < self->num_monitors; ++i) {
- mgl_monitor *monitor = &self->monitors[i];
- if(monitor->id == (int)output_id)
- return monitor;
- }
- return NULL;
-}
-
-static mgl_monitor* x11_context_get_monitor_by_crtc_id(x11_context *self, RRCrtc crtc_id) {
- for(int i = 0; i < self->num_monitors; ++i) {
- mgl_monitor *monitor = &self->monitors[i];
- if(monitor->crtc_id == (int)crtc_id)
- return monitor;
- }
- return NULL;
-}
-
-static bool x11_context_remove_monitor(x11_context *self, RROutput output_id, mgl_event *event) {
- int index_to_remove = -1;
- for(int i = 0; i < self->num_monitors; ++i) {
- mgl_monitor *monitor = &self->monitors[i];
- if(monitor->id == (int)output_id) {
- index_to_remove = i;
- break;
- }
- }
-
- if(index_to_remove == -1)
- return false;
-
- mgl_monitor *monitor = &self->monitors[index_to_remove];
- free((char*)monitor->name);
- monitor->name = NULL;
-
- for(int i = index_to_remove + 1; i < self->num_monitors; ++i) {
- self->monitors[i - 1] = self->monitors[i];
- }
- self->num_monitors--;
-
- event->monitor_disconnected.id = output_id;
- return true;
-}
-
-static int round_int(double value) {
- return value + 0.5;
-}
-
-static int monitor_info_get_framerate(const XRRModeInfo *mode_info) {
- double v_total = mode_info->vTotal;
- if(mode_info->modeFlags & RR_DoubleScan) {
- v_total *= 2;
- }
-
- if(mode_info->modeFlags & RR_Interlace) {
- v_total /= 2;
- }
-
- if(mode_info->hTotal > 0 && v_total > 0.0001) {
- return round_int((double)mode_info->dotClock / ((double)mode_info->hTotal * v_total));
- } else {
- return 0;
- }
-}
-
-static const XRRModeInfo* get_mode_info(const XRRScreenResources *sr, RRMode id) {
- for(int i = 0; i < sr->nmode; ++i) {
- if(sr->modes[i].id == id)
- return &sr->modes[i];
- }
- return NULL;
-}
-
-void mgl_for_each_active_monitor_output(void *display, mgl_active_monitor_callback callback, void *userdata) {
- XRRScreenResources *screen_res = XRRGetScreenResources(display, DefaultRootWindow(display));
- if(!screen_res)
- return;
-
- char display_name[256];
- for(int i = 0; i < screen_res->noutput; ++i) {
- XRROutputInfo *out_info = XRRGetOutputInfo(display, screen_res, screen_res->outputs[i]);
- if(out_info && out_info->crtc && out_info->connection == RR_Connected) {
- XRRCrtcInfo *crt_info = XRRGetCrtcInfo(display, screen_res, out_info->crtc);
- if(crt_info && crt_info->mode) {
- const XRRModeInfo *mode_info = get_mode_info(screen_res, crt_info->mode);
- if(mode_info) {
- snprintf(display_name, sizeof(display_name), "%s", out_info->name);
- mgl_monitor monitor = {
- .id = screen_res->outputs[i],
- .crtc_id = out_info->crtc,
- .name = display_name,
- .pos = { .x = crt_info->x, .y = crt_info->y },
- .size = { .x = (int)crt_info->width, .y = (int)crt_info->height },
- .refresh_rate = monitor_info_get_framerate(mode_info),
- };
- callback(&monitor, userdata);
- }
- }
- if(crt_info)
- XRRFreeCrtcInfo(crt_info);
- }
- if(out_info)
- XRRFreeOutputInfo(out_info);
- }
-
- XRRFreeScreenResources(screen_res);
-}
-
-static void monitor_callback_add_to_x11_context(const mgl_monitor *monitor, void *userdata) {
- x11_context *x11_context = userdata;
- x11_context_add_monitor(x11_context, monitor->id, monitor->crtc_id, monitor->name, monitor->pos, monitor->size, monitor->refresh_rate);
-}
-
-/* TODO: Use gl OML present for other platforms than nvidia? nvidia doesn't support present yet */
-
-/* TODO: check for glx swap control extension string (GLX_EXT_swap_control, etc) */
-static void set_vertical_sync_enabled(mgl_window *self, int enabled) {
- x11_context_set_swap_interval(self->context, self->window, enabled);
-}
-
-static void mgl_window_set_frame_time_limit_monitor(mgl_window *self) {
- int monitor_refresh_rate = 0;
- mgl_vec2i window_center = (mgl_vec2i) { self->pos.x + self->size.x / 2, self->pos.y + self->size.y / 2 };
- for(int i = 0; i < self->num_monitors; ++i) {
- mgl_monitor *monitor = &self->monitors[i];
- if(window_center.x >= monitor->pos.x && window_center.x <= monitor->pos.x + monitor->size.x
- && window_center.y >= monitor->pos.y && window_center.y <= monitor->pos.y + monitor->size.y)
- {
- monitor_refresh_rate = monitor->refresh_rate;
- break;
- }
- }
-
- if(monitor_refresh_rate == 0 && self->num_monitors > 0)
- monitor_refresh_rate = self->monitors[0].refresh_rate;
-
- if(monitor_refresh_rate == 0)
- monitor_refresh_rate = 60;
-
- self->frame_time_limit_monitor = 1.0 / (double)monitor_refresh_rate;
-}
-
-static void mgl_window_on_move(mgl_window *self, int x, int y) {
- self->pos.x = x;
- self->pos.y = y;
- mgl_window_set_frame_time_limit_monitor(self);
-}
-
-static void mgl_window_on_resize(mgl_window *self, int width, int height) {
- self->size.x = width;
- self->size.y = height;
-
- mgl_view view;
- view.position = (mgl_vec2i){ 0, 0 };
- view.size = self->size;
- mgl_window_set_view(self, &view);
- mgl_window_set_scissor(self, &(mgl_scissor){ .position = { 0, 0 }, .size = self->size });
-}
-
-static unsigned long mgl_color_to_x11_pixel(mgl_color color) {
- if(color.a == 0)
- return 0;
- return ((uint32_t)color.a << 24) | (((uint32_t)color.r * color.a / 0xFF) << 16) | (((uint32_t)color.g * color.a / 0xFF) << 8) | ((uint32_t)color.b * color.a / 0xFF);
-}
-
-static void mgl_set_window_type(mgl_window *self, mgl_window_type window_type) {
- mgl_context *context = mgl_get_context();
- x11_context *x11_context = self->context;
- switch(window_type) {
- case MGL_WINDOW_TYPE_NORMAL: {
- XChangeProperty(context->connection, self->window, x11_context->net_wm_window_type_atom, XA_ATOM, 32, PropModeReplace, (unsigned char*)&x11_context->net_wm_window_type_normal_atom, 1L);
- break;
- }
- case MGL_WINDOW_TYPE_DIALOG: {
- XChangeProperty(context->connection, self->window, x11_context->net_wm_window_type_atom, XA_ATOM, 32, PropModeReplace, (unsigned char*)&x11_context->net_wm_window_type_dialog_atom, 1L);
- XChangeProperty(context->connection, self->window, x11_context->net_wm_state_atom, XA_ATOM, 32, PropModeReplace, (unsigned char*)&x11_context->net_wm_state_above_atom, 1L);
- break;
- }
- case MGL_WINDOW_TYPE_NOTIFICATION: {
- const Atom data[2] = {
- x11_context->net_wm_window_type_notification_atom,
- x11_context->net_wm_window_type_utility
- };
- XChangeProperty(context->connection, self->window, x11_context->net_wm_window_type_atom, XA_ATOM, 32, PropModeReplace, (const unsigned char*)data, 2L);
- XChangeProperty(context->connection, self->window, x11_context->net_wm_state_atom, XA_ATOM, 32, PropModeReplace, (unsigned char*)&x11_context->net_wm_state_above_atom, 1L);
- break;
- }
- }
-}
-
-typedef struct {
- unsigned long flags;
- unsigned long functions;
- unsigned long decorations;
- long input_mode;
- unsigned long status;
-} MotifHints;
-
-#define MWM_HINTS_DECORATIONS 2
-
-#define MWM_DECOR_NONE 0
-#define MWM_DECOR_ALL 1
-
-static void mgl_window_set_decorations_visible(mgl_window *self, bool visible) {
- mgl_context *context = mgl_get_context();
- x11_context *x11_context = self->context;
-
- MotifHints motif_hints = {0};
- motif_hints.flags = MWM_HINTS_DECORATIONS;
- motif_hints.decorations = visible ? MWM_DECOR_ALL : MWM_DECOR_NONE;
-
- XChangeProperty(context->connection, self->window, x11_context->motif_wm_hints_atom, x11_context->motif_wm_hints_atom, 32, PropModeReplace, (unsigned char*)&motif_hints, sizeof(motif_hints) / sizeof(long));
-}
-
-static int mgl_window_init(mgl_window *self, const char *title, const mgl_window_create_params *params, Window existing_window) {
- self->window = 0;
- self->context = NULL;
- self->open = false;
- self->focused = false;
+ self->vsync_enabled = true;
self->key_repeat_enabled = true;
- self->frame_time_limit = 0.0;
- self->frame_time_limit_monitor = 0.0;
mgl_clock_init(&self->frame_timer);
- self->clipboard_string = NULL;
- self->clipboard_size = 0;
- self->monitors = NULL;
- self->num_monitors = 0;
- self->pos = (mgl_vec2i){ 0, 0 };
- self->low_latency = false;
-
- mgl_vec2i window_size = params ? params->size : (mgl_vec2i){ 0, 0 };
- if(window_size.x <= 0 || window_size.y <= 0) {
- window_size.x = 640;
- window_size.y = 480;
- }
- self->size = window_size;
-
- mgl_vec2i window_pos = params ? params->position : (mgl_vec2i){ 0, 0 };
-
- self->context = malloc(sizeof(x11_context));
- if(!self->context) {
- fprintf(stderr, "mgl error: failed to allocate x11 context\n");
- return -1;
- }
-
- x11_context *x11_context = self->context;
- if(x11_context_init(x11_context, params ? params->support_alpha : false, params ? params->render_api : MGL_RENDER_API_GLX) != 0) {
- fprintf(stderr, "mgl error: x11_context_init failed\n");
- mgl_window_deinit(self);
- return -1;
- }
-
- self->monitors = x11_context->monitors;
- self->num_monitors = 0;
mgl_context *context = mgl_get_context();
- context->current_window = self;
-
- Window parent_window = params ? params->parent_window : None;
- if(parent_window == 0)
- parent_window = DefaultRootWindow(context->connection);
-
- XVisualInfo *visual_info = x11_context_get_xvisual_info(x11_context);
- x11_context->color_map = XCreateColormap(context->connection, DefaultRootWindow(context->connection), visual_info->visual, AllocNone);
- if(!x11_context->color_map) {
- fprintf(stderr, "mgl error: XCreateColormap failed\n");
- mgl_window_deinit(self);
- return -1;
- }
-
- XSetWindowAttributes window_attr;
- window_attr.override_redirect = params ? params->override_redirect : false;
- window_attr.colormap = x11_context->color_map;
- window_attr.background_pixel = mgl_color_to_x11_pixel(params ? params->background_color : (mgl_color){ .r = 0, .g = 0, .b = 0, .a = 255 });
- window_attr.border_pixel = 0;
- window_attr.bit_gravity = NorthWestGravity;
- window_attr.event_mask =
- KeyPressMask | KeyReleaseMask |
- ButtonPressMask | ButtonReleaseMask |
- PointerMotionMask | Button1MotionMask | Button2MotionMask | Button3MotionMask | Button4MotionMask | Button5MotionMask | ButtonMotionMask |
- StructureNotifyMask | EnterWindowMask | LeaveWindowMask | VisibilityChangeMask | PropertyChangeMask | FocusChangeMask;
-
- const bool hide_window = params ? params->hidden : false;
-
- if(existing_window) {
- if(!XChangeWindowAttributes(context->connection, existing_window, CWColormap | CWEventMask | CWOverrideRedirect | CWBorderPixel | CWBackPixel | CWBitGravity, &window_attr)) {
- fprintf(stderr, "mgl error: XChangeWindowAttributes failed\n");
- mgl_window_deinit(self);
- return -1;
- }
-
- if(params && params->size.x > 0 && params->size.y > 0) {
- XResizeWindow(context->connection, existing_window, params->size.x, params->size.y);
- }
-
- self->window = existing_window;
-
- if(params && params->hide_decorations) {
- mgl_window_set_decorations_visible(self, false);
- }
-
- if(hide_window)
- XUnmapWindow(context->connection, existing_window);
- } else {
- self->window = XCreateWindow(context->connection, parent_window, window_pos.x, window_pos.y,
- window_size.x, window_size.y, 0,
- visual_info->depth, InputOutput, visual_info->visual,
- CWColormap | CWEventMask | CWOverrideRedirect | CWBorderPixel | CWBackPixel | CWBitGravity, &window_attr);
- if(!self->window) {
- fprintf(stderr, "mgl error: XCreateWindow failed\n");
- mgl_window_deinit(self);
- return -1;
- }
-
- if(params && params->hide_decorations) {
- mgl_window_set_decorations_visible(self, false);
- }
-
- mgl_window_set_title(self, title);
- if(!hide_window)
- XMapWindow(context->connection, self->window);
- }
-
- if(params)
- mgl_window_set_size_limits(self, params->min_size, params->max_size);
-
- /* TODO: Call XGetWMProtocols and add wm_delete_window_atom on top, to not overwrite existing wm protocol atoms */
- Atom wm_protocol_atoms[2] = {
- context->wm_delete_window_atom,
- context->net_wm_ping_atom
- };
- XSetWMProtocols(context->connection, self->window, wm_protocol_atoms, 2);
-
- if(context->net_wm_pid_atom) {
- const long pid = getpid();
- XChangeProperty(context->connection, self->window, context->net_wm_pid_atom, XA_CARDINAL,
- 32, PropModeReplace, (const unsigned char*)&pid, 1);
- }
-
- char host_name[HOST_NAME_MAX];
- if(gethostname(host_name, sizeof(host_name)) == 0) {
- XTextProperty txt_prop;
- txt_prop.value = (unsigned char*)host_name;
- txt_prop.encoding = XA_STRING;
- txt_prop.format = 8;
- txt_prop.nitems = strlen(host_name);
- XSetWMClientMachine(context->connection, self->window, &txt_prop);
- }
-
- if(params && params->class_name) {
- XClassHint class_hint = { params->class_name, params->class_name };
- XSetClassHint(context->connection, self->window, &class_hint);
- }
-
- if(params && params->transient_for_window) {
- XSetTransientForHint(context->connection, self->window, params->transient_for_window);
- }
-
- /* TODO: Move this to above XMapWindow? */
- mgl_window_type window_type = params ? params->window_type : MGL_WINDOW_TYPE_NORMAL;
- mgl_set_window_type(self, window_type);
-
- XFlush(context->connection);
-
- if(!x11_context_make_gl_context_current(x11_context, self->window)) {
- fprintf(stderr, "mgl error: failed to make opengl context current!\n");
- mgl_window_deinit(self);
- return -1;
- }
-
- self->vsync_enabled = true;
- set_vertical_sync_enabled(self, self->vsync_enabled ? 1 : 0);
-
- context->gl.glEnable(GL_TEXTURE_2D);
- context->gl.glEnable(GL_BLEND);
- context->gl.glEnable(GL_SCISSOR_TEST);
- context->gl.glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
- context->gl.glEnableClientState(GL_VERTEX_ARRAY);
- context->gl.glEnableClientState(GL_TEXTURE_COORD_ARRAY);
- context->gl.glEnableClientState(GL_COLOR_ARRAY);
- context->gl.glPixelStorei(GL_PACK_ALIGNMENT, 1);
- context->gl.glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
-
- Window dummy_w;
- int dummy_i;
- unsigned int dummy_u;
- XQueryPointer(context->connection, self->window, &dummy_w, &dummy_w, &dummy_i, &dummy_i, &self->cursor_position.x, &self->cursor_position.y, &dummy_u);
-
- x11_context->xim = XOpenIM(context->connection, NULL, NULL, NULL);
- if(!x11_context->xim) {
- fprintf(stderr, "mgl error: XOpenIM failed\n");
- mgl_window_deinit(self);
- return -1;
- }
-
- x11_context->xic = XCreateIC(x11_context->xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, self->window, NULL);
- if(!x11_context->xic) {
- fprintf(stderr, "mgl error: XCreateIC failed\n");
- mgl_window_deinit(self);
- return -1;
+ switch(context->window_system) {
+ case MGL_WINDOW_SYSTEM_NATIVE:
+ assert(false);
+ break;
+ case MGL_WINDOW_SYSTEM_X11:
+ return mgl_window_x11_init(self, title, params, existing_window) ? 0 : -1;
+ case MGL_WINDOW_SYSTEM_WAYLAND:
+ return mgl_window_wayland_init(self, title, params, existing_window) ? 0 : -1;
}
-
- // TODO: This should be done once and monitor events should be done once, no matter how many windows you have
- x11_context_clear_monitors(x11_context);
- mgl_for_each_active_monitor_output(context->connection, monitor_callback_add_to_x11_context, x11_context);
- self->num_monitors = x11_context->num_monitors;
-
- mgl_window_on_resize(self, self->size.x, self->size.y);
- mgl_window_on_move(self, window_pos.x, window_pos.y);
-
- self->open = true;
- self->focused = false; /* TODO: Check if we need to call XGetInputFocus for this, or just wait for focus event */
- return 0;
+ return -1;
}
int mgl_window_create(mgl_window *self, const char *title, const mgl_window_create_params *params) {
- return mgl_window_init(self, title, params, None);
+ return mgl_window_init(self, title, params, 0);
}
-/* TODO: Test this */
int mgl_window_init_from_existing_window(mgl_window *self, mgl_window_handle existing_window) {
return mgl_window_init(self, "", NULL, existing_window);
}
void mgl_window_deinit(mgl_window *self) {
- x11_context *x11_context = self->context;
-
- mgl_window_close(self);
-
- if(x11_context) {
- x11_context_deinit(x11_context);
- free(x11_context);
- self->context = NULL;
- }
-
- if(self->clipboard_string) {
- free(self->clipboard_string);
- self->clipboard_string = NULL;
- }
- self->clipboard_size = 0;
-
- self->open = false;
-
- mgl_context *context = mgl_get_context();
- if(context->current_window == self)
- context->current_window = NULL;
-}
-
-/* Returns MGL_KEY_UNKNOWN on no match */
-static mgl_key x11_keysym_to_mgl_key(KeySym key_sym) {
- if(key_sym >= XK_A && key_sym <= XK_Z)
- return MGL_KEY_A + (key_sym - XK_A);
- /* TODO: Check if this ever happens */
- if(key_sym >= XK_a && key_sym <= XK_z)
- return MGL_KEY_A + (key_sym - XK_a);
- if(key_sym >= XK_0 && key_sym <= XK_9)
- return MGL_KEY_NUM0 + (key_sym - XK_0);
- if(key_sym >= XK_KP_0 && key_sym <= XK_KP_9)
- return MGL_KEY_NUMPAD0 + (key_sym - XK_KP_0);
-
- /* TODO: Fill in the rest */
- switch(key_sym) {
- case XK_space: return MGL_KEY_SPACE;
- case XK_BackSpace: return MGL_KEY_BACKSPACE;
- case XK_Tab: return MGL_KEY_TAB;
- case XK_Return: return MGL_KEY_ENTER;
- case XK_Escape: return MGL_KEY_ESCAPE;
- case XK_Control_L: return MGL_KEY_LCONTROL;
- case XK_Shift_L: return MGL_KEY_LSHIFT;
- case XK_Alt_L: return MGL_KEY_LALT;
- case XK_Super_L: return MGL_KEY_LSYSTEM;
- case XK_Control_R: return MGL_KEY_RCONTROL;
- case XK_Shift_R: return MGL_KEY_RSHIFT;
- case XK_Alt_R: return MGL_KEY_RALT;
- case XK_Super_R: return MGL_KEY_RSYSTEM;
- case XK_Delete: return MGL_KEY_DELETE;
- case XK_Home: return MGL_KEY_HOME;
- case XK_Left: return MGL_KEY_LEFT;
- case XK_Up: return MGL_KEY_UP;
- case XK_Right: return MGL_KEY_RIGHT;
- case XK_Down: return MGL_KEY_DOWN;
- case XK_Page_Up: return MGL_KEY_PAGEUP;
- case XK_Page_Down: return MGL_KEY_PAGEDOWN;
- case XK_End: return MGL_KEY_END;
- case XK_F1: return MGL_KEY_F1;
- case XK_F2: return MGL_KEY_F2;
- case XK_F3: return MGL_KEY_F3;
- case XK_F4: return MGL_KEY_F4;
- case XK_F5: return MGL_KEY_F5;
- case XK_F6: return MGL_KEY_F6;
- case XK_F7: return MGL_KEY_F7;
- case XK_F8: return MGL_KEY_F8;
- case XK_F9: return MGL_KEY_F9;
- case XK_F10: return MGL_KEY_F10;
- case XK_F11: return MGL_KEY_F11;
- case XK_F12: return MGL_KEY_F12;
- case XK_F13: return MGL_KEY_F13;
- case XK_F14: return MGL_KEY_F14;
- case XK_F15: return MGL_KEY_F15;
- case XK_Insert: return MGL_KEY_INSERT;
- case XK_Pause: return MGL_KEY_PAUSE;
- case XK_Print: return MGL_KEY_PRINTSCREEN;
- case XK_KP_Insert: return MGL_KEY_NUMPAD0;
- case XK_KP_End: return MGL_KEY_NUMPAD1;
- case XK_KP_Down: return MGL_KEY_NUMPAD2;
- case XK_KP_Page_Down: return MGL_KEY_NUMPAD3;
- case XK_KP_Left: return MGL_KEY_NUMPAD4;
- case XK_KP_Begin: return MGL_KEY_NUMPAD5;
- case XK_KP_Right: return MGL_KEY_NUMPAD6;
- case XK_KP_Home: return MGL_KEY_NUMPAD7;
- case XK_KP_Up: return MGL_KEY_NUMPAD8;
- case XK_KP_Page_Up: return MGL_KEY_NUMPAD9;
- case XK_KP_Enter: return MGL_KEY_NUMPAD_ENTER;
- case XF86XK_AudioLowerVolume: return MGL_KEY_AUDIO_LOWER_VOLUME;
- case XF86XK_AudioRaiseVolume: return MGL_KEY_AUDIO_RAISE_VOLUME;
- case XF86XK_AudioPlay: return MGL_KEY_AUDIO_PLAY;
- case XF86XK_AudioStop: return MGL_KEY_AUDIO_STOP;
- case XF86XK_AudioPause: return MGL_KEY_AUDIO_PAUSE;
- case XF86XK_AudioMute: return MGL_KEY_AUDIO_MUTE;
- case XF86XK_AudioPrev: return MGL_KEY_AUDIO_PREV;
- case XF86XK_AudioNext: return MGL_KEY_AUDIO_NEXT;
- case XF86XK_AudioRewind: return MGL_KEY_AUDIO_REWIND;
- case XF86XK_AudioForward: return MGL_KEY_AUDIO_FORWARD;
- case XK_dead_acute: return MGL_KEY_DEAD_ACUTE;
- case XK_apostrophe: return MGL_KEY_APOSTROPHE;
- case XK_F16: return MGL_KEY_F16;
- case XK_F17: return MGL_KEY_F17;
- case XK_F18: return MGL_KEY_F18;
- case XK_F19: return MGL_KEY_F19;
- case XK_F20: return MGL_KEY_F20;
- case XK_F21: return MGL_KEY_F21;
- case XK_F22: return MGL_KEY_F22;
- }
- return MGL_KEY_UNKNOWN;
-}
-
-static mgl_mouse_button x11_button_to_mgl_button(unsigned int button) {
- switch(button) {
- case Button1: return MGL_BUTTON_LEFT;
- case Button2: return MGL_BUTTON_MIDDLE;
- case Button3: return MGL_BUTTON_RIGHT;
- case 8: return MGL_BUTTON_XBUTTON1;
- case 9: return MGL_BUTTON_XBUTTON2;
- }
- return MGL_BUTTON_UNKNOWN;
-}
-
-static void mgl_window_handle_key_event(mgl_window *self, XKeyEvent *xkey, mgl_event *event, mgl_context *context) {
- event->key.code = x11_keysym_to_mgl_key(XKeycodeToKeysym(context->connection, xkey->keycode, 0));
- event->key.alt = ((xkey->state & Mod1Mask) != 0) || ((xkey->state & Mod5Mask) != 0);
- event->key.control = ((xkey->state & ControlMask) != 0);
- event->key.shift = ((xkey->state & ShiftMask) != 0);
- event->key.system = ((xkey->state & Mod4Mask) != 0);
-}
-
-static void mgl_window_handle_text_event(mgl_window *self, XEvent *xev) {
- /* Ignore silent keys */
- if(XFilterEvent(xev, None))
- return;
-
- x11_context *x11_context = self->context;
- char buf[128];
-
- Status status;
- KeySym ksym;
- const size_t input_str_len = Xutf8LookupString(x11_context->xic, &xev->xkey, buf, sizeof(buf), &ksym, &status);
- /* TODO: Handle XBufferOverflow */
- if(status == XBufferOverflow || input_str_len == 0)
- return;
-
- /* TODO: Set XIC location on screen with XSetICValues */
-
- for(size_t i = 0; i < input_str_len;) {
- const unsigned char *cp = (const unsigned char*)&buf[i];
- uint32_t codepoint;
- size_t clen;
- if(!mgl_utf8_decode(cp, input_str_len - i, &codepoint, &clen)) {
- codepoint = *cp;
- clen = 1;
- }
-
- mgl_event text_event;
- text_event.type = MGL_EVENT_TEXT_ENTERED;
- text_event.text.codepoint = codepoint;
- text_event.text.size = clen;
- memcpy(text_event.text.str, &buf[i], clen);
- text_event.text.str[clen] = '\0';
- /* Text may contain multiple codepoints so they need to separated into multiple events */
- if(!x11_context_append_event(x11_context, &text_event))
- break;
-
- i += clen;
- }
-}
-
-static bool mgl_on_monitor_added(Display *display, x11_context *x11_context, XRROutputChangeNotifyEvent *rr_output_change_event, XRRScreenResources *screen_res, RROutput output_id, XRROutputInfo *out_info, mgl_event *event) {
- char display_name[256];
- mgl_monitor *monitor = NULL;
- XRRCrtcInfo *crt_info = NULL;
- const XRRModeInfo *mode_info = NULL;
-
- if(!rr_output_change_event->mode)
- return false;
-
- if(x11_context_get_monitor_by_id(x11_context, output_id))
- return false;
-
- if(out_info->nameLen >= (int)sizeof(display_name))
- return false;
-
- crt_info = XRRGetCrtcInfo(display, screen_res, out_info->crtc);
- if(!crt_info)
- goto done;
-
- mode_info = get_mode_info(screen_res, rr_output_change_event->mode);
- if(!mode_info)
- goto done;
-
- memcpy(display_name, out_info->name, out_info->nameLen);
- display_name[out_info->nameLen] = '\0';
-
- monitor = x11_context_add_monitor(x11_context, output_id, out_info->crtc, display_name,
- (mgl_vec2i){ .x = crt_info->x, .y = crt_info->y },
- (mgl_vec2i){ .x = (int)crt_info->width, .y = (int)crt_info->height },
- monitor_info_get_framerate(mode_info));
-
- if(!monitor)
- goto done;
-
- event->monitor_connected.id = monitor->id;
- event->monitor_connected.name = monitor->name;
- event->monitor_connected.x = monitor->pos.x;
- event->monitor_connected.y = monitor->pos.y;
- event->monitor_connected.width = monitor->size.x;
- event->monitor_connected.height = monitor->size.y;
- event->monitor_connected.refresh_rate = monitor->refresh_rate;
-
- done:
- if(crt_info)
- XRRFreeCrtcInfo(crt_info);
- return monitor != NULL;
-}
-
-static bool mgl_on_monitor_state_changed(Display *display, x11_context *x11_context, XRROutputChangeNotifyEvent *rr_output_change_event, mgl_event *event) {
- bool state_changed = false;
- XRROutputInfo *out_info = NULL;
-
- if(!rr_output_change_event->output)
- return false;
-
- XRRScreenResources *screen_res = XRRGetScreenResources(display, DefaultRootWindow(display));
- if(!screen_res)
- return false;
-
- out_info = XRRGetOutputInfo(display, screen_res, rr_output_change_event->output);
- if(out_info && out_info->crtc && out_info->connection == RR_Connected) {
- state_changed = mgl_on_monitor_added(display, x11_context, rr_output_change_event, screen_res, rr_output_change_event->output, out_info, event);
- } else {
- state_changed = x11_context_remove_monitor(x11_context, rr_output_change_event->output, event);
- }
-
- if(out_info)
- XRRFreeOutputInfo(out_info);
-
- XRRFreeScreenResources(screen_res);
- return state_changed;
-}
-
-static bool mgl_on_monitor_property_changed(Display *display, x11_context *x11_context, XRRCrtcChangeNotifyEvent *rr_crtc_change_event, mgl_event *event) {
- if(!rr_crtc_change_event->crtc)
- return false;
-
- mgl_monitor *monitor = x11_context_get_monitor_by_crtc_id(x11_context, rr_crtc_change_event->crtc);
- if(!monitor)
- return false;
-
- XRRScreenResources *screen_res = XRRGetScreenResources(display, DefaultRootWindow(display));
- if(!screen_res)
- return false;
-
- monitor->pos = (mgl_vec2i){ .x = rr_crtc_change_event->x, .y = rr_crtc_change_event->y };
- monitor->size = (mgl_vec2i){ .x = rr_crtc_change_event->width, .y = rr_crtc_change_event->height };
- const XRRModeInfo *mode_info = get_mode_info(screen_res, rr_crtc_change_event->mode);
- if(mode_info)
- monitor->refresh_rate = monitor_info_get_framerate(mode_info);
-
- XRRFreeScreenResources(screen_res);
-
- event->monitor_property_changed.id = monitor->id;
- event->monitor_property_changed.name = monitor->name;
- event->monitor_property_changed.x = monitor->pos.x;
- event->monitor_property_changed.y = monitor->pos.y;
- event->monitor_property_changed.width = monitor->size.x;
- event->monitor_property_changed.height = monitor->size.y;
- event->monitor_property_changed.refresh_rate = monitor->refresh_rate;
- return true;
-}
-
-/* Returns true if an event was generated */
-static bool mgl_on_rr_notify(mgl_context *context, x11_context *x11_context, XEvent *xev, int subtype, mgl_event *event) {
- switch(subtype) {
- case RRNotify_CrtcChange: {
- XRRCrtcChangeNotifyEvent *rr_crtc_change_event = (XRRCrtcChangeNotifyEvent*)xev;
- return mgl_on_monitor_property_changed(context->connection, x11_context, rr_crtc_change_event, event);
- }
- case RRNotify_OutputChange: {
- XRROutputChangeNotifyEvent *rr_output_change_event = (XRROutputChangeNotifyEvent*)xev;
- return mgl_on_monitor_state_changed(context->connection, x11_context, rr_output_change_event, event);
- }
- }
- return false;
-}
-
-static void mgl_window_on_receive_event(mgl_window *self, XEvent *xev, mgl_event *event, mgl_context *context, bool injected) {
- x11_context *x11_context = self->context;
- switch(xev->type - context->randr_event_base) {
- case RRScreenChangeNotify: {
- XRRUpdateConfiguration(xev);
- event->type = MGL_EVENT_UNKNOWN;
- return;
- }
- case RRNotify: {
- XRRNotifyEvent *rr_event = (XRRNotifyEvent*)xev;
- if(mgl_on_rr_notify(context, x11_context, xev, rr_event->subtype, event)) {
- self->num_monitors = x11_context->num_monitors;
- return;
- }
-
- event->type = MGL_EVENT_UNKNOWN;
- return;
- }
- }
-
- switch(xev->type) {
- case KeyPress: {
- if(!self->key_repeat_enabled && xev->xkey.keycode == x11_context->prev_keycode_pressed && !x11_context->key_was_released) {
- event->type = MGL_EVENT_UNKNOWN;
- return;
- }
-
- x11_context->prev_keycode_pressed = xev->xkey.keycode;
- x11_context->key_was_released = false;
-
- event->type = MGL_EVENT_KEY_PRESSED;
- mgl_window_handle_key_event(self, &xev->xkey, event, context);
- mgl_window_handle_text_event(self, xev);
- return;
- }
- case KeyRelease: {
- if(xev->xkey.keycode == x11_context->prev_keycode_pressed)
- x11_context->key_was_released = true;
-
- event->type = MGL_EVENT_KEY_RELEASED;
- mgl_window_handle_key_event(self, &xev->xkey, event, context);
- return;
- }
- case ButtonPress: {
- if(xev->xbutton.button == Button4) {
- /* Mouse scroll up */
- event->type = MGL_EVENT_MOUSE_WHEEL_SCROLLED;
- event->mouse_wheel_scroll.delta = 1;
- event->mouse_wheel_scroll.x = xev->xbutton.x;
- event->mouse_wheel_scroll.y = xev->xbutton.y;
- } else if(xev->xbutton.button == Button5) {
- /* Mouse scroll down */
- event->type = MGL_EVENT_MOUSE_WHEEL_SCROLLED;
- event->mouse_wheel_scroll.delta = -1;
- event->mouse_wheel_scroll.x = xev->xbutton.x;
- event->mouse_wheel_scroll.y = xev->xbutton.y;
- } else {
- event->type = MGL_EVENT_MOUSE_BUTTON_PRESSED;
- event->mouse_button.button = x11_button_to_mgl_button(xev->xbutton.button);
- event->mouse_button.x = xev->xbutton.x;
- event->mouse_button.y = xev->xbutton.y;
- }
- return;
- }
- case ButtonRelease: {
- event->type = MGL_EVENT_MOUSE_BUTTON_RELEASED;
- event->mouse_button.button = x11_button_to_mgl_button(xev->xbutton.button);
- event->mouse_button.x = xev->xbutton.x;
- event->mouse_button.y = xev->xbutton.y;
- return;
- }
- case FocusIn: {
- XSetICFocus(x11_context->xic);
-
- XWMHints* hints = XGetWMHints(context->connection, self->window);
- if(hints) {
- hints->flags &= ~XUrgencyHint;
- XSetWMHints(context->connection, self->window, hints);
- XFree(hints);
- }
-
- event->type = MGL_EVENT_GAINED_FOCUS;
- self->focused = true;
- return;
- }
- case FocusOut: {
- XUnsetICFocus(x11_context->xic);
- event->type = MGL_EVENT_LOST_FOCUS;
- self->focused = false;
- return;
- }
- case ConfigureNotify: {
- if(!injected)
- while(XCheckTypedWindowEvent(context->connection, self->window, ConfigureNotify, xev)) {}
-
- if(xev->xconfigure.x != self->pos.x || xev->xconfigure.y != self->pos.y) {
- mgl_window_on_move(self, xev->xconfigure.x, xev->xconfigure.y);
- }
-
- if(xev->xconfigure.width != self->size.x || xev->xconfigure.height != self->size.y) {
- mgl_window_on_resize(self, xev->xconfigure.width, xev->xconfigure.height);
-
- event->type = MGL_EVENT_RESIZED;
- event->size.width = self->size.x;
- event->size.height = self->size.y;
- return;
- }
-
- event->type = MGL_EVENT_UNKNOWN;
- return;
- }
- case MotionNotify: {
- if(!injected)
- while(XCheckTypedWindowEvent(context->connection, self->window, MotionNotify, xev)) {}
-
- self->cursor_position.x = xev->xmotion.x;
- self->cursor_position.y = xev->xmotion.y;
-
- event->type = MGL_EVENT_MOUSE_MOVED;
- event->mouse_move.x = self->cursor_position.x;
- event->mouse_move.y = self->cursor_position.y;
- return;
- }
- case SelectionClear: {
- event->type = MGL_EVENT_UNKNOWN;
- return;
- }
- case SelectionRequest: {
- XSelectionEvent selection_event;
- selection_event.type = SelectionNotify;
- selection_event.requestor = xev->xselectionrequest.requestor;
- selection_event.selection = xev->xselectionrequest.selection;
- selection_event.property = xev->xselectionrequest.property;
- selection_event.time = xev->xselectionrequest.time;
- selection_event.target = xev->xselectionrequest.target;
-
- if(selection_event.selection == x11_context->clipboard_atom) {
- /* TODO: Support ascii text separately by unsetting the 8th bit in the clipboard string? */
- if(selection_event.target == x11_context->targets_atom) {
- /* A client requested for our valid conversion targets */
- int num_targets = 3;
- Atom targets[4];
- targets[0] = x11_context->targets_atom;
- targets[1] = x11_context->text_atom;
- targets[2] = XA_STRING;
- if(x11_context->utf8_string_atom) {
- targets[3] = x11_context->utf8_string_atom;
- num_targets = 4;
- }
-
- XChangeProperty(context->connection, selection_event.requestor, selection_event.property, XA_ATOM, 32, PropModeReplace, (unsigned char*)targets, num_targets);
- selection_event.target = x11_context->targets_atom;
- XSendEvent(context->connection, selection_event.requestor, True, NoEventMask, (XEvent*)&selection_event);
- XFlush(context->connection);
- event->type = MGL_EVENT_UNKNOWN;
- return;
- } else if(selection_event.target == XA_STRING || (!x11_context->utf8_string_atom && selection_event.target == x11_context->text_atom)) {
- /* A client requested ascii clipboard */
- XChangeProperty(context->connection, selection_event.requestor, selection_event.property, XA_STRING, 8, PropModeReplace, self->clipboard_string, self->clipboard_size);
- selection_event.target = XA_STRING;
- XSendEvent(context->connection, selection_event.requestor, True, NoEventMask, (XEvent*)&selection_event);
- XFlush(context->connection);
- event->type = MGL_EVENT_UNKNOWN;
- return;
- } else if(x11_context->utf8_string_atom && (selection_event.target == x11_context->utf8_string_atom || selection_event.target == x11_context->text_atom)) {
- /* A client requested utf8 clipboard */
- XChangeProperty(context->connection, selection_event.requestor, selection_event.property, x11_context->utf8_string_atom, 8, PropModeReplace, self->clipboard_string, self->clipboard_size);
- selection_event.target = x11_context->utf8_string_atom;
- XSendEvent(context->connection, selection_event.requestor, True, NoEventMask, (XEvent*)&selection_event);
- XFlush(context->connection);
- event->type = MGL_EVENT_UNKNOWN;
- return;
- }
- }
-
- selection_event.property = None;
- XSendEvent(context->connection, selection_event.requestor, True, NoEventMask, (XEvent*)&selection_event);
- XFlush(context->connection);
- event->type = MGL_EVENT_UNKNOWN;
- return;
- }
- case ClientMessage: {
- if(xev->xclient.format == 32 && (unsigned long)xev->xclient.data.l[0] == context->wm_delete_window_atom) {
- event->type = MGL_EVENT_CLOSED;
- self->open = false;
- return;
- } else if(xev->xclient.format == 32 && (unsigned long)xev->xclient.data.l[0] == context->net_wm_ping_atom) {
- xev->xclient.window = DefaultRootWindow(context->connection);
- XSendEvent(context->connection, DefaultRootWindow(context->connection), False, SubstructureNotifyMask | SubstructureRedirectMask, xev);
- XFlush(context->connection);
- }
- event->type = MGL_EVENT_UNKNOWN;
- return;
- }
- case MappingNotify: {
- /* TODO: Only handle this globally once for mgl, not for each window */
- XRefreshKeyboardMapping(&xev->xmapping);
- event->type = MGL_EVENT_MAPPING_CHANGED;
- event->mapping_changed.type = MappingModifier;
- switch(xev->xmapping.request) {
- case MappingModifier:
- event->mapping_changed.type = MGL_MAPPING_CHANGED_MODIFIER;
- break;
- case MappingKeyboard:
- event->mapping_changed.type = MGL_MAPPING_CHANGED_KEYBOARD;
- break;
- case MappingPointer:
- event->mapping_changed.type = MGL_MAPPING_CHANGED_POINTER;
- break;
- }
- return;
- }
- default: {
- event->type = MGL_EVENT_UNKNOWN;
- return;
- }
- }
+ if(self->deinit)
+ self->deinit(self);
}
void mgl_window_clear(mgl_window *self, mgl_color color) {
+ (void)self;
mgl_context *context = mgl_get_context();
context->gl.glClearColor((float)color.r / 255.0f, (float)color.g / 255.0f, (float)color.b / 255.0f, (float)color.a / 255.0f);
context->gl.glClear(GL_COLOR_BUFFER_BIT);
}
bool mgl_window_poll_event(mgl_window *self, mgl_event *event) {
- mgl_context *context = mgl_get_context();
- Display *display = context->connection;
- x11_context *x11_context = self->context;
-
- if(x11_context_pop_event(x11_context, event))
- return true;
-
- if(XPending(display)) {
- XEvent xev; /* TODO: Move to window struct */
- XNextEvent(display, &xev);
- if(xev.xany.window == self->window || xev.type == ClientMessage || xev.type == MappingNotify || xev.type - context->randr_event_base >= 0)
- mgl_window_on_receive_event(self, &xev, event, context, false);
- else
- event->type = MGL_EVENT_UNKNOWN;
- return true;
- } else {
- return false;
- }
+ return self->poll_event(self, event);
}
bool mgl_window_inject_x11_event(mgl_window *self, XEvent *xev, mgl_event *event) {
- mgl_context *context = mgl_get_context();
- event->type = MGL_EVENT_UNKNOWN;
- mgl_window_on_receive_event(self, xev, event, context, true);
- return event->type != MGL_EVENT_UNKNOWN;
+ if(self->inject_x11_event)
+ return self->inject_x11_event(self, xev, event);
+ else
+ return false;
}
void mgl_window_display(mgl_window *self) {
mgl_context *context = mgl_get_context();
- x11_context_swap_buffers(self->context, self->window);
+ self->swap_buffers(self);
if(self->low_latency) {
context->gl.glFlush();
context->gl.glFinish();
@@ -1651,12 +105,7 @@ void mgl_window_get_scissor(mgl_window *self, mgl_scissor *scissor) {
}
void mgl_window_set_visible(mgl_window *self, bool visible) {
- mgl_context *context = mgl_get_context();
- if(visible)
- XMapWindow(context->connection, self->window);
- else
- XUnmapWindow(context->connection, self->window);
- XFlush(context->connection);
+ self->set_visible(self, visible);
}
bool mgl_window_is_open(const mgl_window *self) {
@@ -1672,19 +121,7 @@ bool mgl_window_is_key_pressed(const mgl_window *self, mgl_key key) {
if(key < 0 || key >= __MGL_NUM_KEYS__)
return false;
- mgl_context *context = mgl_get_context();
- const KeySym keysym = mgl_key_to_x11_keysym(key);
- if(keysym == XK_VoidSymbol)
- return false;
-
- KeyCode keycode = XKeysymToKeycode(context->connection, keysym);
- if(keycode == 0)
- return false;
-
- char keys[32];
- XQueryKeymap(context->connection, keys);
-
- return (keys[keycode / 8] & (1 << (keycode % 8))) != 0;
+ return self->is_key_pressed(self, key);
}
/* TODO: Track keys with events instead, but somehow handle window focus lost */
@@ -1692,48 +129,23 @@ bool mgl_window_is_mouse_button_pressed(const mgl_window *self, mgl_mouse_button
if(button < 0 || button >= __MGL_NUM_MOUSE_BUTTONS__)
return false;
- mgl_context *context = mgl_get_context();
- Window root, child;
- int root_x, root_y, win_x, win_y;
-
- unsigned int buttons_mask = 0;
- XQueryPointer(context->connection, DefaultRootWindow(context->connection), &root, &child, &root_x, &root_y, &win_x, &win_y, &buttons_mask);
-
- switch(button) {
- case MGL_BUTTON_LEFT: return buttons_mask & Button1Mask;
- case MGL_BUTTON_MIDDLE: return buttons_mask & Button2Mask;
- case MGL_BUTTON_RIGHT: return buttons_mask & Button3Mask;
- case MGL_BUTTON_XBUTTON1: return false; /* Not supported by x11 */
- case MGL_BUTTON_XBUTTON2: return false; /* Not supported by x11 */
- default: return false;
- }
+ return self->is_mouse_button_pressed(self, button);
+}
- return false;
+mgl_window_handle mgl_window_get_system_handle(const mgl_window *self) {
+ return self->get_system_handle(self);
}
void mgl_window_close(mgl_window *self) {
- mgl_context *context = mgl_get_context();
- if(self->window) {
- XDestroyWindow(context->connection, self->window);
- XSync(context->connection, False);
- self->window = None;
- }
- self->open = false;
+ self->close(self);
}
void mgl_window_set_title(mgl_window *self, const char *title) {
- mgl_context *context = mgl_get_context();
- x11_context *x11_context = self->context;
-
- XStoreName(context->connection, self->window, title);
- XChangeProperty(context->connection, self->window, x11_context->net_wm_name_atom, x11_context->utf8_string_atom, 8, PropModeReplace, (unsigned char*)title, strlen(title));
- XFlush(context->connection);
+ self->set_title(self, title);
}
void mgl_window_set_cursor_visible(mgl_window *self, bool visible) {
- mgl_context *context = mgl_get_context();
- x11_context *x11_context = self->context;
- XDefineCursor(context->connection, self->window, visible ? x11_context->default_cursor : x11_context->invisible_cursor);
+ self->set_cursor_visible(self, visible);
}
void mgl_window_set_framerate_limit(mgl_window *self, int fps) {
@@ -1744,52 +156,19 @@ void mgl_window_set_framerate_limit(mgl_window *self, int fps) {
}
void mgl_window_set_vsync_enabled(mgl_window *self, bool enabled) {
- self->vsync_enabled = enabled;
- set_vertical_sync_enabled(self, self->vsync_enabled ? 1 : 0);
+ self->set_vsync_enabled(self, enabled);
}
bool mgl_window_is_vsync_enabled(const mgl_window *self) {
- return self->vsync_enabled;
+ return self->is_vsync_enabled(self);
}
void mgl_window_set_fullscreen(mgl_window *self, bool fullscreen) {
- mgl_context *context = mgl_get_context();
- x11_context *x11_context = self->context;
-
- XEvent xev;
- xev.type = ClientMessage;
- xev.xclient.window = self->window;
- xev.xclient.message_type = x11_context->net_wm_state_atom;
- xev.xclient.format = 32;
- xev.xclient.data.l[0] = fullscreen ? 1 : 0;
- xev.xclient.data.l[1] = x11_context->net_wm_state_fullscreen_atom;
- xev.xclient.data.l[2] = 0;
- xev.xclient.data.l[3] = 1;
- xev.xclient.data.l[4] = 0;
-
- if(!XSendEvent(context->connection, DefaultRootWindow(context->connection), 0, SubstructureRedirectMask | SubstructureNotifyMask, &xev)) {
- fprintf(stderr, "mgl warning: failed to change window fullscreen state\n");
- return;
- }
-
- XFlush(context->connection);
+ self->set_fullscreen(self, fullscreen);
}
bool mgl_window_is_fullscreen(const mgl_window *self) {
- mgl_context *context = mgl_get_context();
- x11_context *x11_context = self->context;
-
- bool is_fullscreen = false;
- Atom type = None;
- int format = 0;
- unsigned long items = 0;
- unsigned long remaining_bytes = 0;
- unsigned char *data = NULL;
- if(XGetWindowProperty(context->connection, self->window, x11_context->net_wm_state_atom, 0, 1024, False, PropertyNewValue, &type, &format, &items, &remaining_bytes, &data) == Success && data) {
- is_fullscreen = format == 32 && *(unsigned long*)data == x11_context->net_wm_state_fullscreen_atom;
- XFree(data);
- }
- return is_fullscreen;
+ return self->is_fullscreen(self);
}
void mgl_window_set_low_latency(mgl_window *self, bool low_latency) {
@@ -1801,262 +180,23 @@ bool mgl_window_is_low_latency_enabled(const mgl_window *self) {
}
void mgl_window_set_position(mgl_window *self, mgl_vec2i position) {
- XMoveWindow(mgl_get_context()->connection, self->window, position.x, position.y);
- XFlush(mgl_get_context()->connection);
+ self->set_position(self, position);
}
void mgl_window_set_size(mgl_window *self, mgl_vec2i size) {
- if(size.x < 0)
- size.x = 0;
- if(size.y < 0)
- size.y = 0;
- XResizeWindow(mgl_get_context()->connection, self->window, size.x, size.y);
- XFlush(mgl_get_context()->connection);
+ self->set_size(self, size);
}
void mgl_window_set_size_limits(mgl_window *self, mgl_vec2i minimum, mgl_vec2i maximum) {
- XSizeHints *size_hints = XAllocSizeHints();
- if(size_hints) {
- size_hints->width = self->size.x;
- size_hints->min_width = minimum.x;
- size_hints->max_width = maximum.x;
-
- size_hints->height = self->size.y;
- size_hints->min_height = minimum.y;
- size_hints->max_height = maximum.y;
-
- size_hints->flags = PSize;
- if(size_hints->min_width || size_hints->min_height)
- size_hints->flags |= PMinSize;
- if(size_hints->max_width || size_hints->max_height)
- size_hints->flags |= PMaxSize;
-
- mgl_context *context = mgl_get_context();
- XSetWMNormalHints(context->connection, self->window, size_hints);
- XFree(size_hints);
- XFlush(context->connection);
- } else {
- fprintf(stderr, "mgl warning: failed to set window size hints\n");
- }
+ self->set_size_limits(self, minimum, maximum);
}
void mgl_window_set_clipboard(mgl_window *self, const char *str, size_t size) {
- mgl_context *context = mgl_get_context();
- x11_context *x11_context = self->context;
-
- if(self->clipboard_string) {
- free(self->clipboard_string);
- self->clipboard_string = NULL;
- self->clipboard_size = 0;
- }
-
- XSetSelectionOwner(context->connection, x11_context->clipboard_atom, self->window, CurrentTime);
- XFlush(context->connection);
-
- /* Check if setting the selection owner was successful */
- if(XGetSelectionOwner(context->connection, x11_context->clipboard_atom) != self->window) {
- fprintf(stderr, "mgl error: mgl_window_set_clipboard failed\n");
- return;
- }
-
- self->clipboard_string = malloc(size + 1);
- if(!self->clipboard_string) {
- fprintf(stderr, "mgl error: failed to allocate string for clipboard\n");
- return;
- }
- memcpy(self->clipboard_string, str, size);
- self->clipboard_string[size] = '\0';
- self->clipboard_size = size;
-}
-
-static Atom find_matching_atom(const Atom *supported_atoms, size_t num_supported_atoms, const Atom *atoms, size_t num_atoms) {
- for(size_t j = 0; j < num_supported_atoms; ++j) {
- for(size_t i = 0; i < num_atoms; ++i) {
- if(atoms[i] == supported_atoms[j])
- return atoms[i];
- }
- }
- return None;
-}
-
-static mgl_clipboard_type atom_type_to_supported_clipboard_type(x11_context *x11_context, Atom type, int format) {
- if((type == x11_context->utf8_string_atom || type == XA_STRING || type == x11_context->text_atom) && format == 8) {
- return MGL_CLIPBOARD_TYPE_STRING;
- } else if(type == x11_context->image_png_atom && format == 8){
- return MGL_CLIPBOARD_TYPE_IMAGE_PNG;
- } else if((type == x11_context->image_jpg_atom || type == x11_context->image_jpeg_atom) && format == 8){
- return MGL_CLIPBOARD_TYPE_IMAGE_JPG;
- } else if(type == x11_context->image_gif_atom && format == 8){
- return MGL_CLIPBOARD_TYPE_IMAGE_GIF;
- } else {
- return -1;
- }
+ self->set_clipboard(self, str, size);
}
bool mgl_window_get_clipboard(mgl_window *self, mgl_clipboard_callback callback, void *userdata, uint32_t clipboard_types) {
- assert(callback);
-
- mgl_context *context = mgl_get_context();
- x11_context *x11_context = self->context;
-
- Window selection_owner = XGetSelectionOwner(context->connection, x11_context->clipboard_atom);
-
- if(!selection_owner)
- return false;
-
- /* Return data immediately if we are the owner of the clipboard, because we can't process other events in the below event loop */
- if(selection_owner == self->window) {
- if(!self->clipboard_string)
- return false;
-
- return callback((const unsigned char*)self->clipboard_string, self->clipboard_size, MGL_CLIPBOARD_TYPE_STRING, userdata);
- }
-
- XEvent xev;
- while(XCheckTypedWindowEvent(context->connection, self->window, SelectionNotify, &xev)) {}
-
- /* Sorted by preference */
- /* TODO: Support more types (BITMAP?, PIXMAP?) */
- Atom supported_clipboard_types[7];
-
- int supported_clipboard_type_index = 0;
- if(clipboard_types & MGL_CLIPBOARD_TYPE_IMAGE_PNG) {
- supported_clipboard_types[supported_clipboard_type_index++] = x11_context->image_png_atom;
- }
-
- if(clipboard_types & MGL_CLIPBOARD_TYPE_IMAGE_JPG) {
- supported_clipboard_types[supported_clipboard_type_index++] = x11_context->image_jpeg_atom;
- }
-
- if(clipboard_types & MGL_CLIPBOARD_TYPE_IMAGE_GIF) {
- supported_clipboard_types[supported_clipboard_type_index++] = x11_context->image_gif_atom;
- }
-
- if(clipboard_types & MGL_CLIPBOARD_TYPE_STRING) {
- supported_clipboard_types[supported_clipboard_type_index++] = x11_context->utf8_string_atom;
- supported_clipboard_types[supported_clipboard_type_index++] = XA_STRING;
- supported_clipboard_types[supported_clipboard_type_index++] = x11_context->text_atom;
- }
-
- const unsigned long num_supported_clipboard_types = supported_clipboard_type_index;
-
- Atom requested_clipboard_type = None;
- const Atom XA_TARGETS = XInternAtom(context->connection, "TARGETS", False);
- XConvertSelection(context->connection, x11_context->clipboard_atom, XA_TARGETS, x11_context->clipboard_atom, self->window, CurrentTime);
-
- mgl_clock timeout_timer;
- mgl_clock_init(&timeout_timer);
- bool success = false;
-
- const double timeout_seconds = 5.0;
- while(mgl_clock_get_elapsed_time_seconds(&timeout_timer) < timeout_seconds) {
- /* TODO: Wait for SelectionNotify event instead */
- while(XCheckTypedWindowEvent(context->connection, self->window, SelectionNotify, &xev)) {
- if(mgl_clock_get_elapsed_time_seconds(&timeout_timer) >= timeout_seconds)
- break;
-
- if(!xev.xselection.property)
- continue;
-
- if(!xev.xselection.target || xev.xselection.selection != x11_context->clipboard_atom)
- continue;
-
- Atom type = None;
- int format;
- unsigned long items;
- unsigned long remaining_bytes = 0;
- unsigned char *data = NULL;
-
- if(xev.xselection.target == XA_TARGETS && requested_clipboard_type == None) {
- /* TODO: Wait for PropertyNotify event instead */
- if(XGetWindowProperty(context->connection, xev.xselection.requestor, xev.xselection.property, 0, 1024, False, PropertyNewValue, &type, &format, &items, &remaining_bytes, &data) == Success && data) {
- if(type != x11_context->incr_atom && type == XA_ATOM && format == 32) {
- requested_clipboard_type = find_matching_atom(supported_clipboard_types, num_supported_clipboard_types, (Atom*)data, items);
- if(requested_clipboard_type == None) {
- /* Pasting clipboard data type we dont support */
- XFree(data);
- goto done;
- } else {
- XConvertSelection(context->connection, x11_context->clipboard_atom, requested_clipboard_type, x11_context->clipboard_atom, self->window, CurrentTime);
- }
- }
-
- XFree(data);
- }
- } else if(xev.xselection.target == requested_clipboard_type) {
- bool got_data = false;
- long chunk_size = 65536;
-
- //XDeleteProperty(context->connection, self->window, x11_context->incr_atom);
- //XFlush(context->connection);
-
- while(mgl_clock_get_elapsed_time_seconds(&timeout_timer) < timeout_seconds) {
- unsigned long offset = 0;
-
- /* offset is specified in XGetWindowProperty as a multiple of 32-bit (4 bytes) */
- /* TODO: Wait for PropertyNotify event instead */
- while(XGetWindowProperty(context->connection, xev.xselection.requestor, xev.xselection.property, offset/4, chunk_size, True, PropertyNewValue, &type, &format, &items, &remaining_bytes, &data) == Success) {
- if(mgl_clock_get_elapsed_time_seconds(&timeout_timer) >= timeout_seconds)
- break;
-
- if(type == x11_context->incr_atom) {
- if(data)
- chunk_size = *(long*)data;
- XDeleteProperty(context->connection, self->window, x11_context->incr_atom);
- XFlush(context->connection);
- XFree(data);
- data = NULL;
- break;
- } else if(type == requested_clipboard_type) {
- got_data = true;
- }
-
- if(!got_data && items == 0) {
- XDeleteProperty(context->connection, self->window, type);
- XFlush(context->connection);
- if(data) {
- XFree(data);
- data = NULL;
- }
- break;
- }
-
- const size_t num_items_bytes = items * (format/8); /* format is the bit size of the data */
- if(got_data) {
- const mgl_clipboard_type clipboard_type = atom_type_to_supported_clipboard_type(x11_context, type, format);
- if(data && num_items_bytes > 0 && clipboard_type != -1) {
- if(!callback(data, num_items_bytes, clipboard_type, userdata)) {
- XFree(data);
- goto done;
- }
- }
- }
- offset += num_items_bytes;
-
- if(data) {
- XFree(data);
- data = NULL;
- }
-
- if(got_data && num_items_bytes == 0/* && format == 8*/) {
- success = true;
- goto done;
- }
-
- if(remaining_bytes == 0)
- break;
- }
- }
-
- goto done;
- }
- }
- }
-
- done:
- if(requested_clipboard_type)
- XDeleteProperty(context->connection, self->window, requested_clipboard_type);
- return success;
+ return self->get_clipboard(self, callback, userdata, clipboard_types);
}
typedef struct {
@@ -2108,26 +248,21 @@ bool mgl_window_get_clipboard_string(mgl_window *self, char **str, size_t *size)
}
void mgl_window_set_key_repeat_enabled(mgl_window *self, bool enabled) {
- self->key_repeat_enabled = enabled;
+ self->set_key_repeat_enabled(self, enabled);
}
void mgl_window_flush(mgl_window *self) {
- mgl_context *context = mgl_get_context();
- XFlush(context->connection);
+ self->flush(self);
}
void* mgl_window_get_egl_display(mgl_window *self) {
- x11_context *x11_context = self->context;
- if(x11_context->render_api == MGL_RENDER_API_EGL)
- return x11_context->egl.egl_display;
- else
- return NULL;
+ return self->get_egl_display(self);
}
void* mgl_window_get_egl_context(mgl_window *self) {
- x11_context *x11_context = self->context;
- if(x11_context->render_api == MGL_RENDER_API_EGL)
- return x11_context->egl.egl_context;
- else
- return NULL;
+ return self->get_egl_context(self);
+}
+
+void mgl_window_for_each_active_monitor_output(mgl_window *self, mgl_active_monitor_callback callback, void *userdata) {
+ self->for_each_active_monitor_output(self, callback, userdata);
}
diff --git a/src/window/x11.c b/src/window/x11.c
new file mode 100644
index 0000000..c865a98
--- /dev/null
+++ b/src/window/x11.c
@@ -0,0 +1,1600 @@
+#include "../../include/mgl/window/x11.h"
+#include "../../include/mgl/window/event.h"
+#include "../../include/mgl/mgl.h"
+#include "../../include/mgl/system/utf8.h"
+#include "../../include/mgl/system/clock.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <limits.h>
+#include <assert.h>
+#include <unistd.h>
+
+#include <X11/Xutil.h>
+#include <X11/cursorfont.h>
+#include <X11/Xatom.h>
+#include <X11/extensions/Xrender.h>
+#include <X11/extensions/Xrandr.h>
+#include <X11/XF86keysym.h>
+
+static void mgl_window_x11_deinit(mgl_window *self);
+
+/* TODO: Handle XIM better. Set XIM position to text position on screen (for text input) and reset input when selecting a new text input, etc */
+/* TODO: Separate events from windows. Especially when it comes to monitor events */
+
+/* Should be in range [2,] */
+#define MAX_STACKED_EVENTS 32
+
+typedef struct {
+ mgl_event stack[MAX_STACKED_EVENTS];
+ int start;
+ int end;
+ int size;
+} x11_events_circular_buffer;
+
+typedef struct {
+ Window window;
+ char *clipboard_string;
+ size_t clipboard_size;
+
+ mgl_graphics graphics;
+ Colormap color_map;
+ XIM xim;
+ XIC xic;
+ Atom clipboard_atom;
+ Atom targets_atom;
+ Atom text_atom;
+ Atom utf8_string_atom;
+ Atom image_png_atom;
+ Atom image_jpg_atom;
+ Atom image_jpeg_atom;
+ Atom image_gif_atom;
+ Atom incr_atom;
+ Atom net_wm_state_atom;
+ Atom net_wm_state_fullscreen_atom;
+ Atom net_wm_state_above_atom;
+ Atom net_wm_name_atom;
+ Atom net_wm_window_type_atom;
+ Atom net_wm_window_type_normal_atom;
+ Atom net_wm_window_type_dialog_atom;
+ Atom net_wm_window_type_notification_atom;
+ Atom net_wm_window_type_utility;
+ Atom motif_wm_hints_atom;
+ Cursor default_cursor;
+ Cursor invisible_cursor;
+ unsigned int prev_keycode_pressed;
+ bool key_was_released;
+ bool support_alpha;
+
+ XEvent xev;
+
+ /*
+ Used to stack text event on top of key press/release events and other text events.
+ For example pressing a key should give the user both key press and text events
+ and for IM with multiple characters entered (such as chinese), we want to trigger
+ an event for all of them.
+ */
+ x11_events_circular_buffer events;
+} mgl_window_x11;
+
+static void x11_events_circular_buffer_init(x11_events_circular_buffer *self) {
+ self->start = 0;
+ self->end = 0;
+ self->size = 0;
+}
+
+static bool x11_events_circular_buffer_append(x11_events_circular_buffer *self, const mgl_event *event) {
+ if(self->size == MAX_STACKED_EVENTS)
+ return false;
+
+ self->stack[self->end] = *event;
+ self->end = (self->end + 1) % MAX_STACKED_EVENTS;
+ ++self->size;
+ return true;
+}
+
+static bool x11_events_circular_buffer_pop(x11_events_circular_buffer *self, mgl_event *event) {
+ if(self->size == 0)
+ return false;
+
+ *event = self->stack[self->start];
+ self->start = (self->start + 1) % MAX_STACKED_EVENTS;
+ --self->size;
+ return true;
+}
+
+static void mgl_window_x11_clear_monitors(mgl_window *self) {
+ for(int i = 0; i < self->num_monitors; ++i) {
+ mgl_monitor *monitor = &self->monitors[i];
+ if(monitor->name) {
+ free((char*)monitor->name);
+ monitor->name = NULL;
+ }
+ }
+ self->num_monitors = 0;
+}
+
+static bool mgl_window_x11_append_event(mgl_window_x11 *self, const mgl_event *event) {
+ return x11_events_circular_buffer_append(&self->events, event);
+}
+
+static bool mgl_window_x11_pop_event(mgl_window_x11 *self, mgl_event *event) {
+ return x11_events_circular_buffer_pop(&self->events, event);
+}
+
+static mgl_monitor* mgl_window_x11_add_monitor(mgl_window *self, RROutput output_id, RRCrtc crtc_id, const char *name, mgl_vec2i pos, mgl_vec2i size, int refresh_rate) {
+ if(self->num_monitors == MGL_MAX_MONITORS)
+ return NULL;
+
+ mgl_monitor *monitor = &self->monitors[self->num_monitors];
+ monitor->id = output_id;
+ monitor->crtc_id = crtc_id;
+ monitor->name = strdup(name);
+ if(!monitor->name)
+ return NULL;
+ monitor->pos = pos;
+ monitor->size = size;
+ monitor->refresh_rate = refresh_rate;
+ self->num_monitors++;
+
+ return monitor;
+}
+
+static mgl_monitor* mgl_window_x11_get_monitor_by_id(mgl_window *self, RROutput output_id) {
+ for(int i = 0; i < self->num_monitors; ++i) {
+ mgl_monitor *monitor = &self->monitors[i];
+ if(monitor->id == (int)output_id)
+ return monitor;
+ }
+ return NULL;
+}
+
+static mgl_monitor* mgl_window_x11_get_monitor_by_crtc_id(mgl_window *self, RRCrtc crtc_id) {
+ for(int i = 0; i < self->num_monitors; ++i) {
+ mgl_monitor *monitor = &self->monitors[i];
+ if(monitor->crtc_id == (int)crtc_id)
+ return monitor;
+ }
+ return NULL;
+}
+
+static bool mgl_window_x11_remove_monitor(mgl_window *self, RROutput output_id, mgl_event *event) {
+ int index_to_remove = -1;
+ for(int i = 0; i < self->num_monitors; ++i) {
+ mgl_monitor *monitor = &self->monitors[i];
+ if(monitor->id == (int)output_id) {
+ index_to_remove = i;
+ break;
+ }
+ }
+
+ if(index_to_remove == -1)
+ return false;
+
+ mgl_monitor *monitor = &self->monitors[index_to_remove];
+ free((char*)monitor->name);
+ monitor->name = NULL;
+
+ for(int i = index_to_remove + 1; i < self->num_monitors; ++i) {
+ self->monitors[i - 1] = self->monitors[i];
+ }
+ self->num_monitors--;
+
+ event->monitor_disconnected.id = output_id;
+ event->type = MGL_EVENT_MONITOR_DISCONNECTED;
+ return true;
+}
+
+static int round_int(double value) {
+ return value + 0.5;
+}
+
+static int monitor_info_get_framerate(const XRRModeInfo *mode_info) {
+ double v_total = mode_info->vTotal;
+ if(mode_info->modeFlags & RR_DoubleScan) {
+ v_total *= 2;
+ }
+
+ if(mode_info->modeFlags & RR_Interlace) {
+ v_total /= 2;
+ }
+
+ if(mode_info->hTotal > 0 && v_total > 0.0001) {
+ return round_int((double)mode_info->dotClock / ((double)mode_info->hTotal * v_total));
+ } else {
+ return 0;
+ }
+}
+
+static const XRRModeInfo* get_mode_info(const XRRScreenResources *sr, RRMode id) {
+ for(int i = 0; i < sr->nmode; ++i) {
+ if(sr->modes[i].id == id)
+ return &sr->modes[i];
+ }
+ return NULL;
+}
+
+static void mgl_window_x11_for_each_active_monitor_output(mgl_window *self, mgl_active_monitor_callback callback, void *userdata) {
+ (void)self;
+ mgl_context *context = mgl_get_context();
+ XRRScreenResources *screen_res = XRRGetScreenResources(context->connection, DefaultRootWindow(context->connection));
+ if(!screen_res)
+ return;
+
+ char display_name[256];
+ for(int i = 0; i < screen_res->noutput; ++i) {
+ XRROutputInfo *out_info = XRRGetOutputInfo(context->connection, screen_res, screen_res->outputs[i]);
+ if(out_info && out_info->crtc && out_info->connection == RR_Connected) {
+ XRRCrtcInfo *crt_info = XRRGetCrtcInfo(context->connection, screen_res, out_info->crtc);
+ if(crt_info && crt_info->mode) {
+ const XRRModeInfo *mode_info = get_mode_info(screen_res, crt_info->mode);
+ if(mode_info) {
+ snprintf(display_name, sizeof(display_name), "%s", out_info->name);
+ mgl_monitor monitor = {
+ .id = screen_res->outputs[i],
+ .crtc_id = out_info->crtc,
+ .name = display_name,
+ .pos = { .x = crt_info->x, .y = crt_info->y },
+ .size = { .x = (int)crt_info->width, .y = (int)crt_info->height },
+ .refresh_rate = monitor_info_get_framerate(mode_info),
+ };
+ callback(&monitor, userdata);
+ }
+ }
+ if(crt_info)
+ XRRFreeCrtcInfo(crt_info);
+ }
+ if(out_info)
+ XRRFreeOutputInfo(out_info);
+ }
+
+ XRRFreeScreenResources(screen_res);
+}
+
+static void monitor_callback_add_to_mgl_window_x11(const mgl_monitor *monitor, void *userdata) {
+ mgl_window *mgl_window = userdata;
+ mgl_window_x11_add_monitor(mgl_window, monitor->id, monitor->crtc_id, monitor->name, monitor->pos, monitor->size, monitor->refresh_rate);
+}
+
+static void mgl_window_x11_set_frame_time_limit_monitor(mgl_window *self) {
+ int monitor_refresh_rate = 0;
+ mgl_vec2i window_center = (mgl_vec2i) { self->pos.x + self->size.x / 2, self->pos.y + self->size.y / 2 };
+ for(int i = 0; i < self->num_monitors; ++i) {
+ mgl_monitor *monitor = &self->monitors[i];
+ if(window_center.x >= monitor->pos.x && window_center.x <= monitor->pos.x + monitor->size.x
+ && window_center.y >= monitor->pos.y && window_center.y <= monitor->pos.y + monitor->size.y)
+ {
+ monitor_refresh_rate = monitor->refresh_rate;
+ break;
+ }
+ }
+
+ if(monitor_refresh_rate == 0 && self->num_monitors > 0)
+ monitor_refresh_rate = self->monitors[0].refresh_rate;
+
+ if(monitor_refresh_rate == 0)
+ monitor_refresh_rate = 60;
+
+ self->frame_time_limit_monitor = 1.0 / (double)monitor_refresh_rate;
+}
+
+static void mgl_window_x11_on_move(mgl_window *self, int x, int y) {
+ self->pos.x = x;
+ self->pos.y = y;
+ mgl_window_x11_set_frame_time_limit_monitor(self);
+}
+
+static void mgl_window_x11_on_resize(mgl_window *self, int width, int height) {
+ self->size.x = width;
+ self->size.y = height;
+
+ mgl_view view;
+ view.position = (mgl_vec2i){ 0, 0 };
+ view.size = self->size;
+ mgl_window_set_view(self, &view);
+ mgl_window_set_scissor(self, &(mgl_scissor){ .position = { 0, 0 }, .size = self->size });
+}
+
+static unsigned long mgl_color_to_x11_pixel(mgl_color color) {
+ if(color.a == 0)
+ return 0;
+ return ((uint32_t)color.a << 24) | (((uint32_t)color.r * color.a / 0xFF) << 16) | (((uint32_t)color.g * color.a / 0xFF) << 8) | ((uint32_t)color.b * color.a / 0xFF);
+}
+
+static void mgl_set_window_type(mgl_window *self, mgl_window_type window_type) {
+ mgl_window_x11 *impl = self->impl;
+ mgl_context *context = mgl_get_context();
+ switch(window_type) {
+ case MGL_WINDOW_TYPE_NORMAL: {
+ XChangeProperty(context->connection, impl->window, impl->net_wm_window_type_atom, XA_ATOM, 32, PropModeReplace, (unsigned char*)&impl->net_wm_window_type_normal_atom, 1L);
+ break;
+ }
+ case MGL_WINDOW_TYPE_DIALOG: {
+ XChangeProperty(context->connection, impl->window, impl->net_wm_window_type_atom, XA_ATOM, 32, PropModeReplace, (unsigned char*)&impl->net_wm_window_type_dialog_atom, 1L);
+ XChangeProperty(context->connection, impl->window, impl->net_wm_state_atom, XA_ATOM, 32, PropModeReplace, (unsigned char*)&impl->net_wm_state_above_atom, 1L);
+ break;
+ }
+ case MGL_WINDOW_TYPE_NOTIFICATION: {
+ const Atom data[2] = {
+ impl->net_wm_window_type_notification_atom,
+ impl->net_wm_window_type_utility
+ };
+ XChangeProperty(context->connection, impl->window, impl->net_wm_window_type_atom, XA_ATOM, 32, PropModeReplace, (const unsigned char*)data, 2L);
+ XChangeProperty(context->connection, impl->window, impl->net_wm_state_atom, XA_ATOM, 32, PropModeReplace, (unsigned char*)&impl->net_wm_state_above_atom, 1L);
+ break;
+ }
+ }
+}
+
+typedef struct {
+ unsigned long flags;
+ unsigned long functions;
+ unsigned long decorations;
+ long input_mode;
+ unsigned long status;
+} MotifHints;
+
+#define MWM_HINTS_DECORATIONS 2
+
+#define MWM_DECOR_NONE 0
+#define MWM_DECOR_ALL 1
+
+static void mgl_window_x11_set_decorations_visible(mgl_window *self, bool visible) {
+ mgl_window_x11 *impl = self->impl;
+ mgl_context *context = mgl_get_context();
+
+ MotifHints motif_hints = {0};
+ motif_hints.flags = MWM_HINTS_DECORATIONS;
+ motif_hints.decorations = visible ? MWM_DECOR_ALL : MWM_DECOR_NONE;
+
+ XChangeProperty(context->connection, impl->window, impl->motif_wm_hints_atom, impl->motif_wm_hints_atom, 32, PropModeReplace, (unsigned char*)&motif_hints, sizeof(motif_hints) / sizeof(long));
+}
+
+static void mgl_window_x11_set_title(mgl_window *self, const char *title) {
+ mgl_window_x11 *impl = self->impl;
+ mgl_context *context = mgl_get_context();
+
+ XStoreName(context->connection, impl->window, title);
+ XChangeProperty(context->connection, impl->window, impl->net_wm_name_atom, impl->utf8_string_atom, 8, PropModeReplace, (unsigned char*)title, strlen(title));
+ XFlush(context->connection);
+}
+
+static void mgl_window_x11_set_size_limits(mgl_window *self, mgl_vec2i minimum, mgl_vec2i maximum) {
+ mgl_window_x11 *impl = self->impl;
+ XSizeHints *size_hints = XAllocSizeHints();
+ if(size_hints) {
+ size_hints->width = self->size.x;
+ size_hints->min_width = minimum.x;
+ size_hints->max_width = maximum.x;
+
+ size_hints->height = self->size.y;
+ size_hints->min_height = minimum.y;
+ size_hints->max_height = maximum.y;
+
+ size_hints->flags = PSize;
+ if(size_hints->min_width || size_hints->min_height)
+ size_hints->flags |= PMinSize;
+ if(size_hints->max_width || size_hints->max_height)
+ size_hints->flags |= PMaxSize;
+
+ mgl_context *context = mgl_get_context();
+ XSetWMNormalHints(context->connection, impl->window, size_hints);
+ XFree(size_hints);
+ XFlush(context->connection);
+ } else {
+ fprintf(stderr, "mgl warning: failed to set window size hints\n");
+ }
+}
+
+static bool mgl_x11_setup_window(mgl_window *self, const char *title, const mgl_window_create_params *params, Window existing_window) {
+ mgl_window_x11 *impl = self->impl;
+ impl->window = 0;
+ impl->clipboard_string = NULL;
+ impl->clipboard_size = 0;
+
+ mgl_vec2i window_size = params ? params->size : (mgl_vec2i){ 0, 0 };
+ if(window_size.x <= 0 || window_size.y <= 0) {
+ window_size.x = 640;
+ window_size.y = 480;
+ }
+ self->size = window_size;
+
+ mgl_vec2i window_pos = params ? params->position : (mgl_vec2i){ 0, 0 };
+ mgl_context *context = mgl_get_context();
+
+ Window parent_window = params ? (Window)params->parent_window : None;
+ if(parent_window == 0)
+ parent_window = DefaultRootWindow(context->connection);
+
+ XVisualInfo *visual_info = mgl_graphics_get_xvisual_info(&impl->graphics);
+ impl->color_map = XCreateColormap(context->connection, DefaultRootWindow(context->connection), visual_info->visual, AllocNone);
+ if(!impl->color_map) {
+ fprintf(stderr, "mgl error: mgl_x11_setup_window: XCreateColormap failed\n");
+ return false;
+ }
+
+ XSetWindowAttributes window_attr;
+ window_attr.override_redirect = params ? params->override_redirect : false;
+ window_attr.colormap = impl->color_map;
+ window_attr.background_pixel = mgl_color_to_x11_pixel(params ? params->background_color : (mgl_color){ .r = 0, .g = 0, .b = 0, .a = 255 });
+ window_attr.border_pixel = 0;
+ window_attr.bit_gravity = NorthWestGravity;
+ window_attr.event_mask =
+ KeyPressMask | KeyReleaseMask |
+ ButtonPressMask | ButtonReleaseMask |
+ PointerMotionMask | Button1MotionMask | Button2MotionMask | Button3MotionMask | Button4MotionMask | Button5MotionMask | ButtonMotionMask |
+ StructureNotifyMask | EnterWindowMask | LeaveWindowMask | VisibilityChangeMask | PropertyChangeMask | FocusChangeMask;
+
+ const bool hide_window = params ? params->hidden : false;
+
+ if(existing_window) {
+ if(!XChangeWindowAttributes(context->connection, existing_window, CWColormap | CWEventMask | CWOverrideRedirect | CWBorderPixel | CWBackPixel | CWBitGravity, &window_attr)) {
+ fprintf(stderr, "mgl error: mgl_x11_setup_window: XChangeWindowAttributes failed\n");
+ return false;
+ }
+
+ if(params && params->size.x > 0 && params->size.y > 0) {
+ XResizeWindow(context->connection, existing_window, params->size.x, params->size.y);
+ }
+
+ impl->window = existing_window;
+
+ if(params && params->hide_decorations) {
+ mgl_window_x11_set_decorations_visible(self, false);
+ }
+
+ if(hide_window)
+ XUnmapWindow(context->connection, existing_window);
+ } else {
+ impl->window = XCreateWindow(context->connection, parent_window, window_pos.x, window_pos.y,
+ window_size.x, window_size.y, 0,
+ visual_info->depth, InputOutput, visual_info->visual,
+ CWColormap | CWEventMask | CWOverrideRedirect | CWBorderPixel | CWBackPixel | CWBitGravity, &window_attr);
+ if(!impl->window) {
+ fprintf(stderr, "mgl error: mgl_x11_setup_window: XCreateWindow failed\n");
+ return false;
+ }
+
+ if(params && params->hide_decorations) {
+ mgl_window_x11_set_decorations_visible(self, false);
+ }
+
+ mgl_window_x11_set_title(self, title);
+ if(!hide_window)
+ XMapWindow(context->connection, impl->window);
+ }
+
+ if(params)
+ mgl_window_x11_set_size_limits(self, params->min_size, params->max_size);
+
+ /* TODO: Call XGetWMProtocols and add wm_delete_window_atom on top, to not overwrite existing wm protocol atoms */
+ Atom wm_protocol_atoms[2] = {
+ context->wm_delete_window_atom,
+ context->net_wm_ping_atom
+ };
+ XSetWMProtocols(context->connection, impl->window, wm_protocol_atoms, 2);
+
+ if(context->net_wm_pid_atom) {
+ const long pid = getpid();
+ XChangeProperty(context->connection, impl->window, context->net_wm_pid_atom, XA_CARDINAL,
+ 32, PropModeReplace, (const unsigned char*)&pid, 1);
+ }
+
+ char host_name[HOST_NAME_MAX];
+ if(gethostname(host_name, sizeof(host_name)) == 0) {
+ XTextProperty txt_prop;
+ txt_prop.value = (unsigned char*)host_name;
+ txt_prop.encoding = XA_STRING;
+ txt_prop.format = 8;
+ txt_prop.nitems = strlen(host_name);
+ XSetWMClientMachine(context->connection, impl->window, &txt_prop);
+ }
+
+ if(params && params->class_name) {
+ XClassHint class_hint = { (char*)params->class_name, (char*)params->class_name };
+ XSetClassHint(context->connection, impl->window, &class_hint);
+ }
+
+ if(params && params->transient_for_window) {
+ XSetTransientForHint(context->connection, impl->window, (Window)params->transient_for_window);
+ }
+
+ /* TODO: Move this to above XMapWindow? */
+ mgl_window_type window_type = params ? params->window_type : MGL_WINDOW_TYPE_NORMAL;
+ mgl_set_window_type(self, window_type);
+
+ XFlush(context->connection);
+
+ if(!mgl_graphics_make_context_current(&impl->graphics, (mgl_window_handle)impl->window)) {
+ fprintf(stderr, "mgl error: mgl_x11_setup_window: failed to make window context current\n");
+ return false;
+ }
+
+ self->vsync_enabled = true;
+ mgl_graphics_set_swap_interval(&impl->graphics, (mgl_window_handle)impl->window, self->vsync_enabled);
+
+ Window dummy_w;
+ int dummy_i;
+ unsigned int dummy_u;
+ XQueryPointer(context->connection, impl->window, &dummy_w, &dummy_w, &dummy_i, &dummy_i, &self->cursor_position.x, &self->cursor_position.y, &dummy_u);
+
+ impl->xim = XOpenIM(context->connection, NULL, NULL, NULL);
+ if(!impl->xim) {
+ fprintf(stderr, "mgl error: mgl_x11_setup_window: XOpenIM failed\n");
+ return false;
+ }
+
+ impl->xic = XCreateIC(impl->xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, impl->window, NULL);
+ if(!impl->xic) {
+ fprintf(stderr, "mgl error: mgl_x11_setup_window: XCreateIC failed\n");
+ return false;
+ }
+
+ // TODO: This should be done once and monitor events should be done once, no matter how many windows you have
+ mgl_window_x11_clear_monitors(self);
+ mgl_window_x11_for_each_active_monitor_output(self, monitor_callback_add_to_mgl_window_x11, self);
+
+ mgl_window_x11_on_resize(self, self->size.x, self->size.y);
+ mgl_window_x11_on_move(self, window_pos.x, window_pos.y);
+
+ self->open = true;
+ self->focused = false; /* TODO: Check if we need to call XGetInputFocus for this, or just wait for focus event */
+ return true;
+}
+
+/* Returns MGL_KEY_UNKNOWN on no match */
+static mgl_key x11_keysym_to_mgl_key(KeySym key_sym) {
+ if(key_sym >= XK_A && key_sym <= XK_Z)
+ return MGL_KEY_A + (key_sym - XK_A);
+ /* TODO: Check if this ever happens */
+ if(key_sym >= XK_a && key_sym <= XK_z)
+ return MGL_KEY_A + (key_sym - XK_a);
+ if(key_sym >= XK_0 && key_sym <= XK_9)
+ return MGL_KEY_NUM0 + (key_sym - XK_0);
+ if(key_sym >= XK_KP_0 && key_sym <= XK_KP_9)
+ return MGL_KEY_NUMPAD0 + (key_sym - XK_KP_0);
+
+ /* TODO: Fill in the rest */
+ switch(key_sym) {
+ case XK_space: return MGL_KEY_SPACE;
+ case XK_BackSpace: return MGL_KEY_BACKSPACE;
+ case XK_Tab: return MGL_KEY_TAB;
+ case XK_Return: return MGL_KEY_ENTER;
+ case XK_Escape: return MGL_KEY_ESCAPE;
+ case XK_Control_L: return MGL_KEY_LCONTROL;
+ case XK_Shift_L: return MGL_KEY_LSHIFT;
+ case XK_Alt_L: return MGL_KEY_LALT;
+ case XK_Super_L: return MGL_KEY_LSYSTEM;
+ case XK_Control_R: return MGL_KEY_RCONTROL;
+ case XK_Shift_R: return MGL_KEY_RSHIFT;
+ case XK_Alt_R: return MGL_KEY_RALT;
+ case XK_Super_R: return MGL_KEY_RSYSTEM;
+ case XK_Delete: return MGL_KEY_DELETE;
+ case XK_Home: return MGL_KEY_HOME;
+ case XK_Left: return MGL_KEY_LEFT;
+ case XK_Up: return MGL_KEY_UP;
+ case XK_Right: return MGL_KEY_RIGHT;
+ case XK_Down: return MGL_KEY_DOWN;
+ case XK_Page_Up: return MGL_KEY_PAGEUP;
+ case XK_Page_Down: return MGL_KEY_PAGEDOWN;
+ case XK_End: return MGL_KEY_END;
+ case XK_F1: return MGL_KEY_F1;
+ case XK_F2: return MGL_KEY_F2;
+ case XK_F3: return MGL_KEY_F3;
+ case XK_F4: return MGL_KEY_F4;
+ case XK_F5: return MGL_KEY_F5;
+ case XK_F6: return MGL_KEY_F6;
+ case XK_F7: return MGL_KEY_F7;
+ case XK_F8: return MGL_KEY_F8;
+ case XK_F9: return MGL_KEY_F9;
+ case XK_F10: return MGL_KEY_F10;
+ case XK_F11: return MGL_KEY_F11;
+ case XK_F12: return MGL_KEY_F12;
+ case XK_F13: return MGL_KEY_F13;
+ case XK_F14: return MGL_KEY_F14;
+ case XK_F15: return MGL_KEY_F15;
+ case XK_Insert: return MGL_KEY_INSERT;
+ case XK_Pause: return MGL_KEY_PAUSE;
+ case XK_Print: return MGL_KEY_PRINTSCREEN;
+ case XK_KP_Insert: return MGL_KEY_NUMPAD0;
+ case XK_KP_End: return MGL_KEY_NUMPAD1;
+ case XK_KP_Down: return MGL_KEY_NUMPAD2;
+ case XK_KP_Page_Down: return MGL_KEY_NUMPAD3;
+ case XK_KP_Left: return MGL_KEY_NUMPAD4;
+ case XK_KP_Begin: return MGL_KEY_NUMPAD5;
+ case XK_KP_Right: return MGL_KEY_NUMPAD6;
+ case XK_KP_Home: return MGL_KEY_NUMPAD7;
+ case XK_KP_Up: return MGL_KEY_NUMPAD8;
+ case XK_KP_Page_Up: return MGL_KEY_NUMPAD9;
+ case XK_KP_Enter: return MGL_KEY_NUMPAD_ENTER;
+ case XF86XK_AudioLowerVolume: return MGL_KEY_AUDIO_LOWER_VOLUME;
+ case XF86XK_AudioRaiseVolume: return MGL_KEY_AUDIO_RAISE_VOLUME;
+ case XF86XK_AudioPlay: return MGL_KEY_AUDIO_PLAY;
+ case XF86XK_AudioStop: return MGL_KEY_AUDIO_STOP;
+ case XF86XK_AudioPause: return MGL_KEY_AUDIO_PAUSE;
+ case XF86XK_AudioMute: return MGL_KEY_AUDIO_MUTE;
+ case XF86XK_AudioPrev: return MGL_KEY_AUDIO_PREV;
+ case XF86XK_AudioNext: return MGL_KEY_AUDIO_NEXT;
+ case XF86XK_AudioRewind: return MGL_KEY_AUDIO_REWIND;
+ case XF86XK_AudioForward: return MGL_KEY_AUDIO_FORWARD;
+ case XK_dead_acute: return MGL_KEY_DEAD_ACUTE;
+ case XK_apostrophe: return MGL_KEY_APOSTROPHE;
+ case XK_F16: return MGL_KEY_F16;
+ case XK_F17: return MGL_KEY_F17;
+ case XK_F18: return MGL_KEY_F18;
+ case XK_F19: return MGL_KEY_F19;
+ case XK_F20: return MGL_KEY_F20;
+ case XK_F21: return MGL_KEY_F21;
+ case XK_F22: return MGL_KEY_F22;
+ case XK_F23: return MGL_KEY_F23;
+ case XK_F24: return MGL_KEY_F24;
+ }
+ return MGL_KEY_UNKNOWN;
+}
+
+static mgl_mouse_button x11_button_to_mgl_button(unsigned int button) {
+ switch(button) {
+ case Button1: return MGL_BUTTON_LEFT;
+ case Button2: return MGL_BUTTON_MIDDLE;
+ case Button3: return MGL_BUTTON_RIGHT;
+ case 8: return MGL_BUTTON_XBUTTON1;
+ case 9: return MGL_BUTTON_XBUTTON2;
+ }
+ return MGL_BUTTON_UNKNOWN;
+}
+
+static void mgl_window_handle_key_event(XKeyEvent *xkey, mgl_event *event, mgl_context *context) {
+ event->key.code = x11_keysym_to_mgl_key(XKeycodeToKeysym(context->connection, xkey->keycode, 0));
+ event->key.alt = ((xkey->state & Mod1Mask) != 0) || ((xkey->state & Mod5Mask) != 0);
+ event->key.control = ((xkey->state & ControlMask) != 0);
+ event->key.shift = ((xkey->state & ShiftMask) != 0);
+ event->key.system = ((xkey->state & Mod4Mask) != 0);
+}
+
+static void mgl_window_handle_text_event(mgl_window_x11 *self, XEvent *xev) {
+ /* Ignore silent keys */
+ if(XFilterEvent(xev, None))
+ return;
+
+ char buf[128];
+
+ Status status;
+ KeySym ksym;
+ const size_t input_str_len = Xutf8LookupString(self->xic, &xev->xkey, buf, sizeof(buf), &ksym, &status);
+ /* TODO: Handle XBufferOverflow */
+ if(status == XBufferOverflow || input_str_len == 0)
+ return;
+
+ /* TODO: Set XIC location on screen with XSetICValues */
+
+ for(size_t i = 0; i < input_str_len;) {
+ const unsigned char *cp = (const unsigned char*)&buf[i];
+ uint32_t codepoint;
+ size_t clen;
+ if(!mgl_utf8_decode(cp, input_str_len - i, &codepoint, &clen)) {
+ codepoint = *cp;
+ clen = 1;
+ }
+
+ mgl_event text_event;
+ text_event.type = MGL_EVENT_TEXT_ENTERED;
+ text_event.text.codepoint = codepoint;
+ text_event.text.size = clen;
+ memcpy(text_event.text.str, &buf[i], clen);
+ text_event.text.str[clen] = '\0';
+ /* Text may contain multiple codepoints so they need to separated into multiple events */
+ if(!mgl_window_x11_append_event(self, &text_event))
+ break;
+
+ i += clen;
+ }
+}
+
+static bool mgl_on_monitor_added(Display *display, mgl_window *mgl_window, XRROutputChangeNotifyEvent *rr_output_change_event, XRRScreenResources *screen_res, RROutput output_id, XRROutputInfo *out_info, mgl_event *event) {
+ char display_name[256];
+ mgl_monitor *monitor = NULL;
+ XRRCrtcInfo *crt_info = NULL;
+ const XRRModeInfo *mode_info = NULL;
+
+ if(!rr_output_change_event->mode)
+ return false;
+
+ if(mgl_window_x11_get_monitor_by_id(mgl_window, output_id))
+ return false;
+
+ if(out_info->nameLen >= (int)sizeof(display_name))
+ return false;
+
+ crt_info = XRRGetCrtcInfo(display, screen_res, out_info->crtc);
+ if(!crt_info)
+ goto done;
+
+ mode_info = get_mode_info(screen_res, rr_output_change_event->mode);
+ if(!mode_info)
+ goto done;
+
+ memcpy(display_name, out_info->name, out_info->nameLen);
+ display_name[out_info->nameLen] = '\0';
+
+ monitor = mgl_window_x11_add_monitor(mgl_window, output_id, out_info->crtc, display_name,
+ (mgl_vec2i){ .x = crt_info->x, .y = crt_info->y },
+ (mgl_vec2i){ .x = (int)crt_info->width, .y = (int)crt_info->height },
+ monitor_info_get_framerate(mode_info));
+
+ if(!monitor)
+ goto done;
+
+ event->monitor_connected.id = monitor->id;
+ event->monitor_connected.name = monitor->name;
+ event->monitor_connected.x = monitor->pos.x;
+ event->monitor_connected.y = monitor->pos.y;
+ event->monitor_connected.width = monitor->size.x;
+ event->monitor_connected.height = monitor->size.y;
+ event->monitor_connected.refresh_rate = monitor->refresh_rate;
+ event->type = MGL_EVENT_MONITOR_CONNECTED;
+
+ done:
+ if(crt_info)
+ XRRFreeCrtcInfo(crt_info);
+ return monitor != NULL;
+}
+
+static bool mgl_on_monitor_state_changed(Display *display, mgl_window *mgl_window, XRROutputChangeNotifyEvent *rr_output_change_event, mgl_event *event) {
+ bool state_changed = false;
+ XRROutputInfo *out_info = NULL;
+
+ if(!rr_output_change_event->output)
+ return false;
+
+ XRRScreenResources *screen_res = XRRGetScreenResources(display, DefaultRootWindow(display));
+ if(!screen_res)
+ return false;
+
+ out_info = XRRGetOutputInfo(display, screen_res, rr_output_change_event->output);
+ if(out_info && out_info->crtc && out_info->connection == RR_Connected) {
+ state_changed = mgl_on_monitor_added(display, mgl_window, rr_output_change_event, screen_res, rr_output_change_event->output, out_info, event);
+ } else {
+ state_changed = mgl_window_x11_remove_monitor(mgl_window, rr_output_change_event->output, event);
+ }
+
+ if(out_info)
+ XRRFreeOutputInfo(out_info);
+
+ XRRFreeScreenResources(screen_res);
+ return state_changed;
+}
+
+static bool mgl_on_monitor_property_changed(Display *display, mgl_window *mgl_window, XRRCrtcChangeNotifyEvent *rr_crtc_change_event, mgl_event *event) {
+ if(!rr_crtc_change_event->crtc)
+ return false;
+
+ mgl_monitor *monitor = mgl_window_x11_get_monitor_by_crtc_id(mgl_window, rr_crtc_change_event->crtc);
+ if(!monitor)
+ return false;
+
+ XRRScreenResources *screen_res = XRRGetScreenResources(display, DefaultRootWindow(display));
+ if(!screen_res)
+ return false;
+
+ monitor->pos = (mgl_vec2i){ .x = rr_crtc_change_event->x, .y = rr_crtc_change_event->y };
+ monitor->size = (mgl_vec2i){ .x = rr_crtc_change_event->width, .y = rr_crtc_change_event->height };
+ const XRRModeInfo *mode_info = get_mode_info(screen_res, rr_crtc_change_event->mode);
+ if(mode_info)
+ monitor->refresh_rate = monitor_info_get_framerate(mode_info);
+
+ XRRFreeScreenResources(screen_res);
+
+ event->monitor_property_changed.id = monitor->id;
+ event->monitor_property_changed.name = monitor->name;
+ event->monitor_property_changed.x = monitor->pos.x;
+ event->monitor_property_changed.y = monitor->pos.y;
+ event->monitor_property_changed.width = monitor->size.x;
+ event->monitor_property_changed.height = monitor->size.y;
+ event->monitor_property_changed.refresh_rate = monitor->refresh_rate;
+ event->type = MGL_EVENT_MONITOR_PROPERTY_CHANGED;
+ return true;
+}
+
+/* Returns true if an event was generated */
+static bool mgl_on_rr_notify(mgl_context *context, mgl_window *mgl_window, XEvent *xev, int subtype, mgl_event *event) {
+ switch(subtype) {
+ case RRNotify_CrtcChange: {
+ XRRCrtcChangeNotifyEvent *rr_crtc_change_event = (XRRCrtcChangeNotifyEvent*)xev;
+ return mgl_on_monitor_property_changed(context->connection, mgl_window, rr_crtc_change_event, event);
+ }
+ case RRNotify_OutputChange: {
+ XRROutputChangeNotifyEvent *rr_output_change_event = (XRROutputChangeNotifyEvent*)xev;
+ return mgl_on_monitor_state_changed(context->connection, mgl_window, rr_output_change_event, event);
+ }
+ }
+ return false;
+}
+
+static void mgl_window_x11_on_receive_event(mgl_window *self, XEvent *xev, mgl_event *event, mgl_context *context, bool injected) {
+ mgl_window_x11 *impl = self->impl;
+ switch(xev->type - context->randr_event_base) {
+ case RRScreenChangeNotify: {
+ XRRUpdateConfiguration(xev);
+ event->type = MGL_EVENT_UNKNOWN;
+ return;
+ }
+ case RRNotify: {
+ XRRNotifyEvent *rr_event = (XRRNotifyEvent*)xev;
+ if(mgl_on_rr_notify(context, self, xev, rr_event->subtype, event)) {
+ // TODO:
+ //impl->num_monitors = mgl_window_x11->num_monitors;
+ return;
+ }
+
+ event->type = MGL_EVENT_UNKNOWN;
+ return;
+ }
+ }
+
+ switch(xev->type) {
+ case KeyPress: {
+ if(!self->key_repeat_enabled && xev->xkey.keycode == impl->prev_keycode_pressed && !impl->key_was_released) {
+ event->type = MGL_EVENT_UNKNOWN;
+ return;
+ }
+
+ impl->prev_keycode_pressed = xev->xkey.keycode;
+ impl->key_was_released = false;
+
+ event->type = MGL_EVENT_KEY_PRESSED;
+ mgl_window_handle_key_event(&xev->xkey, event, context);
+ mgl_window_handle_text_event(impl, xev);
+ return;
+ }
+ case KeyRelease: {
+ if(xev->xkey.keycode == impl->prev_keycode_pressed)
+ impl->key_was_released = true;
+
+ event->type = MGL_EVENT_KEY_RELEASED;
+ mgl_window_handle_key_event(&xev->xkey, event, context);
+ return;
+ }
+ case ButtonPress: {
+ if(xev->xbutton.button == Button4) {
+ /* Mouse scroll up */
+ event->type = MGL_EVENT_MOUSE_WHEEL_SCROLLED;
+ event->mouse_wheel_scroll.delta = 1;
+ event->mouse_wheel_scroll.x = xev->xbutton.x;
+ event->mouse_wheel_scroll.y = xev->xbutton.y;
+ } else if(xev->xbutton.button == Button5) {
+ /* Mouse scroll down */
+ event->type = MGL_EVENT_MOUSE_WHEEL_SCROLLED;
+ event->mouse_wheel_scroll.delta = -1;
+ event->mouse_wheel_scroll.x = xev->xbutton.x;
+ event->mouse_wheel_scroll.y = xev->xbutton.y;
+ } else {
+ event->type = MGL_EVENT_MOUSE_BUTTON_PRESSED;
+ event->mouse_button.button = x11_button_to_mgl_button(xev->xbutton.button);
+ event->mouse_button.x = xev->xbutton.x;
+ event->mouse_button.y = xev->xbutton.y;
+ }
+ return;
+ }
+ case ButtonRelease: {
+ event->type = MGL_EVENT_MOUSE_BUTTON_RELEASED;
+ event->mouse_button.button = x11_button_to_mgl_button(xev->xbutton.button);
+ event->mouse_button.x = xev->xbutton.x;
+ event->mouse_button.y = xev->xbutton.y;
+ return;
+ }
+ case FocusIn: {
+ XSetICFocus(impl->xic);
+
+ XWMHints* hints = XGetWMHints(context->connection, impl->window);
+ if(hints) {
+ hints->flags &= ~XUrgencyHint;
+ XSetWMHints(context->connection, impl->window, hints);
+ XFree(hints);
+ }
+
+ event->type = MGL_EVENT_GAINED_FOCUS;
+ self->focused = true;
+ return;
+ }
+ case FocusOut: {
+ XUnsetICFocus(impl->xic);
+ event->type = MGL_EVENT_LOST_FOCUS;
+ self->focused = false;
+ return;
+ }
+ case ConfigureNotify: {
+ if(!injected)
+ while(XCheckTypedWindowEvent(context->connection, impl->window, ConfigureNotify, xev)) {}
+
+ if(xev->xconfigure.x != self->pos.x || xev->xconfigure.y != self->pos.y) {
+ mgl_window_x11_on_move(self, xev->xconfigure.x, xev->xconfigure.y);
+ }
+
+ if(xev->xconfigure.width != self->size.x || xev->xconfigure.height != self->size.y) {
+ mgl_window_x11_on_resize(self, xev->xconfigure.width, xev->xconfigure.height);
+
+ event->type = MGL_EVENT_RESIZED;
+ event->size.width = self->size.x;
+ event->size.height = self->size.y;
+ return;
+ }
+
+ event->type = MGL_EVENT_UNKNOWN;
+ return;
+ }
+ case MotionNotify: {
+ if(!injected)
+ while(XCheckTypedWindowEvent(context->connection, impl->window, MotionNotify, xev)) {}
+
+ self->cursor_position.x = xev->xmotion.x;
+ self->cursor_position.y = xev->xmotion.y;
+
+ event->type = MGL_EVENT_MOUSE_MOVED;
+ event->mouse_move.x = self->cursor_position.x;
+ event->mouse_move.y = self->cursor_position.y;
+ return;
+ }
+ case SelectionClear: {
+ event->type = MGL_EVENT_UNKNOWN;
+ return;
+ }
+ case SelectionRequest: {
+ XSelectionEvent selection_event;
+ selection_event.type = SelectionNotify;
+ selection_event.requestor = xev->xselectionrequest.requestor;
+ selection_event.selection = xev->xselectionrequest.selection;
+ selection_event.property = xev->xselectionrequest.property;
+ selection_event.time = xev->xselectionrequest.time;
+ selection_event.target = xev->xselectionrequest.target;
+
+ if(selection_event.selection == impl->clipboard_atom) {
+ /* TODO: Support ascii text separately by unsetting the 8th bit in the clipboard string? */
+ if(selection_event.target == impl->targets_atom) {
+ /* A client requested for our valid conversion targets */
+ int num_targets = 3;
+ Atom targets[4];
+ targets[0] = impl->targets_atom;
+ targets[1] = impl->text_atom;
+ targets[2] = XA_STRING;
+ if(impl->utf8_string_atom) {
+ targets[3] = impl->utf8_string_atom;
+ num_targets = 4;
+ }
+
+ XChangeProperty(context->connection, selection_event.requestor, selection_event.property, XA_ATOM, 32, PropModeReplace, (unsigned char*)targets, num_targets);
+ selection_event.target = impl->targets_atom;
+ XSendEvent(context->connection, selection_event.requestor, True, NoEventMask, (XEvent*)&selection_event);
+ XFlush(context->connection);
+ event->type = MGL_EVENT_UNKNOWN;
+ return;
+ } else if(selection_event.target == XA_STRING || (!impl->utf8_string_atom && selection_event.target == impl->text_atom)) {
+ /* A client requested ascii clipboard */
+ XChangeProperty(context->connection, selection_event.requestor, selection_event.property, XA_STRING, 8, PropModeReplace, (const unsigned char*)impl->clipboard_string, impl->clipboard_size);
+ selection_event.target = XA_STRING;
+ XSendEvent(context->connection, selection_event.requestor, True, NoEventMask, (XEvent*)&selection_event);
+ XFlush(context->connection);
+ event->type = MGL_EVENT_UNKNOWN;
+ return;
+ } else if(impl->utf8_string_atom && (selection_event.target == impl->utf8_string_atom || selection_event.target == impl->text_atom)) {
+ /* A client requested utf8 clipboard */
+ XChangeProperty(context->connection, selection_event.requestor, selection_event.property, impl->utf8_string_atom, 8, PropModeReplace, (const unsigned char*)impl->clipboard_string, impl->clipboard_size);
+ selection_event.target = impl->utf8_string_atom;
+ XSendEvent(context->connection, selection_event.requestor, True, NoEventMask, (XEvent*)&selection_event);
+ XFlush(context->connection);
+ event->type = MGL_EVENT_UNKNOWN;
+ return;
+ }
+ }
+
+ selection_event.property = None;
+ XSendEvent(context->connection, selection_event.requestor, True, NoEventMask, (XEvent*)&selection_event);
+ XFlush(context->connection);
+ event->type = MGL_EVENT_UNKNOWN;
+ return;
+ }
+ case ClientMessage: {
+ if(xev->xclient.format == 32 && (unsigned long)xev->xclient.data.l[0] == context->wm_delete_window_atom) {
+ event->type = MGL_EVENT_CLOSED;
+ self->open = false;
+ return;
+ } else if(xev->xclient.format == 32 && (unsigned long)xev->xclient.data.l[0] == context->net_wm_ping_atom) {
+ xev->xclient.window = DefaultRootWindow(context->connection);
+ XSendEvent(context->connection, DefaultRootWindow(context->connection), False, SubstructureNotifyMask | SubstructureRedirectMask, xev);
+ XFlush(context->connection);
+ }
+ event->type = MGL_EVENT_UNKNOWN;
+ return;
+ }
+ case MappingNotify: {
+ /* TODO: Only handle this globally once for mgl, not for each window */
+ XRefreshKeyboardMapping(&xev->xmapping);
+ event->type = MGL_EVENT_MAPPING_CHANGED;
+ event->mapping_changed.type = MappingModifier;
+ switch(xev->xmapping.request) {
+ case MappingModifier:
+ event->mapping_changed.type = MGL_MAPPING_CHANGED_MODIFIER;
+ break;
+ case MappingKeyboard:
+ event->mapping_changed.type = MGL_MAPPING_CHANGED_KEYBOARD;
+ break;
+ case MappingPointer:
+ event->mapping_changed.type = MGL_MAPPING_CHANGED_POINTER;
+ break;
+ }
+ return;
+ }
+ default: {
+ event->type = MGL_EVENT_UNKNOWN;
+ return;
+ }
+ }
+}
+
+static bool mgl_window_x11_poll_event(mgl_window *self, mgl_event *event) {
+ mgl_window_x11 *impl = self->impl;
+ mgl_context *context = mgl_get_context();
+ Display *display = context->connection;
+
+ if(mgl_window_x11_pop_event(impl, event))
+ return true;
+
+ if(XPending(display)) {
+ XNextEvent(display, &impl->xev);
+ if(impl->xev.xany.window == impl->window || impl->xev.type == ClientMessage || impl->xev.type == MappingNotify || impl->xev.type - context->randr_event_base >= 0)
+ mgl_window_x11_on_receive_event(self, &impl->xev, event, context, false);
+ else
+ event->type = MGL_EVENT_UNKNOWN;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static bool mgl_window_x11_inject_x11_event(mgl_window *self, XEvent *xev, mgl_event *event) {
+ mgl_context *context = mgl_get_context();
+ event->type = MGL_EVENT_UNKNOWN;
+ mgl_window_x11_on_receive_event(self, xev, event, context, true);
+ return event->type != MGL_EVENT_UNKNOWN;
+}
+
+static void mgl_window_x11_swap_buffers(mgl_window *self) {
+ mgl_window_x11 *impl = self->impl;
+ mgl_graphics_swap_buffers(&impl->graphics, (mgl_window_handle)impl->window);
+}
+
+static void mgl_window_x11_set_visible(mgl_window *self, bool visible) {
+ mgl_window_x11 *impl = self->impl;
+ mgl_context *context = mgl_get_context();
+ if(visible)
+ XMapWindow(context->connection, impl->window);
+ else
+ XUnmapWindow(context->connection, impl->window);
+ XFlush(context->connection);
+}
+
+/* TODO: Track keys with events instead, but somehow handle window focus lost */
+static bool mgl_window_x11_is_key_pressed(const mgl_window *self, mgl_key key) {
+ (void)self;
+ if(key < 0 || key >= __MGL_NUM_KEYS__)
+ return false;
+
+ mgl_context *context = mgl_get_context();
+ const KeySym keysym = mgl_key_to_x11_keysym(key);
+ if(keysym == XK_VoidSymbol)
+ return false;
+
+ KeyCode keycode = XKeysymToKeycode(context->connection, keysym);
+ if(keycode == 0)
+ return false;
+
+ char keys[32];
+ XQueryKeymap(context->connection, keys);
+
+ return (keys[keycode / 8] & (1 << (keycode % 8))) != 0;
+}
+
+/* TODO: Track keys with events instead, but somehow handle window focus lost */
+static bool mgl_window_x11_is_mouse_button_pressed(const mgl_window *self, mgl_mouse_button button) {
+ (void)self;
+ if(button < 0 || button >= __MGL_NUM_MOUSE_BUTTONS__)
+ return false;
+
+ mgl_context *context = mgl_get_context();
+ Window root, child;
+ int root_x, root_y, win_x, win_y;
+
+ unsigned int buttons_mask = 0;
+ XQueryPointer(context->connection, DefaultRootWindow(context->connection), &root, &child, &root_x, &root_y, &win_x, &win_y, &buttons_mask);
+
+ switch(button) {
+ case MGL_BUTTON_LEFT: return buttons_mask & Button1Mask;
+ case MGL_BUTTON_MIDDLE: return buttons_mask & Button2Mask;
+ case MGL_BUTTON_RIGHT: return buttons_mask & Button3Mask;
+ case MGL_BUTTON_XBUTTON1: return false; /* Not supported by x11 */
+ case MGL_BUTTON_XBUTTON2: return false; /* Not supported by x11 */
+ default: return false;
+ }
+
+ return false;
+}
+
+static mgl_window_handle mgl_window_x11_get_system_handle(const mgl_window *self) {
+ const mgl_window_x11 *impl = self->impl;
+ return (mgl_window_handle)impl->window;
+}
+
+static void mgl_window_x11_close(mgl_window *self) {
+ mgl_window_x11 *impl = self->impl;
+ mgl_context *context = mgl_get_context();
+ if(impl->window) {
+ XDestroyWindow(context->connection, impl->window);
+ XSync(context->connection, False);
+ impl->window = None;
+ }
+ self->open = false;
+}
+
+static void mgl_window_x11_set_cursor_visible(mgl_window *self, bool visible) {
+ mgl_window_x11 *impl = self->impl;
+ mgl_context *context = mgl_get_context();
+ XDefineCursor(context->connection, impl->window, visible ? impl->default_cursor : impl->invisible_cursor);
+}
+
+static void mgl_window_x11_set_vsync_enabled(mgl_window *self, bool enabled) {
+ mgl_window_x11 *impl = self->impl;
+ self->vsync_enabled = enabled;
+ mgl_graphics_set_swap_interval(&impl->graphics, (mgl_window_handle)impl->window, self->vsync_enabled);
+}
+
+static bool mgl_window_x11_is_vsync_enabled(const mgl_window *self) {
+ return self->vsync_enabled;
+}
+
+static void mgl_window_x11_set_fullscreen(mgl_window *self, bool fullscreen) {
+ mgl_window_x11 *impl = self->impl;
+ mgl_context *context = mgl_get_context();
+
+ XEvent xev;
+ xev.type = ClientMessage;
+ xev.xclient.window = impl->window;
+ xev.xclient.message_type = impl->net_wm_state_atom;
+ xev.xclient.format = 32;
+ xev.xclient.data.l[0] = fullscreen ? 1 : 0;
+ xev.xclient.data.l[1] = impl->net_wm_state_fullscreen_atom;
+ xev.xclient.data.l[2] = 0;
+ xev.xclient.data.l[3] = 1;
+ xev.xclient.data.l[4] = 0;
+
+ if(!XSendEvent(context->connection, DefaultRootWindow(context->connection), 0, SubstructureRedirectMask | SubstructureNotifyMask, &xev)) {
+ fprintf(stderr, "mgl warning: failed to change window fullscreen state\n");
+ return;
+ }
+
+ XFlush(context->connection);
+}
+
+static bool mgl_window_x11_is_fullscreen(const mgl_window *self) {
+ mgl_window_x11 *impl = self->impl;
+ mgl_context *context = mgl_get_context();
+
+ bool is_fullscreen = false;
+ Atom type = None;
+ int format = 0;
+ unsigned long items = 0;
+ unsigned long remaining_bytes = 0;
+ unsigned char *data = NULL;
+ if(XGetWindowProperty(context->connection, impl->window, impl->net_wm_state_atom, 0, 1024, False, PropertyNewValue, &type, &format, &items, &remaining_bytes, &data) == Success && data) {
+ is_fullscreen = format == 32 && *(unsigned long*)data == impl->net_wm_state_fullscreen_atom;
+ XFree(data);
+ }
+ return is_fullscreen;
+}
+
+static void mgl_window_x11_set_position(mgl_window *self, mgl_vec2i position) {
+ mgl_window_x11 *impl = self->impl;
+ XMoveWindow(mgl_get_context()->connection, impl->window, position.x, position.y);
+ XFlush(mgl_get_context()->connection);
+}
+
+static void mgl_window_x11_set_size(mgl_window *self, mgl_vec2i size) {
+ mgl_window_x11 *impl = self->impl;
+ if(size.x < 0)
+ size.x = 0;
+ if(size.y < 0)
+ size.y = 0;
+ XResizeWindow(mgl_get_context()->connection, impl->window, size.x, size.y);
+ XFlush(mgl_get_context()->connection);
+}
+
+static void mgl_window_x11_set_clipboard(mgl_window *self, const char *str, size_t size) {
+ mgl_window_x11 *impl = self->impl;
+ mgl_context *context = mgl_get_context();
+
+ if(impl->clipboard_string) {
+ free(impl->clipboard_string);
+ impl->clipboard_string = NULL;
+ impl->clipboard_size = 0;
+ }
+
+ XSetSelectionOwner(context->connection, impl->clipboard_atom, impl->window, CurrentTime);
+ XFlush(context->connection);
+
+ /* Check if setting the selection owner was successful */
+ if(XGetSelectionOwner(context->connection, impl->clipboard_atom) != impl->window) {
+ fprintf(stderr, "mgl error: mgl_window_x11_set_clipboard failed\n");
+ return;
+ }
+
+ impl->clipboard_string = malloc(size + 1);
+ if(!impl->clipboard_string) {
+ fprintf(stderr, "mgl error: failed to allocate string for clipboard\n");
+ return;
+ }
+ memcpy(impl->clipboard_string, str, size);
+ impl->clipboard_string[size] = '\0';
+ impl->clipboard_size = size;
+}
+
+static Atom find_matching_atom(const Atom *supported_atoms, size_t num_supported_atoms, const Atom *atoms, size_t num_atoms) {
+ for(size_t j = 0; j < num_supported_atoms; ++j) {
+ for(size_t i = 0; i < num_atoms; ++i) {
+ if(atoms[i] == supported_atoms[j])
+ return atoms[i];
+ }
+ }
+ return None;
+}
+
+static mgl_clipboard_type atom_type_to_supported_clipboard_type(mgl_window_x11 *mgl_window_x11, Atom type, int format) {
+ if((type == mgl_window_x11->utf8_string_atom || type == XA_STRING || type == mgl_window_x11->text_atom) && format == 8) {
+ return MGL_CLIPBOARD_TYPE_STRING;
+ } else if(type == mgl_window_x11->image_png_atom && format == 8){
+ return MGL_CLIPBOARD_TYPE_IMAGE_PNG;
+ } else if((type == mgl_window_x11->image_jpg_atom || type == mgl_window_x11->image_jpeg_atom) && format == 8){
+ return MGL_CLIPBOARD_TYPE_IMAGE_JPG;
+ } else if(type == mgl_window_x11->image_gif_atom && format == 8){
+ return MGL_CLIPBOARD_TYPE_IMAGE_GIF;
+ } else {
+ return -1;
+ }
+}
+
+static bool mgl_window_x11_get_clipboard(mgl_window *self, mgl_clipboard_callback callback, void *userdata, uint32_t clipboard_types) {
+ mgl_window_x11 *impl = self->impl;
+ assert(callback);
+
+ mgl_context *context = mgl_get_context();
+ Window selection_owner = XGetSelectionOwner(context->connection, impl->clipboard_atom);
+
+ if(!selection_owner)
+ return false;
+
+ /* Return data immediately if we are the owner of the clipboard, because we can't process other events in the below event loop */
+ if(selection_owner == impl->window) {
+ if(!impl->clipboard_string)
+ return false;
+
+ return callback((const unsigned char*)impl->clipboard_string, impl->clipboard_size, MGL_CLIPBOARD_TYPE_STRING, userdata);
+ }
+
+ XEvent xev;
+ while(XCheckTypedWindowEvent(context->connection, impl->window, SelectionNotify, &xev)) {}
+
+ /* Sorted by preference */
+ /* TODO: Support more types (BITMAP?, PIXMAP?) */
+ Atom supported_clipboard_types[7];
+
+ int supported_clipboard_type_index = 0;
+ if(clipboard_types & MGL_CLIPBOARD_TYPE_IMAGE_PNG) {
+ supported_clipboard_types[supported_clipboard_type_index++] = impl->image_png_atom;
+ }
+
+ if(clipboard_types & MGL_CLIPBOARD_TYPE_IMAGE_JPG) {
+ supported_clipboard_types[supported_clipboard_type_index++] = impl->image_jpeg_atom;
+ }
+
+ if(clipboard_types & MGL_CLIPBOARD_TYPE_IMAGE_GIF) {
+ supported_clipboard_types[supported_clipboard_type_index++] = impl->image_gif_atom;
+ }
+
+ if(clipboard_types & MGL_CLIPBOARD_TYPE_STRING) {
+ supported_clipboard_types[supported_clipboard_type_index++] = impl->utf8_string_atom;
+ supported_clipboard_types[supported_clipboard_type_index++] = XA_STRING;
+ supported_clipboard_types[supported_clipboard_type_index++] = impl->text_atom;
+ }
+
+ const unsigned long num_supported_clipboard_types = supported_clipboard_type_index;
+
+ Atom requested_clipboard_type = None;
+ const Atom XA_TARGETS = XInternAtom(context->connection, "TARGETS", False);
+ XConvertSelection(context->connection, impl->clipboard_atom, XA_TARGETS, impl->clipboard_atom, impl->window, CurrentTime);
+
+ mgl_clock timeout_timer;
+ mgl_clock_init(&timeout_timer);
+ bool success = false;
+
+ const double timeout_seconds = 5.0;
+ while(mgl_clock_get_elapsed_time_seconds(&timeout_timer) < timeout_seconds) {
+ /* TODO: Wait for SelectionNotify event instead */
+ while(XCheckTypedWindowEvent(context->connection, impl->window, SelectionNotify, &xev)) {
+ if(mgl_clock_get_elapsed_time_seconds(&timeout_timer) >= timeout_seconds)
+ break;
+
+ if(!xev.xselection.property)
+ continue;
+
+ if(!xev.xselection.target || xev.xselection.selection != impl->clipboard_atom)
+ continue;
+
+ Atom type = None;
+ int format;
+ unsigned long items;
+ unsigned long remaining_bytes = 0;
+ unsigned char *data = NULL;
+
+ if(xev.xselection.target == XA_TARGETS && requested_clipboard_type == None) {
+ /* TODO: Wait for PropertyNotify event instead */
+ if(XGetWindowProperty(context->connection, xev.xselection.requestor, xev.xselection.property, 0, 1024, False, PropertyNewValue, &type, &format, &items, &remaining_bytes, &data) == Success && data) {
+ if(type != impl->incr_atom && type == XA_ATOM && format == 32) {
+ requested_clipboard_type = find_matching_atom(supported_clipboard_types, num_supported_clipboard_types, (Atom*)data, items);
+ if(requested_clipboard_type == None) {
+ /* Pasting clipboard data type we dont support */
+ XFree(data);
+ goto done;
+ } else {
+ XConvertSelection(context->connection, impl->clipboard_atom, requested_clipboard_type, impl->clipboard_atom, impl->window, CurrentTime);
+ }
+ }
+
+ XFree(data);
+ }
+ } else if(xev.xselection.target == requested_clipboard_type) {
+ bool got_data = false;
+ long chunk_size = 65536;
+
+ //XDeleteProperty(context->connection, self->window, mgl_window_x11->incr_atom);
+ //XFlush(context->connection);
+
+ while(mgl_clock_get_elapsed_time_seconds(&timeout_timer) < timeout_seconds) {
+ unsigned long offset = 0;
+
+ /* offset is specified in XGetWindowProperty as a multiple of 32-bit (4 bytes) */
+ /* TODO: Wait for PropertyNotify event instead */
+ while(XGetWindowProperty(context->connection, xev.xselection.requestor, xev.xselection.property, offset/4, chunk_size, True, PropertyNewValue, &type, &format, &items, &remaining_bytes, &data) == Success) {
+ if(mgl_clock_get_elapsed_time_seconds(&timeout_timer) >= timeout_seconds)
+ break;
+
+ if(type == impl->incr_atom) {
+ if(data)
+ chunk_size = *(long*)data;
+ XDeleteProperty(context->connection, impl->window, impl->incr_atom);
+ XFlush(context->connection);
+ XFree(data);
+ data = NULL;
+ break;
+ } else if(type == requested_clipboard_type) {
+ got_data = true;
+ }
+
+ if(!got_data && items == 0) {
+ XDeleteProperty(context->connection, impl->window, type);
+ XFlush(context->connection);
+ if(data) {
+ XFree(data);
+ data = NULL;
+ }
+ break;
+ }
+
+ const size_t num_items_bytes = items * (format/8); /* format is the bit size of the data */
+ if(got_data) {
+ const mgl_clipboard_type clipboard_type = atom_type_to_supported_clipboard_type(impl, type, format);
+ if(data && num_items_bytes > 0 && (int)clipboard_type != -1) {
+ if(!callback(data, num_items_bytes, clipboard_type, userdata)) {
+ XFree(data);
+ goto done;
+ }
+ }
+ }
+ offset += num_items_bytes;
+
+ if(data) {
+ XFree(data);
+ data = NULL;
+ }
+
+ if(got_data && num_items_bytes == 0/* && format == 8*/) {
+ success = true;
+ goto done;
+ }
+
+ if(remaining_bytes == 0)
+ break;
+ }
+ }
+
+ goto done;
+ }
+ }
+ }
+
+ done:
+ if(requested_clipboard_type)
+ XDeleteProperty(context->connection, impl->window, requested_clipboard_type);
+ return success;
+}
+
+static void mgl_window_x11_set_key_repeat_enabled(mgl_window *self, bool enabled) {
+ self->key_repeat_enabled = enabled;
+}
+
+static void mgl_window_x11_flush(mgl_window *self) {
+ (void)self;
+ mgl_context *context = mgl_get_context();
+ XFlush(context->connection);
+}
+
+static void* mgl_window_x11_get_egl_display(mgl_window *self) {
+ mgl_window_x11 *impl = self->impl;
+ if(impl->graphics.graphics_api == MGL_GRAPHICS_API_EGL)
+ return mgl_graphics_get_display(&impl->graphics);
+ else
+ return NULL;
+}
+
+static void* mgl_window_x11_get_egl_context(mgl_window *self) {
+ mgl_window_x11 *impl = self->impl;
+ if(impl->graphics.graphics_api == MGL_GRAPHICS_API_EGL)
+ return mgl_graphics_get_context(&impl->graphics);
+ else
+ return NULL;
+}
+
+bool mgl_window_x11_init(mgl_window *self, const char *title, const mgl_window_create_params *params, mgl_window_handle existing_window) {
+ mgl_window_x11 *impl = calloc(1, sizeof(mgl_window_x11));
+ if(!impl)
+ return false;
+
+ self->get_system_handle = mgl_window_x11_get_system_handle;
+ self->deinit = mgl_window_x11_deinit;
+ self->close = mgl_window_x11_close;
+ self->inject_x11_event = mgl_window_x11_inject_x11_event;
+ self->poll_event = mgl_window_x11_poll_event;
+ self->swap_buffers = mgl_window_x11_swap_buffers;
+ self->set_visible = mgl_window_x11_set_visible;
+ self->is_key_pressed = mgl_window_x11_is_key_pressed;
+ self->is_mouse_button_pressed = mgl_window_x11_is_mouse_button_pressed;
+ self->set_title = mgl_window_x11_set_title;
+ self->set_cursor_visible = mgl_window_x11_set_cursor_visible;
+ self->set_vsync_enabled = mgl_window_x11_set_vsync_enabled;
+ self->is_vsync_enabled = mgl_window_x11_is_vsync_enabled;
+ self->set_fullscreen = mgl_window_x11_set_fullscreen;
+ self->is_fullscreen = mgl_window_x11_is_fullscreen;
+ self->set_position = mgl_window_x11_set_position;
+ self->set_size = mgl_window_x11_set_size;
+ self->set_size_limits = mgl_window_x11_set_size_limits;
+ self->set_clipboard = mgl_window_x11_set_clipboard;
+ self->get_clipboard = mgl_window_x11_get_clipboard;
+ self->set_key_repeat_enabled = mgl_window_x11_set_key_repeat_enabled;
+ self->flush = mgl_window_x11_flush;
+ self->get_egl_display = mgl_window_x11_get_egl_display;
+ self->get_egl_context = mgl_window_x11_get_egl_context;
+ self->for_each_active_monitor_output = mgl_window_x11_for_each_active_monitor_output;
+ self->impl = impl;
+
+ /* TODO: Use CLIPBOARD_MANAGER and SAVE_TARGETS to save clipboard in clipboard manager on exit */
+
+ mgl_context *context = mgl_get_context();
+ /* TODO: Create all of these with one XInternAtoms call instead */
+ impl->clipboard_atom = XInternAtom(context->connection, "CLIPBOARD", False);
+ impl->targets_atom = XInternAtom(context->connection, "TARGETS", False);
+ impl->text_atom = XInternAtom(context->connection, "TEXT", False);
+ impl->utf8_string_atom = XInternAtom(context->connection, "UTF8_STRING", False);
+ impl->image_png_atom = XInternAtom(context->connection, "image/png", False);
+ impl->image_jpg_atom = XInternAtom(context->connection, "image/jpg", False);
+ impl->image_jpeg_atom = XInternAtom(context->connection, "image/jpeg", False);
+ impl->image_gif_atom = XInternAtom(context->connection, "image/gif", False);
+ impl->incr_atom = XInternAtom(context->connection, "INCR", False);
+ impl->net_wm_state_atom = XInternAtom(context->connection, "_NET_WM_STATE", False);
+ impl->net_wm_state_fullscreen_atom = XInternAtom(context->connection, "_NET_WM_STATE_FULLSCREEN", False);
+ impl->net_wm_state_above_atom = XInternAtom(context->connection, "_NET_WM_STATE_ABOVE", False);
+ impl->net_wm_name_atom = XInternAtom(context->connection, "_NET_WM_NAME", False);
+ impl->net_wm_window_type_atom = XInternAtom(context->connection, "_NET_WM_WINDOW_TYPE", False);
+ impl->net_wm_window_type_normal_atom = XInternAtom(context->connection, "_NET_WM_WINDOW_TYPE_NORMAL", False);
+ impl->net_wm_window_type_dialog_atom = XInternAtom(context->connection, "_NET_WM_WINDOW_TYPE_DIALOG", False);
+ impl->net_wm_window_type_notification_atom = XInternAtom(context->connection, "_NET_WM_WINDOW_TYPE_NOTIFICATION", False);
+ impl->net_wm_window_type_utility = XInternAtom(context->connection, "_NET_WM_WINDOW_TYPE_UTILITY", False);
+ impl->motif_wm_hints_atom = XInternAtom(context->connection, "_MOTIF_WM_HINTS", False);
+
+ impl->support_alpha = params && params->support_alpha;
+
+ const mgl_graphics_api graphics_api = params ? params->graphics_api : MGL_GRAPHICS_API_EGL;
+ const bool alpha = params && params->support_alpha;
+ if(!mgl_graphics_init(&impl->graphics, &(mgl_graphics_create_params){ .graphics_api = graphics_api, .alpha = alpha })) {
+ mgl_window_x11_deinit(self);
+ return false;
+ }
+
+ impl->default_cursor = XCreateFontCursor(context->connection, XC_arrow);
+ if(!impl->default_cursor) {
+ mgl_window_x11_deinit(self);
+ return false;
+ }
+
+ const char data[1] = {0};
+ Pixmap blank_bitmap = XCreateBitmapFromData(context->connection, DefaultRootWindow(context->connection), data, 1, 1);
+ if(!blank_bitmap) {
+ mgl_window_x11_deinit(self);
+ return false;
+ }
+
+ XColor dummy;
+ impl->invisible_cursor = XCreatePixmapCursor(context->connection, blank_bitmap, blank_bitmap, &dummy, &dummy, 0, 0);
+ XFreePixmap(context->connection, blank_bitmap);
+ if(!impl->invisible_cursor) {
+ mgl_window_x11_deinit(self);
+ return false;
+ }
+
+ x11_events_circular_buffer_init(&impl->events);
+
+ if(!mgl_x11_setup_window(self, title, params, (Window)existing_window)) {
+ mgl_window_x11_deinit(self);
+ return false;
+ }
+
+ context->current_window = self;
+ return true;
+}
+
+void mgl_window_x11_deinit(mgl_window *self) {
+ mgl_window_x11 *impl = self->impl;
+ mgl_context *context = mgl_get_context();
+ if(!impl)
+ return;
+
+ mgl_window_x11_clear_monitors(self);
+
+ if(impl->color_map) {
+ XFreeColormap(context->connection, impl->color_map);
+ impl->color_map = None;
+ }
+
+ if(impl->invisible_cursor) {
+ XFreeCursor(context->connection, impl->invisible_cursor);
+ impl->invisible_cursor = None;
+ }
+
+ if(impl->default_cursor) {
+ XFreeCursor(context->connection, impl->default_cursor);
+ impl->default_cursor = None;
+ }
+
+ if(impl->xic) {
+ XDestroyIC(impl->xic);
+ impl->xic = NULL;
+ }
+
+ if(impl->xim) {
+ XCloseIM(impl->xim);
+ impl->xim = NULL;
+ }
+
+ mgl_graphics_deinit(&impl->graphics);
+ mgl_window_x11_close(self);
+
+ if(impl->clipboard_string) {
+ free(impl->clipboard_string);
+ impl->clipboard_string = NULL;
+ }
+ impl->clipboard_size = 0;
+
+ self->open = false;
+
+ if(context->current_window == self)
+ context->current_window = NULL;
+
+ free(self->impl);
+ self->impl = NULL;
+}
diff --git a/tests/main.c b/tests/main.c
index 09e59ed..e3d199c 100644
--- a/tests/main.c
+++ b/tests/main.c
@@ -40,7 +40,7 @@ static void draw(mgl_window *window, void *userdata) {
mgl_sprite sprite;
mgl_sprite_init(&sprite, u->texture);
- mgl_sprite_set_position(&sprite, (mgl_vec2f){ 100.0f - 10.0f + u->texture->width * 0.5f * 2.0f, u->texture->height * 0.5f * 2.0f });
+ mgl_sprite_set_position(&sprite, (mgl_vec2f){ 500.0f - 10.0f + u->texture->width * 0.5f * 2.0f, 500.0f + u->texture->height * 0.5f * 2.0f });
mgl_sprite_set_color(&sprite, (mgl_color){255, 255, 255, 128});
mgl_sprite_set_rotation(&sprite, rot);
mgl_sprite_set_origin(&sprite, (mgl_vec2f){ u->texture->width * 0.5f, u->texture->height * 0.5f });
@@ -234,7 +234,7 @@ int main(void) {
test_hash_map();
test_utf8();
- if(mgl_init() != 0)
+ if(mgl_init(MGL_WINDOW_SYSTEM_X11) != 0)
return 1;
mgl_texture texture;
@@ -262,7 +262,7 @@ int main(void) {
if(mgl_texture_init(&texture) != 0)
return 1;
- if(mgl_texture_load_from_file(&texture, "tests/X11.jpg", &(mgl_texture_load_options){ .compressed = false, .pixel_coordinates = false, .mipmap = false }) != 0)
+ if(mgl_texture_load_from_file(&texture, "tests/X11.jpg", &(mgl_texture_load_options){ .compressed = false, .pixel_coordinates = false, .scale_type = MGL_TEXTURE_SCALE_LINEAR }) != 0)
return 1;
if(mgl_mapped_file_load("/usr/share/fonts/TTF/Hack-Regular.ttf", &font_file, &(mgl_memory_mapped_file_load_options){ .readable = true, .writable = false }) != 0)
@@ -321,6 +321,18 @@ int main(void) {
fprintf(stderr, "key release event, code: %u\n", event.key.code);
break;
}
+ case MGL_EVENT_MONITOR_PROPERTY_CHANGED: {
+ fprintf(stderr, "monitor property changed\n");
+ break;
+ }
+ case MGL_EVENT_MONITOR_CONNECTED: {
+ fprintf(stderr, "monitor connected\n");
+ break;
+ }
+ case MGL_EVENT_MONITOR_DISCONNECTED: {
+ fprintf(stderr, "monitor disconnected\n");
+ break;
+ }
}
}