1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 #ifndef _DTRANS_X11_SELECTION_HXX_ 23 #define _DTRANS_X11_SELECTION_HXX_ 24 25 #include <cppuhelper/compbase3.hxx> 26 #include <cppuhelper/compbase4.hxx> 27 #include <com/sun/star/datatransfer/XTransferable.hpp> 28 #include <com/sun/star/datatransfer/dnd/XDropTarget.hpp> 29 #include <com/sun/star/datatransfer/dnd/XDragSource.hpp> 30 #include <com/sun/star/awt/XDisplayConnection.hpp> 31 #include <com/sun/star/lang/XInitialization.hpp> 32 #include <com/sun/star/lang/XServiceInfo.hpp> 33 #include <com/sun/star/script/XInvocation.hpp> 34 #include <com/sun/star/frame/XDesktop.hpp> 35 #include <osl/thread.h> 36 37 #ifndef _OSL_CONDITION_HXX_ 38 #include <osl/conditn.hxx> 39 #endif 40 41 #include <hash_map> 42 #include <list> 43 44 #include "tools/prex.h" 45 #include <X11/Xlib.h> 46 #include "tools/postx.h" 47 48 #define XDND_IMPLEMENTATION_NAME "com.sun.star.datatransfer.dnd.XdndSupport" 49 #define XDND_DROPTARGET_IMPLEMENTATION_NAME "com.sun.star.datatransfer.dnd.XdndDropTarget" 50 51 using namespace ::com::sun::star::uno; 52 53 namespace x11 { 54 55 class PixmapHolder; // in bmp.hxx 56 57 // ------------------------------------------------------------------------ 58 rtl_TextEncoding getTextPlainEncoding( const ::rtl::OUString& rMimeType ); 59 60 class SelectionAdaptor 61 { 62 public: 63 virtual com::sun::star::uno::Reference< ::com::sun::star::datatransfer::XTransferable > getTransferable() = 0; 64 virtual void clearTransferable() = 0; 65 virtual void fireContentsChanged() = 0; 66 virtual com::sun::star::uno::Reference< XInterface > getReference() = 0; 67 // returns a reference that will keep the SelectionAdaptor alive until the 68 // reference is released 69 }; 70 71 class DropTarget : 72 public ::cppu::WeakComponentImplHelper3< 73 ::com::sun::star::datatransfer::dnd::XDropTarget, 74 ::com::sun::star::lang::XInitialization, 75 ::com::sun::star::lang::XServiceInfo 76 > 77 { 78 public: 79 ::osl::Mutex m_aMutex; 80 bool m_bActive; 81 sal_Int8 m_nDefaultActions; 82 XLIB_Window m_aTargetWindow; 83 class SelectionManager* m_pSelectionManager; 84 com::sun::star::uno::Reference< ::com::sun::star::datatransfer::dnd::XDragSource > 85 m_xSelectionManager; 86 ::std::list< com::sun::star::uno::Reference< ::com::sun::star::datatransfer::dnd::XDropTargetListener > > 87 m_aListeners; 88 89 DropTarget(); 90 virtual ~DropTarget(); 91 92 // convenience functions that loop over listeners 93 void dragEnter( const ::com::sun::star::datatransfer::dnd::DropTargetDragEnterEvent& dtde ) throw(); 94 void dragExit( const ::com::sun::star::datatransfer::dnd::DropTargetEvent& dte ) throw(); 95 void dragOver( const ::com::sun::star::datatransfer::dnd::DropTargetDragEvent& dtde ) throw(); 96 void drop( const ::com::sun::star::datatransfer::dnd::DropTargetDropEvent& dtde ) throw(); 97 98 // XInitialization 99 virtual void SAL_CALL initialize( const Sequence< Any >& args ) throw ( ::com::sun::star::uno::Exception ); 100 101 // XDropTarget 102 virtual void SAL_CALL addDropTargetListener( const com::sun::star::uno::Reference< ::com::sun::star::datatransfer::dnd::XDropTargetListener >& ) throw(); 103 virtual void SAL_CALL removeDropTargetListener( const com::sun::star::uno::Reference< ::com::sun::star::datatransfer::dnd::XDropTargetListener >& ) throw(); 104 virtual sal_Bool SAL_CALL isActive() throw(); 105 virtual void SAL_CALL setActive( sal_Bool active ) throw(); 106 virtual sal_Int8 SAL_CALL getDefaultActions() throw(); 107 virtual void SAL_CALL setDefaultActions( sal_Int8 actions ) throw(); 108 109 // XServiceInfo 110 virtual ::rtl::OUString SAL_CALL getImplementationName() throw(); 111 virtual sal_Bool SAL_CALL supportsService( const ::rtl::OUString& ServiceName ) throw(); 112 virtual ::com::sun::star::uno::Sequence< ::rtl::OUString > 113 SAL_CALL getSupportedServiceNames() throw(); 114 }; 115 116 class SelectionManagerHolder : 117 public ::cppu::WeakComponentImplHelper3< 118 ::com::sun::star::datatransfer::dnd::XDragSource, 119 ::com::sun::star::lang::XInitialization, 120 ::com::sun::star::lang::XServiceInfo 121 > 122 { 123 ::osl::Mutex m_aMutex; 124 com::sun::star::uno::Reference< ::com::sun::star::datatransfer::dnd::XDragSource > 125 m_xRealDragSource; 126 public: 127 SelectionManagerHolder(); 128 virtual ~SelectionManagerHolder(); 129 130 // XServiceInfo 131 virtual ::rtl::OUString SAL_CALL getImplementationName() throw(); 132 virtual sal_Bool SAL_CALL supportsService( const ::rtl::OUString& ServiceName ) throw(); 133 virtual ::com::sun::star::uno::Sequence< ::rtl::OUString > 134 SAL_CALL getSupportedServiceNames() throw(); 135 136 // XInitialization 137 virtual void SAL_CALL initialize( const Sequence< Any >& arguments ) throw( ::com::sun::star::uno::Exception ); 138 139 // XDragSource 140 virtual sal_Bool SAL_CALL isDragImageSupported() throw(); 141 virtual sal_Int32 SAL_CALL getDefaultCursor( sal_Int8 dragAction ) throw(); 142 virtual void SAL_CALL startDrag( 143 const ::com::sun::star::datatransfer::dnd::DragGestureEvent& trigger, 144 sal_Int8 sourceActions, sal_Int32 cursor, sal_Int32 image, 145 const com::sun::star::uno::Reference< ::com::sun::star::datatransfer::XTransferable >& transferable, 146 const com::sun::star::uno::Reference< ::com::sun::star::datatransfer::dnd::XDragSourceListener >& listener 147 ) throw(); 148 }; 149 150 class SelectionManager : 151 public ::cppu::WeakImplHelper4< 152 ::com::sun::star::datatransfer::dnd::XDragSource, 153 ::com::sun::star::lang::XInitialization, 154 ::com::sun::star::awt::XEventHandler, 155 ::com::sun::star::frame::XTerminateListener 156 >, 157 public SelectionAdaptor 158 { 159 static ::std::hash_map< ::rtl::OUString, SelectionManager*, ::rtl::OUStringHash >& getInstances(); 160 161 // for INCR type selection transfer 162 // INCR protocol is used if the data cannot 163 // be transported at once but in parts 164 // IncrementalTransfer holds the bytes to be transmitted 165 // as well as the current position 166 // INCR triggers the delivery of the next part by deleting the 167 // property used to transfer the data 168 struct IncrementalTransfer 169 { 170 Sequence< sal_Int8 > m_aData; 171 int m_nBufferPos; 172 XLIB_Window m_aRequestor; 173 Atom m_aProperty; 174 Atom m_aTarget; 175 int m_nFormat; 176 int m_nTransferStartTime; 177 }; 178 int m_nIncrementalThreshold; 179 180 // a struct to hold the data associated with a selection 181 struct Selection 182 { 183 enum State 184 { 185 Inactive, WaitingForResponse, WaitingForData, IncrementalTransfer 186 }; 187 188 State m_eState; 189 SelectionAdaptor* m_pAdaptor; 190 Atom m_aAtom; 191 ::osl::Condition m_aDataArrived; 192 Sequence< sal_Int8 > m_aData; 193 Sequence< ::com::sun::star::datatransfer::DataFlavor > 194 m_aTypes; 195 std::vector< Atom > m_aNativeTypes; 196 // this is used for caching 197 // m_aTypes is invalid after 2 seconds 198 // m_aNativeTypes contains the corresponding original atom 199 Atom m_aRequestedType; 200 // m_aRequestedType is only valid while WaitingForResponse and WaitingFotData 201 int m_nLastTimestamp; 202 bool m_bHaveUTF16; 203 Atom m_aUTF8Type; 204 bool m_bHaveCompound; 205 bool m_bOwner; 206 XLIB_Window m_aLastOwner; 207 PixmapHolder* m_pPixmap; 208 // m_nOrigXLIB_Timestamp contains the XLIB_Timestamp at which the selection 209 // was acquired; needed for XLIB_TimeSTAMP target 210 XLIB_Time m_nOrigTimestamp; 211 Selectionx11::SelectionManager::Selection212 Selection() : m_eState( Inactive ), 213 m_pAdaptor( NULL ), 214 m_aAtom( None ), 215 m_aRequestedType( None ), 216 m_nLastTimestamp( 0 ), 217 m_bHaveUTF16( false ), 218 m_aUTF8Type( None ), 219 m_bHaveCompound( false ), 220 m_bOwner( false ), 221 m_aLastOwner( None ), 222 m_pPixmap( NULL ), 223 m_nOrigTimestamp( CurrentTime ) 224 {} 225 }; 226 227 // a struct to hold data associated with a XDropTarget 228 struct DropTargetEntry 229 { 230 DropTarget* m_pTarget; 231 XLIB_Window m_aRootWindow; 232 DropTargetEntryx11::SelectionManager::DropTargetEntry233 DropTargetEntry() : m_pTarget( NULL ), m_aRootWindow( None ) {} DropTargetEntryx11::SelectionManager::DropTargetEntry234 DropTargetEntry( DropTarget* pTarget ) : 235 m_pTarget( pTarget ), 236 m_aRootWindow( None ) 237 {} DropTargetEntryx11::SelectionManager::DropTargetEntry238 DropTargetEntry( const DropTargetEntry& rEntry ) : 239 m_pTarget( rEntry.m_pTarget ), 240 m_aRootWindow( rEntry.m_aRootWindow ) 241 {} ~DropTargetEntryx11::SelectionManager::DropTargetEntry242 ~DropTargetEntry() {} 243 operator ->x11::SelectionManager::DropTargetEntry244 DropTarget* operator->() const { return m_pTarget; } operator =x11::SelectionManager::DropTargetEntry245 DropTargetEntry& operator=(const DropTargetEntry& rEntry) 246 { m_pTarget = rEntry.m_pTarget; m_aRootWindow = rEntry.m_aRootWindow; return *this; } 247 }; 248 249 // internal data 250 Display* m_pDisplay; 251 oslThread m_aThread; 252 oslThread m_aDragExecuteThread; 253 ::osl::Condition m_aDragRunning; 254 XLIB_Window m_aWindow; 255 com::sun::star::uno::Reference< ::com::sun::star::awt::XDisplayConnection > 256 m_xDisplayConnection; 257 com::sun::star::uno::Reference< com::sun::star::script::XInvocation > 258 m_xBitmapConverter; 259 sal_Int32 m_nSelectionTimeout; 260 XLIB_Time m_nSelectionTimestamp; 261 262 // members used for Xdnd 263 264 // drop only 265 266 // contains the XdndEnterEvent of a drop action running 267 // with one of our targets. The data.l[0] member 268 // (containing the drag source XLIB_Window) is set 269 // to None while that is not the case 270 XClientMessageEvent m_aDropEnterEvent; 271 // set to false on XdndEnter 272 // set to true on first XdndPosition or XdndLeave 273 bool m_bDropEnterSent; 274 XLIB_Window m_aCurrentDropWindow; 275 // XLIB_Time code of XdndDrop 276 XLIB_Time m_nDropTime; 277 sal_Int8 m_nLastDropAction; 278 // XTransferable for Xdnd with foreign drag source 279 com::sun::star::uno::Reference< ::com::sun::star::datatransfer::XTransferable > 280 m_xDropTransferable; 281 int m_nLastX, m_nLastY; 282 XLIB_Time m_nDropTimestamp; 283 // set to true when calling drop() 284 // if another XdndEnter is received this shows that 285 // someone forgot to call dropComplete - we should reset 286 // and react to the new drop 287 bool m_bDropWaitingForCompletion; 288 289 // drag only 290 291 // None if no Dnd action is running with us as source 292 XLIB_Window m_aDropWindow; 293 // either m_aDropXLIB_Window or its XdndProxy 294 XLIB_Window m_aDropProxy; 295 XLIB_Window m_aDragSourceWindow; 296 // XTransferable for Xdnd when we are drag source 297 com::sun::star::uno::Reference< ::com::sun::star::datatransfer::XTransferable > 298 m_xDragSourceTransferable; 299 com::sun::star::uno::Reference< ::com::sun::star::datatransfer::dnd::XDragSourceListener > 300 m_xDragSourceListener; 301 // root coordinates 302 int m_nLastDragX, m_nLastDragY; 303 Sequence< ::com::sun::star::datatransfer::DataFlavor > 304 m_aDragFlavors; 305 // the rectangle the pointer must leave until a new XdndPosition should 306 // be sent. empty unless the drop target told to fill 307 int m_nNoPosX, m_nNoPosY, m_nNoPosWidth, m_nNoPosHeight; 308 unsigned int m_nDragButton; 309 sal_Int8 m_nUserDragAction; 310 sal_Int8 m_nTargetAcceptAction; 311 sal_Int8 m_nSourceActions; 312 bool m_bLastDropAccepted; 313 bool m_bDropSuccess; 314 bool m_bDropSent; 315 time_t m_nDropTimeout; 316 bool m_bWaitingForPrimaryConversion; 317 XLIB_Time m_nDragTimestamp; 318 319 // drag cursors 320 XLIB_Cursor m_aMoveCursor; 321 XLIB_Cursor m_aCopyCursor; 322 XLIB_Cursor m_aLinkCursor; 323 XLIB_Cursor m_aNoneCursor; 324 XLIB_Cursor m_aCurrentCursor; 325 326 // drag and drop 327 328 int m_nCurrentProtocolVersion; 329 ::std::hash_map< XLIB_Window, DropTargetEntry > 330 m_aDropTargets; 331 332 // some special atoms that are needed often 333 Atom m_nCLIPBOARDAtom; 334 Atom m_nTARGETSAtom; 335 Atom m_nTIMESTAMPAtom; 336 Atom m_nTEXTAtom; 337 Atom m_nINCRAtom; 338 Atom m_nCOMPOUNDAtom; 339 Atom m_nMULTIPLEAtom; 340 Atom m_nUTF16Atom; 341 Atom m_nImageBmpAtom; 342 Atom m_nXdndAware; 343 Atom m_nXdndEnter; 344 Atom m_nXdndLeave; 345 Atom m_nXdndPosition; 346 Atom m_nXdndStatus; 347 Atom m_nXdndDrop; 348 Atom m_nXdndFinished; 349 Atom m_nXdndSelection; 350 Atom m_nXdndTypeList; 351 Atom m_nXdndProxy; 352 Atom m_nXdndActionCopy; 353 Atom m_nXdndActionMove; 354 Atom m_nXdndActionLink; 355 Atom m_nXdndActionAsk; 356 Atom m_nXdndActionPrivate; 357 358 // caching for atoms 359 ::std::hash_map< Atom, ::rtl::OUString > 360 m_aAtomToString; 361 ::std::hash_map< ::rtl::OUString, Atom, ::rtl::OUStringHash > 362 m_aStringToAtom; 363 364 // the registered selections 365 ::std::hash_map< Atom, Selection* > 366 m_aSelections; 367 // IncrementalTransfers in progress 368 std::hash_map< XLIB_Window, std::hash_map< Atom, IncrementalTransfer > > 369 m_aIncrementals; 370 371 // do not use X11 multithreading capabilities 372 // since this leads to deadlocks in different Xlib implementations 373 // (XFree as well as Xsun) use an own mutex instead 374 ::osl::Mutex m_aMutex; 375 bool m_bShutDown; 376 377 SelectionManager(); 378 ~SelectionManager(); 379 380 SelectionAdaptor* getAdaptor( Atom selection ); 381 PixmapHolder* getPixmapHolder( Atom selection ); 382 383 // handle various events 384 bool handleSelectionRequest( XSelectionRequestEvent& rRequest ); 385 bool handleSendPropertyNotify( XPropertyEvent& rNotify ); 386 bool handleReceivePropertyNotify( XPropertyEvent& rNotify ); 387 bool handleSelectionNotify( XSelectionEvent& rNotify ); 388 bool handleDragEvent( XEvent& rMessage ); 389 bool handleDropEvent( XClientMessageEvent& rMessage ); 390 391 // dnd helpers 392 void sendDragStatus( Atom nDropAction ); 393 void sendDropPosition( bool bForce, XLIB_Time eventXLIB_Time ); 394 bool updateDragAction( int modifierState ); 395 int getXdndVersion( XLIB_Window aXLIB_Window, XLIB_Window& rProxy ); 396 XLIB_Cursor createCursor( const char* pPointerData, const char* pMaskData, int width, int height, int hotX, int hotY ); 397 // coordinates on root XLIB_Window 398 void updateDragWindow( int nX, int nY, XLIB_Window aRoot ); 399 400 bool getPasteData( Atom selection, Atom type, Sequence< sal_Int8 >& rData ); 401 // returns true if conversion was successful 402 bool convertData( const com::sun::star::uno::Reference< ::com::sun::star::datatransfer::XTransferable >& xTransferable, 403 Atom nType, 404 Atom nSelection, 405 int & rFormat, 406 Sequence< sal_Int8 >& rData ); 407 bool sendData( SelectionAdaptor* pAdaptor, XLIB_Window requestor, Atom target, Atom property, Atom selection ); 408 409 // thread dispatch loop 410 public: 411 // public for extern "C" stub 412 static void run( void* ); 413 private: 414 void dispatchEvent( int millisec ); 415 // drag thread dispatch 416 public: 417 // public for extern "C" stub 418 static void runDragExecute( void* ); 419 private: 420 void dragDoDispatch(); 421 bool handleXEvent( XEvent& rEvent ); 422 423 // compound text conversion 424 ::rtl::OString convertToCompound( const ::rtl::OUString& rText ); 425 ::rtl::OUString convertFromCompound( const char* pText, int nLen = -1 ); 426 427 sal_Int8 getUserDragAction() const; 428 sal_Int32 getSelectionTimeout(); 429 public: 430 static SelectionManager& get( const ::rtl::OUString& rDisplayName = ::rtl::OUString() ); 431 getDisplay()432 Display * getDisplay() { return m_pDisplay; }; getWindow()433 XLIB_Window getWindow() { return m_aWindow; }; 434 435 void registerHandler( Atom selection, SelectionAdaptor& rAdaptor ); 436 void deregisterHandler( Atom selection ); 437 bool requestOwnership( Atom selection ); 438 439 // allow for synchronization over one mutex for XClipboard getMutex()440 osl::Mutex& getMutex() { return m_aMutex; } 441 442 Atom getAtom( const ::rtl::OUString& rString ); 443 const ::rtl::OUString& getString( Atom nAtom ); 444 445 // type conversion 446 // note: convertTypeToNative does NOT clear the list, so you can append 447 // multiple types to the same list 448 void convertTypeToNative( const ::rtl::OUString& rType, Atom selection, int& rFormat, ::std::list< Atom >& rConversions, bool bPushFront = false ); 449 ::rtl::OUString convertTypeFromNative( Atom nType, Atom selection, int& rFormat ); 450 void getNativeTypeList( const Sequence< com::sun::star::datatransfer::DataFlavor >& rTypes, std::list< Atom >& rOutTypeList, Atom targetselection ); 451 452 // methods for transferable 453 bool getPasteDataTypes( Atom selection, Sequence< ::com::sun::star::datatransfer::DataFlavor >& rTypes ); 454 bool getPasteData( Atom selection, const ::rtl::OUString& rType, Sequence< sal_Int8 >& rData ); 455 456 // for XDropTarget to register/deregister itself 457 void registerDropTarget( XLIB_Window aXLIB_Window, DropTarget* pTarget ); 458 void deregisterDropTarget( XLIB_Window aXLIB_Window ); 459 460 // for XDropTarget{Drag|Drop}Context 461 void accept( sal_Int8 dragOperation, XLIB_Window aDropXLIB_Window, XLIB_Time aXLIB_Timestamp ); 462 void reject( XLIB_Window aDropXLIB_Window, XLIB_Time aXLIB_Timestamp ); 463 void dropComplete( sal_Bool success, XLIB_Window aDropXLIB_Window, XLIB_Time aXLIB_Timestamp ); 464 465 // for XDragSourceContext 466 sal_Int32 getCurrentCursor(); 467 void setCursor( sal_Int32 cursor, XLIB_Window aDropXLIB_Window, XLIB_Time aXLIB_Timestamp ); 468 void setImage( sal_Int32 image, XLIB_Window aDropXLIB_Window, XLIB_Time aXLIB_Timestamp ); 469 void transferablesFlavorsChanged(); 470 471 void shutdown() throw(); 472 473 // XInitialization 474 virtual void SAL_CALL initialize( const Sequence< Any >& arguments ) throw( ::com::sun::star::uno::Exception ); 475 476 // XEventHandler 477 virtual sal_Bool SAL_CALL handleEvent( const Any& event ) throw(); 478 479 // XDragSource 480 virtual sal_Bool SAL_CALL isDragImageSupported() throw(); 481 virtual sal_Int32 SAL_CALL getDefaultCursor( sal_Int8 dragAction ) throw(); 482 virtual void SAL_CALL startDrag( 483 const ::com::sun::star::datatransfer::dnd::DragGestureEvent& trigger, 484 sal_Int8 sourceActions, sal_Int32 cursor, sal_Int32 image, 485 const com::sun::star::uno::Reference< ::com::sun::star::datatransfer::XTransferable >& transferable, 486 const com::sun::star::uno::Reference< ::com::sun::star::datatransfer::dnd::XDragSourceListener >& listener 487 ) throw(); 488 489 // SelectionAdaptor for XdndSelection Drag (we are drag source) 490 virtual com::sun::star::uno::Reference< ::com::sun::star::datatransfer::XTransferable > getTransferable() throw(); 491 virtual void clearTransferable() throw(); 492 virtual void fireContentsChanged() throw(); 493 virtual com::sun::star::uno::Reference< XInterface > getReference() throw(); 494 495 // XEventListener 496 virtual void SAL_CALL disposing( const ::com::sun::star::lang::EventObject& Source ) throw( ::com::sun::star::uno::RuntimeException ); 497 498 // XTerminateListener 499 virtual void SAL_CALL queryTermination( const ::com::sun::star::lang::EventObject& aEvent ) 500 throw( ::com::sun::star::frame::TerminationVetoException, ::com::sun::star::uno::RuntimeException ); 501 virtual void SAL_CALL notifyTermination( const ::com::sun::star::lang::EventObject& aEvent ) 502 throw( ::com::sun::star::uno::RuntimeException ); 503 }; 504 505 // ------------------------------------------------------------------------ 506 507 ::com::sun::star::uno::Sequence< ::rtl::OUString > SAL_CALL Xdnd_getSupportedServiceNames(); 508 ::com::sun::star::uno::Reference< ::com::sun::star::uno::XInterface > SAL_CALL Xdnd_createInstance( 509 const ::com::sun::star::uno::Reference< ::com::sun::star::lang::XMultiServiceFactory > & xMultiServiceFactory); 510 511 ::com::sun::star::uno::Sequence< ::rtl::OUString > SAL_CALL Xdnd_dropTarget_getSupportedServiceNames(); 512 ::com::sun::star::uno::Reference< ::com::sun::star::uno::XInterface > SAL_CALL Xdnd_dropTarget_createInstance( 513 const ::com::sun::star::uno::Reference< ::com::sun::star::lang::XMultiServiceFactory > & xMultiServiceFactory); 514 515 // ------------------------------------------------------------------------ 516 517 } 518 519 #endif 520 521 /* vim: set noet sw=4 ts=4: */ 522