summaryrefslogtreecommitdiff
path: root/modules/window
diff options
context:
space:
mode:
authorClaudius "keldu" Holeksa <mail@keldu.de>2023-12-04 12:18:14 +0100
committerClaudius "keldu" Holeksa <mail@keldu.de>2023-12-04 12:18:14 +0100
commita14896f9ed209dd3f9597722e5a5697bd7dbf531 (patch)
tree089ca5cbbd206d1921f8f6b53292f5bc1902ca5c /modules/window
parent84ecdcbca9e55b1f57fbb832e12ff4fdbb86e7c9 (diff)
meta: Renamed folder containing source
Diffstat (limited to 'modules/window')
-rw-r--r--modules/window/.nix/derivation.nix34
-rw-r--r--modules/window/SConscript38
-rw-r--r--modules/window/SConstruct66
-rw-r--r--modules/window/backends.h10
-rw-r--r--modules/window/device.h18
-rw-r--r--modules/window/linux_xcb.h5
-rw-r--r--modules/window/old.dummy399
-rw-r--r--modules/window/video_mode.h11
-rw-r--r--modules/window/window.h79
-rw-r--r--modules/window/xcb.cpp290
-rw-r--r--modules/window/xcb.h105
11 files changed, 1055 insertions, 0 deletions
diff --git a/modules/window/.nix/derivation.nix b/modules/window/.nix/derivation.nix
new file mode 100644
index 0000000..67a682c
--- /dev/null
+++ b/modules/window/.nix/derivation.nix
@@ -0,0 +1,34 @@
+{ lib
+, stdenv
+, scons
+, clang-tools
+, version
+, forstio
+, xorg
+}:
+
+let
+
+in stdenv.mkDerivation {
+ pname = "forstio-window";
+ inherit version;
+ src = ./..;
+
+ enableParallelBuilding = true;
+
+ nativeBuildInputs = [
+ scons
+ clang-tools
+ ];
+
+ buildInputs = [
+ forstio.core
+ forstio.async
+ forstio.io
+ forstio.codec
+ xorg.libX11
+ xorg.libxcb
+ ];
+
+ outputs = ["out" "dev"];
+}
diff --git a/modules/window/SConscript b/modules/window/SConscript
new file mode 100644
index 0000000..bd830b9
--- /dev/null
+++ b/modules/window/SConscript
@@ -0,0 +1,38 @@
+#!/bin/false
+
+import os
+import os.path
+import glob
+
+
+Import('env')
+
+dir_path = Dir('.').abspath
+
+# Environment for base library
+window_env = env.Clone();
+
+window_env.sources = sorted(glob.glob(dir_path + "/*.cpp"))
+window_env.headers = sorted(glob.glob(dir_path + "/*.h"))
+
+env.sources += window_env.sources;
+env.headers += window_env.headers;
+
+## Shared lib
+objects_shared = []
+window_env.add_source_files(objects_shared, window_env.sources, shared=True);
+window_env.library_shared = window_env.SharedLibrary('#build/forstio-window', [objects_shared]);
+
+## Static lib
+objects_static = []
+window_env.add_source_files(objects_static, window_env.sources, shared=False);
+window_env.library_static = window_env.StaticLibrary('#build/forstio-window', [objects_static]);
+
+# Set Alias
+env.Alias('library_window', [window_env.library_shared, window_env.library_static]);
+
+env.targets += ['library_window'];
+
+# Install
+env.Install('$prefix/lib/', [window_env.library_shared, window_env.library_static]);
+env.Install('$prefix/include/forstio/window/', [window_env.headers]);
diff --git a/modules/window/SConstruct b/modules/window/SConstruct
new file mode 100644
index 0000000..05fc016
--- /dev/null
+++ b/modules/window/SConstruct
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+
+import sys
+import os
+import os.path
+import glob
+import re
+
+
+if sys.version_info < (3,):
+ def isbasestring(s):
+ return isinstance(s,basestring)
+else:
+ def isbasestring(s):
+ return isinstance(s, (str,bytes))
+
+def add_kel_source_files(self, sources, filetype, lib_env=None, shared=False, target_post=""):
+
+ if isbasestring(filetype):
+ dir_path = self.Dir('.').abspath
+ filetype = sorted(glob.glob(dir_path+"/"+filetype))
+
+ for path in filetype:
+ target_name = re.sub( r'(.*?)(\.cpp|\.c\+\+)', r'\1' + target_post, path )
+ if shared:
+ target_name+='.os'
+ sources.append( self.SharedObject( target=target_name, source=path ) )
+ else:
+ target_name+='.o'
+ sources.append( self.StaticObject( target=target_name, source=path ) )
+ pass
+
+def isAbsolutePath(key, dirname, env):
+ assert os.path.isabs(dirname), "%r must have absolute path syntax" % (key,)
+
+env_vars = Variables(
+ args=ARGUMENTS
+)
+
+env_vars.Add('prefix',
+ help='Installation target location of build results and headers',
+ default='/usr/local/',
+ validator=isAbsolutePath
+)
+
+env=Environment(ENV=os.environ, variables=env_vars, CPPPATH=[],
+ CPPDEFINES=['SAW_UNIX', 'SAW_UNIX_XCB'],
+ CXXFLAGS=['-std=c++20','-g','-Wall','-Wextra'],
+ LIBS=['forstio-core', 'forstio-io', 'forstio-async', 'forstio-codec'])
+env.__class__.add_source_files = add_kel_source_files
+env.Tool('compilation_db');
+env.cdb = env.CompilationDatabase('compile_commands.json');
+
+env.objects = [];
+env.sources = [];
+env.headers = [];
+env.targets = [];
+
+Export('env')
+SConscript('SConscript')
+
+env.Alias('cdb', env.cdb);
+env.Alias('all', [env.targets]);
+env.Default('all');
+
+env.Alias('install', '$prefix')
diff --git a/modules/window/backends.h b/modules/window/backends.h
new file mode 100644
index 0000000..e129037
--- /dev/null
+++ b/modules/window/backends.h
@@ -0,0 +1,10 @@
+#pragma once
+
+namespace saw {
+namespace gfx {
+namespace backend {
+struct linux_xcb {};
+struct wasm {};
+}
+}
+}
diff --git a/modules/window/device.h b/modules/window/device.h
new file mode 100644
index 0000000..7d3cdb1
--- /dev/null
+++ b/modules/window/device.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "window.h"
+
+#include <forstio/async/async.h>
+#include <forstio/core/common.h>
+#include <forstio/codec/data.h>
+#include <forstio/io/io.h>
+
+#include <string_view>
+#include <variant>
+
+namespace saw {
+namespace gfx {
+template<typename T>
+class device;
+}
+}
diff --git a/modules/window/linux_xcb.h b/modules/window/linux_xcb.h
new file mode 100644
index 0000000..65ff94d
--- /dev/null
+++ b/modules/window/linux_xcb.h
@@ -0,0 +1,5 @@
+#pragma once
+
+#ifdef SAW_UNIX_XCB
+#include "xcb.h"
+#endif
diff --git a/modules/window/old.dummy b/modules/window/old.dummy
new file mode 100644
index 0000000..c762945
--- /dev/null
+++ b/modules/window/old.dummy
@@ -0,0 +1,399 @@
+#include <X11/Xlib-xcb.h>
+#include <X11/Xlib.h>
+#include <xcb/xcb.h>
+#include <forstio/io/io.h>
+
+#include <map>
+#include <vector>
+
+#include "device.h"
+
+namespace saw {
+class xcb_window;
+class xcb_device final : public device {
+public:
+ ::Display *display;
+ int screen;
+
+ xcb_connection_t *xcb_connection;
+ xcb_screen_t *xcb_screen;
+
+ own<input_stream> async_notifier;
+ conveyor_sink async_conveyor;
+
+ std::map<xcb_window_t, xcb_window *> windows;
+
+ std::vector<xcb_generic_event_t *> pending_events;
+
+public:
+ xcb_device(::Display *display, int screen, xcb_connection_t *xcb_connection,
+ xcb_screen_t *xcb_screen, own<input_stream> &&an);
+ ~xcb_device();
+
+ void window_destroyed(xcb_window_t window_id);
+ void handle_events();
+
+ own<xcb_window> create_xcb_window(const video_mode &mode,
+ std::string_view title_view,
+ int visual_id);
+ own<window> create_window(const video_mode &video_mode,
+ std::string_view title_view) override;
+
+ void flush() override;
+};
+
+own<xcb_device> create_xcb_device(io_provider &provider);
+
+class xcb_window final : public window {
+public:
+ xcb_device &device_;
+
+ xcb_window_t xcb_window_;
+ xcb_colormap_t xcb_colormap_;
+
+ video_mode video_mode_;
+ std::string window_title_;
+
+ own<conveyor_feeder<window::variant_event>> event_feeder = nullptr;
+
+public:
+ xcb_window(xcb_device &dev, xcb_window_t xcb_win,
+ xcb_colormap_t xcb_colormap_, const video_mode &video_mode_,
+ std::string_view title_view_);
+ ~xcb_window();
+
+ void show() override;
+ void hide() override;
+
+ const video_mode &get_video_mode() const override;
+ const std::string_view title() const override;
+
+ void resize(size_t width, size_t height) override;
+
+ conveyor<window::variant_event> on_event() override;
+
+ void resize_event(size_t x, size_t y, size_t width, size_t height);
+ void mouse_event(int16_t x, int16_t y, uint16_t state, bool pressed);
+ void mouse_move_event(int16_t x, int16_t y);
+ void keyboard_event(int16_t x, int16_t y, uint32_t keycode, bool pressed,
+ bool repeat);
+};
+
+xcb_device::xcb_device(::Display *display, int screen,
+ xcb_connection_t *xcb_connection,
+ xcb_screen_t *xcb_screen, own<input_stream> &&an)
+ : display{display}, screen{screen}, xcb_connection{xcb_connection},
+ xcb_screen{xcb_screen}, async_notifier{std::move(an)},
+ async_conveyor{async_notifier->read_ready()
+ .then([this]() { handle_events(); })
+ .sink()} {}
+
+xcb_device::~xcb_device() {
+ if (display) {
+ xcb_flush(xcb_connection);
+ ::XCloseDisplay(display);
+ }
+}
+
+void xcb_device::window_destroyed(xcb_window_t window_id) {
+ windows.erase(window_id);
+}
+
+void xcb_device::handle_events() {
+ while (xcb_generic_event_t *event = xcb_poll_for_event(xcb_connection)) {
+ pending_events.push_back(event);
+ }
+ for (size_t i = 0; i < pending_events.size(); ++i) {
+ xcb_generic_event_t *event = pending_events.at(i);
+ switch (event->response_type & ~0x80) {
+ case XCB_MOTION_NOTIFY: {
+ xcb_motion_notify_event_t *motion =
+ reinterpret_cast<xcb_motion_notify_event_t *>(event);
+ auto find = windows.find(motion->event);
+ if (find != windows.end()) {
+ assert(find->second);
+ find->second->mouse_move_event(motion->event_x,
+ motion->event_y);
+ }
+ } break;
+ case XCB_EXPOSE: {
+ xcb_expose_event_t *expose =
+ reinterpret_cast<xcb_expose_event_t *>(event);
+ auto find = windows.find(expose->window);
+ if (find != windows.end()) {
+ assert(find->second);
+ find->second->resize_event(static_cast<size_t>(expose->x),
+ static_cast<size_t>(expose->y),
+ static_cast<size_t>(expose->width),
+ static_cast<size_t>(expose->height));
+ }
+ } break;
+ case XCB_BUTTON_RELEASE: {
+ xcb_button_release_event_t *button =
+ reinterpret_cast<xcb_button_release_event_t *>(event);
+ auto find = windows.find(button->event);
+ if (find != windows.end()) {
+ assert(find->second);
+ find->second->mouse_event(button->event_x, button->event_y,
+ button->detail, false);
+ }
+ } break;
+ case XCB_BUTTON_PRESS: {
+ xcb_button_press_event_t *button =
+ reinterpret_cast<xcb_button_press_event_t *>(event);
+ auto find = windows.find(button->event);
+ if (find != windows.end()) {
+ assert(find->second);
+ find->second->mouse_event(button->event_x, button->event_y,
+ button->detail, true);
+ }
+ } break;
+ case XCB_KEY_RELEASE: {
+ xcb_key_release_event_t *key =
+ reinterpret_cast<xcb_key_release_event_t *>(event);
+
+ bool repeat = false;
+ /*
+ * Peek into future events
+ */
+ for (size_t j = i + 1; j < pending_events.size(); ++j) {
+ xcb_generic_event_t *f_ev = pending_events.at(j);
+
+ if ((f_ev->response_type & ~0x80) == XCB_KEY_PRESS) {
+ xcb_key_press_event_t *f_key =
+ reinterpret_cast<xcb_key_press_event_t *>(f_ev);
+
+ if (key->detail == f_key->detail &&
+ key->event == f_key->event) {
+ auto iterator = pending_events.begin() + j;
+ assert(iterator != pending_events.end());
+ free(*iterator);
+ pending_events.erase(iterator);
+ repeat = true;
+ break;
+ }
+ }
+ }
+
+ auto find = windows.find(key->event);
+ if (find != windows.end()) {
+ assert(find->second);
+ find->second->keyboard_event(key->event_x, key->event_y,
+ key->detail, repeat, repeat);
+ }
+ } break;
+ case XCB_KEY_PRESS: {
+ xcb_key_press_event_t *key =
+ reinterpret_cast<xcb_key_press_event_t *>(event);
+ auto find = windows.find(key->event);
+ if (find != windows.end()) {
+ assert(find->second);
+ find->second->keyboard_event(key->event_x, key->event_y,
+ key->detail, true, false);
+ }
+ } break;
+ default:
+ break;
+ }
+ }
+
+ for (xcb_generic_event_t *event : pending_events) {
+ free(event);
+ }
+ pending_events.clear();
+}
+
+own<xcb_window> xcb_device::create_xcb_window(const video_mode &video_mode,
+ std::string_view title_view,
+ int visual_id) {
+ assert(xcb_screen);
+ assert(xcb_connection);
+
+ xcb_colormap_t xcb_colormap = xcb_generate_id(xcb_connection);
+ xcb_window_t xcb_window = xcb_generate_id(xcb_connection);
+
+ xcb_create_colormap(xcb_connection, XCB_COLORMAP_ALLOC_NONE, xcb_colormap,
+ xcb_screen->root, visual_id);
+
+ uint32_t eventmask =
+ XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_KEY_PRESS |
+ XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_BUTTON_PRESS |
+ XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION |
+ XCB_EVENT_MASK_BUTTON_MOTION;
+ uint32_t valuelist[] = {eventmask, xcb_colormap, 0};
+ uint32_t valuemask = XCB_CW_EVENT_MASK | XCB_CW_COLORMAP;
+
+ xcb_create_window(xcb_connection, XCB_COPY_FROM_PARENT, xcb_window,
+ xcb_screen->root, 0, 0, video_mode.width,
+ video_mode.height, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
+ visual_id, valuemask, valuelist);
+
+ xcb_change_property(xcb_connection, XCB_PROP_MODE_REPLACE, xcb_window,
+ XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, title_view.size(),
+ title_view.data());
+
+ xcb_flush(xcb_connection);
+ auto window = heap<class xcb_window>(*this, xcb_window, xcb_colormap,
+ video_mode, title_view);
+ windows[xcb_window] = window.get();
+
+ return window;
+}
+
+own<window> xcb_device::create_window(const video_mode &video_mode,
+ std::string_view title_view) {
+ assert(xcb_screen);
+ return create_xcb_window(video_mode, title_view, xcb_screen->root_visual);
+}
+
+void xcb_device::flush() {
+ assert(xcb_connection);
+ xcb_flush(xcb_connection);
+}
+
+own<xcb_device> create_xcb_device(io_provider &provider) {
+ ::Display *display = ::XOpenDisplay(nullptr);
+ if (!display) {
+ /// @todo log errors
+ return nullptr;
+ }
+
+ int screen = ::XDefaultScreen(display);
+
+ xcb_connection_t *xcb_connection = ::XGetXCBConnection(display);
+ if (!xcb_connection) {
+ /// @todo log errors
+ ::XCloseDisplay(display);
+ return nullptr;
+ }
+
+ int fd = xcb_get_file_descriptor(xcb_connection);
+
+ own<input_stream> fd_wrapped = provider.wrap_input_fd(fd);
+ if (!fd_wrapped) {
+ ::XCloseDisplay(display);
+ return nullptr;
+ }
+
+ ::XSetEventQueueOwner(display, XCBOwnsEventQueue);
+
+ xcb_screen_iterator_t screen_iter =
+ xcb_setup_roots_iterator(xcb_get_setup(xcb_connection));
+ for (int screen_i = screen; screen_iter.rem && screen_i > 0;
+ --screen_i, xcb_screen_next(&screen_iter))
+ ;
+
+ xcb_screen_t *xcb_screen = screen_iter.data;
+
+ return heap<xcb_device>(display, screen, xcb_connection, xcb_screen,
+ std::move(fd_wrapped));
+}
+
+own<device> createdevice(io_provider &provider) {
+ return create_xcb_device(provider);
+}
+
+xcb_window::xcb_window(xcb_device &dev, xcb_window_t xcb_win,
+ xcb_colormap_t xcb_colmap, const video_mode &vid_mode,
+ std::string_view title_view_)
+ : device_{dev}, xcb_window_{xcb_win}, xcb_colormap_{xcb_colmap},
+ video_mode_{vid_mode}, window_title_{title_view_} {}
+
+xcb_window::~xcb_window() {
+ device_.window_destroyed(xcb_window_);
+ xcb_destroy_window(device_.xcb_connection, xcb_window_);
+ device_.flush();
+}
+
+void xcb_window::show() {
+ assert(device_.xcb_connection);
+ xcb_map_window(device_.xcb_connection, xcb_window_);
+}
+
+void xcb_window::hide() {
+ assert(device_.xcb_connection);
+ xcb_unmap_window(device_.xcb_connection, xcb_window_);
+}
+
+const video_mode &xcb_window::get_video_mode() const { return video_mode_; }
+
+const std::string_view xcb_window::title() const { return window_title_; }
+
+void xcb_window::resize(size_t width, size_t height) {
+ const uint32_t values[2] = {static_cast<uint32_t>(width),
+ static_cast<uint32_t>(height)};
+
+ xcb_configure_window(device_.xcb_connection, xcb_window_,
+ XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
+ values);
+ video_mode_.width = width;
+ video_mode_.height = height;
+}
+
+conveyor<window::variant_event> xcb_window::on_event() {
+ auto caf = new_conveyor_and_feeder<window::variant_event>();
+ event_feeder = std::move(caf.feeder);
+ return std::move(caf.conveyor);
+}
+
+void xcb_window::resize_event(size_t x, size_t y, size_t width, size_t height) {
+ (void)x;
+ (void)y;
+ /// @todo maybe include x and y?
+ video_mode_.width = width;
+ video_mode_.height = height;
+
+ if (event_feeder) {
+ event_feeder->feed(
+ window::variant_event{window::event::resize{width, height}});
+ }
+}
+
+void xcb_window::mouse_event(int16_t x, int16_t y, uint16_t state,
+ bool pressed) {
+ if (x < 0 || y < 0) {
+ return;
+ }
+ uint32_t ux = static_cast<uint32_t>(x);
+ uint32_t uy = static_cast<uint32_t>(y);
+ if (ux >= video_mode_.width || uy >= video_mode_.height) {
+ return;
+ }
+ if (event_feeder) {
+ event_feeder->feed(window::variant_event{
+ window::event::mouse{state, pressed, ux, uy}});
+ }
+}
+
+void xcb_window::mouse_move_event(int16_t x, int16_t y) {
+ if (x < 0 || y < 0) {
+ return;
+ }
+ uint32_t ux = static_cast<uint32_t>(x);
+ uint32_t uy = static_cast<uint32_t>(y);
+ if (ux >= video_mode_.width || uy >= video_mode_.height) {
+ return;
+ }
+ if (event_feeder) {
+ event_feeder->feed(
+ window::variant_event{window::event::mouse_move{ux, uy}});
+ }
+}
+
+void xcb_window::keyboard_event(int16_t x, int16_t y, uint32_t keycode,
+ bool pressed, bool repeat) {
+ if (x < 0 || y < 0) {
+ return;
+ }
+ uint32_t ux = static_cast<uint32_t>(x);
+ uint32_t uy = static_cast<uint32_t>(y);
+ if (ux >= video_mode_.width || uy >= video_mode_.height) {
+ return;
+ }
+ if (event_feeder) {
+ event_feeder->feed(window::variant_event{
+ window::event::keyboard{keycode, keycode, pressed, repeat}});
+ }
+}
+
+} // namespace saw
diff --git a/modules/window/video_mode.h b/modules/window/video_mode.h
new file mode 100644
index 0000000..a8f1695
--- /dev/null
+++ b/modules/window/video_mode.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <cstddef>
+
+namespace saw {
+class video_mode {
+public:
+ size_t width = 64;
+ size_t height = 64;
+};
+} // namespace saw
diff --git a/modules/window/window.h b/modules/window/window.h
new file mode 100644
index 0000000..36786de
--- /dev/null
+++ b/modules/window/window.h
@@ -0,0 +1,79 @@
+#pragma once
+
+#include "video_mode.h"
+
+#include <forstio/async/async.h>
+#include <forstio/core/common.h>
+#include <forstio/codec/schema.h>
+
+#include <string_view>
+#include <variant>
+
+namespace saw {
+namespace gfx {
+namespace schema {
+using namespace saw::schema;
+using WindowResize = Struct<
+ Member<UInt32, "width">,
+ Member<UInt32, "height">
+>;
+using WindowEvents = Union<
+ Member<WindowResize, "resize">
+>;
+}
+
+template<typename T>
+class window;
+}
+}
+
+#include "linux_xcb.h"
+
+/**
+namespace saw {
+class window {
+public:
+ class event {
+ public:
+ struct resize {
+ size_t width;
+ size_t height;
+ };
+
+ struct keyboard {
+ uint32_t key;
+ uint32_t scan;
+ bool pressed;
+ bool repeat;
+ };
+
+ struct mouse {
+ uint16_t button_mask;
+ bool pressed;
+ uint32_t x;
+ uint32_t y;
+ };
+
+ struct mouse_move {
+ uint32_t x;
+ uint32_t y;
+ };
+ };
+
+ using variant_event = std::variant<event::resize, event::keyboard,
+ event::mouse, event::mouse_move>;
+
+ virtual ~window() = default;
+
+ virtual void show() = 0;
+ virtual void hide() = 0;
+
+ virtual const video_mode &get_video_mode() const = 0;
+ virtual const std::string_view title() const = 0;
+
+ virtual void resize(size_t width, size_t height) = 0;
+
+ virtual conveyor<variant_event> on_event() = 0;
+};
+} // namespace saw
+*/
diff --git a/modules/window/xcb.cpp b/modules/window/xcb.cpp
new file mode 100644
index 0000000..1b804ba
--- /dev/null
+++ b/modules/window/xcb.cpp
@@ -0,0 +1,290 @@
+#ifndef SAW_UNIX_XCB
+#error "XCB is not supported"
+#endif
+
+#include "xcb.h"
+
+namespace saw {
+namespace gfx {
+device<backend::linux_xcb>::device(::Display* disp, int screen, xcb_connection_t *xcb_connection, xcb_screen_t *xcb_screen, own<input_stream>&& an):
+ display_{disp}, screen_{screen}, xcb_connection_{xcb_connection}, xcb_screen_{xcb_screen}, async_notifier_{std::move(an)},
+ async_conveyor_{async_notifier_->read_ready()
+ .then([this]() { handle_events(); })
+ .sink()} {}
+
+device<backend::linux_xcb>::~device(){
+ if (display_) {
+ xcb_flush(xcb_connection_);
+ ::XCloseDisplay(display_);
+ }
+}
+
+void device<backend::linux_xcb>::xcb_window_was_destroyed(xcb_window_t window_id){
+ windows_.erase(window_id);
+}
+
+void device<backend::linux_xcb>::handle_events(){
+ while (xcb_generic_event_t *event = xcb_poll_for_event(xcb_connection_)) {
+ pending_events_.push_back(event);
+ }
+ for (size_t i = 0; i < pending_events_.size(); ++i) {
+ xcb_generic_event_t *event = pending_events_.at(i);
+ switch (event->response_type & ~0x80) {
+ case XCB_MOTION_NOTIFY: {
+ xcb_motion_notify_event_t *motion =
+ reinterpret_cast<xcb_motion_notify_event_t *>(event);
+ auto find = windows_.find(motion->event);
+ if (find != windows_.end()) {
+ assert(find->second);
+ find->second->mouse_move_event(motion->event_x,
+ motion->event_y);
+ }
+ } break;
+ case XCB_EXPOSE: {
+ xcb_expose_event_t *expose =
+ reinterpret_cast<xcb_expose_event_t *>(event);
+ auto find = windows_.find(expose->window);
+ if (find != windows_.end()) {
+ assert(find->second);
+ find->second->resize_event(static_cast<size_t>(expose->x),
+ static_cast<size_t>(expose->y),
+ static_cast<size_t>(expose->width),
+ static_cast<size_t>(expose->height));
+ }
+ } break;
+ case XCB_BUTTON_RELEASE: {
+ xcb_button_release_event_t *button =
+ reinterpret_cast<xcb_button_release_event_t *>(event);
+ auto find = windows_.find(button->event);
+ if (find != windows_.end()) {
+ assert(find->second);
+ find->second->mouse_event(button->event_x, button->event_y,
+ button->detail, false);
+ }
+ } break;
+ case XCB_BUTTON_PRESS: {
+ xcb_button_press_event_t *button =
+ reinterpret_cast<xcb_button_press_event_t *>(event);
+ auto find = windows_.find(button->event);
+ if (find != windows_.end()) {
+ assert(find->second);
+ find->second->mouse_event(button->event_x, button->event_y,
+ button->detail, true);
+ }
+ } break;
+ case XCB_KEY_RELEASE: {
+ xcb_key_release_event_t *key =
+ reinterpret_cast<xcb_key_release_event_t *>(event);
+
+ bool repeat = false;
+ /*
+ * Peek into future events
+ */
+ for (size_t j = i + 1; j < pending_events_.size(); ++j) {
+ xcb_generic_event_t *f_ev = pending_events_.at(j);
+
+ if ((f_ev->response_type & ~0x80) == XCB_KEY_PRESS) {
+ xcb_key_press_event_t *f_key =
+ reinterpret_cast<xcb_key_press_event_t *>(f_ev);
+
+ if (key->detail == f_key->detail &&
+ key->event == f_key->event) {
+ auto iterator = pending_events_.begin() + j;
+ assert(iterator != pending_events_.end());
+ free(*iterator);
+ pending_events_.erase(iterator);
+ repeat = true;
+ break;
+ }
+ }
+ }
+
+ auto find = windows_.find(key->event);
+ if (find != windows_.end()) {
+ assert(find->second);
+ find->second->keyboard_event(key->event_x, key->event_y,
+ key->detail, repeat, repeat);
+ }
+ } break;
+ case XCB_KEY_PRESS: {
+ xcb_key_press_event_t *key =
+ reinterpret_cast<xcb_key_press_event_t *>(event);
+ auto find = windows_.find(key->event);
+ if (find != windows_.end()) {
+ assert(find->second);
+ find->second->keyboard_event(key->event_x, key->event_y,
+ key->detail, true, false);
+ }
+ } break;
+ default:
+ break;
+ }
+ }
+
+ for (xcb_generic_event_t *event : pending_events_) {
+ free(event);
+ }
+ pending_events_.clear();
+}
+
+own<window<backend::linux_xcb>> device<backend::linux_xcb>::create_xcb_window(const video_mode &vid_mode,
+ std::string_view title_view,
+ int visual_id) {
+ assert(xcb_screen_);
+ assert(xcb_connection_);
+
+ xcb_colormap_t xcb_colormap = xcb_generate_id(xcb_connection_);
+ xcb_window_t xcb_window = xcb_generate_id(xcb_connection_);
+
+ xcb_create_colormap(xcb_connection_, XCB_COLORMAP_ALLOC_NONE, xcb_colormap,
+ xcb_screen_->root, visual_id);
+
+ uint32_t eventmask =
+ XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_KEY_PRESS |
+ XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_BUTTON_PRESS |
+ XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION |
+ XCB_EVENT_MASK_BUTTON_MOTION;
+ uint32_t valuelist[] = {eventmask, xcb_colormap, 0};
+ uint32_t valuemask = XCB_CW_EVENT_MASK | XCB_CW_COLORMAP;
+
+ xcb_create_window(xcb_connection_, XCB_COPY_FROM_PARENT, xcb_window,
+ xcb_screen_->root, 0, 0, vid_mode.width,
+ vid_mode.height, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
+ visual_id, valuemask, valuelist);
+
+ xcb_change_property(xcb_connection_, XCB_PROP_MODE_REPLACE, xcb_window,
+ XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, title_view.size(),
+ title_view.data());
+
+ xcb_flush(xcb_connection_);
+ auto win = heap<window<backend::linux_xcb>>(*this, xcb_window, xcb_colormap,
+ vid_mode, title_view);
+ // Insert into map
+ windows_[xcb_window] = win.get();
+
+ return win;
+}
+
+own<window<backend::linux_xcb>> device<backend::linux_xcb>::create_window(const video_mode& vid_mode, std::string_view title_view){
+ assert(xcb_screen_);
+ return create_xcb_window(vid_mode, title_view, xcb_screen_->root_visual);
+}
+
+void device<backend::linux_xcb>::flush(){
+ assert(xcb_connection_);
+ xcb_flush(xcb_connection_);
+}
+
+error_or<own<device<backend::linux_xcb>>> create_xcb_device(io_provider& provider){
+ ::Display *display = ::XOpenDisplay(nullptr);
+ if (!display) {
+ /// @todo log errors
+ return make_error<err::critical>();
+ }
+
+ int screen = ::XDefaultScreen(display);
+
+ xcb_connection_t *xcb_connection = ::XGetXCBConnection(display);
+ if (!xcb_connection) {
+ /// @todo log errors
+ ::XCloseDisplay(display);
+ return make_error<err::critical>();
+ }
+
+ int fd = xcb_get_file_descriptor(xcb_connection);
+
+ own<input_stream> fd_wrapped = provider.wrap_input_fd(fd);
+ if (!fd_wrapped) {
+ ::XCloseDisplay(display);
+ return make_error<err::critical>();
+ }
+
+ ::XSetEventQueueOwner(display, XCBOwnsEventQueue);
+
+ xcb_screen_iterator_t screen_iter =
+ xcb_setup_roots_iterator(xcb_get_setup(xcb_connection));
+ for (int screen_i = screen; screen_iter.rem && screen_i > 0;
+ --screen_i, xcb_screen_next(&screen_iter))
+ ;
+
+ xcb_screen_t *xcb_screen = screen_iter.data;
+
+ return heap<device<backend::linux_xcb>>(display, screen, xcb_connection, xcb_screen, std::move(fd_wrapped));
+}
+
+window<backend::linux_xcb>::window(device<backend::linux_xcb>& dev_, xcb_window_t xcb_win, xcb_colormap_t xcb_colormap_, const video_mode& vid_mode_, const std::string_view& title_view_):
+ device_{&dev_},
+ xcb_window_{xcb_win},
+ xcb_colormap_{xcb_colormap_},
+ video_mode_{vid_mode_},
+ window_title_{title_view_}
+{}
+
+window<backend::linux_xcb>::~window(){
+ assert(device_);
+ device_->xcb_window_was_destroyed(xcb_window_);
+ xcb_destroy_window(device_->xcb_connection_, xcb_window_);
+ device_->flush();
+}
+
+void window<backend::linux_xcb>::show(){
+ assert(device_->xcb_connection_);
+ xcb_map_window(device_->xcb_connection_, xcb_window_);
+}
+
+void window<backend::linux_xcb>::hide(){
+ assert(device_->xcb_connection_);
+ xcb_unmap_window(device_->xcb_connection_, xcb_window_);
+}
+
+const video_mode& window<backend::linux_xcb>::get_video_mode() const {
+ return video_mode_;
+}
+
+const std::string_view window<backend::linux_xcb>::get_title() const {
+ return window_title_;
+}
+
+void window<backend::linux_xcb>::resize(uint64_t w, uint64_t h){
+ const uint32_t values[2] = {
+ static_cast<uint32_t>(w),
+ static_cast<uint32_t>(h)
+ };
+
+ xcb_configure_window(device_->xcb_connection_, xcb_window_,
+ XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
+ values);
+
+ video_mode_.width = w;
+ video_mode_.height = h;
+}
+
+conveyor<data<schema::WindowEvents>> window<backend::linux_xcb>::on_event() {
+ auto caf = new_conveyor_and_feeder<data<schema::WindowEvents>>();
+ event_feeder = std::move(caf.feeder);
+ return std::move(caf.conveyor);
+}
+
+void window<backend::linux_xcb>::resize_event(uint64_t x, uint64_t y, uint64_t width, uint64_t height){
+ /// @todo implement
+ assert(false);
+}
+void window<backend::linux_xcb>::mouse_event(int16_t x, int16_t y, uint16_t state, bool pressed){
+ /// @todo implement
+ assert(false);
+}
+void window<backend::linux_xcb>::mouse_move_event(int16_t x, int16_t y){
+ /// @todo implement
+ assert(false);
+}
+void window<backend::linux_xcb>::keyboard_event(int16_t x, int16_t y, uint32_t keycode, bool pressed, bool repeat){
+ /// @todo implement
+ assert(false);
+}
+
+xcb_window_t window<backend::linux_xcb>::get_xcb_window_handle() const{
+ return xcb_window_;
+}
+
+}
+}
diff --git a/modules/window/xcb.h b/modules/window/xcb.h
new file mode 100644
index 0000000..a2a9b0b
--- /dev/null
+++ b/modules/window/xcb.h
@@ -0,0 +1,105 @@
+#pragma once
+
+#ifndef SAW_UNIX_XCB
+#error "XCB is not supported"
+#endif
+
+#include "backends.h"
+#include "device.h"
+#include "window.h"
+
+#include <map>
+
+#include <X11/Xlib-xcb.h>
+#include <X11/Xlib.h>
+
+namespace saw {
+namespace gfx {
+template<typename T>
+class window;
+
+template<typename T>
+class device;
+
+template<>
+class device<backend::linux_xcb> final {
+private:
+ ::Display *display_;
+ int screen_;
+
+ xcb_connection_t *xcb_connection_;
+ xcb_screen_t *xcb_screen_;
+
+ own<input_stream> async_notifier_;
+ conveyor_sink async_conveyor_;
+
+ std::map<xcb_window_t, window<backend::linux_xcb> *> windows_;
+
+ std::vector<xcb_generic_event_t *> pending_events_;
+
+ friend class window<backend::linux_xcb>;
+public:
+ own<window<backend::linux_xcb>> create_xcb_window(const video_mode& vid_mod, std::string_view title_view, int visual_id);
+ void xcb_window_was_destroyed(xcb_window_t window_id);
+public:
+ device(::Display *display, int screen, xcb_connection_t *xcb_connection,
+ xcb_screen_t *xcb_screen, own<input_stream> && an);
+
+ ~device();
+
+ void handle_events();
+
+ own<window<backend::linux_xcb>> create_window(const video_mode& vid_mod, std::string_view title_view);
+
+ void flush();
+
+ // XCB specific info for other classes
+ ::Display* get_xcb_display() {
+ return display_;
+ }
+
+ int get_xcb_screen() const {
+ return screen_;
+ }
+};
+
+error_or<own<device<backend::linux_xcb>>> create_xcb_device(io_provider& provider);
+
+template<>
+class window<backend::linux_xcb> final {
+private:
+ device<backend::linux_xcb> *device_;
+
+ xcb_window_t xcb_window_;
+ xcb_colormap_t xcb_colormap_;
+
+ video_mode video_mode_;
+ std::string window_title_;
+
+ own<conveyor_feeder<data<schema::WindowEvents>>> event_feeder = nullptr;
+public:
+ window(device<backend::linux_xcb>& dev_, xcb_window_t xcb_win, xcb_colormap_t xcb_colormap_, const video_mode& vid_mode_, const std::string_view& title_view_);
+
+ ~window();
+
+ void show();
+ void hide();
+
+ const video_mode& get_video_mode() const;
+
+ const std::string_view get_title() const;
+
+ void resize(uint64_t width, uint64_t height);
+
+ conveyor<data<schema::WindowEvents>> on_event();
+
+ void resize_event(uint64_t x, uint64_t y, uint64_t width, uint64_t height);
+ void mouse_event(int16_t x, int16_t y, uint16_t state, bool pressed);
+ void mouse_move_event(int16_t x, int16_t y);
+ void keyboard_event(int16_t x, int16_t y, uint32_t keycode, bool pressed, bool repeat);
+
+ // XCB specific things
+ xcb_window_t get_xcb_window_handle() const;
+};
+}
+}