diff --git a/include/wx/x11/clipbrd.h b/include/wx/x11/clipbrd.h index bba17ab1db..3778e98272 100644 --- a/include/wx/x11/clipbrd.h +++ b/include/wx/x11/clipbrd.h @@ -68,6 +68,7 @@ public: private: DECLARE_DYNAMIC_CLASS(wxClipboard) + }; #endif // wxUSE_CLIPBOARD diff --git a/src/x11/app.cpp b/src/x11/app.cpp index 9f6b3e36ff..e5ba515ffd 100644 --- a/src/x11/app.cpp +++ b/src/x11/app.cpp @@ -38,6 +38,7 @@ #include "wx/thread.h" #endif +#include "wx/clipbrd.h" #include "wx/x11/private.h" #include @@ -59,6 +60,11 @@ static wxSize g_initialSize = wxDefaultSize; static wxWindow *g_nextFocus = NULL; static wxWindow *g_prevFocus = NULL; +//------------------------------------------------------------------------ +// X11 clipboard event handling +//------------------------------------------------------------------------ +extern "C" void wxClipboardHandleSelectionRequest(XEvent event); + //------------------------------------------------------------------------ // X11 error handling //------------------------------------------------------------------------ @@ -500,6 +506,11 @@ bool wxApp::ProcessXEvent(WXEvent* _event) } return false; } + case SelectionRequest: + { + //A request to paste has occured. + wxClipboardHandleSelectionRequest(*event); + } #if 0 case DestroyNotify: { diff --git a/src/x11/clipbrd.cpp b/src/x11/clipbrd.cpp index 3ae53ade07..243903d3e9 100644 --- a/src/x11/clipbrd.cpp +++ b/src/x11/clipbrd.cpp @@ -19,8 +19,140 @@ #include "wx/utils.h" #include "wx/dataobj.h" #endif - #include "wx/x11/private.h" +#include "wx/scopedarray.h" + +// for store the formats that in a wxDataObject +typedef wxScopedArray wxDataFormatScopedArray; + +// ---------------------------------------------- +// The mechanism of clipboard under X11 platform: +// ---------------------------------------------- + +// X11 platform doesn't has a global clipboard like Windows by default. So the +// first problem is the application doing the pasting has to first know where +// to get the data from. + +// It solved by X server: The copied data is store in host program(src +// program) it self. When other program(dest program) want to paste data that +// copied in the src program. It will send an paste request, in x11, it will +// send an event which type is SelectionRequest by XConvertSelection, then X +// server will try to find a program that could handle SelectionRequest event. +// In src program, when recieve a SelectionRequest event, it will set the +// copied data to dest program's window property. More specific, the dest +// program is called "requestor", the src program could find which window ask +// for the data, through the event member : "event.xselection.requestor". +// Esentially, the pasting application asks for a list of available formats, +// and then picks the one it deems most suitable. + +// ------------------------- +// X11 clipboard background: +// ------------------------- + +// ----- +// Atoms +// ----- + +// Atom is a 4 byte integer ID that used to identify Properties and +// Selections. Properties and Selection have name. But use Atom is more +// efficiency, because it just needs to pass and compare integer ID rather +// than a character string. + +// XInterAtom() can gets the atom numer corresponding to a string. +// ie: Atom a = XInternAtom(xdisplay, "CLIPBOARD", True); +// XGetAtomName gets the string corresponding ot the atom number. +// ie: string clip = XGetAtomName(disp, a); +// The content of clip will be "CLIPBOARD". + +// ---------- +// Properties +// ---------- + +// Each window has a list of properties. A property is a collection of named, +// typed data. Atom is their name. This is mean the property has an 4 byte +// integer ID and a ASCII string name that store in the correspongding Atom. + +//Window have predefined properties, and users can define custom property. + +// ---------- +// Selections +// ---------- + +// Selection IS property, just renamed it because it used for exchange data +// between applications. + +// Under x11, there have tow useful selections: PRIMARY and CLIPBOARD. PRIMARY +// is for highlight/middle click, CLIPBOARD is for Ctrl+C/Ctrl+V. + +// For compability to other wx ports, here use CLIPBOARD Atom. + +// ------------------------------------ +// X11 clipboard implementation details +// ------------------------------------ + +// For x11, the first thing is define XA_CLIPBOARD atoem. Although +// X11/Xatom.h have some predefined Atoms, but for some unknown reasons, +// XA_CLIPBOARD is not an predefined Atom. +// ie. XA_CLIPBOARD = XInternAtom(xdisplay, "CLIPBOARD", True); + +// The copied data is store in this Atom/Selection. + +// ----- +// Paste +// ----- + +// For paste data. First, we need to know which window owns the XA_CLIPBOAD +// selection: win = XGetSelectionOwner(xdisplay, XA_CLIPBOARD); + +// If find a window that owns XA_CLIPBOARD, convert the selection to a prop +// atom: XConvertSelection(xdisplay, XA_CLIPBOARD, XA_STRING, XA_CLIPBOARD, +// win, CurrentTime); + +// Then get the data in owner's selection through XGetWIndowProperty(). + +// ---- +// Copy +// ---- + +// If we want copy data to clipboard. We must own the XA_CLIPBOARD selection +// through XSetSelectionOwner(). + +// But the data is still host by src program. When src program recieve +// SelectionRequest event type. It set the data to requestor's window +// property, through XCHangeProperty(xdisplay, requestor, ...). The second +// parameter is the requests window. Requestor could find through XEvent. + +// -------------------- +// wxX11 implementation +// -------------------- + +// According the descirption in above. The implementation of clipboard should +// accomlish these things: set wxDataObject to clipboard, get data from +// clipboard and store it to a wxDataObject. + +// ----------------- +// SetData function: +// ----------------- + +// In SetData, due to x11 program does not send data to a gloabal clipboard, +// so the program hold the data, when the program recieve a SelectionRequest +// event, the program set the data to requestor's window property. So in the +// implementation of SetData, it hold the wxDataObject that need to be paste. +// And set XA_CLIPBOARD selection owner. + +// ----------------------------------- +// wxClipboardHandleSelectionRequest() +// ----------------------------------- + +// Add a new function void wxClipboardHandleSelectionRequest(XEvent event), it +// called by wxApp::ProcessXEvent(), when other program want to paste data. +// wxApp will check the wxClipboard whether it has data that conforms with +// the format. If has, set the data to requestor's window property. + +// The GetData function +// GetData function retieve the data in the owner of XA_CLIPBOARD. + + //----------------------------------------------------------------------------- // data @@ -84,6 +216,200 @@ static void InitX11Clipboard() } } +// get the data from XA_CLIPBOARD with specific Atom type. +// currently the 3rd parameter is always XA_CLIPBOARD. +// this is because XA_PRIMARY is not often used. +// the 4th parameter is the format that requestor want get. +unsigned char *GetClipboardDataByFormat(Display* disp, Window win, Atom clipbrdType, Atom target, + unsigned long *length) +{ + // some variables that used to get the data in window property + unsigned char *clipbrddata = NULL; + int read_bytes = 1024; + Atom type; + int format, result; + unsigned long bytes_after; + + XConvertSelection(disp, XA_CLIPBOARD, target, XA_CLIPBOARD, win, CurrentTime); + + do + { + if ( clipbrddata != 0 ) + { + XFree(clipbrddata); + clipbrddata = NULL; + } + + result = XGetWindowProperty(disp, win, clipbrdType, 0, read_bytes, False, AnyPropertyType, + &type, &format, length, &bytes_after, &clipbrddata); + read_bytes *= 2; + } while ( bytes_after != 0 ); + + // if we got any data, copy it. + if ( result == Success && clipbrddata ) + return clipbrddata; + + return NULL; +} + +// get the data for a specific wxDataFormat that stored as a property in Root window +void GetClipboardData(Display* disp, Window win, wxDataObject &data, wxDataFormat dfFormat) +{ + unsigned char *clipbrdData = NULL; + + // some variables that used to get the data in window property + char *text = NULL; + unsigned long len; + + switch ( dfFormat ) + { + case wxDF_INVALID: + { + return; + } + case wxDF_BITMAP: + { + Atom atomArray[] = {XA_IMAGE_BMP, XA_IMAGE_JPG, XA_IMAGE_TIFF, XA_IMAGE_PNG}; + int i; + // check the four atoms in clipboard, try to find whether there has data + // stored in one of these atom. + for ( i = 0; i <= 4; i++ ) + { + + clipbrdData = GetClipboardDataByFormat(disp, win, XA_CLIPBOARD, atomArray[i], &len); + if ( clipbrdData != NULL ) + break; + } + // if we got any data, copy it. + if ( clipbrdData ) + { + text = strdup((char*) clipbrdData); + data.SetData(dfFormat, len, text); + } + break; + } + default: + { + clipbrdData = GetClipboardDataByFormat(disp, win, XA_CLIPBOARD, XA_UTF8_STRING, &len); + // if we got any data, copy it. + if ( clipbrdData ) + { + text = strdup((char*) clipbrdData ); + data.SetData(dfFormat, len, text); + } + } + } +} + +// This function is used to response the paste request. +// It convert the stored data into a acceptable format by destination +// program and send an acknowledgement. +// In x11, the "copied" data stored by original window, +// when a paste request arrived, then the original window will send the +// data to destination. +extern "C" void wxClipboardHandleSelectionRequest(XEvent event) +{ + if( event.type != SelectionRequest ) + return; + + //Extract the relavent data + Atom target = event.xselectionrequest.target; + Atom property = event.xselectionrequest.property; + Window requestor = event.xselectionrequest.requestor; + Time timestamp = event.xselectionrequest.time; + Display* disp = event.xselection.display; + + // A selection request has arrived, we should know these values: + // Selection atom + // Target atom: the format that requetor want to; + // Property atom: ; + // Requestor: the window that want to paste data. + + //Replies to the application requesting a pasting are XEvenst + //sent via XSendEvent + XEvent response; + + //Start by constructing a refusal request. + response.xselection.type = SelectionNotify; + //s.xselection.serial - filled in by server + //s.xselection.send_event - filled in by server + //s.xselection.display - filled in by server + response.xselection.requestor = requestor; + response.xselection.selection = XA_CLIPBOARD; + response.xselection.target = target; + response.xselection.property = None; //This means refusal + response.xselection.time = timestamp; + + // get formats count in the wxDataObject + // for each data format, search it in x11 selection + // and store it to wxDataObject + wxDataObject* m_data = wxTheClipboard->m_data; + size_t count = m_data->GetFormatCount(wxDataObject::Get); + wxDataFormatScopedArray dfarr(count); + m_data->GetAllFormats(dfarr.get(), wxDataObject::Get); + + // retrieve the data with specific image Atom. + Atom atomArray[] = {XA_IMAGE_BMP, XA_IMAGE_JPG, XA_IMAGE_TIFF, XA_IMAGE_PNG}; + +#if wxUSE_UNICODE + wxDataFormat dfFormat = wxDF_UNICODETEXT; +#else + wxDataFormat dfFormat = wxDF_TEXT; +#endif + + int i; + for ( i = 0; i <= 4; i++ ) + { + if ( target == atomArray[i] ) + { + dfFormat = wxDF_BITMAP; + break; + } + } + + // Tell the requestor what we can provide + if ( target == XA_TARGETS ) + { + Atom possibleTargets[] = + { + XA_UTF8_STRING, + XA_IMAGE_BMP, + XA_IMAGE_JPG, + XA_IMAGE_TIFF, + XA_IMAGE_PNG, + XA_TEXT_URI_LIST, + XA_TEXT_URI, + XA_TEXT_PLAIN, + XA_TEXT, + XA_STRING, + }; + + XChangeProperty(disp, requestor, property, XA_ATOM, 32, PropModeReplace, + (unsigned char *)possibleTargets, 2); + } + // the requested target is in possibleTargets + // TODO, when finish the event process issue, improve the check. + else if ( target == XA_UTF8_STRING ) + { + unsigned char* buf = NULL; + size_t size = 0; + size = m_data->GetDataSize(dfFormat); + buf = (unsigned char*)malloc(size); + m_data->GetDataHere(dfFormat, buf); + XChangeProperty(disp, requestor, XA_CLIPBOARD, target, 8, PropModeReplace, + buf, size); + delete buf; + } + else + { + // we could not provide the data with a target that requestor want. + return; + } + + //Reply + XSendEvent(disp, event.xselectionrequest.requestor, True, 0, &response); +} + //----------------------------------------------------------------------------- // wxClipboard //----------------------------------------------------------------------------- @@ -171,38 +497,33 @@ bool wxClipboard::AddData( wxDataObject *data ) /* we can only store one wxDataObject */ Clear(); + // in x11, the "copied data" hold by the program itself. + // so here just use m_data to hold the "copied data" + // use wxApp->ProcessXEvent to check whether there has + // SelectionRequest event arrived. If the event arrived, + // check the request format, if wx program has the request + // format, reply the data. + // Reply the data means fill up the data in requestor's + // window property. + // See HandleSelectionRequest for more details m_data = data; - wxTextDataObject* textdata = (wxTextDataObject*)data; - + // prepare and find the root window, + // the copied data stored in the root window as window property Display* xdisplay = wxGlobalDisplay(); int xscreen = DefaultScreen(xdisplay); Window window = RootWindow(xdisplay, xscreen); - // Send the data to "clipboard". - // "clipboard" means root window property - XChangeProperty(xdisplay, window, XA_CLIPBOARD, XA_CLIPBOARD, 8, PropModeReplace, - (const unsigned char*)textdata->GetText().mb_str().data(), - textdata->GetTextLength()); + size_t size = m_data->GetDataSize(wxDF_UNICODETEXT); + unsigned char* buf = (unsigned char*)malloc(size); + m_data->GetDataHere(wxDF_UNICODETEXT, buf); - Window selectionOwner; + XChangeProperty(xdisplay, window, XA_CLIPBOARD, XA_STRING, 8, PropModeReplace, + buf, size); - // Set this window as the owner of the CLIPBOARD atom XSetSelectionOwner(xdisplay, XA_CLIPBOARD, window, CurrentTime); - - // Check if we accuired ownershop or not - selectionOwner = XGetSelectionOwner(xdisplay, XA_CLIPBOARD); - - // Got ownership - if ( selectionOwner == window ) - { - return true; - } - else - { - return false; - } - + XFlush(xdisplay); + return true; #endif } @@ -228,67 +549,27 @@ bool wxClipboard::IsSupported( const wxDataFormat& format ) bool wxClipboard::GetData( wxDataObject& data ) { + // get formats count in the wxDataObject + // for each data format, search it in x11 selection + // and store it to wxDataObject + size_t count = data.GetFormatCount(wxDataObject::Get); + wxDataFormatScopedArray dfarr(count); + data.GetAllFormats(dfarr.get(), wxDataObject::Get); + // prepare and find the root window, // the copied data stored in the root window as window property Display* xdisplay = wxGlobalDisplay(); int xscreen = DefaultScreen(xdisplay); Window window = RootWindow(xdisplay, xscreen); - // the window that hold the copied data. - // this window just indicate whether there has data copied - Window selectionOwner; - - // Get the owner of the clipboard selection - selectionOwner = XGetSelectionOwner(xdisplay, XA_CLIPBOARD); - - // some variables that used to get the data in window property - char *text = NULL; - Atom type; - unsigned char* textdata = NULL; - int format, result; - unsigned long len, bytesLeft, dummy; - - // there has something in the "clipboard" - if ( selectionOwner != None ) + // retrieve the data in each format. + for( size_t i = 0; i < count; ++i ) { - XConvertSelection( xdisplay, XA_CLIPBOARD, XA_UTF8_STRING, - XA_CLIPBOARD, window, CurrentTime); - - XGetWindowProperty(xdisplay, window, XA_CLIPBOARD, 0, 0, False, AnyPropertyType, &type, - &format, &len, &bytesLeft, &textdata); - - if ( textdata ) - { - XFree(textdata); - textdata = NULL; - } - - if ( bytesLeft ) - { - // Fetch the data - result = XGetWindowProperty(xdisplay, - window, - XA_CLIPBOARD, 0, - bytesLeft, False, AnyPropertyType, - &type, &format, &len, &dummy, &textdata); - - // if we got any data, copy it. - if ( result == Success ) - { - text = strdup((char*) textdata); - XFree(textdata); - XDeleteProperty(xdisplay, window, XA_CLIPBOARD); - - if ( data.IsSupported(wxDF_UNICODETEXT)) - { - data.SetData(wxDF_UNICODETEXT, len, text); - } - return true; - } - } + GetClipboardData(xdisplay, window, data, dfarr[i]); } - return false; + return true; } + #endif // wxUSE_CLIPBOARD