#pragma once
#include <X11/Xos.h>
#include <X11/Xutil.h> // -lX11
#if USE_X_SHM
#include <X11/extensions/XShm.h> // -lXext
#include <sys/ipc.h>
#include <sys/shm.h>
#endif
#include <assert.h>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <thread>
#include "vertex.hpp"
/** @brief X environment as created by @ref XWin. */
struct x_ctx_t {
Display* display{};
Visual* visual{};
Window window{};
std::mutex mtx{};
int fd{-1};
};
/**
* @brief Simple accessor for preallocated buffer (possibly SHM), written to by @ref Renderer.
*
* Pixel format suitable for directly using as X11 TrueColor image.
* There should be only one instance and buffer in @ref XImg.
*/
class XFrame {
public:
struct PACKED rgb_t {
#if !USE_X && USE_PPM
unsigned char r, g, b;
#else
unsigned char b, g, r;
unsigned char _unused{};
#endif
};
private:
const res_t res;
rgb_t* const buf;
area_t dirty;
public:
XFrame(res_t, rgb_t*);
INLINE constexpr rgb_t& at(dcoord_t x, dcoord_t y) {
assert(0 <= x && x < res.w);
assert(0 <= y && y < res.h);
return buf[y * res.w + x];
}
INLINE const res_t& resolution() const {
return res;
}
/** @brief As reused, track min/max of what needs to be overwritten. */
area_t& dirty_box() {
return dirty;
}
};
/** @brief Creation and drawing primitives for X image with buffer in given window. */
class XImg {
private:
struct buf_t {
#if USE_X_SHM
XShmSegmentInfo shm;
#endif
XFrame::rgb_t* data;
} buf;
const res_t off;
x_ctx_t& x;
GC gctx;
XImage* xim;
XFrame frame;
static buf_t create_buf(res_t);
static XImage* create_image(res_t, x_ctx_t&, buf_t&);
public:
XImg(res_t, res_t, x_ctx_t&);
~XImg();
XFrame& get();
void draw();
};
/**
* @brief Generic select() on a file descriptor, waiting for read event with timeout.
*
* X events can be waited or polled for, but there seems to be no blocking wait with a timeout.
* So used on the X server connection.
*/
class FdReadSelector {
private:
const int fd;
const time_t timeout;
fd_set fds{};
timeval tv{};
public:
FdReadSelector(int fd, time_t timeout_ms);
bool wait();
};
/** @brief Called with current key press and release events, measure and pop the overall duration. */
class XKeyEvents {
public:
enum key_t { KEY_UP = 0, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_ACT, KEY_RESTART, KEY_REGEN, KEY_QUIT, KEY_NONE_ };
struct action_t { ///< translated to @ref Renderer::input_t::action
bool action;
bool restart;
bool regen;
bool quit;
};
private:
struct key_state_t {
msec_t start{};
msec_t value{};
};
std::mutex mtx{};
std::condition_variable cond{};
std::array<key_state_t, key_t::KEY_NONE_> keys{};
bool dirty{};
bool is_dirty() const;
bool pop_key(key_t);
public:
void set(key_t, msec_t, bool); ///< key press or release at given timestamp
void notify(); ///< possibly wakeup waiter after multiple @ref set() calls
void wait(); ///< return or wait until 'dirty'
bool pop_movement(float, vertex_t&); ///< translate and reset current key press durations
void pop_action(action_t&); ///< check for and reset current one-time key presses
};
/** @brief Wait for X key events in a separate thread and notify @ref XKeyEvents. */
class XInput {
private:
x_ctx_t& x;
FdReadSelector xfd;
volatile bool shutdown;
XKeyEvents events{};
std::mutex mtx;
std::thread thread;
void worker();
public:
XInput(x_ctx_t&);
~XInput();
XKeyEvents& get_keys();
};
/** @brief Initialize X window i/o - key presses and frame draws, respectively. */
class XWin {
private:
#if USE_X
x_ctx_t x{};
std::unique_ptr<XImg> ximg{};
std::unique_ptr<XInput> xinput{};
#else
std::unique_ptr<XFrame::rgb_t[]> buf{};
std::unique_ptr<XFrame> ximg{};
std::unique_ptr<XKeyEvents> xinput{};
#endif
public:
XWin(res_t, res_t);
~XWin();
void draw();
XFrame& get_frame();
XKeyEvents& get_keys();
};