// along with this program; if not, write to the Free Software
// 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 <simgear/debug/logstream.hxx>
0, 0
) ),
_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_clipboard( XInternAtom(_display, "CLIPBOARD", False) )
{
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);
+ }
}
/**
*/
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
XConvertSelection( _display, atom_type, _atom_targets, atom_type,
_window, CurrentTime );
- XFlush(_display);
Atom requested_type = None;
bool sent_request = false;
XEvent event;
XNextEvent(_display, &event);
- if( event.type == SelectionNotify )
+ if( event.type != SelectionNotify )
{
- 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;
+ handleEvent(event);
+ continue;
+ }
+
+ 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_NASAL,
SG_WARN,
- "ClipboardX11::getText: Conversion failed: "
- "target=" << getAtomName(target)
+ "ClipboardX11::getText: can only handle 8-bit data (is "
+ << prop.format << "-bit) -> retry "
+ << cnt++
);
- break;
- }
- 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);
- XFree(prop.data);
-
- return result;
- }
- else
- {
- SG_LOG
- (
- SG_NASAL,
- SG_WARN,
- "ClipboardX11::getText: wrong target: " << getAtomName(target)
- );
- break;
- }
+ XFree(prop.data);
+ continue;
}
+
+ std::string result((const char*)prop.data, prop.num_items);
+ XFree(prop.data);
+
+ return result;
}
else
{
(
SG_NASAL,
SG_WARN,
- "ClipboardX11::getText: unexpected XEvent: " << event.type
+ "ClipboardX11::getText: wrong target: " << getAtomName(target)
);
break;
}
*/
virtual bool setText(const std::string& text, Type type)
{
- SG_LOG
- (
- SG_NASAL,
- SG_ALERT,
- "ClipboardX11::setText: not yet implemented!"
- );
- return false;
+ Atom atom_type = typeToAtom(type);
+ XSetSelectionOwner(_display, atom_type, _window, CurrentTime);
+ if( XGetSelectionOwner(_display, atom_type) != _window )
+ {
+ SG_LOG
+ (
+ 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:
Display *_display;
Window _window;
Atom _atom_targets,
+ _atom_text,
+ _atom_utf8,
_atom_primary,
_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
{
unsigned char *data;
int actual_format;
unsigned long nitems;
unsigned long bytes_after;
- unsigned char *ret=0;
+ unsigned char *ret = 0;
int read_bytes = 1024;
return p;
}
- std::string getAtomName(Atom atom)
+ std::string getAtomName(Atom atom) const
{
return atom == None ? "None" : XGetAtomName(_display, atom);
}
+ Atom typeToAtom(Type type) const
+ {
+ return type == CLIPBOARD ? _atom_clipboard : _atom_primary;
+ }
+
};
//------------------------------------------------------------------------------
{
if( argc > i )
{
- if( !naIsString(args[i]) )
- naRuntimeError(c, "clipboard: invalid arg (not a string)");
-
- std::string type_str( naStr_data(args[i]) );
- boost::to_upper(type_str);
-
- if( type_str == "CLIPBOARD" )
- return NasalClipboard::CLIPBOARD;
- else if( type_str == "PRIMARY" || type_str == "SELECTION" )
- return NasalClipboard::PRIMARY;
- else
- naRuntimeError(c, "clipboard: unknown clipboard type");
+ if( naIsNum(args[i]) )
+ {
+ if( static_cast<int>(args[i].num) == NasalClipboard::CLIPBOARD )
+ return NasalClipboard::CLIPBOARD;
+ if( static_cast<int>(args[i].num) == NasalClipboard::PRIMARY )
+ return NasalClipboard::PRIMARY;
+ }
+
+ naRuntimeError
+ (
+ c,
+ "clipboard: invalid arg "
+ "(expected clipboard.CLIPBOARD or clipboard.SELECTION)"
+ );
}
return NasalClipboard::CLIPBOARD;
{
if( argc < 1 || argc > 2 )
naRuntimeError( c, "clipboard.setText() expects 1 or 2 arguments: "
- "text, [, type = \"CLIPBOARD\"]" );
+ "text, [, type = clipboard.CLIPBOARD]" );
if( !naIsString(args[0]) )
naRuntimeError(c, "clipboard.setText() invalid arg (arg 0 not a string)");
{
if( argc > 1 )
naRuntimeError(c, "clipboard.getText() accepts max 1 arg: "
- "[type = \"CLIPBOARD\"]" );
+ "[type = clipboard.CLIPBOARD]" );
const std::string& text =
NasalClipboard::getInstance()->getText(parseType(c, argc, args, 0));
}
//------------------------------------------------------------------------------
-// Table of extension functions, terminate with 0,0
+// Table of extension functions
static struct {const char* name; naCFunction func; } funcs[] = {
{ "setText", f_setClipboardText },
- { "getText", f_getClipboardText },
- { 0,0 } // TERMINATION
+ { "getText", f_getClipboardText }
+};
+
+// Table of extension symbols
+static struct {const char* name; naRef val; } symbols[] = {
+ { "CLIPBOARD", naNum(NasalClipboard::CLIPBOARD) },
+ { "SELECTION", naNum(NasalClipboard::PRIMARY) }
};
//------------------------------------------------------------------------------
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
(
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);
}
}