summaryrefslogtreecommitdiff
path: root/src/SFML/Window/Unix/ClipboardImpl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/SFML/Window/Unix/ClipboardImpl.cpp')
-rw-r--r--src/SFML/Window/Unix/ClipboardImpl.cpp459
1 files changed, 291 insertions, 168 deletions
diff --git a/src/SFML/Window/Unix/ClipboardImpl.cpp b/src/SFML/Window/Unix/ClipboardImpl.cpp
index 32ef21b..df2fd95 100644
--- a/src/SFML/Window/Unix/ClipboardImpl.cpp
+++ b/src/SFML/Window/Unix/ClipboardImpl.cpp
@@ -27,233 +27,356 @@
////////////////////////////////////////////////////////////
#include <SFML/Window/Unix/ClipboardImpl.hpp>
#include <SFML/Window/Unix/Display.hpp>
-#include <SFML/System/String.hpp>
-#include <SFML/System/Sleep.hpp>
-#include <iostream>
-#include <string>
-#include <X11/Xlib.h>
+#include <SFML/System/Clock.hpp>
+#include <SFML/System/Err.hpp>
+#include <X11/Xatom.h>
+#include <vector>
namespace
{
+ // Filter the events received by windows (only allow those matching a specific window)
+ Bool checkEvent(::Display*, XEvent* event, XPointer userData)
+ {
+ // Just check if the event matches the window
+ return event->xany.window == reinterpret_cast< ::Window >(userData);
+ }
+}
+
+namespace sf
+{
+namespace priv
+{
+
////////////////////////////////////////////////////////////
-void initClipboard();
-void* hostSelection(void*);
+String ClipboardImpl::getString()
+{
+ return getInstance().getStringImpl();
+}
-sf::String string;
-pthread_mutex_t mutex;
-pthread_t host_thread;
+////////////////////////////////////////////////////////////
+void ClipboardImpl::setString(const String& text)
+{
+ getInstance().setStringImpl(text);
+}
-bool is_fail = false;
-bool is_init = false;
-bool is_host = false;
-Display* display = NULL;
-Window window = 0;
+////////////////////////////////////////////////////////////
+void ClipboardImpl::processEvents()
+{
+ getInstance().processEventsImpl();
+}
-Atom selection = 0;
-Atom atom_targ = 0;
-Atom atom_text = 0;
-Atom utf8_text = 0;
-int xa_string = 31;
-int xa_atom = 4;
////////////////////////////////////////////////////////////
-void initClipboard()
+ClipboardImpl::ClipboardImpl() :
+m_window (0),
+m_requestResponded(false)
{
- is_init = true;
+ // Open a connection with the X server
+ m_display = OpenDisplay();
- display = XOpenDisplay(NULL);
- int screen = DefaultScreen(display);
- window = XCreateSimpleWindow(display, RootWindow(display, screen),
- 0, 0, 1, 1, 0, BlackPixel(display, screen), WhitePixel(display, screen));
+ // Get the atoms we need to make use of the clipboard
+ m_clipboard = getAtom("CLIPBOARD", false);
+ m_targets = getAtom("TARGETS", false);
+ m_text = getAtom("TEXT", false);
+ m_utf8String = getAtom("UTF8_STRING", true );
+ m_targetProperty = getAtom("SFML_CLIPBOARD_TARGET_PROPERTY", false);
- selection = XInternAtom(display, "CLIPBOARD", false);
- atom_targ = XInternAtom(display, "TARGETS", false);
- atom_text = XInternAtom(display, "TEXT", false);
- utf8_text = XInternAtom(display, "UTF8_STRING", true);
+ // Create a hidden window that will broker our clipboard interactions with X
+ m_window = XCreateSimpleWindow(m_display, DefaultRootWindow(m_display), 0, 0, 1, 1, 0, 0, 0);
- if(utf8_text == None)
- {
- std::cerr << "UTF-8 format unavailable on clipboard." << std::endl;
- utf8_text = xa_string;
- }
+ // Register the events we are interested in
+ XSelectInput(m_display, m_window, SelectionNotify | SelectionClear | SelectionRequest);
+}
- if(pthread_mutex_init(&mutex, NULL))
- {
- is_fail = true;
- std::cerr << "Unable to initialize mutex. Failed to initialize clipboard." << std::endl;
- return;
- }
- if(pthread_create(&host_thread, NULL, hostSelection, NULL))
+////////////////////////////////////////////////////////////
+ClipboardImpl::~ClipboardImpl()
+{
+ // Destroy the window
+ if (m_window)
{
- is_fail = true;
- std::cerr << "Unable to create host thread. Failed to initialize clipboard." << std::endl;
- return;
+ XDestroyWindow(m_display, m_window);
+ XFlush(m_display);
}
+
+ // Close the connection with the X server
+ CloseDisplay(m_display);
}
+
////////////////////////////////////////////////////////////
-void* hostSelection(void*)
+ClipboardImpl& ClipboardImpl::getInstance()
{
- while(true)
- {
- if(XPending(display) && is_host)
- {
- XEvent event;
+ static ClipboardImpl instance;
- pthread_mutex_lock(&mutex);
- XNextEvent(display, &event);
- pthread_mutex_unlock(&mutex);
+ return instance;
+}
- switch(event.type)
- {
- case SelectionClear:
- {
- pthread_mutex_lock(&mutex);
- is_host = false;
- pthread_mutex_unlock(&mutex);
- break;
- }
- case SelectionRequest:
- {
- if(event.xselectionrequest.selection == selection)
- {
- XSelectionRequestEvent* sel_req_event = &event.xselectionrequest;
- XSelectionEvent sel_event = {0};
-
- int result = 0;
- sel_event.type = SelectionNotify,
- sel_event.display = sel_req_event->display,
- sel_event.requestor = sel_req_event->requestor,
- sel_event.selection = sel_req_event->selection,
- sel_event.time = sel_req_event->time,
- sel_event.target = sel_req_event->target,
- sel_event.property = sel_req_event->property;
-
- std::basic_string<unsigned char> str = string.toUtf8();
-
- if(sel_event.target == atom_targ)
- result = XChangeProperty(sel_event.display, sel_event.requestor,
- sel_event.property, xa_atom, 32, PropModeReplace,
- reinterpret_cast<unsigned char*>(&utf8_text), 1);
- else if(sel_event.target == xa_string || sel_event.target == atom_text)
- result = XChangeProperty(sel_event.display, sel_event.requestor,
- sel_event.property, xa_string, 8, PropModeReplace,
- reinterpret_cast<unsigned char*>(&str[0]), str.size());
- else if(sel_event.target == utf8_text)
- result = XChangeProperty(sel_event.display, sel_event.requestor,
- sel_event.property, utf8_text, 8, PropModeReplace,
- reinterpret_cast<unsigned char*>(&str[0]), str.size());
- else
- sel_event.property = None;
-
- if((result & 2) == 0)
- XSendEvent(display, sel_event.requestor, 0, 0,
- reinterpret_cast<XEvent*>(&sel_event));
- }
- break;
- }
- default: break;
- }
- }
- else
- sf::sleep(sf::milliseconds(20));
+////////////////////////////////////////////////////////////
+String ClipboardImpl::getStringImpl()
+{
+ // Check if anybody owns the current selection
+ if (XGetSelectionOwner(m_display, m_clipboard) == None)
+ {
+ m_clipboardContents.clear();
+
+ return m_clipboardContents;
}
-}
+
+ // Process any already pending events
+ processEvents();
+
+ m_requestResponded = false;
+
+ // Request the current selection to be converted to UTF-8 (or STRING
+ // if UTF-8 is not available) and written to our window property
+ XConvertSelection(
+ m_display,
+ m_clipboard,
+ (m_utf8String != None) ? m_utf8String : XA_STRING,
+ m_targetProperty,
+ m_window,
+ CurrentTime
+ );
+
+ Clock clock;
+
+ // Wait for a response for up to 1000ms
+ while (!m_requestResponded && (clock.getElapsedTime().asMilliseconds() < 1000))
+ processEvents();
+
+ // If no response was received within the time period, clear our clipboard contents
+ if (!m_requestResponded)
+ m_clipboardContents.clear();
+
+ return m_clipboardContents;
}
-namespace sf
-{
-namespace priv
-{
////////////////////////////////////////////////////////////
-String ClipboardImpl::getString()
+void ClipboardImpl::setStringImpl(const String& text)
{
- if(!is_init)
- initClipboard();
+ m_clipboardContents = text;
+
+ // Set our window as the current owner of the selection
+ XSetSelectionOwner(m_display, m_clipboard, m_window, CurrentTime);
- if(is_fail || is_host)
- return string;
+ // Check if setting the selection owner was successful
+ if (XGetSelectionOwner(m_display, m_clipboard) != m_window)
+ err() << "Cannot set clipboard string: Unable to get ownership of X selection" << std::endl;
+}
- // Dangerous! Wipes all previous events!
- XSync(display, true);
- XConvertSelection(display, selection, utf8_text, atom_text, window, CurrentTime);
+////////////////////////////////////////////////////////////
+void ClipboardImpl::processEventsImpl()
+{
XEvent event;
- pthread_mutex_lock(&mutex);
- XNextEvent(display, &event);
- pthread_mutex_unlock(&mutex);
+ // Pick out the events that are interesting for this window
+ while (XCheckIfEvent(m_display, &event, &checkEvent, reinterpret_cast<XPointer>(m_window)))
+ m_events.push_back(event);
- if(event.type == SelectionNotify)
+ // Handle the events for this window that we just picked out
+ while (!m_events.empty())
{
- if(event.xselection.selection != selection || event.xselection.target != utf8_text)
+ event = m_events.front();
+ m_events.pop_front();
+ processEvent(event);
+ }
+}
+
+
+////////////////////////////////////////////////////////////
+void ClipboardImpl::processEvent(XEvent& windowEvent)
+{
+ switch (windowEvent.type)
+ {
+ case SelectionClear:
{
- std::cerr << "Failed to convert selection." << std::endl;
- return string;
+ // We don't have any resources we need to clean up
+ // when losing selection ownership so we don't do
+ // anything when we receive SelectionClear
+ // We will still respond to any future SelectionRequest
+ // events since doing so doesn't really do any harm
+ break;
}
-
- if(event.xselection.property)
+ case SelectionNotify:
{
- Atom target;
- int format;
- unsigned long size;
- unsigned long byte_left;
- unsigned char* data;
+ // Notification that the current selection owner
+ // has responded to our request
+
+ XSelectionEvent& selectionEvent = *reinterpret_cast<XSelectionEvent*>(&windowEvent.xselection);
- XGetWindowProperty(event.xselection.display,
- event.xselection.requestor, event.xselection.property,
- 0L, (~0L), false, AnyPropertyType,
- &target, &format, &size, &byte_left, &data);
+ m_clipboardContents.clear();
- if(target == utf8_text)
+ // If retrieving the selection fails or conversion is unsuccessful
+ // we leave the contents of the clipboard empty since we don't
+ // own it and we don't know what it could currently be
+ if ((selectionEvent.property == None) || (selectionEvent.selection != m_clipboard))
+ break;
+
+ Atom type;
+ int format;
+ unsigned long items;
+ unsigned long remainingBytes;
+ unsigned char* data = 0;
+
+ // The selection owner should have wrote the selection
+ // data to the specified window property
+ int result = XGetWindowProperty(
+ m_display,
+ m_window,
+ m_targetProperty,
+ 0,
+ 0x7fffffff,
+ False,
+ AnyPropertyType,
+ &type,
+ &format,
+ &items,
+ &remainingBytes,
+ &data
+ );
+
+ if (result == Success)
{
- std::basic_string<unsigned char> str(data, size);
- string = sf::String::fromUtf8(str.begin(), str.end());
+ // We don't support INCR for now
+ // It is very unlikely that this will be returned
+ // for purely text data transfer anyway
+ if (type != getAtom("INCR", false))
+ {
+ // Only copy the data if the format is what we expect
+ if ((type == m_utf8String) && (format == 8))
+ {
+ m_clipboardContents = String::fromUtf8(data, data + items);
+ }
+ else if ((type == XA_STRING) && (format == 8))
+ {
+ // Convert from ANSI std::string to sf::String
+ m_clipboardContents = std::string(data, data + items);
+ }
+ }
XFree(data);
+
+ // The selection requestor must always delete the property themselves
+ XDeleteProperty(m_display, m_window, m_targetProperty);
}
- XDeleteProperty(event.xselection.display, event.xselection.requestor, event.xselection.property);
+ m_requestResponded = true;
+
+ break;
}
- }
+ case SelectionRequest:
+ {
+ // Respond to a request for our clipboard contents
+ XSelectionRequestEvent& selectionRequestEvent = *reinterpret_cast<XSelectionRequestEvent*>(&windowEvent.xselectionrequest);
- return string;
-}
+ // Our reply
+ XSelectionEvent selectionEvent;
+ selectionEvent.type = SelectionNotify;
+ selectionEvent.requestor = selectionRequestEvent.requestor;
+ selectionEvent.selection = selectionRequestEvent.selection;
+ selectionEvent.property = selectionRequestEvent.property;
+ selectionEvent.time = selectionRequestEvent.time;
-////////////////////////////////////////////////////////////
-void ClipboardImpl::setString(const String& text)
-{
- if(!is_init)
- initClipboard();
+ if (selectionRequestEvent.selection == m_clipboard)
+ {
+ if (selectionRequestEvent.target == m_targets)
+ {
+ // Respond to a request for our valid conversion targets
+ std::vector<Atom> targets;
- if(is_fail)
- return;
+ targets.push_back(m_targets);
+ targets.push_back(m_text);
+ targets.push_back(XA_STRING);
- if(!is_host)
- {
- XSetSelectionOwner(display, selection, window, CurrentTime);
+ if (m_utf8String != None)
+ targets.push_back(m_utf8String);
- if(XGetSelectionOwner(display, selection) != window)
- {
- std::cerr << "Unable to get ownership of selection." << std::endl;
- return;
- }
+ XChangeProperty(
+ m_display,
+ selectionRequestEvent.requestor,
+ selectionRequestEvent.property,
+ XA_ATOM,
+ 32,
+ PropModeReplace,
+ reinterpret_cast<unsigned char*>(&targets[0]),
+ targets.size()
+ );
- pthread_mutex_lock(&mutex);
- is_host = true;
- pthread_mutex_unlock(&mutex);
- }
+ // Notify the requestor that they can read the targets from their window property
+ selectionEvent.target = m_targets;
+
+ XSendEvent(m_display, selectionRequestEvent.requestor, True, NoEventMask, reinterpret_cast<XEvent*>(&selectionEvent));
+
+ break;
+ }
+ else if ((selectionRequestEvent.target == XA_STRING) || ((m_utf8String == None) && (selectionRequestEvent.target == m_text)))
+ {
+ // Respond to a request for conversion to a Latin-1 string
+ std::string data = m_clipboardContents.toAnsiString();
+
+ XChangeProperty(
+ m_display,
+ selectionRequestEvent.requestor,
+ selectionRequestEvent.property,
+ XA_STRING,
+ 8,
+ PropModeReplace,
+ reinterpret_cast<const unsigned char*>(data.c_str()),
+ data.size()
+ );
- pthread_mutex_lock(&mutex);
- string = text;
- pthread_mutex_unlock(&mutex);
+ // Notify the requestor that they can read the data from their window property
+ selectionEvent.target = XA_STRING;
+
+ XSendEvent(m_display, selectionRequestEvent.requestor, True, NoEventMask, reinterpret_cast<XEvent*>(&selectionEvent));
+
+ break;
+ }
+ else if ((m_utf8String != None) && ((selectionRequestEvent.target == m_utf8String) || (selectionRequestEvent.target == m_text)))
+ {
+ // Respond to a request for conversion to a UTF-8 string
+ // or an encoding of our choosing (we always choose UTF-8)
+ std::basic_string<Uint8> data = m_clipboardContents.toUtf8();
+
+ XChangeProperty(
+ m_display,
+ selectionRequestEvent.requestor,
+ selectionRequestEvent.property,
+ m_utf8String,
+ 8,
+ PropModeReplace,
+ reinterpret_cast<const unsigned char*>(data.c_str()),
+ data.size()
+ );
+
+ // Notify the requestor that they can read the data from their window property
+ selectionEvent.target = m_utf8String;
+
+ XSendEvent(m_display, selectionRequestEvent.requestor, True, NoEventMask, reinterpret_cast<XEvent*>(&selectionEvent));
+
+ break;
+ }
+ }
+
+ // Notify the requestor that we could not respond to their request
+ selectionEvent.target = selectionRequestEvent.target;
+ selectionEvent.property = None;
+
+ XSendEvent(m_display, selectionRequestEvent.requestor, True, NoEventMask, reinterpret_cast<XEvent*>(&selectionEvent));
+
+ break;
+ }
+ default:
+ break;
+ }
}
} // namespace priv