//----------------------------------------------------------------------------- // Torque Game Engine // Copyright (C) GarageGames.com, Inc. //----------------------------------------------------------------------------- #include "console/console.h" #include "core/fileStream.h" #include "game/resource.h" #include "game/version.h" #include "math/mRandom.h" #include "platformX86UNIX/platformX86UNIX.h" #include "platformX86UNIX/x86UNIXStdConsole.h" #include "platform/event.h" #include "platform/gameInterface.h" #include "platform/platform.h" #include "platform/platformAL.h" #include "platform/platformInput.h" #include "platform/platformVideo.h" #include "platform/profiler.h" #include "platformX86UNIX/platformGL.h" #include "platformX86UNIX/x86UNIXOGLVideo.h" #include "platformX86UNIX/x86UNIXState.h" #ifndef DEDICATED #include "platformX86UNIX/x86UNIXMessageBox.h" #include "platformX86UNIX/x86UNIXInputManager.h" #endif #include #include #include #include // fork, execvp, chdir #include // nanosleep #ifndef DEDICATED #include #include #include #include #include #endif x86UNIXPlatformState *x86UNIXState; bool DisplayPtrManager::sgDisplayLocked = false; LockFunc_t DisplayPtrManager::sgLockFunc = NULL; LockFunc_t DisplayPtrManager::sgUnlockFunc = NULL; static U32 lastTimeTick; static MRandomLCG sgPlatRandom; #ifndef DEDICATED extern void InstallRedBookDevices(); extern void PollRedbookDevices(); extern bool InitOpenGL(); // This is called when some X client sends // a selection event (e.g. SelectionRequest) // to the window extern void NotifySelectionEvent(XEvent& event); #endif //------------------------------------------------------------------------------ static S32 ParseCommandLine(S32 argc, const char **argv, Vector& newCommandLine) { x86UNIXState->setExePathName(argv[0]); bool foundDedicated = false; for ( int i=0; i < argc; i++ ) { // look for platform specific args if (dStrcmp(argv[i], "-version") == 0) { dPrintf("%s (built on %s)\n", getVersionString(), getCompileTimeString()); dPrintf("gcc: %s\n", __VERSION__); return 1; } if (dStrcmp(argv[i], "-cdaudio") == 0) { x86UNIXState->setCDAudioEnabled(true); continue; } if (dStrcmp(argv[i], "-dedicated") == 0) { foundDedicated = true; // no continue because dedicated is also handled by script } if (dStrcmp(argv[i], "-dsleep") == 0) { x86UNIXState->setDSleep(true); continue; } if (dStrcmp(argv[i], "-nohomedir") == 0) { x86UNIXState->setUseRedirect(false); continue; } if (dStrcmp(argv[i], "-chdir") == 0) { if ( ++i >= argc ) { dPrintf("Follow -chdir option with the desired working directory.\n"); return 1; } if (chdir(argv[i]) == -1) { dPrintf("Unable to chdir to %s: %s\n", argv[i], strerror(errno)); return 1; } continue; } // copy the arg into newCommandLine int argLen = dStrlen(argv[i]) + 1; char* argBuf = new char[argLen]; // this memory is deleted in main() dStrncpy(argBuf, argv[i], argLen); newCommandLine.push_back(argBuf); } x86UNIXState->setDedicated(foundDedicated); #if defined(DEDICATED) && !defined(TORQUE_ENGINE) if (!foundDedicated) { dPrintf("This is a dedicated server build. You must supply the -dedicated command line parameter.\n"); return 1; } #endif return 0; } static void DetectWindowingSystem() { #ifndef DEDICATED Display* dpy = XOpenDisplay(NULL); if (dpy != NULL) { x86UNIXState->setXWindowsRunning(true); XCloseDisplay(dpy); } #endif } //------------------------------------------------------------------------------ static void InitWindow(const Point2I &initialSize, const char *name) { x86UNIXState->setWindowSize(initialSize); x86UNIXState->setWindowName(name); } #ifndef DEDICATED //------------------------------------------------------------------------------ static bool InitSDL() { if (SDL_Init(SDL_INIT_VIDEO) != 0) return false; atexit(SDL_Quit); SDL_SysWMinfo sysinfo; SDL_VERSION(&sysinfo.version); if (SDL_GetWMInfo(&sysinfo) == 0) return false; x86UNIXState->setDisplayPointer(sysinfo.info.x11.display); DisplayPtrManager::setDisplayLockFunction(sysinfo.info.x11.lock_func); DisplayPtrManager::setDisplayUnlockFunction(sysinfo.info.x11.unlock_func); DisplayPtrManager xdisplay; Display* display = xdisplay.getDisplayPointer(); x86UNIXState->setScreenNumber( DefaultScreen( display ) ); x86UNIXState->setScreenPointer( DefaultScreenOfDisplay( display ) ); x86UNIXState->setDesktopSize( (S32) DisplayWidth( display, x86UNIXState->getScreenNumber()), (S32) DisplayHeight( display, x86UNIXState->getScreenNumber()) ); x86UNIXState->setDesktopBpp( (S32) DefaultDepth( display, x86UNIXState->getScreenNumber())); // indicate that we want sys WM messages SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); return true; } //------------------------------------------------------------------------------ static void ProcessSYSWMEvent(const SDL_Event& event) { XEvent& xevent = event.syswm.msg->event.xevent; //Con::printf("xevent : %d", xevent.type); switch (xevent.type) { case SelectionRequest: // somebody wants our clipboard NotifySelectionEvent(xevent); break; } } //------------------------------------------------------------------------------ static void SetAppState() { U8 state = SDL_GetAppState(); // if we're not active but we have appactive and inputfocus, set window // active and reactivate input if ((!x86UNIXState->windowActive() || !Input::isActive()) && state & SDL_APPACTIVE && state & SDL_APPINPUTFOCUS) { x86UNIXState->setWindowActive(true); Input::reactivate(); } // if we are active, but we don't have appactive or input focus, // deactivate input (if window not locked) and clear windowActive else if (x86UNIXState->windowActive() && !(state & SDL_APPACTIVE && state & SDL_APPINPUTFOCUS)) { if (x86UNIXState->windowLocked()) Input::deactivate(); x86UNIXState->setWindowActive(false); } } //------------------------------------------------------------------------------ static S32 NumEventsPending() { static const int MaxEvents = 255; static SDL_Event events[MaxEvents]; SDL_PumpEvents(); return SDL_PeepEvents(events, MaxEvents, SDL_PEEKEVENT, SDL_ALLEVENTS); } //------------------------------------------------------------------------------ static void PrintSDLEventQueue() { static const int MaxEvents = 255; static SDL_Event events[MaxEvents]; SDL_PumpEvents(); S32 numEvents = SDL_PeepEvents( events, MaxEvents, SDL_PEEKEVENT, SDL_ALLEVENTS); if (numEvents <= 0) { dPrintf("SDL Event Queue is empty\n"); return; } dPrintf("SDL Event Queue:\n"); for (int i = 0; i < numEvents; ++i) { const char *eventType; switch (events[i].type) { case SDL_NOEVENT: eventType = "SDL_NOEVENT"; break; case SDL_ACTIVEEVENT: eventType = "SDL_ACTIVEEVENT"; break; case SDL_KEYDOWN: eventType = "SDL_KEYDOWN"; break; case SDL_KEYUP: eventType = "SDL_KEYUP"; break; case SDL_MOUSEMOTION: eventType = "SDL_MOUSEMOTION"; break; case SDL_MOUSEBUTTONDOWN: eventType = "SDL_MOUSEBUTTONDOWN"; break; case SDL_MOUSEBUTTONUP: eventType = "SDL_MOUSEBUTTONUP"; break; case SDL_JOYAXISMOTION: eventType = "SDL_JOYAXISMOTION"; break; case SDL_JOYBALLMOTION: eventType = "SDL_JOYBALLMOTION"; break; case SDL_JOYHATMOTION: eventType = "SDL_JOYHATMOTION"; break; case SDL_JOYBUTTONDOWN: eventType = "SDL_JOYBUTTONDOWN"; break; case SDL_JOYBUTTONUP: eventType = "SDL_JOYBUTTONUP"; break; case SDL_QUIT: eventType = "SDL_QUIT"; break; case SDL_SYSWMEVENT: eventType = "SDL_SYSWMEVENT"; break; case SDL_VIDEORESIZE: eventType = "SDL_VIDEORESIZE"; break; case SDL_VIDEOEXPOSE: eventType = "SDL_VIDEOEXPOSE"; break; /* Events SDL_USEREVENT through SDL_MAXEVENTS-1 are for your use */ case SDL_USEREVENT: eventType = "SDL_USEREVENT"; break; default: eventType = "UNKNOWN!"; break; } dPrintf("Event %d: %s\n", i, eventType); } } //------------------------------------------------------------------------------ static bool ProcessMessages() { static const int MaxEvents = 255; static const U32 Mask = SDL_QUITMASK | SDL_VIDEORESIZEMASK | SDL_VIDEOEXPOSEMASK | SDL_ACTIVEEVENTMASK | SDL_SYSWMEVENTMASK | SDL_EVENTMASK(SDL_USEREVENT); static SDL_Event events[MaxEvents]; SDL_PumpEvents(); S32 numEvents = SDL_PeepEvents(events, MaxEvents, SDL_GETEVENT, Mask); if (numEvents == 0) return true; for (int i = 0; i < numEvents; ++i) { SDL_Event& event = events[i]; switch (event.type) { case SDL_QUIT: return false; break; case SDL_VIDEORESIZE: case SDL_VIDEOEXPOSE: Game->refreshWindow(); break; case SDL_USEREVENT: if (event.user.code == TORQUE_SETVIDEOMODE) { SetAppState(); // SDL will send a motion event to restore the mouse position // on the new window. Ignore that if the window is locked. if (x86UNIXState->windowLocked()) { SDL_Event tempEvent; SDL_PeepEvents(&tempEvent, 1, SDL_GETEVENT, SDL_MOUSEMOTIONMASK); } } break; case SDL_ACTIVEEVENT: SetAppState(); break; case SDL_SYSWMEVENT: ProcessSYSWMEvent(event); break; } } return true; } //------------------------------------------------------------------------------ // send a destroy window event to the window. assumes // window is created. void SendQuitEvent() { SDL_Event quitevent; quitevent.type = SDL_QUIT; SDL_PushEvent(&quitevent); } #endif // DEDICATED //------------------------------------------------------------------------------ static inline void Sleep(int secs, int nanoSecs) { timespec sleeptime; sleeptime.tv_sec = secs; sleeptime.tv_nsec = nanoSecs; nanosleep(&sleeptime, NULL); } #ifndef DEDICATED struct AlertWinState { bool fullScreen; bool cursorHidden; bool inputGrabbed; }; //------------------------------------------------------------------------------ void DisplayErrorAlert(const char* errMsg, bool showSDLError) { char fullErrMsg[2048]; dStrncpy(fullErrMsg, errMsg, sizeof(fullErrMsg)); if (showSDLError) { char* sdlerror = SDL_GetError(); if (sdlerror != NULL && dStrlen(sdlerror) > 0) { dStrcat(fullErrMsg, " (Error: "); dStrcat(fullErrMsg, sdlerror); dStrcat(fullErrMsg, ")"); } } Platform::AlertOK("Error", fullErrMsg); } //------------------------------------------------------------------------------ static inline void AlertDisableVideo(AlertWinState& state) { state.fullScreen = Video::isFullScreen(); state.cursorHidden = (SDL_ShowCursor(SDL_QUERY) == SDL_DISABLE); state.inputGrabbed = (SDL_WM_GrabInput(SDL_GRAB_QUERY) == SDL_GRAB_ON); if (state.fullScreen) SDL_WM_ToggleFullScreen(SDL_GetVideoSurface()); if (state.cursorHidden) SDL_ShowCursor(SDL_ENABLE); if (state.inputGrabbed) SDL_WM_GrabInput(SDL_GRAB_OFF); } //------------------------------------------------------------------------------ static inline void AlertEnableVideo(AlertWinState& state) { if (state.fullScreen) SDL_WM_ToggleFullScreen(SDL_GetVideoSurface()); if (state.cursorHidden) SDL_ShowCursor(SDL_DISABLE); if (state.inputGrabbed) SDL_WM_GrabInput(SDL_GRAB_ON); } #endif // DEDICATED //------------------------------------------------------------------------------ void Platform::AlertOK(const char *windowTitle, const char *message) { #ifndef DEDICATED if (x86UNIXState->isXWindowsRunning()) { AlertWinState state; AlertDisableVideo(state); DisplayPtrManager xdisplay; XMessageBox mBox(xdisplay.getDisplayPointer()); mBox.alertOK(windowTitle, message); AlertEnableVideo(state); } else #endif { if (Con::isActive() && StdConsole::isEnabled()) Con::printf("Alert: %s %s", windowTitle, message); else dPrintf("Alert: %s %s\n", windowTitle, message); } } //------------------------------------------------------------------------------ bool Platform::AlertOKCancel(const char *windowTitle, const char *message) { #ifndef DEDICATED if (x86UNIXState->isXWindowsRunning()) { AlertWinState state; AlertDisableVideo(state); DisplayPtrManager xdisplay; XMessageBox mBox(xdisplay.getDisplayPointer()); bool val = mBox.alertOKCancel(windowTitle, message) == XMessageBox::OK; AlertEnableVideo(state); return val; } else #endif { if (Con::isActive() && StdConsole::isEnabled()) Con::printf("Alert: %s %s", windowTitle, message); else dPrintf("Alert: %s %s\n", windowTitle, message); return false; } } //------------------------------------------------------------------------------ bool Platform::AlertRetry(const char *windowTitle, const char *message) { #ifndef DEDICATED if (x86UNIXState->isXWindowsRunning()) { AlertWinState state; AlertDisableVideo(state); DisplayPtrManager xdisplay; XMessageBox mBox(xdisplay.getDisplayPointer()); bool val = mBox.alertRetryCancel(windowTitle, message) == XMessageBox::Retry; AlertEnableVideo(state); return val; } else #endif { if (Con::isActive() && StdConsole::isEnabled()) Con::printf("Alert: %s %s", windowTitle, message); else dPrintf("Alert: %s %s\n", windowTitle, message); return false; } } //------------------------------------------------------------------------------ bool Platform::excludeOtherInstances(const char *mutexName) { return AcquireProcessMutex(mutexName); } //------------------------------------------------------------------------------ void Platform::enableKeyboardTranslation(void) { #ifndef DEDICATED // JMQ: not sure if this is needed for i18n keyboards //SDL_EnableUNICODE( 1 ); // SDL_EnableKeyRepeat( // SDL_DEFAULT_REPEAT_DELAY, // SDL_DEFAULT_REPEAT_INTERVAL); #endif } //------------------------------------------------------------------------------ void Platform::disableKeyboardTranslation(void) { #ifndef DEDICATED //SDL_EnableUNICODE( 0 ); // SDL_EnableKeyRepeat(0, 0); #endif } //------------------------------------------------------------------------------ void Platform::setWindowLocked(bool locked) { #ifndef DEDICATED x86UNIXState->setWindowLocked(locked); UInputManager* uInputManager = dynamic_cast( Input::getManager() ); if ( uInputManager && uInputManager->isEnabled() && Input::isActive() ) uInputManager->setWindowLocked(locked); #endif } //------------------------------------------------------------------------------ void Platform::minimizeWindow() { #ifndef DEDICATED if (x86UNIXState->windowCreated()) SDL_WM_IconifyWindow(); #endif } //------------------------------------------------------------------------------ void Platform::process() { PROFILE_START(XUX_PlatformProcess); stdConsole->process(); if (x86UNIXState->windowCreated()) { #ifndef DEDICATED // process window events PROFILE_START(XUX_ProcessMessages); bool quit = !ProcessMessages(); PROFILE_END(); if(quit) { // generate a quit event Event quitEvent; quitEvent.type = QuitEventType; Game->postEvent(quitEvent); } // process input events PROFILE_START(XUX_InputProcess); Input::process(); PROFILE_END(); // poll redbook state PROFILE_START(XUX_PollRedbookDevices); PollRedbookDevices(); PROFILE_END(); // if we're not the foreground window, sleep for 1 ms if (!x86UNIXState->windowActive()) Sleep(0, getBackgroundSleepTime() * 1000000); #endif } else { // no window // if we're not in journal mode, sleep for 1 ms // JMQ: since linux's minimum sleep latency seems to be 20ms, this can // increase player pings by 10-20ms in the dedicated server. So // you have to use -dsleep to enable it. the server sleeps anyway when // there are no players connected. // JMQ: recent kernels (such as RH 8.0 2.4.18) reduce the latency // to 2-4 ms on average. if (!Game->isJournalReading() && (x86UNIXState->getDSleep() || Con::getIntVariable("Server::PlayerCount") - Con::getIntVariable("Server::BotCount") <= 0)) { PROFILE_START(XUX_Sleep); Sleep(0, getBackgroundSleepTime() * 1000000); PROFILE_END(); } } #ifndef DEDICATED #if 0 // JMQ: disabled this because it may fire mistakenly in some configurations. // sdl's default event handling scheme should be enough. // crude check to make sure that we're not loading up events. the sdl // event queue should never have more than (say) 25 events in it at this // point const int MaxEvents = 25; if (NumEventsPending() > MaxEvents) { PrintSDLEventQueue(); AssertFatal(false, "The SDL event queue has too many events!"); } #endif #endif PROFILE_END(); } // extern U32 calculateCRC(void * buffer, S32 len, U32 crcVal ); // #if defined(DEBUG) || defined(INTERNAL_RELEASE) // static U32 stubCRC = 0; // #else // static U32 stubCRC = 0xEA63F56C; // #endif //------------------------------------------------------------------------------ const Point2I &Platform::getWindowSize() { return x86UNIXState->getWindowSize(); } //------------------------------------------------------------------------------ void Platform::setWindowSize( U32 newWidth, U32 newHeight ) { x86UNIXState->setWindowSize( (S32) newWidth, (S32) newHeight ); } //------------------------------------------------------------------------------ void Platform::shutdown() { Cleanup(); } //------------------------------------------------------------------------------ void Platform::init() { // Set the platform variable for the scripts Con::setVariable( "$platform", "x86UNIX" ); #if defined(__linux__) Con::setVariable( "$platformUnixType", "Linux" ); #elif defined(__OpenBSD__) Con::setVariable( "$platformUnixType", "OpenBSD" ); #else Con::setVariable( "$platformUnixType", "Unknown" ); #endif StdConsole::create(); #ifndef DEDICATED // if we're not dedicated do more initialization if (!x86UNIXState->isDedicated()) { // init SDL if (!InitSDL()) { DisplayErrorAlert("Unable to initialize SDL."); ImmediateShutdown(1); } // initialize input Input::init(); // initialize redbook devices if (x86UNIXState->getCDAudioEnabled()) InstallRedBookDevices(); Con::printf( "Video Init:" ); // load gl library if (!GLLoader::OpenGLInit()) { DisplayErrorAlert("Unable to initialize OpenGL."); ImmediateShutdown(1); } // initialize video Video::init(); if ( Video::installDevice( OpenGLDevice::create() ) ) Con::printf( " OpenGL display device detected." ); else Con::printf( " OpenGL display device not detected." ); Con::printf(" "); } #endif // if we are dedicated, do sleep timing and display results if (x86UNIXState->isDedicated()) { const S32 MaxSleepIter = 10; U32 totalSleepTime = 0; U32 start; for (S32 i = 0; i < MaxSleepIter; ++i) { start = Platform::getRealMilliseconds(); Sleep(0, 1000000); totalSleepTime += Platform::getRealMilliseconds() - start; } U32 average = static_cast(totalSleepTime / MaxSleepIter); Con::printf("Sleep latency: %ums", average); // dPrintf as well, since console output won't be visible yet dPrintf("Sleep latency: %ums\n", average); if (!x86UNIXState->getDSleep() && average < 10) { const char* msg = "Sleep latency ok, enabling dsleep for lower cpu " \ "utilization"; Con::printf("%s", msg); dPrintf("%s\n", msg); x86UNIXState->setDSleep(true); } } } //------------------------------------------------------------------------------ void Platform::initWindow(const Point2I &initialSize, const char *name) { #ifndef DEDICATED // initialize window InitWindow(initialSize, name); if (!InitOpenGL()) ImmediateShutdown(1); #endif } //------------------------------------------------------------------------------- F32 Platform::getRandom() { return sgPlatRandom.randF(); } //------------------------------------------------------------------------------ // Web browser function: //------------------------------------------------------------------------------ bool Platform::openWebBrowser( const char* webAddress ) { if (!webAddress || dStrlen(webAddress)==0) return false; // look for a browser preference variable // JMQTODO: be nice to implement some UI to customize this const char* webBrowser = Con::getVariable("Pref::Unix::WebBrowser"); if (dStrlen(webBrowser) == 0) webBrowser = NULL; pid_t pid = fork(); if (pid == -1) { Con::printf("WARNING: Platform::openWebBrowser failed to fork"); return false; } else if (pid != 0) { // parent if (Video::isFullScreen()) Video::toggleFullScreen(); return true; } else if (pid == 0) { // child // try to exec konqueror, then netscape char* argv[3]; argv[0] = ""; argv[1] = const_cast(webAddress); argv[2] = 0; int ok = -1; // if execvp returns, it means it couldn't execute the program if (webBrowser != NULL) ok = execvp(webBrowser, argv); ok = execvp("konqueror", argv); ok = execvp("mozilla", argv); ok = execvp("netscape", argv); // use dPrintf instead of Con here since we're now in another process, dPrintf("WARNING: Platform::openWebBrowser: couldn't launch a web browser\n"); _exit(-1); return false; } else { Con::printf("WARNING: Platform::openWebBrowser: forking problem"); return false; } } //------------------------------------------------------------------------------ // Login password routines: //------------------------------------------------------------------------------ const char* Platform::getLoginPassword() { Con::printf("WARNING: Platform::getLoginPassword() is unimplemented"); return ""; } //------------------------------------------------------------------------------ bool Platform::setLoginPassword( const char* password ) { Con::printf("WARNING: Platform::setLoginPassword is unimplemented"); return false; } //------------------------------------------------------------------------------- void TimeManager::process() { U32 curTime = Platform::getRealMilliseconds(); TimeEvent event; event.elapsedTime = curTime - lastTimeTick; if(event.elapsedTime > sgTimeManagerProcessInterval) { lastTimeTick = curTime; Game->postEvent(event); } } //------------------------------------------------------------------------------ ConsoleFunction( getDesktopResolution, const char*, 1, 1, "getDesktopResolution()" ) { if (!x86UNIXState->windowCreated()) return "0 0 0"; char buffer[256]; char* returnString = Con::getReturnBuffer( dStrlen( buffer ) + 1 ); dSprintf( buffer, sizeof( buffer ), "%d %d %d", x86UNIXState->getDesktopSize().x, x86UNIXState->getDesktopSize().y, x86UNIXState->getDesktopBpp() ); dStrcpy( returnString, buffer ); return( returnString ); } //------------------------------------------------------------------------------ // Silly Korean registry key checker: //------------------------------------------------------------------------------ ConsoleFunction( isKoreanBuild, bool, 1, 1, "isKoreanBuild()" ) { Con::printf("WARNING: isKoreanBuild() is unimplemented"); return false; } //------------------------------------------------------------------------------ int main(S32 argc, const char **argv) { // init platform state x86UNIXState = new x86UNIXPlatformState; // parse the command line for unix-specific params Vector newCommandLine; S32 returnVal = ParseCommandLine(argc, argv, newCommandLine); if (returnVal != 0) return returnVal; // init lastTimeTick for TimeManager::process() lastTimeTick = Platform::getRealMilliseconds(); // init process control stuff ProcessControlInit(); // check to see if X is running DetectWindowingSystem(); // run the game returnVal = Game->main(newCommandLine.size(), const_cast(newCommandLine.address())); // dispose of command line for(U32 i = 0; i < newCommandLine.size(); i++) delete [] newCommandLine[i]; // dispose of state delete x86UNIXState; return returnVal; } void Platform::setWindowTitle( const char* title ) { #ifndef DEDICATED x86UNIXState->setWindowName(title); SDL_WM_SetCaption(x86UNIXState->getWindowName(), NULL); #endif } Resolution Video::getDesktopResolution() { Resolution Result; Result.h = x86UNIXState->getDesktopSize().x; Result.w = x86UNIXState->getDesktopSize().y; Result.bpp = x86UNIXState->getDesktopBpp(); return Result; }