Complete X11 clipboard support.
- Now ClipboarX11 also supports writing to the clipboard and sending the data to another application if requested.
This commit is contained in:
parent
aa9d0e3a8a
commit
d6a5a911bc
4 changed files with 247 additions and 84 deletions
|
@ -16,6 +16,14 @@
|
||||||
// along with this program; if not, write to the Free Software
|
// along with this program; if not, write to the Free Software
|
||||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* See the following links for more information on X11 clipboard:
|
||||||
|
*
|
||||||
|
* http://www.jwz.org/doc/x-cut-and-paste.html
|
||||||
|
* http://michael.toren.net/mirrors/doc/X-copy+paste.txt
|
||||||
|
* https://github.com/kfish/xsel/blob/master/xsel.c
|
||||||
|
*/
|
||||||
|
|
||||||
#include "NasalClipboard.hxx"
|
#include "NasalClipboard.hxx"
|
||||||
|
|
||||||
#include <simgear/debug/logstream.hxx>
|
#include <simgear/debug/logstream.hxx>
|
||||||
|
@ -37,13 +45,34 @@ class ClipboardX11:
|
||||||
0, 0
|
0, 0
|
||||||
) ),
|
) ),
|
||||||
_atom_targets( XInternAtom(_display, "TARGETS", False) ),
|
_atom_targets( XInternAtom(_display, "TARGETS", False) ),
|
||||||
|
_atom_text( XInternAtom(_display, "TEXT", False) ),
|
||||||
|
_atom_utf8( XInternAtom(_display, "UTF8_STRING", False) ),
|
||||||
_atom_primary( XInternAtom(_display, "PRIMARY", False) ),
|
_atom_primary( XInternAtom(_display, "PRIMARY", False) ),
|
||||||
_atom_clipboard( XInternAtom(_display, "CLIPBOARD", False) )
|
_atom_clipboard( XInternAtom(_display, "CLIPBOARD", False) )
|
||||||
{
|
{
|
||||||
assert(_display);
|
assert(_display);
|
||||||
assert(_atom_targets != None);
|
}
|
||||||
assert(_atom_primary != None);
|
|
||||||
assert(_atom_clipboard != None);
|
virtual ~ClipboardX11()
|
||||||
|
{
|
||||||
|
// Ensure we get rid of any selection ownership
|
||||||
|
if( XGetSelectionOwner(_display, _atom_primary) )
|
||||||
|
XSetSelectionOwner(_display, _atom_primary, None, CurrentTime);
|
||||||
|
if( XGetSelectionOwner(_display, _atom_clipboard) )
|
||||||
|
XSetSelectionOwner(_display, _atom_clipboard, None, CurrentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We need to run an event queue to check for selection request
|
||||||
|
*/
|
||||||
|
virtual void update()
|
||||||
|
{
|
||||||
|
while( XPending(_display) )
|
||||||
|
{
|
||||||
|
XEvent event;
|
||||||
|
XNextEvent(_display, &event);
|
||||||
|
handleEvent(event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -51,12 +80,11 @@ class ClipboardX11:
|
||||||
*/
|
*/
|
||||||
virtual std::string getText(Type type)
|
virtual std::string getText(Type type)
|
||||||
{
|
{
|
||||||
Atom atom_type = (type == CLIPBOARD ? _atom_clipboard : _atom_primary);
|
Atom atom_type = typeToAtom(type);
|
||||||
|
|
||||||
//Request a list of possible conversions
|
//Request a list of possible conversions
|
||||||
XConvertSelection( _display, atom_type, _atom_targets, atom_type,
|
XConvertSelection( _display, atom_type, _atom_targets, atom_type,
|
||||||
_window, CurrentTime );
|
_window, CurrentTime );
|
||||||
XFlush(_display);
|
|
||||||
|
|
||||||
Atom requested_type = None;
|
Atom requested_type = None;
|
||||||
bool sent_request = false;
|
bool sent_request = false;
|
||||||
|
@ -66,67 +94,58 @@ class ClipboardX11:
|
||||||
XEvent event;
|
XEvent event;
|
||||||
XNextEvent(_display, &event);
|
XNextEvent(_display, &event);
|
||||||
|
|
||||||
if( event.type == SelectionNotify )
|
if( event.type != SelectionNotify )
|
||||||
{
|
{
|
||||||
Atom target = event.xselection.target;
|
handleEvent(event);
|
||||||
if(event.xselection.property == None)
|
continue;
|
||||||
{
|
}
|
||||||
if( target == _atom_targets )
|
|
||||||
// If TARGETS can not be converted no selection is available
|
|
||||||
break;
|
|
||||||
|
|
||||||
|
Atom target = event.xselection.target;
|
||||||
|
if(event.xselection.property == None)
|
||||||
|
{
|
||||||
|
if( target == _atom_targets )
|
||||||
|
// If TARGETS can not be converted no selection is available
|
||||||
|
break;
|
||||||
|
|
||||||
|
SG_LOG
|
||||||
|
(
|
||||||
|
SG_NASAL,
|
||||||
|
SG_WARN,
|
||||||
|
"ClipboardX11::getText: Conversion failed: "
|
||||||
|
"target=" << getAtomName(target)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//If we're being given a list of targets (possible conversions)
|
||||||
|
if(target == _atom_targets && !sent_request)
|
||||||
|
{
|
||||||
|
sent_request = true;
|
||||||
|
requested_type = XA_STRING; // TODO select other type
|
||||||
|
XConvertSelection( _display, atom_type, requested_type, atom_type,
|
||||||
|
_window, CurrentTime );
|
||||||
|
}
|
||||||
|
else if(target == requested_type)
|
||||||
|
{
|
||||||
|
Property prop = readProperty(_window, atom_type);
|
||||||
|
if( prop.format != 8 )
|
||||||
|
{
|
||||||
SG_LOG
|
SG_LOG
|
||||||
(
|
(
|
||||||
SG_NASAL,
|
SG_NASAL,
|
||||||
SG_WARN,
|
SG_WARN,
|
||||||
"ClipboardX11::getText: Conversion failed: "
|
"ClipboardX11::getText: can only handle 8-bit data (is "
|
||||||
"target=" << getAtomName(target)
|
<< prop.format << "-bit) -> retry "
|
||||||
|
<< cnt++
|
||||||
);
|
);
|
||||||
break;
|
XFree(prop.data);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
//If we're being given a list of targets (possible conversions)
|
|
||||||
if(target == _atom_targets && !sent_request)
|
|
||||||
{
|
|
||||||
sent_request = true;
|
|
||||||
requested_type = XA_STRING; // TODO select other type
|
|
||||||
XConvertSelection( _display, atom_type, requested_type, atom_type,
|
|
||||||
_window, CurrentTime );
|
|
||||||
}
|
|
||||||
else if(target == requested_type)
|
|
||||||
{
|
|
||||||
Property prop = readProperty(_window, atom_type);
|
|
||||||
if( prop.format != 8 )
|
|
||||||
{
|
|
||||||
SG_LOG
|
|
||||||
(
|
|
||||||
SG_NASAL,
|
|
||||||
SG_WARN,
|
|
||||||
"ClipboardX11::getText: can only handle 8-bit data (is "
|
|
||||||
<< prop.format << "-bit) -> retry "
|
|
||||||
<< cnt++
|
|
||||||
);
|
|
||||||
XFree(prop.data);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string result((const char*)prop.data, prop.num_items);
|
std::string result((const char*)prop.data, prop.num_items);
|
||||||
XFree(prop.data);
|
XFree(prop.data);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SG_LOG
|
|
||||||
(
|
|
||||||
SG_NASAL,
|
|
||||||
SG_WARN,
|
|
||||||
"ClipboardX11::getText: wrong target: " << getAtomName(target)
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -134,7 +153,7 @@ class ClipboardX11:
|
||||||
(
|
(
|
||||||
SG_NASAL,
|
SG_NASAL,
|
||||||
SG_WARN,
|
SG_WARN,
|
||||||
"ClipboardX11::getText: unexpected XEvent: " << event.type
|
"ClipboardX11::getText: wrong target: " << getAtomName(target)
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -148,13 +167,27 @@ class ClipboardX11:
|
||||||
*/
|
*/
|
||||||
virtual bool setText(const std::string& text, Type type)
|
virtual bool setText(const std::string& text, Type type)
|
||||||
{
|
{
|
||||||
SG_LOG
|
Atom atom_type = typeToAtom(type);
|
||||||
(
|
XSetSelectionOwner(_display, atom_type, _window, CurrentTime);
|
||||||
SG_NASAL,
|
if( XGetSelectionOwner(_display, atom_type) != _window )
|
||||||
SG_ALERT,
|
{
|
||||||
"ClipboardX11::setText: not yet implemented!"
|
SG_LOG
|
||||||
);
|
(
|
||||||
return false;
|
SG_NASAL,
|
||||||
|
SG_ALERT,
|
||||||
|
"ClipboardX11::setText: failed to get selection owner!"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to store the text for sending it to another application upon
|
||||||
|
// request
|
||||||
|
if( type == CLIPBOARD )
|
||||||
|
_clipboard = text;
|
||||||
|
else
|
||||||
|
_selection = text;
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -162,9 +195,116 @@ class ClipboardX11:
|
||||||
Display *_display;
|
Display *_display;
|
||||||
Window _window;
|
Window _window;
|
||||||
Atom _atom_targets,
|
Atom _atom_targets,
|
||||||
|
_atom_text,
|
||||||
|
_atom_utf8,
|
||||||
_atom_primary,
|
_atom_primary,
|
||||||
_atom_clipboard;
|
_atom_clipboard;
|
||||||
|
|
||||||
|
std::string _clipboard,
|
||||||
|
_selection;
|
||||||
|
|
||||||
|
void handleEvent(const XEvent& event)
|
||||||
|
{
|
||||||
|
switch( event.type )
|
||||||
|
{
|
||||||
|
case SelectionRequest:
|
||||||
|
handleSelectionRequest(event.xselectionrequest);
|
||||||
|
break;
|
||||||
|
case SelectionClear:
|
||||||
|
if( event.xselectionclear.selection == _atom_clipboard )
|
||||||
|
_clipboard.clear();
|
||||||
|
else
|
||||||
|
_selection.clear();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
SG_LOG
|
||||||
|
(
|
||||||
|
SG_NASAL,
|
||||||
|
SG_WARN,
|
||||||
|
"ClipboardX11: unexpected XEvent: " << event.type
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleSelectionRequest(const XSelectionRequestEvent& sel_req)
|
||||||
|
{
|
||||||
|
SG_LOG
|
||||||
|
(
|
||||||
|
SG_NASAL,
|
||||||
|
SG_DEBUG,
|
||||||
|
"ClipboardX11: handle selection request: "
|
||||||
|
"selection=" << getAtomName(sel_req.selection) << ", "
|
||||||
|
"target=" << getAtomName(sel_req.target)
|
||||||
|
);
|
||||||
|
|
||||||
|
const std::string& buf = sel_req.selection == _atom_clipboard
|
||||||
|
? _clipboard
|
||||||
|
: _selection;
|
||||||
|
|
||||||
|
// Prepare response to notify whether we have written to the property or
|
||||||
|
// are unable to do the conversion
|
||||||
|
XSelectionEvent response;
|
||||||
|
response.type = SelectionNotify;
|
||||||
|
response.display = sel_req.display;
|
||||||
|
response.requestor = sel_req.requestor;
|
||||||
|
response.selection = sel_req.selection;
|
||||||
|
response.target = sel_req.target;
|
||||||
|
response.property = sel_req.property;
|
||||||
|
response.time = sel_req.time;
|
||||||
|
|
||||||
|
if( sel_req.target == _atom_targets )
|
||||||
|
{
|
||||||
|
static Atom supported[] = {
|
||||||
|
XA_STRING,
|
||||||
|
_atom_text,
|
||||||
|
_atom_utf8
|
||||||
|
};
|
||||||
|
|
||||||
|
changeProperty
|
||||||
|
(
|
||||||
|
sel_req.requestor,
|
||||||
|
sel_req.property,
|
||||||
|
sel_req.target,
|
||||||
|
&supported,
|
||||||
|
sizeof(supported)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else if( sel_req.target == XA_STRING
|
||||||
|
|| sel_req.target == _atom_text
|
||||||
|
|| sel_req.target == _atom_utf8 )
|
||||||
|
{
|
||||||
|
changeProperty
|
||||||
|
(
|
||||||
|
sel_req.requestor,
|
||||||
|
sel_req.property,
|
||||||
|
sel_req.target,
|
||||||
|
buf.data(),
|
||||||
|
buf.size()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Notify requestor that we are unable to perform requested conversion
|
||||||
|
response.property = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
XSendEvent(_display, sel_req.requestor, False, 0, (XEvent*)&response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void changeProperty( Window w,
|
||||||
|
Atom prop,
|
||||||
|
Atom type,
|
||||||
|
const void *data,
|
||||||
|
size_t size )
|
||||||
|
{
|
||||||
|
XChangeProperty
|
||||||
|
(
|
||||||
|
_display, w, prop, type, 8, PropModeReplace,
|
||||||
|
static_cast<const unsigned char*>(data), size
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
struct Property
|
struct Property
|
||||||
{
|
{
|
||||||
unsigned char *data;
|
unsigned char *data;
|
||||||
|
@ -179,7 +319,7 @@ class ClipboardX11:
|
||||||
int actual_format;
|
int actual_format;
|
||||||
unsigned long nitems;
|
unsigned long nitems;
|
||||||
unsigned long bytes_after;
|
unsigned long bytes_after;
|
||||||
unsigned char *ret=0;
|
unsigned char *ret = 0;
|
||||||
|
|
||||||
int read_bytes = 1024;
|
int read_bytes = 1024;
|
||||||
|
|
||||||
|
@ -204,11 +344,16 @@ class ClipboardX11:
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string getAtomName(Atom atom)
|
std::string getAtomName(Atom atom) const
|
||||||
{
|
{
|
||||||
return atom == None ? "None" : XGetAtomName(_display, atom);
|
return atom == None ? "None" : XGetAtomName(_display, atom);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Atom typeToAtom(Type type) const
|
||||||
|
{
|
||||||
|
return type == CLIPBOARD ? _atom_clipboard : _atom_primary;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
|
@ -34,18 +34,20 @@ static NasalClipboard::Type parseType(naContext c, int argc, naRef* args, int i)
|
||||||
{
|
{
|
||||||
if( argc > i )
|
if( argc > i )
|
||||||
{
|
{
|
||||||
if( !naIsString(args[i]) )
|
if( naIsNum(args[i]) )
|
||||||
naRuntimeError(c, "clipboard: invalid arg (not a string)");
|
{
|
||||||
|
if( static_cast<int>(args[i].num) == NasalClipboard::CLIPBOARD )
|
||||||
|
return NasalClipboard::CLIPBOARD;
|
||||||
|
if( static_cast<int>(args[i].num) == NasalClipboard::PRIMARY )
|
||||||
|
return NasalClipboard::PRIMARY;
|
||||||
|
}
|
||||||
|
|
||||||
std::string type_str( naStr_data(args[i]) );
|
naRuntimeError
|
||||||
boost::to_upper(type_str);
|
(
|
||||||
|
c,
|
||||||
if( type_str == "CLIPBOARD" )
|
"clipboard: invalid arg "
|
||||||
return NasalClipboard::CLIPBOARD;
|
"(expected clipboard.CLIPBOARD or clipboard.SELECTION)"
|
||||||
else if( type_str == "PRIMARY" || type_str == "SELECTION" )
|
);
|
||||||
return NasalClipboard::PRIMARY;
|
|
||||||
else
|
|
||||||
naRuntimeError(c, "clipboard: unknown clipboard type");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return NasalClipboard::CLIPBOARD;
|
return NasalClipboard::CLIPBOARD;
|
||||||
|
@ -56,7 +58,7 @@ static naRef f_setClipboardText(naContext c, naRef me, int argc, naRef* args)
|
||||||
{
|
{
|
||||||
if( argc < 1 || argc > 2 )
|
if( argc < 1 || argc > 2 )
|
||||||
naRuntimeError( c, "clipboard.setText() expects 1 or 2 arguments: "
|
naRuntimeError( c, "clipboard.setText() expects 1 or 2 arguments: "
|
||||||
"text, [, type = \"CLIPBOARD\"]" );
|
"text, [, type = clipboard.CLIPBOARD]" );
|
||||||
|
|
||||||
if( !naIsString(args[0]) )
|
if( !naIsString(args[0]) )
|
||||||
naRuntimeError(c, "clipboard.setText() invalid arg (arg 0 not a string)");
|
naRuntimeError(c, "clipboard.setText() invalid arg (arg 0 not a string)");
|
||||||
|
@ -74,7 +76,7 @@ static naRef f_getClipboardText(naContext c, naRef me, int argc, naRef* args)
|
||||||
{
|
{
|
||||||
if( argc > 1 )
|
if( argc > 1 )
|
||||||
naRuntimeError(c, "clipboard.getText() accepts max 1 arg: "
|
naRuntimeError(c, "clipboard.getText() accepts max 1 arg: "
|
||||||
"[type = \"CLIPBOARD\"]" );
|
"[type = clipboard.CLIPBOARD]" );
|
||||||
|
|
||||||
const std::string& text =
|
const std::string& text =
|
||||||
NasalClipboard::getInstance()->getText(parseType(c, argc, args, 0));
|
NasalClipboard::getInstance()->getText(parseType(c, argc, args, 0));
|
||||||
|
@ -86,11 +88,16 @@ static naRef f_getClipboardText(naContext c, naRef me, int argc, naRef* args)
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Table of extension functions, terminate with 0,0
|
// Table of extension functions
|
||||||
static struct {const char* name; naCFunction func; } funcs[] = {
|
static struct {const char* name; naCFunction func; } funcs[] = {
|
||||||
{ "setText", f_setClipboardText },
|
{ "setText", f_setClipboardText },
|
||||||
{ "getText", f_getClipboardText },
|
{ "getText", f_getClipboardText }
|
||||||
{ 0,0 } // TERMINATION
|
};
|
||||||
|
|
||||||
|
// Table of extension symbols
|
||||||
|
static struct {const char* name; naRef val; } symbols[] = {
|
||||||
|
{ "CLIPBOARD", naNum(NasalClipboard::CLIPBOARD) },
|
||||||
|
{ "SELECTION", naNum(NasalClipboard::PRIMARY) }
|
||||||
};
|
};
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
@ -111,7 +118,7 @@ void NasalClipboard::init(FGNasalSys *nasal)
|
||||||
|
|
||||||
nasal->globalsSet("clipboard", _clipboard_hash);
|
nasal->globalsSet("clipboard", _clipboard_hash);
|
||||||
|
|
||||||
for(size_t i=0;funcs[i].name;i++)
|
for( size_t i = 0; i < sizeof(funcs)/sizeof(funcs[0]); ++i )
|
||||||
{
|
{
|
||||||
nasal->hashset
|
nasal->hashset
|
||||||
(
|
(
|
||||||
|
@ -120,7 +127,14 @@ void NasalClipboard::init(FGNasalSys *nasal)
|
||||||
naNewFunc(nasal->context(), naNewCCode(nasal->context(), funcs[i].func))
|
naNewFunc(nasal->context(), naNewCCode(nasal->context(), funcs[i].func))
|
||||||
);
|
);
|
||||||
|
|
||||||
SG_LOG(SG_NASAL, SG_DEBUG, "Adding clipboard function: " << funcs[i].name );
|
SG_LOG(SG_NASAL, SG_DEBUG, "Adding clipboard function: " << funcs[i].name);
|
||||||
|
}
|
||||||
|
|
||||||
|
for( size_t i = 0; i < sizeof(symbols)/sizeof(symbols[0]); ++i )
|
||||||
|
{
|
||||||
|
nasal->hashset(_clipboard_hash, symbols[i].name, symbols[i].val);
|
||||||
|
|
||||||
|
SG_LOG(SG_NASAL, SG_DEBUG, "Adding clipboard symbol: " << symbols[i].name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@ class NasalClipboard
|
||||||
|
|
||||||
typedef boost::shared_ptr<NasalClipboard> Ptr;
|
typedef boost::shared_ptr<NasalClipboard> Ptr;
|
||||||
|
|
||||||
|
virtual void update() {}
|
||||||
virtual std::string getText(Type type = CLIPBOARD) = 0;
|
virtual std::string getText(Type type = CLIPBOARD) = 0;
|
||||||
virtual bool setText( const std::string& text,
|
virtual bool setText( const std::string& text,
|
||||||
Type type = CLIPBOARD ) = 0;
|
Type type = CLIPBOARD ) = 0;
|
||||||
|
|
|
@ -588,6 +588,9 @@ void FGNasalSys::init()
|
||||||
|
|
||||||
void FGNasalSys::update(double)
|
void FGNasalSys::update(double)
|
||||||
{
|
{
|
||||||
|
if( NasalClipboard::getInstance() )
|
||||||
|
NasalClipboard::getInstance()->update();
|
||||||
|
|
||||||
if(!_dead_listener.empty()) {
|
if(!_dead_listener.empty()) {
|
||||||
vector<FGNasalListener *>::iterator it, end = _dead_listener.end();
|
vector<FGNasalListener *>::iterator it, end = _dead_listener.end();
|
||||||
for(it = _dead_listener.begin(); it != end; ++it) delete *it;
|
for(it = _dead_listener.begin(); it != end; ++it) delete *it;
|
||||||
|
|
Loading…
Add table
Reference in a new issue