diff options
Diffstat (limited to 'src/SFML/Window/Unix/ClipboardImpl.cpp')
-rw-r--r-- | src/SFML/Window/Unix/ClipboardImpl.cpp | 459 |
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 |