UI-less Linux applications


#1

Hi Jules,

The Juce messaging stuff is used at many places in JUCE code. Even in modules that are not related to graphical user interface (all the audio stuff for example). The problem on linux is that the messaging uses the X server to transmit internal juce messages, all none of this stuff works when you run it without a X server. I believe this is quite easily fixable by rewriting the juce_linux_Messaging.cpp file, in order to replace the XSendEvent stuff for internal juce messages by a custom message queue. The “juce_dispatchNextMessageOnSystemQueue” would then poll events from that queue, and from the X queue (when “display” is not null). That way we would be able to use more Juce classes in command-line apps.

This is something that I may try to do in the future, but I wanted to share my thoughts about this first.


#2

i would like to see how this can be done properly cause it will be more than useful ! and still you can use the juce power in the command line…


#3

I do agree !

I wrote it yesterday, and so far it seems to work perfectly (no regressions, only improvements). Here is the drop-in replacement for juce_linux_Messaging.cpp

[code]/*

This file is part of the JUCE library - "Jules’ Utility Class Extensions"
Copyright 2004-7 by Raw Material Software ltd.


JUCE can be redistributed and/or modified 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.

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.

You should have received a copy of the GNU General Public License
along with JUCE; if not, visit www.gnu.org/licenses or write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA


If you’d like to release a closed-source product which uses JUCE, commercial
licenses are also available: visit www.rawmaterialsoftware.com/juce for
more information.

==============================================================================
*/

#include “…/…/…/juce_Config.h”

#include “linuxincludes.h”
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xresource.h>
#include <X11/Xutil.h>
#include “…/…/…/src/juce_core/basics/juce_StandardHeader.h”

BEGIN_JUCE_NAMESPACE

#include “…/…/…/src/juce_appframework/events/juce_MessageManager.h”
#include “…/…/…/src/juce_core/threads/juce_WaitableEvent.h”
#include “…/…/…/src/juce_core/threads/juce_Process.h”
#include “…/…/…/src/juce_core/threads/juce_ScopedLock.h”
#include “…/…/…/src/juce_core/containers/juce_OwnedArray.h”

#ifdef JUCE_DEBUG
#define JUCE_DEBUG_XERRORS 1
#endif

Display* display = 0; // This is also referenced from WindowDriver.cpp
Window juce_messageWindowHandle = None;

#define SpecialAtom “JUCESpecialAtom”
#define BroadcastAtom “JUCEBroadcastAtom”
#define SpecialCallbackAtom “JUCESpecialCallbackAtom”

static Atom specialId;
static Atom broadcastId;
static Atom specialCallbackId;

// This is referenced from WindowDriver.cpp
XContext improbableNumber;

// Defined in WindowDriver.cpp
extern void juce_windowMessageReceive (XEvent* event);

struct MessageThreadFuncCall
{
MessageCallbackFunction* func;
void* parameter;
void* result;
CriticalSection lock;
WaitableEvent event;
};

struct InternalMessageQueue {
CriticalSection mutex;
OwnedArray queue;
int fd[2];
public:
InternalMessageQueue() {
int ret = ::socketpair(AF_LOCAL, SOCK_STREAM, 0, fd);
jassert(ret == 0);
}
~InternalMessageQueue() {
close(fd[0]);
close(fd[1]);
}
void postMessage(Message *msg) {
ScopedLock lock(mutex); queue.add(msg);
unsigned char x = 0xff;
write(fd[0], &x, 1);
}
bool isEmpty() { ScopedLock lock(mutex); return queue.size() == 0; }
Message *popMessage() {
ScopedLock lock(mutex);
Message m = 0;
if (queue.size()) {
unsigned char x;
read(fd[1], &x, 1);
m = queue.getUnchecked(0);
queue.remove(0, false /
deleteObject */);
}
return m;
}
int getWaitHandle() { return fd[1]; }
};

static InternalMessageQueue *internal_message_queue;

static bool errorCondition = false;
static XErrorHandler oldErrorHandler = (XErrorHandler) 0;
static XIOErrorHandler oldIOErrorHandler = (XIOErrorHandler) 0;

// (defined in another file to avoid problems including certain headers in this one)
extern bool juce_isRunningAsApplication();
extern void juce_x11_handleSelectionRequest(XSelectionRequestEvent &evt);

// Usually happens when client-server connection is broken
static int ioErrorHandler (Display* display)
{
DBG (T(“ERROR: connection to X server broken… terminating.”));

errorCondition = true;

if (juce_isRunningAsApplication())
    Process::terminate();

return 0;

}

// A protocol error has occurred
static int errorHandler (Display* display, XErrorEvent* event)
{
#ifdef JUCE_DEBUG_XERRORS
char errorStr[64] = { 0 };
char requestStr[64] = { 0 };

XGetErrorText (display, event->error_code, errorStr, 64);

XGetErrorDatabaseText (display,
                       "XRequest",
                       (const char*) String (event->request_code),
                       "Unknown",
                       requestStr,
                       64);

DBG (T("ERROR: X returned ") + String (errorStr) + T(" for operation ") + String (requestStr));

#endif

return 0;

}

static bool breakIn = false;

// Breakin from keyboard
static void sig_handler (int sig)
{
if (sig == SIGINT)
{
breakIn = true;
return;
}

static bool reentrant = false;

if (reentrant == false)
{
    reentrant = true;

    // Illegal instruction
    fflush (stdout);
    Logger::outputDebugString ("ERROR: Program executed illegal instruction.. terminating");

    errorCondition = true;

    if (juce_isRunningAsApplication())
        Process::terminate();
}
else
{
    if (juce_isRunningAsApplication())
        exit(0);
}

}

//==============================================================================
void MessageManager::doPlatformSpecificInitialisation()
{
// 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.");

        if (juce_isRunningAsApplication())
            Process::terminate();

        return;
    }

    initThreadCalled = true;
}

// This is called if the client/server connection is broken
oldIOErrorHandler = XSetIOErrorHandler (ioErrorHandler);

// This is called if a protocol error occurs
oldErrorHandler = XSetErrorHandler (errorHandler);

// Install signal handler for break-in
struct sigaction saction;
sigset_t maskSet;
sigemptyset (&maskSet);
saction.sa_handler = sig_handler;
saction.sa_mask = maskSet;
saction.sa_flags = 0;
sigaction (SIGINT, &saction, NULL);

#ifndef _DEBUG
// Setup signal handlers for various fatal errors
sigaction (SIGILL, &saction, NULL);
sigaction (SIGBUS, &saction, NULL);
sigaction (SIGFPE, &saction, NULL);
sigaction (SIGSEGV, &saction, NULL);
sigaction (SIGSYS, &saction, NULL);
#endif

internal_message_queue = new InternalMessageQueue();

String displayName (getenv ("DISPLAY"));
if (displayName.isEmpty())
    displayName = T(":0.0");

display = XOpenDisplay (displayName);

if (display == 0)
{
    // This is no more fatal! check if display was opened with Desktop::isAvailable()
    /*Logger::outputDebugString ("Failed to open the X display.");

    if (juce_isRunningAsApplication())
        Process::terminate();*/

    return;
}

// Get defaults for various properties
int screen = DefaultScreen (display);
Window root = RootWindow (display, screen);
Visual* visual = DefaultVisual (display, screen);

// Create atoms for our ClientMessages (these cannot be deleted)
specialId = XInternAtom (display, SpecialAtom, false);
broadcastId = XInternAtom (display, BroadcastAtom, false);
specialCallbackId = XInternAtom (display, SpecialCallbackAtom, false);

// Create a context to store user data associated with Windows we
// create in WindowDriver
improbableNumber = 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)
juce_messageWindowHandle = XCreateWindow (display, root,
                                          0, 0, 1, 1, 0, 0, InputOnly,
                                          visual, CWEventMask, &swa);

}

void MessageManager::doPlatformSpecificShutdown()
{
if (internal_message_queue) {
deleteAndZero(internal_message_queue);
}
if (errorCondition == false && display)
{
XDestroyWindow (display, juce_messageWindowHandle);
XCloseDisplay (display);

    // reset pointers
    juce_messageWindowHandle = 0;
    display = 0;

    // Restore original error handlers
    XSetIOErrorHandler (oldIOErrorHandler);
    oldIOErrorHandler = 0;
    XSetErrorHandler (oldErrorHandler);
    oldErrorHandler = 0;
}

}

bool juce_postMessageToSystemQueue (void* message)
{
if (errorCondition)
return false;

internal_message_queue->postMessage((Message*)message);
return true;

}

bool juce_postMessageToX11Queue(void *message)
{
XClientMessageEvent clientMsg;
clientMsg.display = display;
clientMsg.window = juce_messageWindowHandle;
clientMsg.type = ClientMessage;
clientMsg.format = 32;
clientMsg.message_type = specialId;
#if JUCE_64BIT
clientMsg.data.l[0] = (long) (0x00000000ffffffff & (((uint64) message) >> 32));
clientMsg.data.l[1] = (long) (0x00000000ffffffff & (long) message);
#else
clientMsg.data.l[0] = (long) message;
#endif

XSendEvent (display, juce_messageWindowHandle, false,
            NoEventMask, (XEvent*) &clientMsg);

XFlush (display); // This is necessary to ensure the event is delivered
return true;

}

void MessageManager::broadcastMessage (const String& value) throw()
{
/* TODO */
}

struct InternalCallFunctionMessage : public Message {
MessageThreadFuncCall *ctx;
InternalCallFunctionMessage(MessageThreadFuncCall *ctx_) : ctx(ctx_) {
intParameter1 = 0x73774623;
}
};

void* MessageManager::callFunctionOnMessageThread (MessageCallbackFunction* func,
void* parameter)
{
void* retVal = 0;

if (! errorCondition)
{
    if (! isThisTheMessageThread())
    {
        static MessageThreadFuncCall messageFuncCallContext;

        const ScopedLock sl (messageFuncCallContext.lock);

        messageFuncCallContext.func = func;
        messageFuncCallContext.parameter = parameter;
        internal_message_queue->postMessage(new InternalCallFunctionMessage(&messageFuncCallContext));

        // Wait for it to complete before continuing
        messageFuncCallContext.event.wait();

        retVal = messageFuncCallContext.result;
    }
    else
    {
        // Just call the function directly
        retVal = func (parameter);
    }
}

return retVal;

}

/* wait for an event (either XEvent, or an internal Message) */
static bool juce_sleepUntilEvent(int timeout_ms)
{
if (display && XPending(display)) return true;
if (!internal_message_queue->isEmpty()) return true;

struct timeval tv;
tv.tv_sec=0;
tv.tv_usec=timeout_ms * 1000;
int fd0   = internal_message_queue->getWaitHandle();
int fdmax = fd0;

fd_set readset;
FD_ZERO(&readset);
FD_SET(fd0, &readset);
if (display) {
  int fd1 = XConnectionNumber(display);
  FD_SET(fd1, &readset);
  fdmax = jmax(fd0, fd1);
}
int ret = select( fdmax+1, &readset, 0, 0, &tv );

return (ret > 0); // ret <= 0 if error or timeout

}

/* handle next XEvent (if any) /
static bool juce_dispatchNextXEvent()
{
if (!display || !XPending(display)) return false;
XEvent evt;
XNextEvent(display, &evt);
/
requires my version of juce_linux_Clipboard.cpp /
//if (evt.type == SelectionRequest && evt.xany.window == juce_messageWindowHandle) {
// juce_x11_handleSelectionRequest(evt.xselectionrequest);
//} else if (evt.type == SelectionClear && evt.xany.window == juce_messageWindowHandle) {
// /
another window just grabbed the selection – we just don’t care */
//} else
if (evt.xany.window != juce_messageWindowHandle) {
juce_windowMessageReceive (&evt);
}
return true;
}

/* handle next internal Message (if any) /
static bool juce_dispatchNextInternalMessage()
{
if (internal_message_queue->isEmpty()) return false;
Message m = internal_message_queue->popMessage();
InternalCallFunctionMessage cfm = 0;
/
handle function call /
if (m->intParameter1 == 0x73774623 && (cfm = dynamic_cast<InternalCallFunctionMessage
>(m))) {
MessageThreadFuncCall
const call = cfm->ctx;
MessageCallbackFunction
func = call->func;
call->result = (func) (call->parameter);
call->event.signal();
deleteAndZero(m);
} else {
/
handle “normal” messages */
MessageManager::getInstance()->deliverMessage(m);
}
return true;
}

/* this function expects that it will NEVER be called simultaneously for two concurrent threads */
bool juce_dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages)
{
while (1) {
if (errorCondition)
return false;

  if (breakIn) {
    errorCondition = true;
    
    if (juce_isRunningAsApplication())
      Process::terminate();
    
    return false;
  }
  
  static int event_cnt=0;
  ++event_cnt;
  /* the purpose here is to give either priority to XEvents or
     to internal messages This is necessary to keep a "good"
     behaviour when the cpu is overloaded
  */
  if ((event_cnt & 1)) {
    if (juce_dispatchNextXEvent() || juce_dispatchNextInternalMessage()) return true;
  } else {
    if (juce_dispatchNextInternalMessage() || juce_dispatchNextXEvent()) return true;
  }
  
  if (returnIfNoPendingMessages) // early exit
    return false;
  
  juce_sleepUntilEvent(4000 /* milliseconds */); // the timeout is to be on the safe side, but it does not seem to be useful
}
return true;

}

END_JUCE_NAMESPACE
[/code]

(I don’t claim any copyright on anything in this file, so anyone is free to do whatever they want with my changes)

As you can see , it is really simple indeed. Just an OwnedArray for the queue of internal juce messages, a pair of sockets for notifying the event loop. The juce_dispatchNextMessageOnSystemQueue now waits for 2 types of events instead of only XEvents. The only significant change, is that the order of events is not the same as it used to be since there are two events queues instead of one, and no queue should starve the other when they are both flooded with events (see the comment about the “event_cnt” variable). I have built the juce demo, the juce plugin host, and my own application and did not notice any regression.

A side effect is that is fixes the issue that I mentionned here:
http://www.rawmaterialsoftware.com/juceforum/viewtopic.php?t=4000 (I have spent days on this one)

(As a side note, I now believe that this issue was due to the non-threadsafe use of X11 functions – as far as I understand, when more that one thread may simultaneously use the same ‘display*’ object, each X function call should be protected by XLockDisplay()/XUnlockDisplay() . By default these function do nothing, except when XInitThreads has been called before XOpenDisplay)

Other small changes that I had to do to allow my app to run “headless”, is to add a function Desktop::isAvailable() function which always return juce_isRunningAsApplication() on windows and mac, and also tests if display != 0 on linux:

bool Desktop::isAvailable() throw() { return juce_isRunningAsApplication() && display != 0; }

And to allow the app to startup when no DISPLAY is available, I had to add “if (!display) return;” at the beginning of juce_updateMultiMonitorInfo .

(this is not the part i am most proud of , to be honest)

Jules: when you have time, (and after a few weeks in order to let me test this a bit more), I hope that you will review this change to the juce_linux_Messaging.cpp file. I think that fixing the thread-safety problem of the original juce_postMessageToSystemQueue is enough to justify it.


#4

Cool - thanks, keep me posted if you make any changes!


#5

It looks like there is an issue with repaints, some elements of the gui are sometimes repainted at the wrong locations. There is probably something between the LinuxRepaintManager and the LinuxComponentPeer that assumes a relative ordering between X11 messages and juce messages. Too bad…


#6

Now I’m understanding the issue with repaints. It happens only when the XSHM extension is used. The LinuxRepaintManager is then using a shared memory ximage to hold the stuff that is repainted. What happens is that XShmPutImage does not display the content of the image immediately. So if one modifies the content of the image buffer just after XShmPutImage has been called, then it is the modified content that will be drawn !

Since the LinuxRepaintManager::performAnyPendingRepaintsNow() re-uses the same xshm image to draw various parts of the window, sometimes (on slow computers, with a slow Xorg) it happens that one overwrites the content of the image before it has been displayed with another content.

I see two solutions:

  • (solution 1) the current test that determines if one can re-use the current ximage checks only that the drawing area can fit in the image:

if (image == 0 || image->getWidth() < totalArea.getWidth() || image->getHeight() < totalArea.getHeight()) { //delete image and create a fresh one ... }

if could be replaced by a check that the drawing area lies inside the area affected to the image:

if (image == 0 || !Rectangle(image_x, image_y, image->getWidth(), image->getHeight()).contains(totalArea)) { //delete image and create a fresh one ... image_x = totalArea.getX(); image_y = totalArea.getY(); }

that way the race condition will still occur, but no part of the image will get drawn at the wrong place.

  • (solution 2) do not allow performAnyPendingRepaintsNow to execute until the XShmPutImage has completed. That is done by setting to True the last argument of the call to XShmPutImage , and check for the completion event in the window event handler:

[code] void handleWindowMessage (XEvent* event)
{
int CompletionNotify = XShmGetEventBase(display);

    switch (event->xany.type)
    {
       /* ...(snip).... */

        default:
          if (event->xany.type == CompletionNotify) {
            repainter->shmCompleted();
          }
            break;
    }[/code]

I believe this later approach is better from a performance point of view. This is the one I am testing right now.

I don’t know why this bug does not show up with the regular the juce_linux_Messaging.cpp , I believe it should also happen.


#7

[quote=“jpo”]

I don’t know why this bug does not show up with the regular the juce_linux_Messaging.cpp , I believe it should also happen.[/quote]

I actually think I may have seen it happen, but it was on an old Mandrake box now retired. At the time it was one of those “I’ve never seen this happen before, and if I never see it happen again then I’m going to pretend I didn’t see it happen here” moments.

Since it only ever happened once, I chose not to care. IIRC, and this was a while ago, a list box was partially drawn at some arbitrary location on screen. Causing a repaint made it go away, and that was end of it.


#8

Ah, good to know that it does happen :slight_smile:


#9

Thanks - let me know if your fix seems to be stable, and I’ll add it.


#10

Mmmm now I changed my mind, it looks like, in a perfect world, calling XInitThread is sufficient, and XLockDisplay / XUnlockDisplay should be used only when one need to execute atomically a block of X calls. The locking issues I am seeing on some machines (a debian lenny for example) are due to bugs in XCB 1.1, which is unfortunately the version used by debian lenny, and ubuntu 8.04 …

https://bugs.freedesktop.org/show_bug.cgi?id=16617
http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=463159

Sometimes it is also causing a crash, just like in:
https://bugs.launchpad.net/ubuntu/+source/libx11/+bug/232476/comments/3

(in case of crash (very very hard to reproduce indeed, I have seen it two or three times only so far), the backtrace shows that it is due to the call to MessageManager::inactivityCheckCallback(); in InternalTimerThread, which ends up calling XQueryPointer ).

I don’t understand why debian lenny and ubuntu LTQ 8.04 are still using xcb 1.1 since it is so much broken.


#11

So finally, does the code on the first post works ?


#12

Yes I’ve been using it since then, and got no issue (except the bugs in xcb)


#13

Ok.

I still don’t understand how you use the “non X” code.
Do I have to start an instance of JUCEApplication, or do I need to call initialiseJUCE_nonGUI followed by a MessageManager::getInstance() ?

A quick example will help me a lot.


#14

Yes both methods should work. I’m using it inside a JUCEApplication instance (so the initialiseJUCE_GUI() is being called)


#15

i’m interested in this too. i’ll take a look and see if i can drop in replacement this with ease.

is the problems with XSHM and redraws still here ?


#16

No I have not encountered it anymore (I’m using the “solution 2”).


#17

cool, probably it’s time to promote your code in the trunk :slight_smile:


#18

Cool. Could you send me your latest version of the file? I had a look at the snippets but am slightly confused by what by the shmCompleted() call is supposed to do…


#19

Hi!
so can I create linux console application or there is still a need to patch Juce code?
Thanks!


#20

[quote=“Buncker”]Hi!
so can I create linux console application or there is still a need to patch Juce code?
Thanks![/quote]

It’s all different in the new modularised version - there are separate event/gui modules and you can use whichever bits you need.

But don’t get confused by this thread - for a pure console app that didn’t use events, there was never a need to patch anything.