tge/engine/platformX86UNIX/x86UNIXInput.cc
2025-02-17 23:17:30 -06:00

590 lines
16 KiB
C++
Executable File

//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platformX86UNIX/platformX86UNIX.h"
#include "platform/platformInput.h"
#include "platform/platformVideo.h"
#include "platform/event.h"
#include "platform/gameInterface.h"
#include "console/console.h"
#include "platformX86UNIX/x86UNIXState.h"
#include "platformX86UNIX/x86UNIXInputManager.h"
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/keysym.h>
#include <SDL/SDL.h>
#ifdef LOG_INPUT
#include <time.h>
#include <stdarg.h>
#include <fcntl.h>
#include <platformX86UNIX/x86UNIXUtils.h>
extern int x86UNIXOpen(const char *path, int oflag);
extern int x86UNIXClose(int fd);
extern ssize_t x86UNIXWrite(int fd, const void *buf, size_t nbytes);
#endif
class XClipboard
{
private:
Atom mClipboardProperty;
Atom mClipboard;
Atom mPrimary;
bool mInitialized;
U8 *mXData;
char *mTData;
S32 mTDataSize;
void init();
void freeXData();
void freeTData();
void checkTDataSize(S32 requestedSize);
public:
XClipboard();
~XClipboard();
bool setClipboard(const char *text);
const char* getClipboard();
void handleSelectionRequest(XSelectionRequestEvent& request);
};
// Static class variables:
InputManager* Input::smManager;
// smActive is not maintained under unix. Use Input::isActive()
// instead
bool Input::smActive = false;
// unix platform state
extern x86UNIXPlatformState * x86UNIXState;
extern AsciiData AsciiTable[NUM_KEYS];
static XClipboard xclipboard;
#ifdef LOG_INPUT
S32 gInputLog = -1;
#endif
//------------------------------------------------------------------------------
void Input::init()
{
Con::printf( "Input Init:" );
destroy();
#ifdef LOG_INPUT
struct tm* newTime;
time_t aclock;
time( &aclock );
newTime = localtime( &aclock );
asctime( newTime );
gInputLog = x86UNIXOpen("input.log", O_WRONLY | O_CREAT);
log("Input log opened at %s\n", asctime( newTime ) );
log("Operating System:\n" );
log(" %s", UUtils->getOSName());
log("\n");
#endif
smActive = false;
smManager = NULL;
UInputManager *uInputManager = new UInputManager;
if ( !uInputManager->enable() )
{
Con::errorf( " Failed to enable Input Manager." );
delete uInputManager;
return;
}
uInputManager->init();
smManager = uInputManager;
Con::printf(" Input initialized");
Con::printf(" ");
}
//------------------------------------------------------------------------------
ConsoleFunction( isJoystickDetected, bool, 1, 1, "isJoystickDetected()" )
{
argc; argv;
UInputManager* manager = dynamic_cast<UInputManager*>(Input::getManager());
if (manager)
return manager->joystickDetected();
else
return false;
}
//------------------------------------------------------------------------------
ConsoleFunction( getJoystickAxes, const char*, 2, 2, "getJoystickAxes( instance )" )
{
argc; argv;
UInputManager* manager = dynamic_cast<UInputManager*>(Input::getManager());
if (manager)
return manager->getJoystickAxesString(dAtoi(argv[1]));
else
return "";
}
//------------------------------------------------------------------------------
U16 Input::getKeyCode( U16 asciiCode )
{
U16 keyCode = 0;
U16 i;
// This is done three times so the lowerkey will always
// be found first. Some foreign keyboards have duplicate
// chars on some keys.
for ( i = KEY_FIRST; i < NUM_KEYS && !keyCode; i++ )
{
if ( AsciiTable[i].lower.ascii == asciiCode )
{
keyCode = i;
break;
};
}
for ( i = KEY_FIRST; i < NUM_KEYS && !keyCode; i++ )
{
if ( AsciiTable[i].upper.ascii == asciiCode )
{
keyCode = i;
break;
};
}
for ( i = KEY_FIRST; i < NUM_KEYS && !keyCode; i++ )
{
if ( AsciiTable[i].goofy.ascii == asciiCode )
{
keyCode = i;
break;
};
}
return( keyCode );
}
//-----------------------------------------------------------------------------
//
// This function gets the standard ASCII code corresponding to our key code
// and the existing modifier key state.
//
//-----------------------------------------------------------------------------
U16 Input::getAscii( U16 keyCode, KEY_STATE keyState )
{
if ( keyCode >= NUM_KEYS )
return 0;
switch ( keyState )
{
case STATE_LOWER:
return AsciiTable[keyCode].lower.ascii;
case STATE_UPPER:
return AsciiTable[keyCode].upper.ascii;
case STATE_GOOFY:
return AsciiTable[keyCode].goofy.ascii;
default:
return(0);
}
}
//------------------------------------------------------------------------------
void Input::destroy()
{
#ifdef LOG_INPUT
if ( gInputLog != -1 )
{
log( "*** CLOSING LOG ***\n" );
x86UNIXClose(gInputLog);
gInputLog = -1;
}
#endif
if ( smManager && smManager->isEnabled() )
{
smManager->disable();
delete smManager;
smManager = NULL;
}
}
//------------------------------------------------------------------------------
bool Input::enable()
{
if ( smManager && !smManager->isEnabled() )
return( smManager->enable() );
return( false );
}
//------------------------------------------------------------------------------
void Input::disable()
{
if ( smManager && smManager->isEnabled() )
smManager->disable();
}
//------------------------------------------------------------------------------
void Input::activate()
{
if ( smManager && smManager->isEnabled() && !isActive())
{
#ifdef LOG_INPUT
Input::log( "Activating Input...\n" );
#endif
UInputManager* uInputManager = dynamic_cast<UInputManager*>( smManager );
if ( uInputManager )
uInputManager->activate();
}
}
//------------------------------------------------------------------------------
void Input::deactivate()
{
if ( smManager && smManager->isEnabled() && isActive() )
{
#ifdef LOG_INPUT
Input::log( "Deactivating Input...\n" );
#endif
UInputManager* uInputManager = dynamic_cast<UInputManager*>( smManager );
if ( uInputManager )
uInputManager->deactivate();
}
}
//------------------------------------------------------------------------------
void Input::reactivate()
{
Input::deactivate();
Input::activate();
}
//------------------------------------------------------------------------------
bool Input::isEnabled()
{
if ( smManager )
return smManager->isEnabled();
return false;
}
//------------------------------------------------------------------------------
bool Input::isActive()
{
UInputManager* uInputManager = dynamic_cast<UInputManager*>( smManager );
if ( uInputManager )
return uInputManager->isActive();
return false;
}
//------------------------------------------------------------------------------
void Input::process()
{
if ( smManager )
smManager->process();
}
//------------------------------------------------------------------------------
InputManager* Input::getManager()
{
return smManager;
}
#ifdef LOG_INPUT
//------------------------------------------------------------------------------
void Input::log( const char* format, ... )
{
if ( gInputLog == -1)
return;
va_list argptr;
va_start( argptr, format );
const int BufSize = 4096;
char buffer[BufSize];
dVsprintf( buffer, BufSize, format, argptr );
x86UNIXWrite(gInputLog, buffer, dStrlen( buffer ));
va_end( argptr );
}
ConsoleFunction( inputLog, void, 2, 2, "inputLog( string )" )
{
argc;
Input::log( "%s\n", argv[1] );
}
#endif // LOG_INPUT
//------------------------------------------------------------------------------
void NotifySelectionEvent(XEvent& event)
{
// somebody sent us a select event
if (event.type == SelectionRequest)
xclipboard.handleSelectionRequest(event.xselectionrequest);
}
//------------------------------------------------------------------------------
const char* Platform::getClipboard()
{
return xclipboard.getClipboard();
}
//------------------------------------------------------------------------------
bool Platform::setClipboard(const char *text)
{
return xclipboard.setClipboard(text);
}
//-----------------------------------------------------------------------------
// XClipboard members
XClipboard::XClipboard()
{
mInitialized = false;
}
//------------------------------------------------------------------------------
XClipboard::~XClipboard()
{
freeXData();
freeTData();
}
//------------------------------------------------------------------------------
void XClipboard::init()
{
DisplayPtrManager xdisplay;
Display* display = xdisplay.getDisplayPointer();
mClipboardProperty = XInternAtom(display,
"TORQUE_CLIPBOARD_ATOM", False);
mClipboard = XInternAtom(display, "CLIPBOARD",
False);
mPrimary = XA_PRIMARY; //XInternAtom(display, "PRIMARY", False);
mXData = NULL;
mTData = NULL;
mTDataSize = 0;
mInitialized = true;
}
//------------------------------------------------------------------------------
inline void XClipboard::freeXData()
{
if (mXData != NULL)
{
XFree(mXData);
mXData = NULL;
}
}
//------------------------------------------------------------------------------
inline void XClipboard::freeTData()
{
if (mTData != NULL)
{
dRealFree(mTData);
mTData = NULL;
mTDataSize = 0;
}
}
//
// JMQ: As you might expect, X clipboard usage is bizarre. I
// found this document to be useful.
//
// http://www.freedesktop.org/standards/clipboards.txt
//
// JMQ: later note: programming the X clipboard is not just
// bizarre, it SUCKS. No wonder so many apps have
// clipboard problems.
//
//------------------------------------------------------------------------------
const char* XClipboard::getClipboard()
{
DisplayPtrManager xdisplay;
Display* display = xdisplay.getDisplayPointer();
if (!mInitialized)
init();
// find the owner of the clipboard
Atom targetSelection = mClipboard;
Window clipOwner = XGetSelectionOwner(display,
targetSelection);
if (clipOwner == None)
{
// It seems like KDE/QT reads the clipboard but doesn't set it.
// This is a bug, that supposedly will be fixed in QT3.
// I tried working around this by using
// PRIMARY instead of CLIPBOARD, but this has some nonintuitive
// side effects. So, no pasting from KDE apps for now.
//targetSelection = mPrimary;
//clipOwner = XGetSelectionOwner(display, targetSelection);
}
if (clipOwner == None)
// oh well
return "";
// request that the owner convert the selection to a string
XConvertSelection(display, targetSelection,
XA_STRING, mClipboardProperty, x86UNIXState->getWindow(), CurrentTime);
// flush the output buffer to make sure the selection request event gets
// sent now
XFlush(display);
XEvent xevent;
// if our window is the current owner, (e.g. copy from one part of
// torque and paste to another), then we just sent an event to our
// window that won't get processed until we get back to the event
// loop in x86Unixwindow. So look for selection request events in
// the event queue immediately and handle them.
while (XCheckTypedWindowEvent(display,
x86UNIXState->getWindow(), SelectionRequest, &xevent))
handleSelectionRequest(xevent.xselectionrequest);
// poll for the SelectionNotify event for 5 seconds. in most cases
// we should get the event very quickly
U32 startTime = Platform::getRealMilliseconds();
bool timeOut = false;
while (!XCheckTypedWindowEvent(display,
x86UNIXState->getWindow(), SelectionNotify, &xevent) &&
!timeOut)
{
// we'll be spinning here, but who cares
if ((Platform::getRealMilliseconds() - startTime) > 5000)
timeOut = true;
}
if (timeOut)
{
Con::warnf(ConsoleLogEntry::General,
"XClipboard: waited too long for owner to convert selection");
return "";
}
if (xevent.xselection.property == None)
return "";
// free the X data from a previous get
freeXData();
// grab the string data from the property
Atom actual_type;
int actual_format;
unsigned long bytes_after;
unsigned long nitems;
// query the property length the 250000 is "the length in 32-bit
// multiples of the data to be retrieved". so we support up to a
// million bytes of returned data.
int numToRetrieve = 250000;
int status = XGetWindowProperty(display,
x86UNIXState->getWindow(),
mClipboardProperty, 0, numToRetrieve, True, XA_STRING,
&actual_type, &actual_format, &nitems, &bytes_after, &mXData);
// we should have returned OK, with string type, 8bit data,
// and > 0 items.
if ((status != Success) || (actual_type != XA_STRING) ||
(actual_format != 8) || (nitems == 0))
return "";
// if there is data left in the clipboard, warn about it
if (bytes_after > 0)
Con::warnf(ConsoleLogEntry::General,
"XClipboard: some data was not retrieved");
return reinterpret_cast<const char *>(mXData);
}
//------------------------------------------------------------------------------
void XClipboard::checkTDataSize(S32 requestedSize)
{
if (mTDataSize < requestedSize)
{
freeTData();
mTData = static_cast<char*>(dRealMalloc(sizeof(char) * requestedSize));
AssertFatal(mTData, "unable to allocate clipboard buffer data!");
mTDataSize = requestedSize;
}
}
//------------------------------------------------------------------------------
bool XClipboard::setClipboard(const char *text)
{
DisplayPtrManager xdisplay;
Display* display = xdisplay.getDisplayPointer();
if (!mInitialized)
init();
// get the length of the text
S32 len = dStrlen(text) + 1;
// reallocate the storage buffer if necessary
checkTDataSize(len);
// copy the data into the storage buffer
dStrcpy(mTData, text);
// tell X that we own the clipboard. (we'll get events
// if an app tries to paste)
XSetSelectionOwner(display, mClipboard,
x86UNIXState->getWindow(), CurrentTime);
return true;
}
//------------------------------------------------------------------------------
void XClipboard::handleSelectionRequest(XSelectionRequestEvent& request)
{
DisplayPtrManager xdisplay;
Display* display = xdisplay.getDisplayPointer();
// init our response
XSelectionEvent notify;
notify.type = SelectionNotify;
notify.display = display;
notify.requestor = request.requestor;
notify.selection = request.selection;
notify.target = XA_STRING;
notify.property = None;
notify.time = CurrentTime;
// make sure the owner is our window, and that the
// requestor wants the clipboard
if (request.owner == x86UNIXState->getWindow() &&
request.selection == mClipboard)
{
notify.property = request.property;
// check to see if they did not set the property
if (notify.property == None)
notify.property = mClipboardProperty;
// get the length of the data in the clipboard
S32 length = dStrlen(mTData);
// set the property on the requestor window
XChangeProperty(display, request.requestor,
notify.property, XA_STRING,
8, PropModeReplace, reinterpret_cast<const unsigned char*>(mTData),
length);
}
XSendEvent(display, notify.requestor, False, 0,
reinterpret_cast<XEvent*>(&notify));
// flush the output buffer to send the event now
XFlush(display);
}