From aa9d0e3a8a2ef8570f18f463b1dac6afe8777953 Mon Sep 17 00:00:00 2001 From: Thomas Geymayer Date: Sat, 4 Aug 2012 00:24:26 +0200 Subject: [PATCH] Basic clipboard access from Nasal. - Add platform independent clipboard layer for Nasal access to clipboard (thanks to Hooray for basic code) - Add Windows clipboard access - Add partial X11 clipboard access (only reading from clipboard) - Add fallback clipboard for application internal clipboard if platform not supported - Add some helper functions to FGNasalSys --- src/Scripting/CMakeLists.txt | 14 +- src/Scripting/ClipboardFallback.cxx | 64 ++++++++ src/Scripting/ClipboardWindows.cxx | 108 ++++++++++++++ src/Scripting/ClipboardX11.cxx | 218 ++++++++++++++++++++++++++++ src/Scripting/NasalClipboard.cxx | 131 +++++++++++++++++ src/Scripting/NasalClipboard.hxx | 72 +++++++++ src/Scripting/NasalSys.cxx | 28 ++-- src/Scripting/NasalSys.hxx | 11 +- 8 files changed, 634 insertions(+), 12 deletions(-) create mode 100644 src/Scripting/ClipboardFallback.cxx create mode 100644 src/Scripting/ClipboardWindows.cxx create mode 100644 src/Scripting/ClipboardX11.cxx create mode 100644 src/Scripting/NasalClipboard.cxx create mode 100644 src/Scripting/NasalClipboard.hxx diff --git a/src/Scripting/CMakeLists.txt b/src/Scripting/CMakeLists.txt index 44c9563b2..4a528c91f 100644 --- a/src/Scripting/CMakeLists.txt +++ b/src/Scripting/CMakeLists.txt @@ -5,14 +5,26 @@ set(SOURCES nasal-props.cxx NasalPositioned.cxx NasalCanvas.cxx + NasalClipboard.cxx ) set(HEADERS NasalSys.hxx NasalPositioned.hxx NasalCanvas.hxx + NasalClipboard.hxx ) - +if(WIN32) + list(APPEND SOURCES ClipboardWindows.cxx) +else() + find_package(X11) + if(X11_FOUND) + list(APPEND SOURCES ClipboardX11.cxx) + else() + list(APPEND SOURCES ClipboardFallback.cxx) + endif() +endif() + flightgear_component(Scripting "${SOURCES}" "${HEADERS}") diff --git a/src/Scripting/ClipboardFallback.cxx b/src/Scripting/ClipboardFallback.cxx new file mode 100644 index 000000000..840be24ce --- /dev/null +++ b/src/Scripting/ClipboardFallback.cxx @@ -0,0 +1,64 @@ +// Fallback implementation of clipboard access for Nasal. Copy and edit for +// implementing support of other platforms +// +// Copyright (C) 2012 Thomas Geymayer +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +#include "NasalClipboard.hxx" + +#include + + +/** + * Provide a basic clipboard whose contents are only available to FlightGear + * itself + */ +class ClipboardFallback: + public NasalClipboard +{ + public: + + /** + * Get clipboard contents as text + */ + virtual std::string getText(Type type) + { + return type == CLIPBOARD ? _clipboard : _selection; + } + + /** + * Set clipboard contents as text + */ + virtual bool setText(const std::string& text, Type type) + { + if( type == CLIPBOARD ) + _clipboard = text; + else + _selection = text; + return true; + } + + protected: + + std::string _clipboard, + _selection; +}; + +//------------------------------------------------------------------------------ +NasalClipboard::Ptr NasalClipboard::create() +{ + return NasalClipboard::Ptr(new ClipboardFallback); +} diff --git a/src/Scripting/ClipboardWindows.cxx b/src/Scripting/ClipboardWindows.cxx new file mode 100644 index 000000000..1ae606d5d --- /dev/null +++ b/src/Scripting/ClipboardWindows.cxx @@ -0,0 +1,108 @@ +// Windows implementation of clipboard access for Nasal +// +// Copyright (C) 2012 Thomas Geymayer +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +#include "NasalClipboard.hxx" + +#include +#include + +/** + * Windows does only support on clipboard and no selection. We fake also the X11 + * selection buffer - at least inside FlightGear + */ +class ClipboardWindows: + public NasalClipboard +{ + public: + + /** + * Get clipboard contents as text + */ + virtual std::string getText(Type type) + { + if( type == CLIPBOARD ) + { + std::string data; + + if( !OpenClipboard(NULL) ) + return data; + + HANDLE hData = GetClipboardData( CF_TEXT ); + char* buff = (char*)GlobalLock( hData ); + if (buff) + data = buff; + GlobalUnlock( hData ); + CloseClipboard(); + + return data; + } + else + return _selection; + } + + /** + * Set clipboard contents as text + */ + virtual bool setText(const std::string& text, Type type) + { + if( type == CLIPBOARD ) + { + if( !OpenClipboard(NULL) ) + return false; + + bool ret = true; + if( !EmptyClipboard() ) + ret = false; + else if( !text.empty() ) + { + HGLOBAL hGlob = GlobalAlloc(GMEM_MOVEABLE, text.size() + 1); + if( !hGlob ) + ret = false; + else + { + memcpy(GlobalLock(hGlob), (char*)&text[0], text.size() + 1); + GlobalUnlock(hGlob); + + if( !SetClipboardData(CF_TEXT, hGlob) ) + { + GlobalFree(hGlob); + ret = false; + } + } + } + + CloseClipboard(); + return ret; + } + else + { + _selection = text; + return true; + } + } + + protected: + + std::string _selection; +}; + +//------------------------------------------------------------------------------ +NasalClipboard::Ptr NasalClipboard::create() +{ + return NasalClipboard::Ptr(new ClipboardWindows); +} diff --git a/src/Scripting/ClipboardX11.cxx b/src/Scripting/ClipboardX11.cxx new file mode 100644 index 000000000..6108c5242 --- /dev/null +++ b/src/Scripting/ClipboardX11.cxx @@ -0,0 +1,218 @@ +// X11 implementation of clipboard access for Nasal +// +// Copyright (C) 2012 Thomas Geymayer +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +#include "NasalClipboard.hxx" + +#include + +#include +#include + +class ClipboardX11: + public NasalClipboard +{ + public: + ClipboardX11(): + _display( XOpenDisplay(NULL) ), + _window( XCreateSimpleWindow( + _display, + DefaultRootWindow(_display), + 0, 0, 1, 1, // dummy dimensions -> window will never be mapped + 0, + 0, 0 + ) ), + _atom_targets( XInternAtom(_display, "TARGETS", 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); + } + + /** + * Get clipboard contents as text + */ + virtual std::string getText(Type type) + { + Atom atom_type = (type == CLIPBOARD ? _atom_clipboard : _atom_primary); + + //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; + + for(int cnt = 0; cnt < 5;) + { + XEvent event; + XNextEvent(_display, &event); + + 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; + + SG_LOG + ( + SG_NASAL, + SG_WARN, + "ClipboardX11::getText: Conversion failed: " + "target=" << getAtomName(target) + ); + 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; + } + } + } + else + { + SG_LOG + ( + SG_NASAL, + SG_WARN, + "ClipboardX11::getText: unexpected XEvent: " << event.type + ); + break; + } + } + + return std::string(); + } + + /** + * Set clipboard contents as text + */ + virtual bool setText(const std::string& text, Type type) + { + SG_LOG + ( + SG_NASAL, + SG_ALERT, + "ClipboardX11::setText: not yet implemented!" + ); + return false; + } + + protected: + + Display *_display; + Window _window; + Atom _atom_targets, + _atom_primary, + _atom_clipboard; + + struct Property + { + unsigned char *data; + int format, num_items; + Atom type; + }; + + // Get all data from a property + Property readProperty(Window w, Atom property) + { + Atom actual_type; + int actual_format; + unsigned long nitems; + unsigned long bytes_after; + unsigned char *ret=0; + + int read_bytes = 1024; + + //Keep trying to read the property until there are no + //bytes unread. + do + { + if( ret ) + XFree(ret); + + XGetWindowProperty + ( + _display, w, property, 0, read_bytes, False, AnyPropertyType, + &actual_type, &actual_format, &nitems, &bytes_after, + &ret + ); + + read_bytes *= 2; + } while( bytes_after ); + + Property p = {ret, actual_format, nitems, actual_type}; + return p; + } + + std::string getAtomName(Atom atom) + { + return atom == None ? "None" : XGetAtomName(_display, atom); + } + +}; + +//------------------------------------------------------------------------------ +NasalClipboard::Ptr NasalClipboard::create() +{ + return NasalClipboard::Ptr(new ClipboardX11); +} diff --git a/src/Scripting/NasalClipboard.cxx b/src/Scripting/NasalClipboard.cxx new file mode 100644 index 000000000..db148dce0 --- /dev/null +++ b/src/Scripting/NasalClipboard.cxx @@ -0,0 +1,131 @@ +// X11 implementation of clipboard access for Nasal +// +// Copyright (C) 2012 Thomas Geymayer +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "NasalClipboard.hxx" +#include "NasalSys.hxx" + +#include +#include + +/* + * Nasal wrappers for setting/getting clipboard text + */ +//------------------------------------------------------------------------------ +static NasalClipboard::Type parseType(naContext c, int argc, naRef* args, int i) +{ + 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"); + } + + return NasalClipboard::CLIPBOARD; +} + +//------------------------------------------------------------------------------ +static naRef f_setClipboardText(naContext c, naRef me, int argc, naRef* args) +{ + if( argc < 1 || argc > 2 ) + naRuntimeError( c, "clipboard.setText() expects 1 or 2 arguments: " + "text, [, type = \"CLIPBOARD\"]" ); + + if( !naIsString(args[0]) ) + naRuntimeError(c, "clipboard.setText() invalid arg (arg 0 not a string)"); + + return + naNum + ( + NasalClipboard::getInstance()->setText( naStr_data(args[0]), + parseType(c, argc, args, 1) ) + ); +} + +//------------------------------------------------------------------------------ +static naRef f_getClipboardText(naContext c, naRef me, int argc, naRef* args) +{ + if( argc > 1 ) + naRuntimeError(c, "clipboard.getText() accepts max 1 arg: " + "[type = \"CLIPBOARD\"]" ); + + const std::string& text = + NasalClipboard::getInstance()->getText(parseType(c, argc, args, 0)); + + // TODO create some nasal helper functions (eg. stringToNasal) + // some functions are available spread over different files (eg. + // NasalPositioned.cxx) + return naStr_fromdata(naNewString(c), text.c_str(), text.length()); +} + +//------------------------------------------------------------------------------ +// Table of extension functions, terminate with 0,0 +static struct {const char* name; naCFunction func; } funcs[] = { + { "setText", f_setClipboardText }, + { "getText", f_getClipboardText }, + { 0,0 } // TERMINATION +}; + +//------------------------------------------------------------------------------ +NasalClipboard::Ptr NasalClipboard::_clipboard; +naRef NasalClipboard::_clipboard_hash; + +//------------------------------------------------------------------------------ +NasalClipboard::~NasalClipboard() +{ + +} + +//------------------------------------------------------------------------------ +void NasalClipboard::init(FGNasalSys *nasal) +{ + _clipboard = create(); + _clipboard_hash = naNewHash(nasal->context()); + + nasal->globalsSet("clipboard", _clipboard_hash); + + for(size_t i=0;funcs[i].name;i++) + { + nasal->hashset + ( + _clipboard_hash, + funcs[i].name, + naNewFunc(nasal->context(), naNewCCode(nasal->context(), funcs[i].func)) + ); + + SG_LOG(SG_NASAL, SG_DEBUG, "Adding clipboard function: " << funcs[i].name ); + } +} + +//------------------------------------------------------------------------------ +NasalClipboard::Ptr NasalClipboard::getInstance() +{ + return _clipboard; +} diff --git a/src/Scripting/NasalClipboard.hxx b/src/Scripting/NasalClipboard.hxx new file mode 100644 index 000000000..fca3979c6 --- /dev/null +++ b/src/Scripting/NasalClipboard.hxx @@ -0,0 +1,72 @@ +// Clipboard access for Nasal +// +// Copyright (C) 2012 Thomas Geymayer +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +#ifndef NASAL_CLIPOARD_HXX_ +#define NASAL_CLIPOARD_HXX_ + +#include +#include +#include + +class FGNasalSys; +class NasalClipboard +{ + public: + + enum Type + { + /// Standard clipboard as supported by nearly all operating systems + CLIPBOARD, + + /// X11 platforms support also a mode called PRIMARY selection which + /// contains the current (mouse) selection and can typically be inserted + /// via a press on the middle mouse button + PRIMARY + }; + + typedef boost::shared_ptr Ptr; + + virtual std::string getText(Type type = CLIPBOARD) = 0; + virtual bool setText( const std::string& text, + Type type = CLIPBOARD ) = 0; + + /** + * Sets up the clipboard and puts all the extension functions into a new + * "clipboard" namespace. + */ + static void init(FGNasalSys *nasal); + + /** + * Get clipboard platform specific instance + */ + static Ptr getInstance(); + + protected: + + static Ptr _clipboard; + static naRef _clipboard_hash; + + /** + * Implementation supplied by actual platform implementation + */ + static Ptr create(); + + virtual ~NasalClipboard() = 0; +}; + +#endif /* NASAL_CLIPOARD_HXX_ */ diff --git a/src/Scripting/NasalSys.cxx b/src/Scripting/NasalSys.cxx index a1de1e794..5dd28ed1b 100644 --- a/src/Scripting/NasalSys.cxx +++ b/src/Scripting/NasalSys.cxx @@ -28,11 +28,13 @@ #include "NasalSys.hxx" #include "NasalPositioned.hxx" #include "NasalCanvas.hxx" +#include "NasalClipboard.hxx" #include
#include
#include
+ using std::map; static FGNasalSys* nasalSys = 0; @@ -101,6 +103,20 @@ FGNasalSys::FGNasalSys() _callCount = 0; } +// Utility. Sets a named key in a hash by C string, rather than nasal +// string object. +void FGNasalSys::hashset(naRef hash, const char* key, naRef val) +{ + naRef s = naNewString(_context); + naStr_fromdata(s, (char*)key, strlen(key)); + naHash_set(hash, s, val); +} + +void FGNasalSys::globalsSet(const char* key, naRef val) +{ + hashset(_globals, key, val); +} + naRef FGNasalSys::call(naRef code, int argc, naRef* args, naRef locals) { return callMethod(code, naNil(), argc, args, locals); @@ -170,15 +186,6 @@ FGNasalScript* FGNasalSys::parseScript(const char* src, const char* name) return script; } -// Utility. Sets a named key in a hash by C string, rather than nasal -// string object. -void FGNasalSys::hashset(naRef hash, const char* key, naRef val) -{ - naRef s = naNewString(_context); - naStr_fromdata(s, (char*)key, strlen(key)); - naHash_set(hash, s, val); -} - // The get/setprop functions accept a *list* of strings and walk // through the property tree with them to find the appropriate node. // This allows a Nasal object to hold onto a property path and use it @@ -551,7 +558,8 @@ void FGNasalSys::init() initNasalPositioned(_globals, _context, _gcHash); initNasalCanvas(_globals, _context, _gcHash); - + NasalClipboard::init(this); + // Now load the various source files in the Nasal directory simgear::Dir nasalDir(SGPath(globals->get_fg_root(), "Nasal")); loadScriptDirectory(nasalDir); diff --git a/src/Scripting/NasalSys.hxx b/src/Scripting/NasalSys.hxx index 0dad5fd53..18ef05314 100644 --- a/src/Scripting/NasalSys.hxx +++ b/src/Scripting/NasalSys.hxx @@ -114,6 +114,16 @@ public: void deleteModule(const char* moduleName); + /** + * Set member of specified hash to given value + */ + void hashset(naRef hash, const char* key, naRef val); + + /** + * Set member of globals hash to given value + */ + void globalsSet(const char* key, naRef val); + naRef call(naRef code, int argc, naRef* args, naRef locals); naRef callMethod(naRef code, naRef self, int argc, naRef* args, naRef locals); @@ -168,7 +178,6 @@ private: void loadPropertyScripts(SGPropertyNode* n); void loadScriptDirectory(simgear::Dir nasalDir); void addModule(string moduleName, simgear::PathList scripts); - void hashset(naRef hash, const char* key, naRef val); void logError(naContext); naRef parse(const char* filename, const char* buf, int len); naRef genPropsModule();