Skip to content

Instantly share code, notes, and snippets.

@cfrank
Last active May 1, 2023 08:12
Show Gist options
  • Save cfrank/6de45b5a4f2fc3e93dc2f4e6281380b7 to your computer and use it in GitHub Desktop.
Save cfrank/6de45b5a4f2fc3e93dc2f4e6281380b7 to your computer and use it in GitHub Desktop.
Implementation of a container window for the CEF browser window - In the end this was not needed.
// SPDX-License-Identifier: BSD-2-Clause
#include <iostream>
#include <sys/select.h>
// #include "ContainerWindow.hpp"
class ContainerWindow {
public:
struct Delegate {
virtual void OnContainerWindowCreate(xcb_window_t) const = 0;
virtual void OnContainerWindowDestroyRequest() const = 0;
};
explicit ContainerWindow(Delegate&) noexcept;
~ContainerWindow();
/**
* Creates the container window, and initializes the event listeners
*
* @return True if successful
*/
bool Create(xcb_window_t);
/**
* Destroy the container window
*/
void Destroy();
/**
* Return the X window handle of the container window
*
* @return The X window handle
*/
[[nodiscard]] xcb_window_t GetWindowHandle() const noexcept;
/**
* Initializes the connection to the X server
*
* @return True if successful
*/
bool MakeXConnection() noexcept;
void StartEventLoop();
private:
static xcb_connection_t* Connect(int* screen);
/**
* Find the preferred screen, as identified by `xcb_connect`.
*
* When calling `xcb_connect` we are passed a preferred screen number. By default this is `0`
* but in the case this is something else we will iterate over the available screens
* and return back the one which was requested.
*/
static xcb_screen_t* FindPreferredScreen(xcb_connection_t* connection, int preferredScreen);
/**
* Event loop worker
*/
void EventLoop();
/**
* Handles the XCB window destroy request
*/
void HandleDestroyRequest() const;
/**
* Process events from the container window
*/
void ProcessXEvent(const xcb_generic_event_t&) const;
enum class EventLoopStatus {
STOPPED,
RUNNING,
};
std::unique_ptr<std::thread> m_eventLoopThread;
Delegate& m_delegate;
xcb_connection_t* m_connection;
xcb_screen_t* m_screen;
EventLoopStatus m_status = EventLoopStatus::STOPPED;
// Container window handle
xcb_window_t m_window;
};
namespace {
constexpr auto HOST_WINDOW_NAME = "jbw";
}
ContainerWindow::ContainerWindow(ContainerWindow::Delegate& delegate) noexcept
: m_delegate(delegate)
, m_connection(nullptr)
, m_screen(nullptr)
, m_window(XCB_NONE)
{
}
ContainerWindow::~ContainerWindow()
{
std::cout << "~ContainerWindow" << '\n';
xcb_disconnect(m_connection);
}
bool ContainerWindow::Create(xcb_window_t hostWindow)
{
m_window = xcb_generate_id(m_connection);
const uint32_t values[] = { XCB_EVENT_MASK_STRUCTURE_NOTIFY };
xcb_create_window(m_connection, XCB_COPY_FROM_PARENT, m_window, hostWindow, 0, 0, 1014, 96, 0,
XCB_WINDOW_CLASS_INPUT_OUTPUT, m_screen->root_visual, XCB_CW_EVENT_MASK, values);
xcb_change_property(m_connection, XCB_PROP_MODE_REPLACE, m_window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8,
std::strlen(HOST_WINDOW_NAME), HOST_WINDOW_NAME);
xcb_map_window(m_connection, m_window);
xcb_flush(m_connection);
m_delegate.OnContainerWindowCreate(m_window);
return true;
}
void ContainerWindow::Destroy()
{
xcb_destroy_window(m_connection, m_window);
xcb_flush(m_connection);
if (m_status == EventLoopStatus::RUNNING) {
m_status = EventLoopStatus::STOPPED;
}
if (m_eventLoopThread && m_eventLoopThread->joinable()) {
m_eventLoopThread->join();
}
}
xcb_window_t ContainerWindow::GetWindowHandle() const noexcept { return m_window; }
bool ContainerWindow::MakeXConnection() noexcept
{
int preferredScreen = 0;
m_connection = Connect(&preferredScreen);
if (!m_connection) {
return false;
}
m_screen = FindPreferredScreen(m_connection, preferredScreen);
if (!m_screen) {
return false;
}
StartEventLoop();
return true;
}
void ContainerWindow::StartEventLoop()
{
if (m_status == EventLoopStatus::RUNNING || m_eventLoopThread != nullptr) {
return;
}
m_status = EventLoopStatus::RUNNING;
m_eventLoopThread = std::make_unique<std::thread>(&ContainerWindow::EventLoop, this);
}
// static
xcb_connection_t* ContainerWindow::Connect(int* screen)
{
xcb_connection_t* connection = xcb_connect(nullptr, screen);
int connection_error = xcb_connection_has_error(connection);
if (connection_error) {
// Still need to free connection
xcb_disconnect(connection);
return nullptr;
}
return connection;
}
// static
xcb_screen_t* ContainerWindow::FindPreferredScreen(xcb_connection_t* connection, int preferredScreen)
{
const xcb_setup_t* setup = xcb_get_setup(connection);
xcb_screen_iterator_t itr = xcb_setup_roots_iterator(setup);
for (int i = 0; itr.rem; ++i, xcb_screen_next(&itr)) {
if (i == preferredScreen) {
return itr.data;
}
}
return nullptr;
}
void ContainerWindow::EventLoop()
{
fd_set fds;
int xcb_fd = xcb_get_file_descriptor(m_connection);
// If there hasn't been an X event in 25 milliseconds then we time out
// and check for interrupts or errors
struct timespec ts = { .tv_sec = 0, .tv_nsec = 25000000 };
xcb_generic_event_t* event = XCB_NONE;
while (m_status == EventLoopStatus::RUNNING) {
FD_ZERO(&fds);
FD_SET(xcb_fd, &fds);
if (xcb_connection_has_error(m_connection) && m_status == EventLoopStatus::RUNNING) {
// TODO: handle error
return;
}
int eventCount = pselect(xcb_fd + 1, &fds, nullptr, nullptr, &ts, nullptr);
if (eventCount < 0) {
// pselect failed
// TODO: Handle error
return;
}
if (eventCount == 0) {
// Nothing to process
continue;
}
while ((event = xcb_poll_for_event(m_connection))) {
ProcessXEvent(*event);
free(event);
xcb_flush(m_connection);
}
}
}
void ContainerWindow::HandleDestroyRequest() const { }
void ContainerWindow::ProcessXEvent(const xcb_generic_event_t& event) const
{
auto code = event.response_type & ~0x80;
if (code == XCB_DESTROY_NOTIFY) {
HandleDestroyRequest();
} else {
std::cout << "Handle X Event" << '\n';
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment