diff --git a/CMakeLists.txt b/CMakeLists.txt index 1db8a16..fc7b528 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,7 +87,7 @@ elseif(UNIX AND NOT EMSCRIPTEN) endif() target_compile_definitions(clip PRIVATE -DHAVE_XCB_XLIB_H) - target_link_libraries(clip xcb pthread) + target_link_libraries(clip pthread) if(CLIP_ENABLE_IMAGE AND CLIP_X11_WITH_PNG) check_include_files(png.h HAVE_PNG_H) @@ -101,6 +101,7 @@ elseif(UNIX AND NOT EMSCRIPTEN) endif() target_link_libraries(clip ${PNG_LIBRARY}) endif() + target_sources(clip PRIVATE clip_xcb_functions.cpp) target_sources(clip PRIVATE clip_x11.cpp) else() target_sources(clip PRIVATE clip_none.cpp) diff --git a/README.md b/README.md index 7b42a75..74d55b0 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ int main() { * **Windows**: - [Limited number of clipboard formats on Windows](https://web.archive.org/web/20250126161802/https://devblogs.microsoft.com/oldnewthing/20080430-00/?p=22523) * **Linux**: - - To be able to copy/paste on Linux you need `libx11-dev`/`libX11-devel` package. + - To be able to copy/paste on Linux you need `libxcb1-dev`/`libxcb-devel` package during build and `libxcb1`/`libxcb` package during usage, please note this library is loaded dynamically and not linked. - To copy/paste images you will need `libpng-dev`/`libpng-devel` package. ## Compilation Flags diff --git a/clip.cpp b/clip.cpp index 16ead84..8b682e1 100644 --- a/clip.cpp +++ b/clip.cpp @@ -189,4 +189,8 @@ void set_x11_wait_timeout(int) { } int get_x11_wait_timeout() { return 1000; } #endif +clip_exception::clip_exception(const std::string& what) + : std::runtime_error(what) +{ +} } // namespace clip diff --git a/clip.h b/clip.h index 2b72fbb..93658c5 100644 --- a/clip.h +++ b/clip.h @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -208,6 +209,13 @@ namespace clip { CLIP_EXTERN void set_x11_wait_timeout(int msecs); CLIP_EXTERN int get_x11_wait_timeout(); + /** + * An exception that can be thrown by clip in case of failure + */ + struct clip_exception : public std::runtime_error + { + explicit clip_exception(const std::string& what = ""); + }; } // namespace clip #endif // CLIP_H_INCLUDED diff --git a/clip_x11.cpp b/clip_x11.cpp index b12d09f..3f2c74e 100644 --- a/clip_x11.cpp +++ b/clip_x11.cpp @@ -6,8 +6,7 @@ #include "clip.h" #include "clip_lock_impl.h" - -#include +#include "clip_xcb_functions.h" #include #include @@ -75,17 +74,27 @@ class Manager { Manager() : m_lock(m_mutex, std::defer_lock) - , m_connection(xcb_connect(nullptr, nullptr)) , m_window(0) , m_incr_process(false) { + + try + { + clip_xcb_functions_initialize(); + } + catch (const xcb_load_failure_exception& ex) + { + throw clip_exception(ex.what()); + } + + m_connection = clip_xcb_connect(nullptr, nullptr); if (!m_connection) return; - const xcb_setup_t* setup = xcb_get_setup(m_connection); + const xcb_setup_t* setup = clip_xcb_get_setup(m_connection); if (!setup) return; - xcb_screen_t* screen = xcb_setup_roots_iterator(setup).data; + xcb_screen_t* screen = clip_xcb_setup_roots_iterator(setup).data; if (!screen) return; @@ -96,8 +105,8 @@ class Manager { // To receive DestroyNotify event and stop the message loop. XCB_EVENT_MASK_STRUCTURE_NOTIFY; - m_window = xcb_generate_id(m_connection); - xcb_create_window(m_connection, 0, + m_window = clip_xcb_generate_id(m_connection); + clip_xcb_create_window(m_connection, 0, m_window, screen->root, 0, 0, 1, 1, 0, @@ -137,15 +146,17 @@ class Manager { #endif if (m_window) { - xcb_destroy_window(m_connection, m_window); - xcb_flush(m_connection); + clip_xcb_destroy_window(m_connection, m_window); + clip_xcb_flush(m_connection); } if (m_thread.joinable()) m_thread.join(); if (m_connection) - xcb_disconnect(m_connection); + clip_xcb_disconnect(m_connection); + + clip_xcb_functions_finalize(); } bool try_lock() { @@ -194,12 +205,12 @@ class Manager { event.owner = owner; event.selection = get_atom(CLIPBOARD); - xcb_send_event(m_connection, false, + clip_xcb_send_event(m_connection, false, owner, XCB_EVENT_MASK_NO_EVENT, (const char*)&event); - xcb_flush(m_connection); + clip_xcb_flush(m_connection); } } @@ -411,7 +422,7 @@ class Manager { void process_x11_events() { bool stop = false; xcb_generic_event_t* event; - while (!stop && (event = xcb_wait_for_event(m_connection))) { + while (!stop && (event = clip_xcb_wait_for_event(m_connection))) { int type = (event->response_type & ~0x80); switch (type) { @@ -474,7 +485,7 @@ class Manager { // Set the "property" of "requestor" with the clipboard // formats ("targets", atoms) that we provide. - xcb_change_property( + clip_xcb_change_property( m_connection, XCB_PROP_MODE_REPLACE, event->requestor, @@ -496,8 +507,8 @@ class Manager { false); if (reply) { for (xcb_atom_t - *ptr=(xcb_atom_t*)xcb_get_property_value(reply), - *end=ptr + (xcb_get_property_value_length(reply)/sizeof(xcb_atom_t)); + *ptr=(xcb_atom_t*)clip_xcb_get_property_value(reply), + *end=ptr + (clip_xcb_get_property_value_length(reply)/sizeof(xcb_atom_t)); ptrrequestor, property, target)) { - xcb_change_property( + clip_xcb_change_property( m_connection, XCB_PROP_MODE_REPLACE, event->requestor, @@ -548,12 +559,12 @@ class Manager { notify.target = event->target; notify.property = event->property; - xcb_send_event(m_connection, false, + clip_xcb_send_event(m_connection, false, event->requestor, XCB_EVENT_MASK_NO_EVENT, // SelectionNotify events go without mask (const char*)¬ify); - xcb_flush(m_connection); + clip_xcb_flush(m_connection); } bool set_requestor_property_with_clipboard_content(const xcb_atom_t requestor, @@ -578,7 +589,7 @@ class Manager { // Set the "property" of "requestor" with the // clipboard content in the requested format ("target"). - xcb_change_property( + clip_xcb_change_property( m_connection, XCB_PROP_MODE_REPLACE, requestor, @@ -612,8 +623,8 @@ class Manager { event->property, get_atom(INCR)); if (reply) { - if (xcb_get_property_value_length(reply) == 4) { - uint32_t n = *(uint32_t*)xcb_get_property_value(reply); + if (clip_xcb_get_property_value_length(reply) == 4) { + uint32_t n = *(uint32_t*)clip_xcb_get_property_value(reply); m_reply_data = std::make_shared>(n); m_reply_offset = 0; m_incr_process = true; @@ -649,7 +660,7 @@ class Manager { // When the length is 0 it means that the content was // completely sent by the selection owner. - if (xcb_get_property_value_length(reply) > 0) { + if (clip_xcb_get_property_value_length(reply) > 0) { copy_reply_data(reply); } else { @@ -668,7 +679,7 @@ class Manager { xcb_atom_t atom, bool delete_prop = true) { xcb_get_property_cookie_t cookie = - xcb_get_property(m_connection, + clip_xcb_get_property(m_connection, delete_prop, window, property, @@ -677,7 +688,7 @@ class Manager { xcb_generic_error_t* err = nullptr; xcb_get_property_reply_t* reply = - xcb_get_property_reply(m_connection, cookie, &err); + clip_xcb_get_property_reply(m_connection, cookie, &err); if (err) { // TODO report error free(err); @@ -688,9 +699,9 @@ class Manager { // Concatenates the new data received in "reply" into "m_reply_data" // buffer. void copy_reply_data(xcb_get_property_reply_t* reply) { - const uint8_t* src = (const uint8_t*)xcb_get_property_value(reply); + const uint8_t* src = (const uint8_t*)clip_xcb_get_property_value(reply); // n = length of "src" in bytes - size_t n = xcb_get_property_value_length(reply); + size_t n = clip_xcb_get_property_value_length(reply); size_t req = m_reply_offset+n; if (!m_reply_data) { @@ -735,14 +746,14 @@ class Manager { // Ask to the selection owner for its content on each known // text format/atom. for (xcb_atom_t atom : atoms) { - xcb_convert_selection(m_connection, + clip_xcb_convert_selection(m_connection, m_window, // Send us the result selection, // Clipboard selection atom, // The clipboard format that we're requesting get_atom(CLIPBOARD), // Leave result in this window's property XCB_CURRENT_TIME); - xcb_flush(m_connection); + clip_xcb_flush(m_connection); // We use the "m_incr_received" to wait several timeouts in case // that we've received the INCR SelectionNotify or @@ -777,7 +788,7 @@ class Manager { if (it != m_atoms.end()) result[i] = it->second; else - cookies[i] = xcb_intern_atom( + cookies[i] = clip_xcb_intern_atom( m_connection, 0, std::strlen(names[i]), names[i]); } @@ -785,7 +796,7 @@ class Manager { for (int i=0; iowner; free(reply); diff --git a/clip_xcb_functions.cpp b/clip_xcb_functions.cpp new file mode 100644 index 0000000..9b964e3 --- /dev/null +++ b/clip_xcb_functions.cpp @@ -0,0 +1,121 @@ +// Clip Library +// Copyright (c) 2015-2026 David Capello +// +// This file is released under the terms of the MIT license. +// Read LICENSE.txt for more information. +// +// This file was originally copied from VTK, under BSD-3-Clause license +// Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen + +#include "clip_xcb_functions.h" + +#include +#include + +#define NULLIFY_POINTER_TO_FUNCTION(name) name = nullptr +#define DEFINE_POINTER_TO_FUNCTION(name) name##_type NULLIFY_POINTER_TO_FUNCTION(name) +#define LOAD_POINTER_TO_FUNCTION(lib, symbol, name) \ +name = reinterpret_cast(dlsym(lib, #symbol)); \ +if (name == nullptr) \ +{ \ + throw xcb_load_failure_exception(std::string("Symbol ") + #symbol + " is missing from libxcb"); \ +} + +static void* libxcb = nullptr; +DEFINE_POINTER_TO_FUNCTION(clip_xcb_connect); +DEFINE_POINTER_TO_FUNCTION(clip_xcb_get_setup); +DEFINE_POINTER_TO_FUNCTION(clip_xcb_setup_roots_iterator); +DEFINE_POINTER_TO_FUNCTION(clip_xcb_generate_id); +DEFINE_POINTER_TO_FUNCTION(clip_xcb_create_window); +DEFINE_POINTER_TO_FUNCTION(clip_xcb_destroy_window); +DEFINE_POINTER_TO_FUNCTION(clip_xcb_flush); +DEFINE_POINTER_TO_FUNCTION(clip_xcb_disconnect); +DEFINE_POINTER_TO_FUNCTION(clip_xcb_send_event); +DEFINE_POINTER_TO_FUNCTION(clip_xcb_wait_for_event); +DEFINE_POINTER_TO_FUNCTION(clip_xcb_change_property); +DEFINE_POINTER_TO_FUNCTION(clip_xcb_get_property_value); +DEFINE_POINTER_TO_FUNCTION(clip_xcb_get_property_value_length); +DEFINE_POINTER_TO_FUNCTION(clip_xcb_get_property); +DEFINE_POINTER_TO_FUNCTION(clip_xcb_get_property_reply); +DEFINE_POINTER_TO_FUNCTION(clip_xcb_convert_selection); +DEFINE_POINTER_TO_FUNCTION(clip_xcb_intern_atom); +DEFINE_POINTER_TO_FUNCTION(clip_xcb_intern_atom_reply); +DEFINE_POINTER_TO_FUNCTION(clip_xcb_set_selection_owner_checked); +DEFINE_POINTER_TO_FUNCTION(clip_xcb_get_selection_owner); +DEFINE_POINTER_TO_FUNCTION(clip_xcb_get_selection_owner_reply); +DEFINE_POINTER_TO_FUNCTION(clip_xcb_request_check); + +void clip_xcb_functions_initialize() +{ + for (const char* libName : { "libxcb.so.1.1.0", "libxcb.so.1", "libxcb.so" }) + { + libxcb = dlopen(libName, RTLD_LAZY | RTLD_LOCAL); + if (libxcb != nullptr) + { + break; + } + } + if (libxcb == nullptr) + { + throw xcb_load_failure_exception("Could not find libxcb"); + } + LOAD_POINTER_TO_FUNCTION(libxcb, xcb_connect, clip_xcb_connect); + LOAD_POINTER_TO_FUNCTION(libxcb, xcb_get_setup, clip_xcb_get_setup); + LOAD_POINTER_TO_FUNCTION(libxcb, xcb_setup_roots_iterator, clip_xcb_setup_roots_iterator); + LOAD_POINTER_TO_FUNCTION(libxcb, xcb_generate_id, clip_xcb_generate_id); + LOAD_POINTER_TO_FUNCTION(libxcb, xcb_create_window, clip_xcb_create_window); + LOAD_POINTER_TO_FUNCTION(libxcb, xcb_destroy_window, clip_xcb_destroy_window); + LOAD_POINTER_TO_FUNCTION(libxcb, xcb_flush, clip_xcb_flush); + LOAD_POINTER_TO_FUNCTION(libxcb, xcb_disconnect, clip_xcb_disconnect); + LOAD_POINTER_TO_FUNCTION(libxcb, xcb_send_event, clip_xcb_send_event); + LOAD_POINTER_TO_FUNCTION(libxcb, xcb_wait_for_event, clip_xcb_wait_for_event); + LOAD_POINTER_TO_FUNCTION(libxcb, xcb_change_property, clip_xcb_change_property); + LOAD_POINTER_TO_FUNCTION(libxcb, xcb_get_property_value, clip_xcb_get_property_value); + LOAD_POINTER_TO_FUNCTION(libxcb, xcb_get_property_value_length, clip_xcb_get_property_value_length); + LOAD_POINTER_TO_FUNCTION(libxcb, xcb_get_property, clip_xcb_get_property); + LOAD_POINTER_TO_FUNCTION(libxcb, xcb_get_property_reply, clip_xcb_get_property_reply); + LOAD_POINTER_TO_FUNCTION(libxcb, xcb_convert_selection, clip_xcb_convert_selection); + LOAD_POINTER_TO_FUNCTION(libxcb, xcb_intern_atom, clip_xcb_intern_atom); + LOAD_POINTER_TO_FUNCTION(libxcb, xcb_intern_atom_reply, clip_xcb_intern_atom_reply); + LOAD_POINTER_TO_FUNCTION(libxcb, xcb_set_selection_owner_checked, clip_xcb_set_selection_owner_checked); + LOAD_POINTER_TO_FUNCTION(libxcb, xcb_get_selection_owner, clip_xcb_get_selection_owner); + LOAD_POINTER_TO_FUNCTION(libxcb, xcb_get_selection_owner_reply, clip_xcb_get_selection_owner_reply); + LOAD_POINTER_TO_FUNCTION(libxcb, xcb_request_check, clip_xcb_request_check); +} + +void clip_xcb_functions_finalize() +{ + NULLIFY_POINTER_TO_FUNCTION(clip_xcb_connect); + NULLIFY_POINTER_TO_FUNCTION(clip_xcb_get_setup); + NULLIFY_POINTER_TO_FUNCTION(clip_xcb_setup_roots_iterator); + NULLIFY_POINTER_TO_FUNCTION(clip_xcb_generate_id); + NULLIFY_POINTER_TO_FUNCTION(clip_xcb_create_window); + NULLIFY_POINTER_TO_FUNCTION(clip_xcb_destroy_window); + NULLIFY_POINTER_TO_FUNCTION(clip_xcb_flush); + NULLIFY_POINTER_TO_FUNCTION(clip_xcb_disconnect); + NULLIFY_POINTER_TO_FUNCTION(clip_xcb_send_event); + NULLIFY_POINTER_TO_FUNCTION(clip_xcb_wait_for_event); + NULLIFY_POINTER_TO_FUNCTION(clip_xcb_change_property); + NULLIFY_POINTER_TO_FUNCTION(clip_xcb_get_property_value); + NULLIFY_POINTER_TO_FUNCTION(clip_xcb_get_property_value_length); + NULLIFY_POINTER_TO_FUNCTION(clip_xcb_get_property); + NULLIFY_POINTER_TO_FUNCTION(clip_xcb_get_property_reply); + NULLIFY_POINTER_TO_FUNCTION(clip_xcb_convert_selection); + NULLIFY_POINTER_TO_FUNCTION(clip_xcb_intern_atom); + NULLIFY_POINTER_TO_FUNCTION(clip_xcb_intern_atom_reply); + NULLIFY_POINTER_TO_FUNCTION(clip_xcb_set_selection_owner_checked); + NULLIFY_POINTER_TO_FUNCTION(clip_xcb_get_selection_owner); + NULLIFY_POINTER_TO_FUNCTION(clip_xcb_get_selection_owner_reply); + NULLIFY_POINTER_TO_FUNCTION(clip_xcb_request_check); + + if (libxcb) + { + dlclose(libxcb); + libxcb = nullptr; + } +} + +xcb_load_failure_exception::xcb_load_failure_exception(const std::string& what) + : std::runtime_error(what) +{ +} diff --git a/clip_xcb_functions.h b/clip_xcb_functions.h new file mode 100644 index 0000000..e92f4ca --- /dev/null +++ b/clip_xcb_functions.h @@ -0,0 +1,90 @@ +// Clip Library +// Copyright (c) 2015-2026 David Capello +// +// This file is released under the terms of the MIT license. +// Read LICENSE.txt for more information. +// +// This file was originally copied from VTK, under BSD-3-Clause license +// Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen +// +// Dynamically load libxcb and provides function pointers to the API clip needs +// in clip_x11.cpp + +#ifndef CLIB_XCB_FUNCTIONS_H_INCLUDED +#define CLIB_XCB_FUNCTIONS_H_INCLUDED + +#define CLIP_XCB_EXPORT __attribute__((visibility("default"))) + +#include +#include +#include + +typedef xcb_connection_t* (*clip_xcb_connect_type)(const char *, int *); +typedef const struct xcb_setup_t* (*clip_xcb_get_setup_type)(xcb_connection_t *); +typedef xcb_screen_iterator_t (*clip_xcb_setup_roots_iterator_type) (const xcb_setup_t*); +typedef uint32_t (*clip_xcb_generate_id_type)(xcb_connection_t*); +typedef xcb_void_cookie_t (*clip_xcb_create_window_type)(xcb_connection_t*, uint8_t, xcb_window_t, xcb_window_t, int16_t, int16_t, uint16_t, uint16_t, uint16_t , uint16_t, xcb_visualid_t, uint32_t, const void*); +typedef xcb_void_cookie_t (*clip_xcb_destroy_window_type)(xcb_connection_t*, xcb_window_t); +typedef int (*clip_xcb_flush_type)(xcb_connection_t*); +typedef void (*clip_xcb_disconnect_type)(xcb_connection_t*); +typedef xcb_void_cookie_t (*clip_xcb_send_event_type)(xcb_connection_t*, uint8_t, xcb_window_t, uint32_t, const char*); +typedef xcb_generic_event_t* (*clip_xcb_wait_for_event_type)(xcb_connection_t*); +typedef xcb_void_cookie_t (*clip_xcb_change_property_type)(xcb_connection_t*, uint8_t, xcb_window_t, xcb_atom_t, xcb_atom_t, uint8_t, uint32_t, const void*); +typedef void* (*clip_xcb_get_property_value_type)(const xcb_get_property_reply_t*); +typedef int (*clip_xcb_get_property_value_length_type)(const xcb_get_property_reply_t*); +typedef xcb_get_property_cookie_t (*clip_xcb_get_property_type)(xcb_connection_t*, uint8_t, xcb_window_t, xcb_atom_t, xcb_atom_t, uint32_t, uint32_t); +typedef xcb_get_property_reply_t* (*clip_xcb_get_property_reply_type)(xcb_connection_t*, xcb_get_property_cookie_t, xcb_generic_error_t**); +typedef xcb_void_cookie_t (*clip_xcb_convert_selection_type)(xcb_connection_t*, xcb_window_t, xcb_atom_t, xcb_atom_t, xcb_atom_t, xcb_timestamp_t); +typedef xcb_intern_atom_cookie_t (*clip_xcb_intern_atom_type)(xcb_connection_t*, uint8_t, uint16_t, const char*); +typedef xcb_intern_atom_reply_t* (*clip_xcb_intern_atom_reply_type)(xcb_connection_t*, xcb_intern_atom_cookie_t, xcb_generic_error_t**); +typedef xcb_void_cookie_t (*clip_xcb_set_selection_owner_checked_type)(xcb_connection_t*, xcb_window_t, xcb_atom_t, xcb_timestamp_t); +typedef xcb_get_selection_owner_cookie_t (*clip_xcb_get_selection_owner_type)(xcb_connection_t*, xcb_atom_t); +typedef xcb_get_selection_owner_reply_t* (*clip_xcb_get_selection_owner_reply_type)(xcb_connection_t*, xcb_get_selection_owner_cookie_t, xcb_generic_error_t**); +typedef xcb_generic_error_t* (*clip_xcb_request_check_type)(xcb_connection_t*, xcb_void_cookie_t); + +CLIP_XCB_EXPORT extern clip_xcb_connect_type clip_xcb_connect; +CLIP_XCB_EXPORT extern clip_xcb_get_setup_type clip_xcb_get_setup; +CLIP_XCB_EXPORT extern clip_xcb_setup_roots_iterator_type clip_xcb_setup_roots_iterator; +CLIP_XCB_EXPORT extern clip_xcb_generate_id_type clip_xcb_generate_id; +CLIP_XCB_EXPORT extern clip_xcb_create_window_type clip_xcb_create_window; +CLIP_XCB_EXPORT extern clip_xcb_destroy_window_type clip_xcb_destroy_window; +CLIP_XCB_EXPORT extern clip_xcb_flush_type clip_xcb_flush; +CLIP_XCB_EXPORT extern clip_xcb_disconnect_type clip_xcb_disconnect; +CLIP_XCB_EXPORT extern clip_xcb_send_event_type clip_xcb_send_event; +CLIP_XCB_EXPORT extern clip_xcb_wait_for_event_type clip_xcb_wait_for_event; +CLIP_XCB_EXPORT extern clip_xcb_change_property_type clip_xcb_change_property; +CLIP_XCB_EXPORT extern clip_xcb_get_property_value_type clip_xcb_get_property_value; +CLIP_XCB_EXPORT extern clip_xcb_get_property_value_length_type clip_xcb_get_property_value_length; +CLIP_XCB_EXPORT extern clip_xcb_get_property_type clip_xcb_get_property; +CLIP_XCB_EXPORT extern clip_xcb_get_property_reply_type clip_xcb_get_property_reply; +CLIP_XCB_EXPORT extern clip_xcb_convert_selection_type clip_xcb_convert_selection; +CLIP_XCB_EXPORT extern clip_xcb_intern_atom_type clip_xcb_intern_atom; +CLIP_XCB_EXPORT extern clip_xcb_intern_atom_reply_type clip_xcb_intern_atom_reply; +CLIP_XCB_EXPORT extern clip_xcb_set_selection_owner_checked_type clip_xcb_set_selection_owner_checked; +CLIP_XCB_EXPORT extern clip_xcb_get_selection_owner_type clip_xcb_get_selection_owner; +CLIP_XCB_EXPORT extern clip_xcb_get_selection_owner_reply_type clip_xcb_get_selection_owner_reply; +CLIP_XCB_EXPORT extern clip_xcb_request_check_type clip_xcb_request_check; + +/** + * Initialize the xcb function pointers by dynamically loading them from libxcb.so. + * Must be called before using any of the function pointers. + * Safe to call multiple times; subsequent calls have no effect. + */ +CLIP_XCB_EXPORT void clip_xcb_functions_initialize(); + +/** + * Finalize the xcb function pointers, releasing any resources. + * Should be called when done using the function pointers. + * Safe to call multiple times; subsequent calls have no effect. + */ +CLIP_XCB_EXPORT void clip_xcb_functions_finalize(); + +/** + * An exception that can be thrown by the xcb loader on failure + */ +struct xcb_load_failure_exception : public std::runtime_error +{ + explicit xcb_load_failure_exception(const std::string& what = ""); +}; + +#endif // CLIB_XCB_FUNCTIONS_H_INCLUDED