/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2015 - ROLI Ltd. Permission is granted to use this software under the terms of either: a) the GPL v2 (or any later version) b) the Affero GPL v3 Details of these licenses can be found at: www.gnu.org/licenses JUCE 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. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.juce.com for more information. ============================================================================== */ #if JUCE_DEBUG && ! defined (JUCE_DEBUG_XERRORS) #define JUCE_DEBUG_XERRORS 1 #endif Display* display = nullptr; Window juce_messageWindowHandle = None; XContext windowHandleXContext; // This is referenced from Windowing.cpp typedef bool (*WindowMessageReceiveCallback) (XEvent&); WindowMessageReceiveCallback dispatchWindowMessage = nullptr; typedef void (*SelectionRequestCallback) (XSelectionRequestEvent&); SelectionRequestCallback handleSelectionRequest = nullptr; //============================================================================== ScopedXLock::ScopedXLock() { if (display != nullptr) XLockDisplay (display); } ScopedXLock::~ScopedXLock() { if (display != nullptr) XUnlockDisplay (display); } //============================================================================== class InternalMessageQueue { public: InternalMessageQueue() : bytesInSocket (0), totalEventCount (0) { int ret = ::socketpair (AF_LOCAL, SOCK_STREAM, 0, fd); (void) ret; jassert (ret == 0); } ~InternalMessageQueue() { close (fd[0]); close (fd[1]); clearSingletonInstance(); } //============================================================================== void postMessage (MessageManager::MessageBase* const msg) { const int maxBytesInSocketQueue = 128; ScopedLock sl (lock); queue.add (msg); if (bytesInSocket < maxBytesInSocketQueue) { ++bytesInSocket; ScopedUnlock ul (lock); const unsigned char x = 0xff; ssize_t bytesWritten = write (fd[0], &x, 1); (void) bytesWritten; } } bool isEmpty() const { ScopedLock sl (lock); return queue.size() == 0; } bool dispatchNextEvent() { // This alternates between giving priority to XEvents or internal messages, // to keep everything running smoothly.. if ((++totalEventCount & 1) != 0) return dispatchNextXEvent() || dispatchNextInternalMessage(); return dispatchNextInternalMessage() || dispatchNextXEvent(); } // Wait for an event (either XEvent, or an internal Message) bool sleepUntilEvent (const int timeoutMs) { if (! isEmpty()) return true; if (display != nullptr) { ScopedXLock xlock; if (XPending (display)) return true; } struct timeval tv; tv.tv_sec = 0; tv.tv_usec = timeoutMs * 1000; int fd0 = getWaitHandle(); int fdmax = fd0; fd_set readset; FD_ZERO (&readset); FD_SET (fd0, &readset); if (display != nullptr) { ScopedXLock xlock; int fd1 = XConnectionNumber (display); FD_SET (fd1, &readset); fdmax = jmax (fd0, fd1); } const int ret = select (fdmax + 1, &readset, 0, 0, &tv); return (ret > 0); // ret <= 0 if error or timeout } //============================================================================== juce_DeclareSingleton_SingleThreaded_Minimal (InternalMessageQueue) private: CriticalSection lock; ReferenceCountedArray queue; int fd[2]; int bytesInSocket; int totalEventCount; int getWaitHandle() const noexcept { return fd[1]; } static bool setNonBlocking (int handle) { int socketFlags = fcntl (handle, F_GETFL, 0); if (socketFlags == -1) return false; socketFlags |= O_NONBLOCK; return fcntl (handle, F_SETFL, socketFlags) == 0; } static bool dispatchNextXEvent() { if (display == nullptr) return false; XEvent evt; { ScopedXLock xlock; if (! XPending (display)) return false; XNextEvent (display, &evt); } if (evt.type == SelectionRequest && evt.xany.window == juce_messageWindowHandle && handleSelectionRequest != nullptr) handleSelectionRequest (evt.xselectionrequest); else if (evt.xany.window != juce_messageWindowHandle && dispatchWindowMessage != nullptr) dispatchWindowMessage (evt); return true; } MessageManager::MessageBase::Ptr popNextMessage() { const ScopedLock sl (lock); if (bytesInSocket > 0) { --bytesInSocket; const ScopedUnlock ul (lock); unsigned char x; ssize_t numBytes = read (fd[1], &x, 1); (void) numBytes; } return queue.removeAndReturn (0); } bool dispatchNextInternalMessage() { if (const MessageManager::MessageBase::Ptr msg = popNextMessage()) { JUCE_TRY { msg->messageCallback(); return true; } JUCE_CATCH_EXCEPTION } return false; } }; juce_ImplementSingleton_SingleThreaded (InternalMessageQueue) //============================================================================== namespace LinuxErrorHandling { static bool errorOccurred = false; static bool keyboardBreakOccurred = false; static XErrorHandler oldErrorHandler = (XErrorHandler) 0; static XIOErrorHandler oldIOErrorHandler = (XIOErrorHandler) 0; //============================================================================== // Usually happens when client-server connection is broken int ioErrorHandler (Display*) { DBG ("ERROR: connection to X server broken.. terminating."); if (JUCEApplicationBase::isStandaloneApp()) MessageManager::getInstance()->stopDispatchLoop(); errorOccurred = true; return 0; } int errorHandler (Display* display, XErrorEvent* event) { (void) display; (void) event; #if JUCE_DEBUG_XERRORS char errorStr[64] = { 0 }; char requestStr[64] = { 0 }; XGetErrorText (display, event->error_code, errorStr, 64); XGetErrorDatabaseText (display, "XRequest", String (event->request_code).toUTF8(), "Unknown", requestStr, 64); DBG ("ERROR: X returned " << errorStr << " for operation " << requestStr); #endif return 0; } void installXErrorHandlers() { oldIOErrorHandler = XSetIOErrorHandler (ioErrorHandler); oldErrorHandler = XSetErrorHandler (errorHandler); } void removeXErrorHandlers() { if (JUCEApplicationBase::isStandaloneApp()) { XSetIOErrorHandler (oldIOErrorHandler); oldIOErrorHandler = 0; XSetErrorHandler (oldErrorHandler); oldErrorHandler = 0; } } //============================================================================== void keyboardBreakSignalHandler (int sig) { if (sig == SIGINT) keyboardBreakOccurred = true; } void installKeyboardBreakHandler() { struct sigaction saction; sigset_t maskSet; sigemptyset (&maskSet); saction.sa_handler = keyboardBreakSignalHandler; saction.sa_mask = maskSet; saction.sa_flags = 0; sigaction (SIGINT, &saction, 0); } } //============================================================================== void MessageManager::doPlatformSpecificInitialisation() { if (JUCEApplicationBase::isStandaloneApp()) { // Initialise xlib for multiple thread support static bool initThreadCalled = false; if (! initThreadCalled) { if (! XInitThreads()) { // This is fatal! Print error and closedown Logger::outputDebugString ("Failed to initialise xlib thread support."); Process::terminate(); return; } initThreadCalled = true; } LinuxErrorHandling::installXErrorHandlers(); LinuxErrorHandling::installKeyboardBreakHandler(); } // Create the internal message queue InternalMessageQueue::getInstance(); // Try to connect to a display String displayName (getenv ("DISPLAY")); if (displayName.isEmpty()) displayName = ":0.0"; display = XOpenDisplay (displayName.toUTF8()); if (display != nullptr) // This is not fatal! we can run headless. { // Create a context to store user data associated with Windows we create windowHandleXContext = XUniqueContext(); // We're only interested in client messages for this window, which are always sent XSetWindowAttributes swa; swa.event_mask = NoEventMask; // Create our message window (this will never be mapped) const int screen = DefaultScreen (display); juce_messageWindowHandle = XCreateWindow (display, RootWindow (display, screen), 0, 0, 1, 1, 0, 0, InputOnly, DefaultVisual (display, screen), CWEventMask, &swa); } } void MessageManager::doPlatformSpecificShutdown() { InternalMessageQueue::deleteInstance(); if (display != nullptr && ! LinuxErrorHandling::errorOccurred) { XDestroyWindow (display, juce_messageWindowHandle); XCloseDisplay (display); juce_messageWindowHandle = 0; display = nullptr; LinuxErrorHandling::removeXErrorHandlers(); } } bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message) { if (LinuxErrorHandling::errorOccurred) return false; if (InternalMessageQueue* const queue = InternalMessageQueue::getInstanceWithoutCreating()) { queue->postMessage (message); return true; } return false; } void MessageManager::broadcastMessage (const String& /* value */) { /* TODO */ } // this function expects that it will NEVER be called simultaneously for two concurrent threads bool MessageManager::dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages) { while (! LinuxErrorHandling::errorOccurred) { if (LinuxErrorHandling::keyboardBreakOccurred) { LinuxErrorHandling::errorOccurred = true; if (JUCEApplicationBase::isStandaloneApp()) Process::terminate(); break; } InternalMessageQueue* const queue = InternalMessageQueue::getInstanceWithoutCreating(); jassert (queue != nullptr); if (queue->dispatchNextEvent()) return true; if (returnIfNoPendingMessages) break; queue->sleepUntilEvent (2000); } return false; }