diff options
author | Claudius "keldu" Holeksa <mail@keldu.de> | 2023-12-04 12:18:14 +0100 |
---|---|---|
committer | Claudius "keldu" Holeksa <mail@keldu.de> | 2023-12-04 12:18:14 +0100 |
commit | a14896f9ed209dd3f9597722e5a5697bd7dbf531 (patch) | |
tree | 089ca5cbbd206d1921f8f6b53292f5bc1902ca5c /modules/window | |
parent | 84ecdcbca9e55b1f57fbb832e12ff4fdbb86e7c9 (diff) |
meta: Renamed folder containing source
Diffstat (limited to 'modules/window')
-rw-r--r-- | modules/window/.nix/derivation.nix | 34 | ||||
-rw-r--r-- | modules/window/SConscript | 38 | ||||
-rw-r--r-- | modules/window/SConstruct | 66 | ||||
-rw-r--r-- | modules/window/backends.h | 10 | ||||
-rw-r--r-- | modules/window/device.h | 18 | ||||
-rw-r--r-- | modules/window/linux_xcb.h | 5 | ||||
-rw-r--r-- | modules/window/old.dummy | 399 | ||||
-rw-r--r-- | modules/window/video_mode.h | 11 | ||||
-rw-r--r-- | modules/window/window.h | 79 | ||||
-rw-r--r-- | modules/window/xcb.cpp | 290 | ||||
-rw-r--r-- | modules/window/xcb.h | 105 |
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; +}; +} +} |