Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions clip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 8 additions & 0 deletions clip.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#include <cassert>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>

Expand Down Expand Up @@ -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
85 changes: 48 additions & 37 deletions clip_x11.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@

#include "clip.h"
#include "clip_lock_impl.h"

#include <xcb/xcb.h>
#include "clip_xcb_functions.h"

#include <atomic>
#include <algorithm>
Expand Down Expand Up @@ -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;

Expand All @@ -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,
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand All @@ -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));
ptr<end; ) {
xcb_atom_t target = *ptr++;
xcb_atom_t property = *ptr++;
Expand All @@ -506,7 +517,7 @@ class Manager {
event->requestor,
property,
target)) {
xcb_change_property(
clip_xcb_change_property(
m_connection,
XCB_PROP_MODE_REPLACE,
event->requestor,
Expand Down Expand Up @@ -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*)&notify);

xcb_flush(m_connection);
clip_xcb_flush(m_connection);
}

bool set_requestor_property_with_clipboard_content(const xcb_atom_t requestor,
Expand All @@ -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,
Expand Down Expand Up @@ -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<std::vector<uint8_t>>(n);
m_reply_offset = 0;
m_incr_process = true;
Expand Down Expand Up @@ -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 {
Expand All @@ -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,
Expand All @@ -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);
Expand All @@ -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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -777,15 +788,15 @@ 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]);
}

for (int i=0; i<n; ++i) {
if (result[i] == 0) {
xcb_intern_atom_reply_t* reply =
xcb_intern_atom_reply(m_connection,
clip_xcb_intern_atom_reply(m_connection,
cookies[i],
nullptr);
if (reply) {
Expand All @@ -805,11 +816,11 @@ class Manager {

xcb_atom_t result = 0;
xcb_intern_atom_cookie_t cookie =
xcb_intern_atom(m_connection, 0,
clip_xcb_intern_atom(m_connection, 0,
std::strlen(name), name);

xcb_intern_atom_reply_t* reply =
xcb_intern_atom_reply(m_connection,
clip_xcb_intern_atom_reply(m_connection,
cookie,
nullptr);
if (reply) {
Expand Down Expand Up @@ -905,12 +916,12 @@ class Manager {

bool set_x11_selection_owner() const {
xcb_void_cookie_t cookie =
xcb_set_selection_owner_checked(m_connection,
clip_xcb_set_selection_owner_checked(m_connection,
m_window,
get_atom(CLIPBOARD),
XCB_CURRENT_TIME);
xcb_generic_error_t* err =
xcb_request_check(m_connection,
clip_xcb_request_check(m_connection,
cookie);
if (err) {
free(err);
Expand All @@ -922,11 +933,11 @@ class Manager {
xcb_window_t get_x11_selection_owner() const {
xcb_window_t result = 0;
xcb_get_selection_owner_cookie_t cookie =
xcb_get_selection_owner(m_connection,
clip_xcb_get_selection_owner(m_connection,
get_atom(CLIPBOARD));

xcb_get_selection_owner_reply_t* reply =
xcb_get_selection_owner_reply(m_connection, cookie, nullptr);
clip_xcb_get_selection_owner_reply(m_connection, cookie, nullptr);
if (reply) {
result = reply->owner;
free(reply);
Expand Down
Loading
Loading