MIDI: Several ALSA Seq improvements =================================== Many things were done in this patch (I couldn't really split in several patches): * Only one ALSA Sequencer client per application * ALSA Sequencer client name is the application's name by default * Fixed a bug when getDeivces() would return devices created by the application itself * Only ports created with createNewDevice() are allowed to be subscribed, other ports (created by openDevice()) doesn't allow subscription * AlsaPort is now handled by AlsaClient, basically having the proper representation such as the ALSA Sequencer has. Files: Fix default directory paths ================================== Some information on: * Linux LSB FHS: http://www.linuxfoundation.org/collaborate/workgroups/lsb/fhs-30 * https://wiki.archlinux.org/index.php/Xdg_user_directories Refactor Event loop to remove X11 dependency ============================================ The goal of this refactor was to remove X11 dependencies on juce_events to enable non-gui applications (example, Embedded Linux apps) to have no libx11 dependency. The side-effect of this refactor is easy implementation of other Linux graphical back-end, cleanup some code, better handling of X displays and other benefits. I removed a lot of the code from juce_linux_Windowing to separate files for clarity. I also renamed all Linux X11 files to *linux_X11* instead of just *linux*. X11: Remove unnecessary XGrabButton call ======================================== This call is made unnecessary if used proper window flags when a window is created.tags/2021-05-28
@@ -31,118 +31,276 @@ | |||||
#if JUCE_ALSA | #if JUCE_ALSA | ||||
// You can define these strings in your app if you want to override the default names: | // You can define these strings in your app if you want to override the default names: | ||||
#ifndef JUCE_ALSA_MIDI_INPUT_NAME | |||||
#define JUCE_ALSA_MIDI_INPUT_NAME "Juce Midi Input" | |||||
#endif | |||||
#ifndef JUCE_ALSA_MIDI_OUTPUT_NAME | |||||
#define JUCE_ALSA_MIDI_OUTPUT_NAME "Juce Midi Output" | |||||
#ifndef JUCE_ALSA_MIDI_NAME | |||||
#define JUCE_ALSA_MIDI_NAME JUCEApplicationBase::getInstance()->getApplicationName().toUTF8() | |||||
#endif | #endif | ||||
//============================================================================== | //============================================================================== | ||||
namespace | namespace | ||||
{ | { | ||||
class AlsaPortAndCallback; | |||||
//============================================================================== | //============================================================================== | ||||
class AlsaClient : public ReferenceCountedObject | class AlsaClient : public ReferenceCountedObject | ||||
{ | { | ||||
public: | public: | ||||
typedef ReferenceCountedObjectPtr<AlsaClient> Ptr; | typedef ReferenceCountedObjectPtr<AlsaClient> Ptr; | ||||
static Ptr getInstance (bool forInput) | |||||
//============================================================================== | |||||
// represents an input or output port of the supplied AlsaClient | |||||
class Port | |||||
{ | { | ||||
AlsaClient*& instance = (forInput ? inInstance : outInstance); | |||||
if (instance == nullptr) | |||||
instance = new AlsaClient (forInput); | |||||
public: | |||||
Port (AlsaClient& c, bool forInput) noexcept | |||||
: portId (-1), | |||||
callbackEnabled (false), | |||||
client (c), | |||||
isInput (forInput), | |||||
callback (nullptr), | |||||
maxEventSize (4 * 1024), | |||||
midiInput (nullptr) | |||||
{} | |||||
~Port() | |||||
{ | |||||
if (isValid()) | |||||
{ | |||||
if (isInput) | |||||
enableCallback (false); | |||||
else | |||||
snd_midi_event_free (midiParser); | |||||
return instance; | |||||
} | |||||
snd_seq_delete_simple_port (client.get(), portId); | |||||
} | |||||
} | |||||
void connectWith (int sourceClient, int sourcePort) const noexcept | |||||
{ | |||||
if (isInput) | |||||
snd_seq_connect_from (client.get(), portId, sourceClient, sourcePort); | |||||
else | |||||
snd_seq_connect_to (client.get(), portId, sourceClient, sourcePort); | |||||
} | |||||
bool isInput() const noexcept { return input; } | |||||
bool isValid() const noexcept | |||||
{ | |||||
return client.get() != nullptr && portId >= 0; | |||||
} | |||||
void registerCallback (AlsaPortAndCallback* cb) | |||||
{ | |||||
if (cb != nullptr) | |||||
void setupInput(MidiInput* input, MidiInputCallback* cb) | |||||
{ | |||||
jassert (cb && input); | |||||
callback = cb; | |||||
midiInput = input; | |||||
} | |||||
void setupOutput() | |||||
{ | |||||
jassert (! isInput); | |||||
snd_midi_event_new ((size_t) maxEventSize, &midiParser); | |||||
} | |||||
void enableCallback (bool enable) | |||||
{ | { | ||||
if (callbackEnabled != enable) | |||||
{ | { | ||||
const ScopedLock sl (callbackLock); | |||||
activeCallbacks.add (cb); | |||||
callbackEnabled = enable; | |||||
if (inputThread == nullptr) | |||||
inputThread = new MidiInputThread (*this); | |||||
if (enable) | |||||
client.registerCallback(); | |||||
else | |||||
client.unregisterCallback(); | |||||
} | } | ||||
} | |||||
inputThread->startThread(); | |||||
bool sendMessageNow (const MidiMessage& message) | |||||
{ | |||||
if (message.getRawDataSize() > maxEventSize) | |||||
{ | |||||
maxEventSize = message.getRawDataSize(); | |||||
snd_midi_event_free (midiParser); | |||||
snd_midi_event_new ((size_t) maxEventSize, &midiParser); | |||||
} | |||||
snd_seq_event_t event; | |||||
snd_seq_ev_clear (&event); | |||||
long numBytes = (long) message.getRawDataSize(); | |||||
const uint8* data = message.getRawData(); | |||||
snd_seq_t* seqHandle = client.get(); | |||||
bool success = true; | |||||
while (numBytes > 0) | |||||
{ | |||||
const long numSent = snd_midi_event_encode (midiParser, data, numBytes, &event); | |||||
if (numSent <= 0) | |||||
{ | |||||
success = numSent == 0; | |||||
break; | |||||
} | |||||
numBytes -= numSent; | |||||
data += numSent; | |||||
snd_seq_ev_set_source (&event, portId); | |||||
snd_seq_ev_set_subs (&event); | |||||
snd_seq_ev_set_direct (&event); | |||||
if (snd_seq_event_output_direct (seqHandle, &event) < 0) | |||||
{ | |||||
success = false; | |||||
break; | |||||
} | |||||
} | |||||
snd_midi_event_reset_encode (midiParser); | |||||
return success; | |||||
} | |||||
bool operator== (const Port& lhs) const noexcept | |||||
{ | |||||
return portId != -1 && portId == lhs.portId; | |||||
} | } | ||||
int portId; | |||||
bool callbackEnabled; | |||||
private: | |||||
friend class AlsaClient; | |||||
AlsaClient& client; | |||||
bool isInput; | |||||
MidiInputCallback* callback; | |||||
snd_midi_event_t* midiParser; | |||||
int maxEventSize; | |||||
MidiInput* midiInput; | |||||
void createPort (const String& name, bool enableSubscription) | |||||
{ | |||||
if (snd_seq_t* seqHandle = client.get()) | |||||
{ | |||||
const unsigned int caps = | |||||
isInput | |||||
? (SND_SEQ_PORT_CAP_WRITE | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_WRITE : 0)) | |||||
: (SND_SEQ_PORT_CAP_WRITE | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_READ : 0)); | |||||
portId = snd_seq_create_simple_port (seqHandle, name.toUTF8(), caps, | |||||
SND_SEQ_PORT_TYPE_MIDI_GENERIC | | |||||
SND_SEQ_PORT_TYPE_APPLICATION); | |||||
} | |||||
} | |||||
void handleIncomingMidiMessage (const MidiMessage& message) const | |||||
{ | |||||
callback->handleIncomingMidiMessage (midiInput, message); | |||||
} | |||||
void handlePartialSysexMessage (const uint8* messageData, int numBytesSoFar, double timeStamp) | |||||
{ | |||||
callback->handlePartialSysexMessage (midiInput, messageData, numBytesSoFar, timeStamp); | |||||
} | |||||
}; | |||||
static Ptr getInstance() | |||||
{ | |||||
if (instance == nullptr) | |||||
instance = new AlsaClient(); | |||||
return instance; | |||||
} | } | ||||
void unregisterCallback (AlsaPortAndCallback* cb) | |||||
void registerCallback () | |||||
{ | { | ||||
const ScopedLock sl (callbackLock); | |||||
if (inputThread == nullptr) | |||||
inputThread = new MidiInputThread (*this); | |||||
jassert (activeCallbacks.contains (cb)); | |||||
activeCallbacks.removeAllInstancesOf (cb); | |||||
if (++activeCallbacks - 1 == 0) | |||||
inputThread->startThread(); | |||||
} | |||||
if (activeCallbacks.size() == 0 && inputThread->isThreadRunning()) | |||||
void unregisterCallback () | |||||
{ | |||||
jassert (activeCallbacks.get() > 0); | |||||
if (--activeCallbacks == 0 && inputThread->isThreadRunning()) | |||||
inputThread->signalThreadShouldExit(); | inputThread->signalThreadShouldExit(); | ||||
} | } | ||||
void handleIncomingMidiMessage (snd_seq_event*, const MidiMessage&); | |||||
void handlePartialSysexMessage (snd_seq_event*, const uint8*, int, double); | |||||
void handleIncomingMidiMessage (snd_seq_event* event, const MidiMessage& message) | |||||
{ | |||||
if (event->dest.port < ports.size() | |||||
&& ports[event->dest.port]->callbackEnabled) | |||||
ports[event->dest.port]->handleIncomingMidiMessage (message); | |||||
} | |||||
void handlePartialSysexMessage (snd_seq_event* event, const uint8* messageData, int numBytesSoFar, double timeStamp) | |||||
{ | |||||
if (event->dest.port < ports.size() | |||||
&& ports[event->dest.port]->callbackEnabled) | |||||
ports[event->dest.port]->handlePartialSysexMessage (messageData, numBytesSoFar, timeStamp); | |||||
} | |||||
snd_seq_t* get() const noexcept { return handle; } | snd_seq_t* get() const noexcept { return handle; } | ||||
int getId() const noexcept { return clientId; } | |||||
Port* createPort (const String& name, bool forInput, bool enableSubscription) | |||||
{ | |||||
Port* port = new Port (*this, forInput); | |||||
port->createPort (name, enableSubscription); | |||||
ports.set (port->portId, port); | |||||
incReferenceCount(); | |||||
return port; | |||||
} | |||||
void deletePort (Port* port) | |||||
{ | |||||
ports.remove (port->portId); | |||||
decReferenceCount(); | |||||
} | |||||
private: | private: | ||||
bool input; | |||||
snd_seq_t* handle; | snd_seq_t* handle; | ||||
Array<AlsaPortAndCallback*> activeCallbacks; | |||||
int clientId; | |||||
OwnedArray<Port> ports; | |||||
Atomic<int> activeCallbacks; | |||||
CriticalSection callbackLock; | CriticalSection callbackLock; | ||||
static AlsaClient* inInstance; | |||||
static AlsaClient* outInstance; | |||||
static AlsaClient* instance; | |||||
//============================================================================== | //============================================================================== | ||||
friend class ReferenceCountedObjectPtr<AlsaClient>; | friend class ReferenceCountedObjectPtr<AlsaClient>; | ||||
friend struct ContainerDeletePolicy<AlsaClient>; | friend struct ContainerDeletePolicy<AlsaClient>; | ||||
AlsaClient (bool forInput) | |||||
: input (forInput), handle (nullptr) | |||||
AlsaClient () | |||||
: handle (nullptr), | |||||
inputThread (nullptr) | |||||
{ | { | ||||
AlsaClient*& instance = (input ? inInstance : outInstance); | |||||
jassert (instance == nullptr); | jassert (instance == nullptr); | ||||
instance = this; | |||||
snd_seq_open (&handle, "default", SND_SEQ_OPEN_DUPLEX, 0); | |||||
snd_seq_nonblock (handle, SND_SEQ_NONBLOCK); | |||||
snd_seq_set_client_name (handle, JUCE_ALSA_MIDI_NAME); | |||||
clientId = snd_seq_client_id(handle); | |||||
snd_seq_open (&handle, "default", forInput ? SND_SEQ_OPEN_INPUT | |||||
: SND_SEQ_OPEN_OUTPUT, 0); | |||||
snd_seq_set_client_name (handle, forInput ? JUCE_ALSA_MIDI_INPUT_NAME | |||||
: JUCE_ALSA_MIDI_OUTPUT_NAME); | |||||
// It's good idea to pre-allocate a good number of elements | |||||
ports.ensureStorageAllocated (32); | |||||
} | } | ||||
~AlsaClient() | ~AlsaClient() | ||||
{ | { | ||||
AlsaClient*& instance = (input ? inInstance : outInstance); | |||||
jassert (instance != nullptr); | jassert (instance != nullptr); | ||||
instance = nullptr; | instance = nullptr; | ||||
if (handle != nullptr) | if (handle != nullptr) | ||||
{ | |||||
snd_seq_close (handle); | snd_seq_close (handle); | ||||
handle = nullptr; | |||||
} | |||||
jassert (activeCallbacks.size() == 0); | |||||
jassert (activeCallbacks.get() == 0); | |||||
if (inputThread) | if (inputThread) | ||||
{ | |||||
inputThread->stopThread (3000); | inputThread->stopThread (3000); | ||||
inputThread = nullptr; | |||||
} | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
@@ -152,7 +310,7 @@ private: | |||||
MidiInputThread (AlsaClient& c) | MidiInputThread (AlsaClient& c) | ||||
: Thread ("Juce MIDI Input"), client (c), concatenator (2048) | : Thread ("Juce MIDI Input"), client (c), concatenator (2048) | ||||
{ | { | ||||
jassert (client.input && client.get() != nullptr); | |||||
jassert (client.get() != nullptr); | |||||
} | } | ||||
void run() override | void run() override | ||||
@@ -176,8 +334,6 @@ private: | |||||
if (threadShouldExit()) | if (threadShouldExit()) | ||||
break; | break; | ||||
snd_seq_nonblock (seqHandle, 1); | |||||
do | do | ||||
{ | { | ||||
snd_seq_event_t* inputEvent = nullptr; | snd_seq_event_t* inputEvent = nullptr; | ||||
@@ -213,202 +369,91 @@ private: | |||||
ScopedPointer<MidiInputThread> inputThread; | ScopedPointer<MidiInputThread> inputThread; | ||||
}; | }; | ||||
AlsaClient* AlsaClient::inInstance = nullptr; | |||||
AlsaClient* AlsaClient::outInstance = nullptr; | |||||
AlsaClient* AlsaClient::instance = nullptr; | |||||
//============================================================================== | //============================================================================== | ||||
// represents an input or output port of the supplied AlsaClient | |||||
class AlsaPort | |||||
static AlsaClient::Port* iterateMidiClient (const AlsaClient::Ptr& client, | |||||
snd_seq_client_info_t* clientInfo, | |||||
const bool forInput, | |||||
StringArray& deviceNamesFound, | |||||
const int deviceIndexToOpen) | |||||
{ | { | ||||
public: | |||||
AlsaPort() noexcept : portId (-1) {} | |||||
AlsaPort (const AlsaClient::Ptr& c, int port) noexcept : client (c), portId (port) {} | |||||
void createPort (const AlsaClient::Ptr& c, const String& name, bool forInput) | |||||
{ | |||||
client = c; | |||||
AlsaClient::Port* port = nullptr; | |||||
if (snd_seq_t* handle = client->get()) | |||||
portId = snd_seq_create_simple_port (handle, name.toUTF8(), | |||||
forInput ? (SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE) | |||||
: (SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ), | |||||
SND_SEQ_PORT_TYPE_MIDI_GENERIC); | |||||
} | |||||
void deletePort() | |||||
{ | |||||
if (isValid()) | |||||
{ | |||||
snd_seq_delete_simple_port (client->get(), portId); | |||||
portId = -1; | |||||
} | |||||
} | |||||
void connectWith (int sourceClient, int sourcePort) | |||||
{ | |||||
if (client->isInput()) | |||||
snd_seq_connect_from (client->get(), portId, sourceClient, sourcePort); | |||||
else | |||||
snd_seq_connect_to (client->get(), portId, sourceClient, sourcePort); | |||||
} | |||||
bool isValid() const noexcept | |||||
{ | |||||
return client != nullptr && client->get() != nullptr && portId >= 0; | |||||
} | |||||
AlsaClient::Ptr client; | |||||
int portId; | |||||
}; | |||||
snd_seq_t* seqHandle = client->get(); | |||||
snd_seq_port_info_t* portInfo = nullptr; | |||||
//============================================================================== | |||||
class AlsaPortAndCallback | |||||
{ | |||||
public: | |||||
AlsaPortAndCallback (AlsaPort p, MidiInput* in, MidiInputCallback* cb) | |||||
: port (p), midiInput (in), callback (cb), callbackEnabled (false) | |||||
{ | |||||
} | |||||
snd_seq_port_info_alloca (&portInfo); | |||||
jassert (portInfo); | |||||
int numPorts = snd_seq_client_info_get_num_ports (clientInfo); | |||||
const int sourceClient = snd_seq_client_info_get_client (clientInfo); | |||||
~AlsaPortAndCallback() | |||||
{ | |||||
enableCallback (false); | |||||
port.deletePort(); | |||||
} | |||||
snd_seq_port_info_set_client (portInfo, sourceClient); | |||||
snd_seq_port_info_set_port (portInfo, -1); | |||||
void enableCallback (bool enable) | |||||
while (--numPorts >= 0) | |||||
{ | { | ||||
if (callbackEnabled != enable) | |||||
if (snd_seq_query_next_port (seqHandle, portInfo) == 0 | |||||
&& (snd_seq_port_info_get_capability (portInfo) | |||||
& (forInput ? SND_SEQ_PORT_CAP_SUBS_WRITE : SND_SEQ_PORT_CAP_SUBS_READ)) != 0) | |||||
{ | { | ||||
callbackEnabled = enable; | |||||
if (enable) | |||||
port.client->registerCallback (this); | |||||
else | |||||
port.client->unregisterCallback (this); | |||||
} | |||||
} | |||||
void handleIncomingMidiMessage (const MidiMessage& message) const | |||||
{ | |||||
callback->handleIncomingMidiMessage (midiInput, message); | |||||
} | |||||
void handlePartialSysexMessage (const uint8* messageData, int numBytesSoFar, double timeStamp) | |||||
{ | |||||
callback->handlePartialSysexMessage (midiInput, messageData, numBytesSoFar, timeStamp); | |||||
} | |||||
private: | |||||
AlsaPort port; | |||||
MidiInput* midiInput; | |||||
MidiInputCallback* callback; | |||||
bool callbackEnabled; | |||||
const String portName = snd_seq_port_info_get_name(portInfo); | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AlsaPortAndCallback) | |||||
}; | |||||
void AlsaClient::handleIncomingMidiMessage (snd_seq_event_t* event, const MidiMessage& message) | |||||
{ | |||||
const ScopedLock sl (callbackLock); | |||||
deviceNamesFound.add (portName); | |||||
if (AlsaPortAndCallback* const cb = activeCallbacks[event->dest.port]) | |||||
cb->handleIncomingMidiMessage (message); | |||||
} | |||||
void AlsaClient::handlePartialSysexMessage (snd_seq_event* event, const uint8* messageData, int numBytesSoFar, double timeStamp) | |||||
{ | |||||
const ScopedLock sl (callbackLock); | |||||
if (AlsaPortAndCallback* const cb = activeCallbacks[event->dest.port]) | |||||
cb->handlePartialSysexMessage (messageData, numBytesSoFar, timeStamp); | |||||
} | |||||
//============================================================================== | |||||
static AlsaPort iterateMidiClient (const AlsaClient::Ptr& seq, | |||||
snd_seq_client_info_t* clientInfo, | |||||
const bool forInput, | |||||
StringArray& deviceNamesFound, | |||||
const int deviceIndexToOpen) | |||||
{ | |||||
AlsaPort port; | |||||
snd_seq_t* seqHandle = seq->get(); | |||||
snd_seq_port_info_t* portInfo = nullptr; | |||||
if (snd_seq_port_info_malloc (&portInfo) == 0) | |||||
{ | |||||
int numPorts = snd_seq_client_info_get_num_ports (clientInfo); | |||||
const int client = snd_seq_client_info_get_client (clientInfo); | |||||
snd_seq_port_info_set_client (portInfo, client); | |||||
snd_seq_port_info_set_port (portInfo, -1); | |||||
while (--numPorts >= 0) | |||||
{ | |||||
if (snd_seq_query_next_port (seqHandle, portInfo) == 0 | |||||
&& (snd_seq_port_info_get_capability (portInfo) & (forInput ? SND_SEQ_PORT_CAP_READ | |||||
: SND_SEQ_PORT_CAP_WRITE)) != 0) | |||||
if (deviceNamesFound.size() == deviceIndexToOpen + 1) | |||||
{ | { | ||||
const String clientName = snd_seq_client_info_get_name (clientInfo); | |||||
const String portName = snd_seq_port_info_get_name(portInfo); | |||||
if (clientName == portName) | |||||
deviceNamesFound.add (clientName); | |||||
else | |||||
deviceNamesFound.add (clientName + ": " + portName); | |||||
if (deviceNamesFound.size() == deviceIndexToOpen + 1) | |||||
const int sourcePort = snd_seq_port_info_get_port (portInfo); | |||||
if (sourcePort != -1) | |||||
{ | { | ||||
const int sourcePort = snd_seq_port_info_get_port (portInfo); | |||||
if (sourcePort != -1) | |||||
{ | |||||
const int sourceClient = snd_seq_client_info_get_client (clientInfo); | |||||
port.createPort (seq, portName, forInput); | |||||
port.connectWith (sourceClient, sourcePort); | |||||
} | |||||
port = client->createPort (portName, forInput, false); | |||||
jassert (port->isValid()); | |||||
port->connectWith (sourceClient, sourcePort); | |||||
break; | |||||
} | } | ||||
} | } | ||||
} | } | ||||
snd_seq_port_info_free (portInfo); | |||||
} | } | ||||
return port; | return port; | ||||
} | } | ||||
static AlsaPort iterateMidiDevices (const bool forInput, | |||||
StringArray& deviceNamesFound, | |||||
const int deviceIndexToOpen) | |||||
static AlsaClient::Port* iterateMidiDevices (const bool forInput, | |||||
StringArray& deviceNamesFound, | |||||
const int deviceIndexToOpen) | |||||
{ | { | ||||
AlsaPort port; | |||||
const AlsaClient::Ptr client (AlsaClient::getInstance (forInput)); | |||||
AlsaClient::Port* port = nullptr; | |||||
const AlsaClient::Ptr client (AlsaClient::getInstance()); | |||||
if (snd_seq_t* const seqHandle = client->get()) | if (snd_seq_t* const seqHandle = client->get()) | ||||
{ | { | ||||
snd_seq_system_info_t* systemInfo = nullptr; | snd_seq_system_info_t* systemInfo = nullptr; | ||||
snd_seq_client_info_t* clientInfo = nullptr; | snd_seq_client_info_t* clientInfo = nullptr; | ||||
if (snd_seq_system_info_malloc (&systemInfo) == 0) | |||||
snd_seq_system_info_alloca (&systemInfo); | |||||
jassert(systemInfo); | |||||
if (snd_seq_system_info (seqHandle, systemInfo) == 0) | |||||
{ | { | ||||
if (snd_seq_system_info (seqHandle, systemInfo) == 0 | |||||
&& snd_seq_client_info_malloc (&clientInfo) == 0) | |||||
{ | |||||
int numClients = snd_seq_system_info_get_cur_clients (systemInfo); | |||||
snd_seq_client_info_alloca (&clientInfo); | |||||
jassert(clientInfo); | |||||
int numClients = snd_seq_system_info_get_cur_clients (systemInfo); | |||||
while (--numClients >= 0 && ! port.isValid()) | |||||
if (snd_seq_query_next_client (seqHandle, clientInfo) == 0) | |||||
while (--numClients >= 0) | |||||
{ | |||||
if (snd_seq_query_next_client (seqHandle, clientInfo) == 0) | |||||
{ | |||||
const int sourceClient = snd_seq_client_info_get_client (clientInfo); | |||||
if (sourceClient != client->getId() | |||||
&& sourceClient != SND_SEQ_CLIENT_SYSTEM) | |||||
{ | |||||
port = iterateMidiClient (client, clientInfo, forInput, | port = iterateMidiClient (client, clientInfo, forInput, | ||||
deviceNamesFound, deviceIndexToOpen); | deviceNamesFound, deviceIndexToOpen); | ||||
snd_seq_client_info_free (clientInfo); | |||||
if (port) | |||||
break; | |||||
} | |||||
} | |||||
} | } | ||||
snd_seq_system_info_free (systemInfo); | |||||
} | } | ||||
} | } | ||||
deviceNamesFound.appendNumbersToDuplicates (true, true); | deviceNamesFound.appendNumbersToDuplicates (true, true); | ||||
@@ -416,80 +461,6 @@ static AlsaPort iterateMidiDevices (const bool forInput, | |||||
return port; | return port; | ||||
} | } | ||||
//============================================================================== | |||||
class MidiOutputDevice | |||||
{ | |||||
public: | |||||
MidiOutputDevice (MidiOutput* const output, const AlsaPort& p) | |||||
: midiOutput (output), port (p), | |||||
maxEventSize (16 * 1024) | |||||
{ | |||||
jassert (port.isValid() && midiOutput != nullptr); | |||||
snd_midi_event_new ((size_t) maxEventSize, &midiParser); | |||||
} | |||||
~MidiOutputDevice() | |||||
{ | |||||
snd_midi_event_free (midiParser); | |||||
port.deletePort(); | |||||
} | |||||
bool sendMessageNow (const MidiMessage& message) | |||||
{ | |||||
if (message.getRawDataSize() > maxEventSize) | |||||
{ | |||||
maxEventSize = message.getRawDataSize(); | |||||
snd_midi_event_free (midiParser); | |||||
snd_midi_event_new ((size_t) maxEventSize, &midiParser); | |||||
} | |||||
snd_seq_event_t event; | |||||
snd_seq_ev_clear (&event); | |||||
long numBytes = (long) message.getRawDataSize(); | |||||
const uint8* data = message.getRawData(); | |||||
snd_seq_t* seqHandle = port.client->get(); | |||||
bool success = true; | |||||
while (numBytes > 0) | |||||
{ | |||||
const long numSent = snd_midi_event_encode (midiParser, data, numBytes, &event); | |||||
if (numSent <= 0) | |||||
{ | |||||
success = numSent == 0; | |||||
break; | |||||
} | |||||
numBytes -= numSent; | |||||
data += numSent; | |||||
snd_seq_ev_set_source (&event, port.portId); | |||||
snd_seq_ev_set_subs (&event); | |||||
snd_seq_ev_set_direct (&event); | |||||
if (snd_seq_event_output_direct (seqHandle, &event) < 0) | |||||
{ | |||||
success = false; | |||||
break; | |||||
} | |||||
} | |||||
snd_midi_event_reset_encode (midiParser); | |||||
return success; | |||||
} | |||||
private: | |||||
MidiOutput* const midiOutput; | |||||
AlsaPort port; | |||||
snd_midi_event_t* midiParser; | |||||
int maxEventSize; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutputDevice) | |||||
}; | |||||
} // namespace | } // namespace | ||||
StringArray MidiOutput::getDevices() | StringArray MidiOutput::getDevices() | ||||
@@ -509,13 +480,16 @@ MidiOutput* MidiOutput::openDevice (int deviceIndex) | |||||
MidiOutput* newDevice = nullptr; | MidiOutput* newDevice = nullptr; | ||||
StringArray devices; | StringArray devices; | ||||
AlsaPort port (iterateMidiDevices (false, devices, deviceIndex)); | |||||
AlsaClient::Port* port = iterateMidiDevices (false, devices, deviceIndex); | |||||
if (port.isValid()) | |||||
{ | |||||
newDevice = new MidiOutput (devices [deviceIndex]); | |||||
newDevice->internal = new MidiOutputDevice (newDevice, port); | |||||
} | |||||
if (port == nullptr) | |||||
return nullptr; | |||||
jassert (port->isValid()); | |||||
newDevice = new MidiOutput (devices [deviceIndex]); | |||||
port->setupOutput(); | |||||
newDevice->internal = port; | |||||
return newDevice; | return newDevice; | ||||
} | } | ||||
@@ -523,17 +497,16 @@ MidiOutput* MidiOutput::openDevice (int deviceIndex) | |||||
MidiOutput* MidiOutput::createNewDevice (const String& deviceName) | MidiOutput* MidiOutput::createNewDevice (const String& deviceName) | ||||
{ | { | ||||
MidiOutput* newDevice = nullptr; | MidiOutput* newDevice = nullptr; | ||||
AlsaPort port; | |||||
const AlsaClient::Ptr client (AlsaClient::getInstance (false)); | |||||
const AlsaClient::Ptr client (AlsaClient::getInstance()); | |||||
port.createPort (client, deviceName, false); | |||||
AlsaClient::Port* port = client->createPort (deviceName, false, true); | |||||
if (port.isValid()) | |||||
{ | |||||
newDevice = new MidiOutput (deviceName); | |||||
newDevice->internal = new MidiOutputDevice (newDevice, port); | |||||
} | |||||
jassert (port->isValid()); | |||||
newDevice = new MidiOutput (deviceName); | |||||
port->setupOutput(); | |||||
newDevice->internal = port; | |||||
return newDevice; | return newDevice; | ||||
} | } | ||||
@@ -542,12 +515,13 @@ MidiOutput::~MidiOutput() | |||||
{ | { | ||||
stopBackgroundThread(); | stopBackgroundThread(); | ||||
delete static_cast<MidiOutputDevice*> (internal); | |||||
AlsaClient::Ptr client (AlsaClient::getInstance()); | |||||
client->deletePort (static_cast<AlsaClient::Port*> (internal)); | |||||
} | } | ||||
void MidiOutput::sendMessageNow (const MidiMessage& message) | void MidiOutput::sendMessageNow (const MidiMessage& message) | ||||
{ | { | ||||
static_cast<MidiOutputDevice*> (internal)->sendMessageNow (message); | |||||
static_cast<AlsaClient::Port*> (internal)->sendMessageNow (message); | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
@@ -559,17 +533,18 @@ MidiInput::MidiInput (const String& nm) | |||||
MidiInput::~MidiInput() | MidiInput::~MidiInput() | ||||
{ | { | ||||
stop(); | stop(); | ||||
delete static_cast<AlsaPortAndCallback*> (internal); | |||||
AlsaClient::Ptr client (AlsaClient::getInstance()); | |||||
client->deletePort (static_cast<AlsaClient::Port*> (internal)); | |||||
} | } | ||||
void MidiInput::start() | void MidiInput::start() | ||||
{ | { | ||||
static_cast<AlsaPortAndCallback*> (internal)->enableCallback (true); | |||||
static_cast<AlsaClient::Port*> (internal)->enableCallback (true); | |||||
} | } | ||||
void MidiInput::stop() | void MidiInput::stop() | ||||
{ | { | ||||
static_cast<AlsaPortAndCallback*> (internal)->enableCallback (false); | |||||
static_cast<AlsaClient::Port*> (internal)->enableCallback (false); | |||||
} | } | ||||
int MidiInput::getDefaultDeviceIndex() | int MidiInput::getDefaultDeviceIndex() | ||||
@@ -589,13 +564,16 @@ MidiInput* MidiInput::openDevice (int deviceIndex, MidiInputCallback* callback) | |||||
MidiInput* newDevice = nullptr; | MidiInput* newDevice = nullptr; | ||||
StringArray devices; | StringArray devices; | ||||
AlsaPort port (iterateMidiDevices (true, devices, deviceIndex)); | |||||
AlsaClient::Port* port = iterateMidiDevices (true, devices, deviceIndex); | |||||
if (port.isValid()) | |||||
{ | |||||
newDevice = new MidiInput (devices [deviceIndex]); | |||||
newDevice->internal = new AlsaPortAndCallback (port, newDevice, callback); | |||||
} | |||||
if (port == nullptr) | |||||
return nullptr; | |||||
jassert (port->isValid()); | |||||
newDevice = new MidiInput (devices [deviceIndex]); | |||||
port->setupInput (newDevice, callback); | |||||
newDevice->internal = port; | |||||
return newDevice; | return newDevice; | ||||
} | } | ||||
@@ -603,17 +581,16 @@ MidiInput* MidiInput::openDevice (int deviceIndex, MidiInputCallback* callback) | |||||
MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback) | MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback) | ||||
{ | { | ||||
MidiInput* newDevice = nullptr; | MidiInput* newDevice = nullptr; | ||||
AlsaPort port; | |||||
const AlsaClient::Ptr client (AlsaClient::getInstance (true)); | |||||
AlsaClient::Ptr client (AlsaClient::getInstance()); | |||||
port.createPort (client, deviceName, true); | |||||
AlsaClient::Port* port = client->createPort (deviceName, true, true); | |||||
if (port.isValid()) | |||||
{ | |||||
newDevice = new MidiInput (deviceName); | |||||
newDevice->internal = new AlsaPortAndCallback (port, newDevice, callback); | |||||
} | |||||
jassert (port->isValid()); | |||||
newDevice = new MidiInput (deviceName); | |||||
port->setupInput (newDevice, callback); | |||||
newDevice->internal = port; | |||||
return newDevice; | return newDevice; | ||||
} | } | ||||
@@ -101,10 +101,6 @@ namespace juce | |||||
#endif | #endif | ||||
#endif | #endif | ||||
#if JUCE_LINUX | |||||
extern Display* display; | |||||
#endif | |||||
extern JUCE_API bool handleManufacturerSpecificVST2Opcode (int32, pointer_sized_int, void*, float); | extern JUCE_API bool handleManufacturerSpecificVST2Opcode (int32, pointer_sized_int, void*, float); | ||||
} | } | ||||
@@ -332,6 +328,10 @@ public: | |||||
vstEffect.flags |= vstEffectFlagDataInChunks; | vstEffect.flags |= vstEffectFlagDataInChunks; | ||||
#if JUCE_LINUX | |||||
display = XWindowSystem::getInstance()->displayRef(); | |||||
#endif | |||||
activePlugins.add (this); | activePlugins.add (this); | ||||
} | } | ||||
@@ -370,6 +370,10 @@ public: | |||||
messageThreadIsDefinitelyCorrect = false; | messageThreadIsDefinitelyCorrect = false; | ||||
#endif | #endif | ||||
} | } | ||||
#if JUCE_LINUX | |||||
display = XWindowSystem::getInstance()->displayUnref(); | |||||
#endif | |||||
} | } | ||||
} | } | ||||
@@ -1343,7 +1347,11 @@ public: | |||||
Rectangle<int> childBounds (child->getWidth(), child->getHeight()); | Rectangle<int> childBounds (child->getWidth(), child->getHeight()); | ||||
childBounds *= scale; | childBounds *= scale; | ||||
XResizeWindow (display, (Window) getWindowHandle(), childBounds.getWidth(), childBounds.getHeight()); | |||||
{ | |||||
ScopedXDisplay xDisplay; | |||||
::Display* display = xDisplay.get(); | |||||
XResizeWindow (display, (Window) getWindowHandle(), childBounds.getWidth(), childBounds.getHeight()); | |||||
} | |||||
#endif | #endif | ||||
#if JUCE_MAC | #if JUCE_MAC | ||||
@@ -1404,6 +1412,7 @@ private: | |||||
#if JUCE_MAC | #if JUCE_MAC | ||||
void* hostWindow; | void* hostWindow; | ||||
#elif JUCE_LINUX | #elif JUCE_LINUX | ||||
::Display* display; | |||||
Window hostWindow; | Window hostWindow; | ||||
#else | #else | ||||
HWND hostWindow; | HWND hostWindow; | ||||
@@ -224,14 +224,11 @@ static pointer_sized_int VSTINTERFACECALL audioMaster (VstEffectInterface* effec | |||||
//============================================================================== | //============================================================================== | ||||
#if JUCE_LINUX | #if JUCE_LINUX | ||||
extern Display* display; | |||||
extern XContext windowHandleXContext; | |||||
namespace | namespace | ||||
{ | { | ||||
static bool xErrorTriggered = false; | static bool xErrorTriggered = false; | ||||
static int temporaryErrorHandler (Display*, XErrorEvent*) | |||||
static int temporaryErrorHandler (::Display*, XErrorEvent*) | |||||
{ | { | ||||
xErrorTriggered = true; | xErrorTriggered = true; | ||||
return 0; | return 0; | ||||
@@ -249,8 +246,13 @@ namespace | |||||
unsigned char* data; | unsigned char* data; | ||||
Atom userType; | Atom userType; | ||||
XGetWindowProperty (display, handle, atom, 0, 1, false, AnyPropertyType, | |||||
&userType, &userSize, &userCount, &bytes, &data); | |||||
{ | |||||
ScopedXDisplay xDisplay; | |||||
::Display* display = xDisplay.get(); | |||||
XGetWindowProperty (display, handle, atom, 0, 1, false, AnyPropertyType, | |||||
&userType, &userSize, &userCount, &bytes, &data); | |||||
} | |||||
XSetErrorHandler (oldErrorHandler); | XSetErrorHandler (oldErrorHandler); | ||||
@@ -263,7 +265,12 @@ namespace | |||||
Window* childWindows; | Window* childWindows; | ||||
unsigned int numChildren = 0; | unsigned int numChildren = 0; | ||||
XQueryTree (display, windowToCheck, &rootWindow, &parentWindow, &childWindows, &numChildren); | |||||
{ | |||||
ScopedXDisplay xDisplay; | |||||
::Display* display = xDisplay.get(); | |||||
XQueryTree (display, windowToCheck, &rootWindow, &parentWindow, &childWindows, &numChildren); | |||||
} | |||||
if (numChildren > 0) | if (numChildren > 0) | ||||
return childWindows [0]; | return childWindows [0]; | ||||
@@ -2025,6 +2032,7 @@ public: | |||||
#elif JUCE_LINUX | #elif JUCE_LINUX | ||||
pluginWindow = None; | pluginWindow = None; | ||||
pluginProc = None; | pluginProc = None; | ||||
display = XWindowSystem::getInstance()->displayRef(); | |||||
#elif JUCE_MAC | #elif JUCE_MAC | ||||
ignoreUnused (recursiveResize, pluginRefusesToResize, alreadyInside); | ignoreUnused (recursiveResize, pluginRefusesToResize, alreadyInside); | ||||
@@ -2053,6 +2061,8 @@ public: | |||||
carbonWrapper = nullptr; | carbonWrapper = nullptr; | ||||
#endif | #endif | ||||
cocoaWrapper = nullptr; | cocoaWrapper = nullptr; | ||||
#elif JUCE_LINUX | |||||
display = XWindowSystem::getInstance()->displayUnref(); | |||||
#endif | #endif | ||||
activeVSTWindows.removeFirstMatchingValue (this); | activeVSTWindows.removeFirstMatchingValue (this); | ||||
@@ -2253,6 +2263,7 @@ private: | |||||
void* originalWndProc; | void* originalWndProc; | ||||
int sizeCheckCount; | int sizeCheckCount; | ||||
#elif JUCE_LINUX | #elif JUCE_LINUX | ||||
::Display* display; | |||||
Window pluginWindow; | Window pluginWindow; | ||||
EventProcPtr pluginProc; | EventProcPtr pluginProc; | ||||
#endif | #endif | ||||
@@ -810,6 +810,7 @@ public: | |||||
On Windows, this might be "\Documents and Settings\username\Application Data". | On Windows, this might be "\Documents and Settings\username\Application Data". | ||||
On the Mac, it might be "~/Library". If you're going to store your settings in here, | On the Mac, it might be "~/Library". If you're going to store your settings in here, | ||||
always create your own sub-folder to put them in, to avoid making a mess. | always create your own sub-folder to put them in, to avoid making a mess. | ||||
On GNU/Linux it is "~/.config". | |||||
*/ | */ | ||||
userApplicationDataDirectory, | userApplicationDataDirectory, | ||||
@@ -819,6 +820,8 @@ public: | |||||
On the Mac it'll be "/Library", on Windows, it could be something like | On the Mac it'll be "/Library", on Windows, it could be something like | ||||
"\Documents and Settings\All Users\Application Data". | "\Documents and Settings\All Users\Application Data". | ||||
On GNU/Linux it is "/opt". | |||||
Depending on the setup, this folder may be read-only. | Depending on the setup, this folder may be read-only. | ||||
*/ | */ | ||||
commonApplicationDataDirectory, | commonApplicationDataDirectory, | ||||
@@ -120,29 +120,22 @@ File File::getSpecialLocation (const SpecialLocationType type) | |||||
return File(); | return File(); | ||||
} | } | ||||
case userDocumentsDirectory: return resolveXDGFolder ("XDG_DOCUMENTS_DIR", "~"); | |||||
case userMusicDirectory: return resolveXDGFolder ("XDG_MUSIC_DIR", "~"); | |||||
case userMoviesDirectory: return resolveXDGFolder ("XDG_VIDEOS_DIR", "~"); | |||||
case userPicturesDirectory: return resolveXDGFolder ("XDG_PICTURES_DIR", "~"); | |||||
case userDocumentsDirectory: return resolveXDGFolder ("XDG_DOCUMENTS_DIR", "~/Documents"); | |||||
case userMusicDirectory: return resolveXDGFolder ("XDG_MUSIC_DIR", "~/Music"); | |||||
case userMoviesDirectory: return resolveXDGFolder ("XDG_VIDEOS_DIR", "~/Videos"); | |||||
case userPicturesDirectory: return resolveXDGFolder ("XDG_PICTURES_DIR", "~/Pictures"); | |||||
case userDesktopDirectory: return resolveXDGFolder ("XDG_DESKTOP_DIR", "~/Desktop"); | case userDesktopDirectory: return resolveXDGFolder ("XDG_DESKTOP_DIR", "~/Desktop"); | ||||
case userApplicationDataDirectory: return resolveXDGFolder ("XDG_CONFIG_HOME", "~"); | |||||
case userApplicationDataDirectory: return resolveXDGFolder ("XDG_CONFIG_HOME", "~/.config"); | |||||
case commonDocumentsDirectory: | case commonDocumentsDirectory: | ||||
case commonApplicationDataDirectory: return File ("/var"); | |||||
case commonApplicationDataDirectory: return File ("/opt"); | |||||
case globalApplicationsDirectory: return File ("/usr"); | case globalApplicationsDirectory: return File ("/usr"); | ||||
case tempDirectory: | case tempDirectory: | ||||
{ | { | ||||
File tmp ("/var/tmp"); | |||||
if (const char* tmpDir = getenv ("TMPDIR")) | |||||
return File (CharPointer_UTF8 (tmpDir)); | |||||
if (! tmp.isDirectory()) | |||||
{ | |||||
tmp = "/tmp"; | |||||
if (! tmp.isDirectory()) | |||||
tmp = File::getCurrentWorkingDirectory(); | |||||
} | |||||
return tmp; | |||||
return File ("/tmp"); | |||||
} | } | ||||
case invokedExecutableFile: | case invokedExecutableFile: | ||||
@@ -53,10 +53,6 @@ | |||||
#import <IOKit/pwr_mgt/IOPMLib.h> | #import <IOKit/pwr_mgt/IOPMLib.h> | ||||
#elif JUCE_LINUX | #elif JUCE_LINUX | ||||
#include <X11/Xlib.h> | |||||
#include <X11/Xresource.h> | |||||
#include <X11/Xutil.h> | |||||
#undef KeyPress | |||||
#include <unistd.h> | #include <unistd.h> | ||||
#endif | #endif | ||||
@@ -90,7 +86,6 @@ namespace juce | |||||
#include "native/juce_win32_Messaging.cpp" | #include "native/juce_win32_Messaging.cpp" | ||||
#elif JUCE_LINUX | #elif JUCE_LINUX | ||||
#include "native/juce_ScopedXLock.h" | |||||
#include "native/juce_linux_Messaging.cpp" | #include "native/juce_linux_Messaging.cpp" | ||||
#elif JUCE_ANDROID | #elif JUCE_ANDROID | ||||
@@ -46,7 +46,6 @@ | |||||
license: ISC | license: ISC | ||||
dependencies: juce_core | dependencies: juce_core | ||||
linuxPackages: x11 | |||||
END_JUCE_MODULE_DECLARATION | END_JUCE_MODULE_DECLARATION | ||||
@@ -80,7 +79,11 @@ namespace juce | |||||
#include "interprocess/juce_InterprocessConnection.h" | #include "interprocess/juce_InterprocessConnection.h" | ||||
#include "interprocess/juce_InterprocessConnectionServer.h" | #include "interprocess/juce_InterprocessConnectionServer.h" | ||||
#include "interprocess/juce_ConnectedChildProcess.h" | #include "interprocess/juce_ConnectedChildProcess.h" | ||||
#include "native/juce_ScopedXLock.h" | |||||
#if JUCE_LINUX | |||||
#include "native/juce_linux_EventLoop.h" | |||||
#endif | |||||
#if JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW && JUCE_WINDOWS | #if JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW && JUCE_WINDOWS | ||||
#include "native/juce_win32_HiddenMessageWindow.h" | #include "native/juce_win32_HiddenMessageWindow.h" | ||||
@@ -1,54 +0,0 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2016 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
Permission to use, copy, modify, and/or distribute this software for any | |||||
purpose with or without fee is hereby granted, provided that the above | |||||
copyright notice and this permission notice appear in all copies. | |||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
OF THIS SOFTWARE. | |||||
----------------------------------------------------------------------------- | |||||
To release a closed-source product which uses other parts of JUCE not | |||||
licensed under the ISC terms, commercial licenses are available: visit | |||||
www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#pragma once | |||||
//============================================================================== | |||||
#if JUCE_LINUX || DOXYGEN | |||||
/** A handy class that uses XLockDisplay and XUnlockDisplay to lock the X server | |||||
using RAII (Only available in Linux!). | |||||
*/ | |||||
class ScopedXLock | |||||
{ | |||||
public: | |||||
/** Creating a ScopedXLock object locks the X display. | |||||
This uses XLockDisplay() to grab the display that Juce is using. | |||||
*/ | |||||
ScopedXLock(); | |||||
/** Deleting a ScopedXLock object unlocks the X display. | |||||
This calls XUnlockDisplay() to release the lock. | |||||
*/ | |||||
~ScopedXLock(); | |||||
}; | |||||
#endif |
@@ -0,0 +1,33 @@ | |||||
#ifndef JUCE_LINUX_EVENTLOOP_H_INCLUDED | |||||
#define JUCE_LINUX_EVENTLOOP_H_INCLUDED | |||||
namespace LinuxEventLoop | |||||
{ | |||||
struct CallbackFunctionBase | |||||
{ | |||||
virtual ~CallbackFunctionBase() {} | |||||
virtual bool operator()(int fd) = 0; | |||||
bool active = true; | |||||
}; | |||||
template <typename FdCallbackFunction> | |||||
struct CallbackFunction : public CallbackFunctionBase | |||||
{ | |||||
FdCallbackFunction callback; | |||||
CallbackFunction (FdCallbackFunction c) : callback (c) {} | |||||
bool operator() (int fd) override { return callback (fd); } | |||||
}; | |||||
template <typename FdCallbackFunction> | |||||
void setWindowSystemFd (int fd, FdCallbackFunction readCallback) | |||||
{ | |||||
setWindowSystemFdInternal (fd, new CallbackFunction<FdCallbackFunction> (readCallback)); | |||||
} | |||||
void removeWindowSystemFd() noexcept; | |||||
void setWindowSystemFdInternal (int fd, CallbackFunctionBase* readCallback) noexcept; | |||||
} | |||||
#endif /* JUCE_LINUX_EVENTLOOP_H_INCLUDED */ |
@@ -28,112 +28,115 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
#if JUCE_DEBUG && ! defined (JUCE_DEBUG_XERRORS) | |||||
#define JUCE_DEBUG_XERRORS 1 | |||||
#endif | |||||
#include <poll.h> | |||||
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); } | |||||
enum FdType { | |||||
INTERNAL_QUEUE_FD, | |||||
WINDOW_SYSTEM_FD, | |||||
FD_COUNT, | |||||
}; | |||||
//============================================================================== | //============================================================================== | ||||
class InternalMessageQueue | class InternalMessageQueue | ||||
{ | { | ||||
public: | public: | ||||
InternalMessageQueue() | InternalMessageQueue() | ||||
: bytesInSocket (0), | |||||
totalEventCount (0) | |||||
: fdCount (1), | |||||
loopCount (0), | |||||
bytesInSocket (0) | |||||
{ | { | ||||
int ret = ::socketpair (AF_LOCAL, SOCK_STREAM, 0, fd); | int ret = ::socketpair (AF_LOCAL, SOCK_STREAM, 0, fd); | ||||
ignoreUnused (ret); jassert (ret == 0); | ignoreUnused (ret); jassert (ret == 0); | ||||
auto internalQueueCb = [this] (int _fd) | |||||
{ | |||||
if (const MessageManager::MessageBase::Ptr msg = this->popNextMessage (_fd)) | |||||
{ | |||||
JUCE_TRY | |||||
{ | |||||
msg->messageCallback(); | |||||
return true; | |||||
} | |||||
JUCE_CATCH_EXCEPTION | |||||
} | |||||
return false; | |||||
}; | |||||
pfds[INTERNAL_QUEUE_FD].fd = getReadHandle(); | |||||
pfds[INTERNAL_QUEUE_FD].events = POLLIN; | |||||
readCallback[INTERNAL_QUEUE_FD] = new LinuxEventLoop::CallbackFunction<decltype(internalQueueCb)> (internalQueueCb); | |||||
} | } | ||||
~InternalMessageQueue() | ~InternalMessageQueue() | ||||
{ | { | ||||
close (fd[0]); | |||||
close (fd[1]); | |||||
close (getReadHandle()); | |||||
close (getWriteHandle()); | |||||
clearSingletonInstance(); | clearSingletonInstance(); | ||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
void postMessage (MessageManager::MessageBase* const msg) | |||||
void postMessage (MessageManager::MessageBase* const msg) noexcept | |||||
{ | { | ||||
const int maxBytesInSocketQueue = 128; | |||||
ScopedLock sl (lock); | ScopedLock sl (lock); | ||||
queue.add (msg); | queue.add (msg); | ||||
const int maxBytesInSocketQueue = 128; | |||||
if (bytesInSocket < maxBytesInSocketQueue) | if (bytesInSocket < maxBytesInSocketQueue) | ||||
{ | { | ||||
++bytesInSocket; | |||||
bytesInSocket++; | |||||
ScopedUnlock ul (lock); | ScopedUnlock ul (lock); | ||||
const unsigned char x = 0xff; | const unsigned char x = 0xff; | ||||
ssize_t bytesWritten = write (fd[0], &x, 1); | |||||
ssize_t bytesWritten = write (getWriteHandle(), &x, 1); | |||||
ignoreUnused (bytesWritten); | ignoreUnused (bytesWritten); | ||||
} | } | ||||
} | } | ||||
bool isEmpty() const | |||||
void setWindowSystemFd (int _fd, LinuxEventLoop::CallbackFunctionBase* _readCallback) | |||||
{ | { | ||||
jassert (fdCount == 1); | |||||
ScopedLock sl (lock); | ScopedLock sl (lock); | ||||
return queue.size() == 0; | |||||
fdCount = 2; | |||||
pfds[WINDOW_SYSTEM_FD].fd = _fd; | |||||
pfds[WINDOW_SYSTEM_FD].events = POLLIN; | |||||
readCallback[WINDOW_SYSTEM_FD] = _readCallback; | |||||
readCallback[WINDOW_SYSTEM_FD]->active = true; | |||||
} | } | ||||
bool dispatchNextEvent() | |||||
void removeWindowSystemFd () | |||||
{ | { | ||||
// This alternates between giving priority to XEvents or internal messages, | |||||
// to keep everything running smoothly.. | |||||
if ((++totalEventCount & 1) != 0) | |||||
return dispatchNextXEvent() || dispatchNextInternalMessage(); | |||||
jassert (fdCount == FD_COUNT); | |||||
ScopedLock sl (lock); | |||||
return dispatchNextInternalMessage() || dispatchNextXEvent(); | |||||
fdCount = 1; | |||||
readCallback[WINDOW_SYSTEM_FD]->active = false; | |||||
} | } | ||||
// Wait for an event (either XEvent, or an internal Message) | |||||
bool sleepUntilEvent (const int timeoutMs) | |||||
bool dispatchNextEvent() noexcept | |||||
{ | { | ||||
if (! isEmpty()) | |||||
return true; | |||||
if (display != nullptr) | |||||
for (int counter = 0; counter < fdCount; counter++) | |||||
{ | { | ||||
ScopedXLock xlock; | |||||
if (XPending (display)) | |||||
return true; | |||||
const int i = loopCount++; | |||||
loopCount %= fdCount; | |||||
if (readCallback[i] != nullptr && readCallback[i]->active) | |||||
{ | |||||
if ((*readCallback[i]) (pfds[i].fd)) | |||||
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); | |||||
} | |||||
return false; | |||||
} | |||||
const int ret = select (fdmax + 1, &readset, 0, 0, &tv); | |||||
return (ret > 0); // ret <= 0 if error or timeout | |||||
bool sleepUntilEvent (const int timeoutMs) | |||||
{ | |||||
const int pnum = poll (pfds, static_cast<nfds_t> (fdCount), timeoutMs); | |||||
return (pnum > 0); | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
@@ -143,46 +146,16 @@ private: | |||||
CriticalSection lock; | CriticalSection lock; | ||||
ReferenceCountedArray <MessageManager::MessageBase> queue; | ReferenceCountedArray <MessageManager::MessageBase> queue; | ||||
int fd[2]; | int fd[2]; | ||||
pollfd pfds[FD_COUNT]; | |||||
ScopedPointer<LinuxEventLoop::CallbackFunctionBase> readCallback[FD_COUNT]; | |||||
int fdCount; | |||||
int loopCount; | |||||
int bytesInSocket; | 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; | |||||
} | |||||
int getWriteHandle() const noexcept { return fd[0]; } | |||||
int getReadHandle() const noexcept { return fd[1]; } | |||||
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() | |||||
MessageManager::MessageBase::Ptr popNextMessage (int _fd) noexcept | |||||
{ | { | ||||
const ScopedLock sl (lock); | const ScopedLock sl (lock); | ||||
@@ -192,27 +165,12 @@ private: | |||||
const ScopedUnlock ul (lock); | const ScopedUnlock ul (lock); | ||||
unsigned char x; | unsigned char x; | ||||
ssize_t numBytes = read (fd[1], &x, 1); | |||||
ssize_t numBytes = read (_fd, &x, 1); | |||||
ignoreUnused (numBytes); | ignoreUnused (numBytes); | ||||
} | } | ||||
return queue.removeAndReturn (0); | 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) | juce_ImplementSingleton_SingleThreaded (InternalMessageQueue) | ||||
@@ -221,57 +179,7 @@ juce_ImplementSingleton_SingleThreaded (InternalMessageQueue) | |||||
//============================================================================== | //============================================================================== | ||||
namespace LinuxErrorHandling | namespace LinuxErrorHandling | ||||
{ | { | ||||
static bool errorOccurred = false; | |||||
static bool keyboardBreakOccurred = 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) | |||||
{ | |||||
ignoreUnused (display, 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) | void keyboardBreakSignalHandler (int sig) | ||||
@@ -297,78 +205,25 @@ void MessageManager::doPlatformSpecificInitialisation() | |||||
{ | { | ||||
if (JUCEApplicationBase::isStandaloneApp()) | 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(); | LinuxErrorHandling::installKeyboardBreakHandler(); | ||||
} | } | ||||
// Create the internal message queue | // 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); | |||||
} | |||||
InternalMessageQueue* queue = InternalMessageQueue::getInstance(); | |||||
ignoreUnused (queue); | |||||
} | } | ||||
void MessageManager::doPlatformSpecificShutdown() | void MessageManager::doPlatformSpecificShutdown() | ||||
{ | { | ||||
InternalMessageQueue::deleteInstance(); | InternalMessageQueue::deleteInstance(); | ||||
if (display != nullptr && ! LinuxErrorHandling::errorOccurred) | |||||
{ | |||||
XDestroyWindow (display, juce_messageWindowHandle); | |||||
juce_messageWindowHandle = 0; | |||||
display = nullptr; | |||||
LinuxErrorHandling::removeXErrorHandlers(); | |||||
} | |||||
} | } | ||||
bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message) | bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message) | ||||
{ | { | ||||
if (! LinuxErrorHandling::errorOccurred) | |||||
if (InternalMessageQueue* queue = InternalMessageQueue::getInstanceWithoutCreating()) | |||||
{ | { | ||||
if (InternalMessageQueue* queue = InternalMessageQueue::getInstanceWithoutCreating()) | |||||
{ | |||||
queue->postMessage (message); | |||||
return true; | |||||
} | |||||
queue->postMessage (message); | |||||
return true; | |||||
} | } | ||||
return false; | return false; | ||||
@@ -382,29 +237,38 @@ void MessageManager::broadcastMessage (const String& /* value */) | |||||
// this function expects that it will NEVER be called simultaneously for two concurrent threads | // this function expects that it will NEVER be called simultaneously for two concurrent threads | ||||
bool MessageManager::dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages) | bool MessageManager::dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages) | ||||
{ | { | ||||
while (! LinuxErrorHandling::errorOccurred) | |||||
for (;;) | |||||
{ | { | ||||
if (LinuxErrorHandling::keyboardBreakOccurred) | if (LinuxErrorHandling::keyboardBreakOccurred) | ||||
{ | |||||
LinuxErrorHandling::errorOccurred = true; | |||||
if (JUCEApplicationBase::isStandaloneApp()) | |||||
Process::terminate(); | |||||
break; | |||||
} | |||||
JUCEApplicationBase::getInstance()->quit(); | |||||
if (InternalMessageQueue* queue = InternalMessageQueue::getInstanceWithoutCreating()) | if (InternalMessageQueue* queue = InternalMessageQueue::getInstanceWithoutCreating()) | ||||
{ | { | ||||
if (queue->dispatchNextEvent()) | if (queue->dispatchNextEvent()) | ||||
return true; | |||||
if (returnIfNoPendingMessages) | |||||
break; | break; | ||||
else if (returnIfNoPendingMessages) | |||||
return false; | |||||
// wait for 2000ms for next events if necessary | |||||
queue->sleepUntilEvent (2000); | queue->sleepUntilEvent (2000); | ||||
} | } | ||||
} | } | ||||
return false; | |||||
return true; | |||||
} | |||||
//============================================================================== | |||||
void LinuxEventLoop::setWindowSystemFdInternal (int fd, LinuxEventLoop::CallbackFunctionBase* readCallback) noexcept | |||||
{ | |||||
if (InternalMessageQueue* queue = InternalMessageQueue::getInstanceWithoutCreating()) | |||||
queue->setWindowSystemFd (fd, readCallback); | |||||
} | |||||
void LinuxEventLoop::removeWindowSystemFd() noexcept | |||||
{ | |||||
if (InternalMessageQueue* queue = InternalMessageQueue::getInstanceWithoutCreating()) | |||||
queue->removeWindowSystemFd(); | |||||
} | } |
@@ -174,7 +174,7 @@ void MessageManager::broadcastMessage (const String& value) | |||||
data.lpData = (void*) localCopy.toUTF32().getAddress(); | data.lpData = (void*) localCopy.toUTF32().getAddress(); | ||||
DWORD_PTR result; | DWORD_PTR result; | ||||
SendMessageTimeout (windows.getUnchecked(i), WM_COPYDATA, | |||||
SendMessageTimeout (windows.getUnchecked (i), WM_COPYDATA, | |||||
(WPARAM) juce_messageWindowHandle, | (WPARAM) juce_messageWindowHandle, | ||||
(LPARAM) &data, | (LPARAM) &data, | ||||
SMTO_BLOCK | SMTO_ABORTIFHUNG, 8000, &result); | SMTO_BLOCK | SMTO_ABORTIFHUNG, 8000, &result); | ||||
@@ -282,8 +282,9 @@ extern bool juce_areThereAnyAlwaysOnTopWindows(); | |||||
#include "native/juce_win32_FileChooser.cpp" | #include "native/juce_win32_FileChooser.cpp" | ||||
#elif JUCE_LINUX | #elif JUCE_LINUX | ||||
#include "native/juce_linux_Clipboard.cpp" | |||||
#include "native/juce_linux_Windowing.cpp" | |||||
#include "native/juce_linux_X11.cpp" | |||||
#include "native/juce_linux_X11_Clipboard.cpp" | |||||
#include "native/juce_linux_X11_Windowing.cpp" | |||||
#include "native/juce_linux_FileChooser.cpp" | #include "native/juce_linux_FileChooser.cpp" | ||||
#elif JUCE_ANDROID | #elif JUCE_ANDROID | ||||
@@ -281,6 +281,10 @@ class FlexBox; | |||||
#include "lookandfeel/juce_LookAndFeel_V1.h" | #include "lookandfeel/juce_LookAndFeel_V1.h" | ||||
#include "lookandfeel/juce_LookAndFeel_V3.h" | #include "lookandfeel/juce_LookAndFeel_V3.h" | ||||
#if JUCE_LINUX | |||||
#include "native/juce_linux_X11.h" | |||||
#endif | |||||
// these classes are C++11-only | // these classes are C++11-only | ||||
#if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS && JUCE_COMPILER_SUPPORTS_LAMBDAS | #if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS && JUCE_COMPILER_SUPPORTS_LAMBDAS | ||||
#include "layout/juce_FlexItem.h" | #include "layout/juce_FlexItem.h" | ||||
@@ -0,0 +1,312 @@ | |||||
typedef void (*WindowMessageReceiveCallback) (XEvent&); | |||||
WindowMessageReceiveCallback dispatchWindowMessage = nullptr; | |||||
typedef void (*SelectionRequestCallback) (XSelectionRequestEvent&); | |||||
SelectionRequestCallback handleSelectionRequest = nullptr; | |||||
::Window juce_messageWindowHandle; | |||||
XContext windowHandleXContext; | |||||
//============================================================================== | |||||
namespace X11ErrorHandling | |||||
{ | |||||
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(); | |||||
// set this somewhere | |||||
// errorOccurred = true; | |||||
return 0; | |||||
} | |||||
int errorHandler (::Display* display, XErrorEvent* event) | |||||
{ | |||||
ignoreUnused (display, 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() | |||||
{ | |||||
XSetIOErrorHandler (oldIOErrorHandler); | |||||
oldIOErrorHandler = 0; | |||||
XSetErrorHandler (oldErrorHandler); | |||||
oldErrorHandler = 0; | |||||
} | |||||
} | |||||
//============================================================================== | |||||
XWindowSystem::XWindowSystem() noexcept | |||||
: display (nullptr) | |||||
{ | |||||
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; | |||||
} | |||||
X11ErrorHandling::installXErrorHandlers(); | |||||
} | |||||
} | |||||
XWindowSystem::~XWindowSystem() noexcept | |||||
{ | |||||
if (JUCEApplicationBase::isStandaloneApp()) | |||||
X11ErrorHandling::removeXErrorHandlers(); | |||||
clearSingletonInstance(); | |||||
} | |||||
::Display* XWindowSystem::displayRef() noexcept | |||||
{ | |||||
if (++displayCount - 1 == 0) | |||||
{ | |||||
String displayName (getenv ("DISPLAY")); | |||||
if (displayName.isEmpty()) | |||||
displayName = ":0.0"; | |||||
display = XOpenDisplay (displayName.toUTF8()); | |||||
initialiseXDisplay(); | |||||
} | |||||
return this->display; | |||||
} | |||||
::Display* XWindowSystem::displayUnref() noexcept | |||||
{ | |||||
jassert (display != nullptr); | |||||
jassert (displayCount.get() > 0); | |||||
if (--displayCount == 0) | |||||
{ | |||||
destroyXDisplay(); | |||||
XCloseDisplay (display); | |||||
display = nullptr; | |||||
} | |||||
return display; | |||||
} | |||||
void XWindowSystem::initialiseXDisplay() noexcept | |||||
{ | |||||
// This is fatal! Print error and closedown | |||||
if (display == nullptr) | |||||
{ | |||||
Logger::outputDebugString ("Failed to connect to the X Server."); | |||||
Process::terminate(); | |||||
} | |||||
// 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); | |||||
XSync (display, False); | |||||
// Setup input event handler | |||||
int fd = XConnectionNumber (display); | |||||
LinuxEventLoop::setWindowSystemFd | |||||
(fd, | |||||
[this](int /*fd*/) { | |||||
do | |||||
{ | |||||
XEvent evt; | |||||
{ | |||||
ScopedXLock xlock (display); | |||||
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); | |||||
} while (display != nullptr); | |||||
return false; | |||||
}); | |||||
} | |||||
void XWindowSystem::destroyXDisplay() noexcept | |||||
{ | |||||
ScopedXLock xlock (display); | |||||
XDestroyWindow (display, juce_messageWindowHandle); | |||||
juce_messageWindowHandle = 0; | |||||
XSync (display, True); | |||||
LinuxEventLoop::removeWindowSystemFd(); | |||||
} | |||||
juce_ImplementSingleton (XWindowSystem) | |||||
//============================================================================== | |||||
ScopedXDisplay::ScopedXDisplay() | |||||
{ | |||||
display = XWindowSystem::getInstance()->displayRef(); | |||||
} | |||||
ScopedXDisplay::~ScopedXDisplay() | |||||
{ | |||||
XWindowSystem::getInstance()->displayUnref(); | |||||
} | |||||
::Display* ScopedXDisplay::get() | |||||
{ | |||||
return display; | |||||
} | |||||
//============================================================================== | |||||
ScopedXLock::ScopedXLock(::Display* _display) | |||||
: display (_display) | |||||
{ | |||||
if (display != nullptr) XLockDisplay (display); | |||||
} | |||||
ScopedXLock::~ScopedXLock() | |||||
{ | |||||
if (display != nullptr) XUnlockDisplay (display); | |||||
} | |||||
//============================================================================== | |||||
Atoms::Atoms(::Display* display) | |||||
{ | |||||
protocols = getIfExists (display, "WM_PROTOCOLS"); | |||||
protocolList [TAKE_FOCUS] = getIfExists (display, "WM_TAKE_FOCUS"); | |||||
protocolList [DELETE_WINDOW] = getIfExists (display, "WM_DELETE_WINDOW"); | |||||
protocolList [PING] = getIfExists (display, "_NET_WM_PING"); | |||||
changeState = getIfExists (display, "WM_CHANGE_STATE"); | |||||
state = getIfExists (display, "WM_STATE"); | |||||
userTime = getCreating (display, "_NET_WM_USER_TIME"); | |||||
activeWin = getCreating (display, "_NET_ACTIVE_WINDOW"); | |||||
pid = getCreating (display, "_NET_WM_PID"); | |||||
windowType = getIfExists (display, "_NET_WM_WINDOW_TYPE"); | |||||
windowState = getIfExists (display, "_NET_WM_STATE"); | |||||
XdndAware = getCreating (display, "XdndAware"); | |||||
XdndEnter = getCreating (display, "XdndEnter"); | |||||
XdndLeave = getCreating (display, "XdndLeave"); | |||||
XdndPosition = getCreating (display, "XdndPosition"); | |||||
XdndStatus = getCreating (display, "XdndStatus"); | |||||
XdndDrop = getCreating (display, "XdndDrop"); | |||||
XdndFinished = getCreating (display, "XdndFinished"); | |||||
XdndSelection = getCreating (display, "XdndSelection"); | |||||
XdndTypeList = getCreating (display, "XdndTypeList"); | |||||
XdndActionList = getCreating (display, "XdndActionList"); | |||||
XdndActionCopy = getCreating (display, "XdndActionCopy"); | |||||
XdndActionPrivate = getCreating (display, "XdndActionPrivate"); | |||||
XdndActionDescription = getCreating (display, "XdndActionDescription"); | |||||
allowedMimeTypes[0] = getCreating (display, "UTF8_STRING"); | |||||
allowedMimeTypes[1] = getCreating (display, "text/plain;charset=utf-8"); | |||||
allowedMimeTypes[2] = getCreating (display, "text/plain"); | |||||
allowedMimeTypes[3] = getCreating (display, "text/uri-list"); | |||||
allowedActions[0] = getCreating (display, "XdndActionMove"); | |||||
allowedActions[1] = XdndActionCopy; | |||||
allowedActions[2] = getCreating (display, "XdndActionLink"); | |||||
allowedActions[3] = getCreating (display, "XdndActionAsk"); | |||||
allowedActions[4] = XdndActionPrivate; | |||||
} | |||||
Atom Atoms::getIfExists (::Display* display, const char* name) | |||||
{ | |||||
return XInternAtom (display, name, True); | |||||
} | |||||
Atom Atoms::getCreating (::Display* display, const char* name) | |||||
{ | |||||
return XInternAtom (display, name, False); | |||||
} | |||||
String Atoms::getName (::Display* display, const Atom atom) | |||||
{ | |||||
if (atom == None) | |||||
return "None"; | |||||
return String (XGetAtomName (display, atom)); | |||||
} | |||||
bool Atoms::isMimeTypeFile (::Display* display, const Atom atom) | |||||
{ | |||||
return getName (display, atom).equalsIgnoreCase ("text/uri-list"); | |||||
} | |||||
const unsigned long Atoms::DndVersion = 3; | |||||
//============================================================================== | |||||
GetXProperty::GetXProperty (::Display* display, Window window, Atom atom, | |||||
long offset, long length, bool shouldDelete, | |||||
Atom requestedType) | |||||
: data (nullptr) | |||||
{ | |||||
success = (XGetWindowProperty (display, window, atom, offset, length, | |||||
(Bool) shouldDelete, requestedType, &actualType, | |||||
&actualFormat, &numItems, &bytesLeft, &data) == Success) | |||||
&& data != nullptr; | |||||
} | |||||
GetXProperty::~GetXProperty() | |||||
{ | |||||
if (data != nullptr) | |||||
XFree (data); | |||||
} |
@@ -0,0 +1,114 @@ | |||||
#ifndef JUCE_XWINDOWSYSTEM_H_INCLUDED | |||||
#define JUCE_XWINDOWSYSTEM_H_INCLUDED | |||||
// Hack to forward declare _XDisplay outside the | |||||
// juce namespace | |||||
} | |||||
struct _XDisplay; | |||||
#define ATOM_TYPE unsigned long | |||||
#define WINDOW_TYPE unsigned long | |||||
namespace juce { | |||||
//============================================================================== | |||||
class XWindowSystem | |||||
{ | |||||
public: | |||||
::_XDisplay* displayRef() noexcept; | |||||
::_XDisplay* displayUnref() noexcept; | |||||
juce_DeclareSingleton (XWindowSystem, false) | |||||
private: | |||||
::_XDisplay* display; | |||||
Atomic<int> displayCount; | |||||
XWindowSystem() noexcept; | |||||
~XWindowSystem() noexcept; | |||||
void initialiseXDisplay() noexcept; | |||||
void destroyXDisplay() noexcept; | |||||
}; | |||||
//============================================================================== | |||||
class ScopedXDisplay | |||||
{ | |||||
public: | |||||
ScopedXDisplay(); | |||||
~ScopedXDisplay(); | |||||
::_XDisplay* get(); | |||||
private: | |||||
::_XDisplay* display; | |||||
}; | |||||
/** A handy class that uses XLockDisplay and XUnlockDisplay to lock the X server | |||||
using RAII (Only available in Linux!). | |||||
*/ | |||||
class ScopedXLock | |||||
{ | |||||
public: | |||||
/** Creating a ScopedXLock object locks the X display. | |||||
This uses XLockDisplay() to grab the display that Juce is using. | |||||
*/ | |||||
ScopedXLock (::_XDisplay* _display); | |||||
/** Deleting a ScopedXLock object unlocks the X display. | |||||
This calls XUnlockDisplay() to release the lock. | |||||
*/ | |||||
~ScopedXLock(); | |||||
private: | |||||
// defined in juce_linux_X11.h | |||||
::_XDisplay* display; | |||||
}; | |||||
//============================================================================== | |||||
struct Atoms | |||||
{ | |||||
Atoms(::_XDisplay* display); | |||||
enum ProtocolItems | |||||
{ | |||||
TAKE_FOCUS = 0, | |||||
DELETE_WINDOW = 1, | |||||
PING = 2 | |||||
}; | |||||
ATOM_TYPE protocols, protocolList[3], changeState, state, userTime, | |||||
activeWin, pid, windowType, windowState, | |||||
XdndAware, XdndEnter, XdndLeave, XdndPosition, XdndStatus, | |||||
XdndDrop, XdndFinished, XdndSelection, XdndTypeList, XdndActionList, | |||||
XdndActionDescription, XdndActionCopy, XdndActionPrivate, | |||||
allowedActions[5], | |||||
allowedMimeTypes[4]; | |||||
static const unsigned long DndVersion; | |||||
static ATOM_TYPE getIfExists (::_XDisplay* display, const char* name); | |||||
static ATOM_TYPE getCreating (::_XDisplay* display, const char* name); | |||||
static String getName (::_XDisplay* display, const ATOM_TYPE atom); | |||||
static bool isMimeTypeFile (::_XDisplay* display, const ATOM_TYPE atom); | |||||
}; | |||||
//============================================================================== | |||||
struct GetXProperty | |||||
{ | |||||
GetXProperty (::_XDisplay* display, WINDOW_TYPE window, ATOM_TYPE atom, | |||||
long offset, long length, bool shouldDelete, | |||||
ATOM_TYPE requestedType); | |||||
~GetXProperty(); | |||||
bool success; | |||||
unsigned char* data; | |||||
unsigned long numItems, bytesLeft; | |||||
ATOM_TYPE actualType; | |||||
int actualFormat; | |||||
}; | |||||
#undef ATOM_TYPE | |||||
#endif // JUCE_XWINDOWSYSTEM_H_INCLUDED |
@@ -22,7 +22,6 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
extern ::Display* display; | |||||
extern ::Window juce_messageWindowHandle; | extern ::Window juce_messageWindowHandle; | ||||
namespace ClipboardHelpers | namespace ClipboardHelpers | ||||
@@ -33,7 +32,7 @@ namespace ClipboardHelpers | |||||
static Atom atom_TARGETS; | static Atom atom_TARGETS; | ||||
//============================================================================== | //============================================================================== | ||||
static void initSelectionAtoms() | |||||
static void initSelectionAtoms (::Display* display) | |||||
{ | { | ||||
static bool isInitialised = false; | static bool isInitialised = false; | ||||
@@ -41,16 +40,16 @@ namespace ClipboardHelpers | |||||
{ | { | ||||
isInitialised = true; | isInitialised = true; | ||||
atom_UTF8_STRING = XInternAtom (display, "UTF8_STRING", False); | |||||
atom_CLIPBOARD = XInternAtom (display, "CLIPBOARD", False); | |||||
atom_TARGETS = XInternAtom (display, "TARGETS", False); | |||||
atom_UTF8_STRING = Atoms::getCreating (display, "UTF8_STRING"); | |||||
atom_CLIPBOARD = Atoms::getCreating (display, "CLIPBOARD"); | |||||
atom_TARGETS = Atoms::getCreating (display, "TARGETS"); | |||||
} | } | ||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
// Read the content of a window property as either a locale-dependent string or an utf8 string | // Read the content of a window property as either a locale-dependent string or an utf8 string | ||||
// works only for strings shorter than 1000000 bytes | // works only for strings shorter than 1000000 bytes | ||||
static String readWindowProperty (Window window, Atom prop) | |||||
static String readWindowProperty (::Display* display, Window window, Atom prop) | |||||
{ | { | ||||
String returnData; | String returnData; | ||||
@@ -86,7 +85,8 @@ namespace ClipboardHelpers | |||||
//============================================================================== | //============================================================================== | ||||
// Send a SelectionRequest to the window owning the selection and waits for its answer (with a timeout) */ | // Send a SelectionRequest to the window owning the selection and waits for its answer (with a timeout) */ | ||||
static bool requestSelectionContent (String& selectionContent, Atom selection, Atom requestedFormat) | |||||
static bool requestSelectionContent (::Display* display, String& selectionContent, | |||||
Atom selection, Atom requestedFormat) | |||||
{ | { | ||||
Atom property_name = XInternAtom (display, "JUCE_SEL", false); | Atom property_name = XInternAtom (display, "JUCE_SEL", false); | ||||
@@ -107,7 +107,7 @@ namespace ClipboardHelpers | |||||
{ | { | ||||
jassert (event.xselection.requestor == juce_messageWindowHandle); | jassert (event.xselection.requestor == juce_messageWindowHandle); | ||||
selectionContent = readWindowProperty (event.xselection.requestor, | |||||
selectionContent = readWindowProperty (display, event.xselection.requestor, | |||||
event.xselection.property); | event.xselection.property); | ||||
return true; | return true; | ||||
} | } | ||||
@@ -128,69 +128,66 @@ namespace ClipboardHelpers | |||||
// Called from the event loop in juce_linux_Messaging in response to SelectionRequest events | // Called from the event loop in juce_linux_Messaging in response to SelectionRequest events | ||||
static void handleSelection (XSelectionRequestEvent& evt) | static void handleSelection (XSelectionRequestEvent& evt) | ||||
{ | { | ||||
if (display != nullptr) | |||||
ClipboardHelpers::initSelectionAtoms (evt.display); | |||||
// the selection content is sent to the target window as a window property | |||||
XSelectionEvent reply; | |||||
reply.type = SelectionNotify; | |||||
reply.display = evt.display; | |||||
reply.requestor = evt.requestor; | |||||
reply.selection = evt.selection; | |||||
reply.target = evt.target; | |||||
reply.property = None; // == "fail" | |||||
reply.time = evt.time; | |||||
HeapBlock<char> data; | |||||
int propertyFormat = 0; | |||||
size_t numDataItems = 0; | |||||
if (evt.selection == XA_PRIMARY || evt.selection == ClipboardHelpers::atom_CLIPBOARD) | |||||
{ | { | ||||
ClipboardHelpers::initSelectionAtoms(); | |||||
// the selection content is sent to the target window as a window property | |||||
XSelectionEvent reply; | |||||
reply.type = SelectionNotify; | |||||
reply.display = evt.display; | |||||
reply.requestor = evt.requestor; | |||||
reply.selection = evt.selection; | |||||
reply.target = evt.target; | |||||
reply.property = None; // == "fail" | |||||
reply.time = evt.time; | |||||
HeapBlock<char> data; | |||||
int propertyFormat = 0; | |||||
size_t numDataItems = 0; | |||||
if (evt.selection == XA_PRIMARY || evt.selection == ClipboardHelpers::atom_CLIPBOARD) | |||||
if (evt.target == XA_STRING || evt.target == ClipboardHelpers::atom_UTF8_STRING) | |||||
{ | { | ||||
if (evt.target == XA_STRING || evt.target == ClipboardHelpers::atom_UTF8_STRING) | |||||
{ | |||||
// translate to utf8 | |||||
numDataItems = ClipboardHelpers::localClipboardContent.getNumBytesAsUTF8() + 1; | |||||
data.calloc (numDataItems + 1); | |||||
ClipboardHelpers::localClipboardContent.copyToUTF8 (data, numDataItems); | |||||
propertyFormat = 8; // bits/item | |||||
} | |||||
else if (evt.target == ClipboardHelpers::atom_TARGETS) | |||||
{ | |||||
// another application wants to know what we are able to send | |||||
numDataItems = 2; | |||||
propertyFormat = 32; // atoms are 32-bit | |||||
data.calloc (numDataItems * 4); | |||||
Atom* atoms = reinterpret_cast<Atom*> (data.getData()); | |||||
atoms[0] = ClipboardHelpers::atom_UTF8_STRING; | |||||
atoms[1] = XA_STRING; | |||||
evt.target = XA_ATOM; | |||||
} | |||||
// translate to utf8 | |||||
numDataItems = ClipboardHelpers::localClipboardContent.getNumBytesAsUTF8() + 1; | |||||
data.calloc (numDataItems + 1); | |||||
ClipboardHelpers::localClipboardContent.copyToUTF8 (data, numDataItems); | |||||
propertyFormat = 8; // bits/item | |||||
} | } | ||||
else | |||||
else if (evt.target == ClipboardHelpers::atom_TARGETS) | |||||
{ | { | ||||
DBG ("requested unsupported clipboard"); | |||||
// another application wants to know what we are able to send | |||||
numDataItems = 2; | |||||
propertyFormat = 32; // atoms are 32-bit | |||||
data.calloc (numDataItems * 4); | |||||
Atom* atoms = reinterpret_cast<Atom*> (data.getData()); | |||||
atoms[0] = ClipboardHelpers::atom_UTF8_STRING; | |||||
atoms[1] = XA_STRING; | |||||
evt.target = XA_ATOM; | |||||
} | } | ||||
} | |||||
else | |||||
{ | |||||
DBG ("requested unsupported clipboard"); | |||||
} | |||||
if (data != nullptr) | |||||
{ | |||||
const size_t maxReasonableSelectionSize = 1000000; | |||||
if (data != nullptr) | |||||
{ | |||||
const size_t maxReasonableSelectionSize = 1000000; | |||||
// for very big chunks of data, we should use the "INCR" protocol , which is a pain in the *ss | |||||
if (evt.property != None && numDataItems < maxReasonableSelectionSize) | |||||
{ | |||||
XChangeProperty (evt.display, evt.requestor, | |||||
evt.property, evt.target, | |||||
propertyFormat /* 8 or 32 */, PropModeReplace, | |||||
reinterpret_cast<const unsigned char*> (data.getData()), (int) numDataItems); | |||||
reply.property = evt.property; // " == success" | |||||
} | |||||
// for very big chunks of data, we should use the "INCR" protocol , which is a pain in the *ss | |||||
if (evt.property != None && numDataItems < maxReasonableSelectionSize) | |||||
{ | |||||
XChangeProperty (evt.display, evt.requestor, | |||||
evt.property, evt.target, | |||||
propertyFormat /* 8 or 32 */, PropModeReplace, | |||||
reinterpret_cast<const unsigned char*> (data.getData()), (int) numDataItems); | |||||
reply.property = evt.property; // " == success" | |||||
} | } | ||||
XSendEvent (evt.display, evt.requestor, 0, NoEventMask, (XEvent*) &reply); | |||||
} | } | ||||
XSendEvent (evt.display, evt.requestor, 0, NoEventMask, (XEvent*) &reply); | |||||
} | } | ||||
} | } | ||||
@@ -211,9 +208,12 @@ static ClipboardCallbackInitialiser clipboardInitialiser; | |||||
//============================================================================== | //============================================================================== | ||||
void SystemClipboard::copyTextToClipboard (const String& clipText) | void SystemClipboard::copyTextToClipboard (const String& clipText) | ||||
{ | { | ||||
ScopedXDisplay xDisplay; | |||||
::Display* display = xDisplay.get(); | |||||
if (display != nullptr) | if (display != nullptr) | ||||
{ | { | ||||
ClipboardHelpers::initSelectionAtoms(); | |||||
ClipboardHelpers::initSelectionAtoms (display); | |||||
ClipboardHelpers::localClipboardContent = clipText; | ClipboardHelpers::localClipboardContent = clipText; | ||||
XSetSelectionOwner (display, XA_PRIMARY, juce_messageWindowHandle, CurrentTime); | XSetSelectionOwner (display, XA_PRIMARY, juce_messageWindowHandle, CurrentTime); | ||||
@@ -224,10 +224,12 @@ void SystemClipboard::copyTextToClipboard (const String& clipText) | |||||
String SystemClipboard::getTextFromClipboard() | String SystemClipboard::getTextFromClipboard() | ||||
{ | { | ||||
String content; | String content; | ||||
ScopedXDisplay xDisplay; | |||||
::Display* display = xDisplay.get(); | |||||
if (display != nullptr) | if (display != nullptr) | ||||
{ | { | ||||
ClipboardHelpers::initSelectionAtoms(); | |||||
ClipboardHelpers::initSelectionAtoms (display); | |||||
/* 1) try to read from the "CLIPBOARD" selection first (the "high | /* 1) try to read from the "CLIPBOARD" selection first (the "high | ||||
level" clipboard that is supposed to be filled by ctrl-C | level" clipboard that is supposed to be filled by ctrl-C | ||||
@@ -256,12 +258,14 @@ String SystemClipboard::getTextFromClipboard() | |||||
else | else | ||||
{ | { | ||||
// first try: we want an utf8 string | // first try: we want an utf8 string | ||||
bool ok = ClipboardHelpers::requestSelectionContent (content, selection, ClipboardHelpers::atom_UTF8_STRING); | |||||
bool ok = ClipboardHelpers::requestSelectionContent (display, content, | |||||
selection, ClipboardHelpers::atom_UTF8_STRING); | |||||
if (! ok) | if (! ok) | ||||
{ | { | ||||
// second chance, ask for a good old locale-dependent string .. | // second chance, ask for a good old locale-dependent string .. | ||||
ok = ClipboardHelpers::requestSelectionContent (content, selection, XA_STRING); | |||||
ok = ClipboardHelpers::requestSelectionContent (display, content, | |||||
selection, XA_STRING); | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -124,9 +124,9 @@ namespace juce | |||||
//============================================================================== | //============================================================================== | ||||
#elif JUCE_LINUX | #elif JUCE_LINUX | ||||
#if JUCE_WEB_BROWSER | #if JUCE_WEB_BROWSER | ||||
#include "native/juce_linux_WebBrowserComponent.cpp" | |||||
#include "native/juce_linux_X11_WebBrowserComponent.cpp" | |||||
#endif | #endif | ||||
#include "native/juce_linux_SystemTrayIcon.cpp" | |||||
#include "native/juce_linux_X11_SystemTrayIcon.cpp" | |||||
//============================================================================== | //============================================================================== | ||||
#elif JUCE_ANDROID | #elif JUCE_ANDROID | ||||
@@ -22,22 +22,23 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
extern ::Display* display; | |||||
//============================================================================== | //============================================================================== | ||||
class SystemTrayIconComponent::Pimpl | class SystemTrayIconComponent::Pimpl | ||||
{ | { | ||||
public: | public: | ||||
Pimpl (const Image& im, Window windowH) : image (im) | Pimpl (const Image& im, Window windowH) : image (im) | ||||
{ | { | ||||
ScopedXLock xlock; | |||||
ScopedXDisplay xDisplay; | |||||
::Display* display = xDisplay.get(); | |||||
ScopedXLock xlock (display); | |||||
Screen* const screen = XDefaultScreenOfDisplay (display); | Screen* const screen = XDefaultScreenOfDisplay (display); | ||||
const int screenNumber = XScreenNumberOfScreen (screen); | const int screenNumber = XScreenNumberOfScreen (screen); | ||||
String screenAtom ("_NET_SYSTEM_TRAY_S"); | String screenAtom ("_NET_SYSTEM_TRAY_S"); | ||||
screenAtom << screenNumber; | screenAtom << screenNumber; | ||||
Atom selectionAtom = XInternAtom (display, screenAtom.toUTF8(), false); | |||||
Atom selectionAtom = Atoms::getCreating (display, screenAtom.toUTF8()); | |||||
XGrabServer (display); | XGrabServer (display); | ||||
Window managerWin = XGetSelectionOwner (display, selectionAtom); | Window managerWin = XGetSelectionOwner (display, selectionAtom); | ||||
@@ -53,7 +54,7 @@ public: | |||||
XEvent ev = { 0 }; | XEvent ev = { 0 }; | ||||
ev.xclient.type = ClientMessage; | ev.xclient.type = ClientMessage; | ||||
ev.xclient.window = managerWin; | ev.xclient.window = managerWin; | ||||
ev.xclient.message_type = XInternAtom (display, "_NET_SYSTEM_TRAY_OPCODE", False); | |||||
ev.xclient.message_type = Atoms::getCreating (display, "_NET_SYSTEM_TRAY_OPCODE"); | |||||
ev.xclient.format = 32; | ev.xclient.format = 32; | ||||
ev.xclient.data.l[0] = CurrentTime; | ev.xclient.data.l[0] = CurrentTime; | ||||
ev.xclient.data.l[1] = 0 /*SYSTEM_TRAY_REQUEST_DOCK*/; | ev.xclient.data.l[1] = 0 /*SYSTEM_TRAY_REQUEST_DOCK*/; | ||||
@@ -67,11 +68,11 @@ public: | |||||
// For older KDE's ... | // For older KDE's ... | ||||
long atomData = 1; | long atomData = 1; | ||||
Atom trayAtom = XInternAtom (display, "KWM_DOCKWINDOW", false); | |||||
Atom trayAtom = Atoms::getCreating (display, "KWM_DOCKWINDOW"); | |||||
XChangeProperty (display, windowH, trayAtom, trayAtom, 32, PropModeReplace, (unsigned char*) &atomData, 1); | XChangeProperty (display, windowH, trayAtom, trayAtom, 32, PropModeReplace, (unsigned char*) &atomData, 1); | ||||
// For more recent KDE's... | // For more recent KDE's... | ||||
trayAtom = XInternAtom (display, "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR", false); | |||||
trayAtom = Atoms::getCreating (display, "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR"); | |||||
XChangeProperty (display, windowH, trayAtom, XA_WINDOW, 32, PropModeReplace, (unsigned char*) &windowH, 1); | XChangeProperty (display, windowH, trayAtom, XA_WINDOW, 32, PropModeReplace, (unsigned char*) &windowH, 1); | ||||
// A minimum size must be specified for GNOME and Xfce, otherwise the icon is displayed with a width of 1 | // A minimum size must be specified for GNOME and Xfce, otherwise the icon is displayed with a width of 1 |
@@ -208,7 +208,7 @@ private: | |||||
#include "native/juce_OpenGL_win32.h" | #include "native/juce_OpenGL_win32.h" | ||||
#elif JUCE_LINUX | #elif JUCE_LINUX | ||||
#include "native/juce_OpenGL_linux.h" | |||||
#include "native/juce_OpenGL_linux_X11.h" | |||||
#elif JUCE_ANDROID | #elif JUCE_ANDROID | ||||
#include "native/juce_OpenGL_android.h" | #include "native/juce_OpenGL_android.h" | ||||
@@ -22,7 +22,6 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
extern ::Display* display; | |||||
extern XContext windowHandleXContext; | extern XContext windowHandleXContext; | ||||
//============================================================================== | //============================================================================== | ||||
@@ -61,7 +60,9 @@ public: | |||||
: component (comp), renderContext (0), embeddedWindow (0), swapFrames (0), bestVisual (0), | : component (comp), renderContext (0), embeddedWindow (0), swapFrames (0), bestVisual (0), | ||||
contextToShareWith (shareContext), context (nullptr), dummy (*this) | contextToShareWith (shareContext), context (nullptr), dummy (*this) | ||||
{ | { | ||||
ScopedXLock xlock; | |||||
display = XWindowSystem::getInstance()->displayRef(); | |||||
ScopedXLock xlock (display); | |||||
XSync (display, False); | XSync (display, False); | ||||
GLint attribs[] = | GLint attribs[] = | ||||
@@ -125,18 +126,20 @@ public: | |||||
if (embeddedWindow != 0) | if (embeddedWindow != 0) | ||||
{ | { | ||||
ScopedXLock xlock; | |||||
ScopedXLock xlock (display); | |||||
XUnmapWindow (display, embeddedWindow); | XUnmapWindow (display, embeddedWindow); | ||||
XDestroyWindow (display, embeddedWindow); | XDestroyWindow (display, embeddedWindow); | ||||
} | } | ||||
if (bestVisual != nullptr) | if (bestVisual != nullptr) | ||||
XFree (bestVisual); | XFree (bestVisual); | ||||
XWindowSystem::getInstance()->displayUnref(); | |||||
} | } | ||||
void initialiseOnRenderThread (OpenGLContext& c) | void initialiseOnRenderThread (OpenGLContext& c) | ||||
{ | { | ||||
ScopedXLock xlock; | |||||
ScopedXLock xlock (display); | |||||
renderContext = glXCreateContext (display, bestVisual, (GLXContext) contextToShareWith, GL_TRUE); | renderContext = glXCreateContext (display, bestVisual, (GLXContext) contextToShareWith, GL_TRUE); | ||||
c.makeActive(); | c.makeActive(); | ||||
context = &c; | context = &c; | ||||
@@ -163,6 +166,8 @@ public: | |||||
static void deactivateCurrentContext() | static void deactivateCurrentContext() | ||||
{ | { | ||||
ScopedXDisplay xDisplay; | |||||
::Display* display = xDisplay.get(); | |||||
glXMakeCurrent (display, None, 0); | glXMakeCurrent (display, None, 0); | ||||
} | } | ||||
@@ -178,7 +183,7 @@ public: | |||||
const Rectangle<int> physicalBounds = | const Rectangle<int> physicalBounds = | ||||
juce_LinuxScaledToPhysicalBounds (component.getPeer(), bounds); | juce_LinuxScaledToPhysicalBounds (component.getPeer(), bounds); | ||||
ScopedXLock xlock; | |||||
ScopedXLock xlock (display); | |||||
XMoveResizeWindow (display, embeddedWindow, | XMoveResizeWindow (display, embeddedWindow, | ||||
physicalBounds.getX(), physicalBounds.getY(), | physicalBounds.getX(), physicalBounds.getY(), | ||||
(unsigned int) jmax (1, physicalBounds.getWidth()), | (unsigned int) jmax (1, physicalBounds.getWidth()), | ||||
@@ -229,12 +234,17 @@ private: | |||||
OpenGLContext* context; | OpenGLContext* context; | ||||
DummyComponent dummy; | DummyComponent dummy; | ||||
::Display* display; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeContext) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeContext) | ||||
}; | }; | ||||
//============================================================================== | //============================================================================== | ||||
bool OpenGLHelpers::isContextActive() | bool OpenGLHelpers::isContextActive() | ||||
{ | { | ||||
ScopedXLock xlock; | |||||
ScopedXDisplay xDisplay; | |||||
::Display* display = xDisplay.get(); | |||||
ScopedXLock xlock (display); | |||||
return glXGetCurrentContext() != 0; | return glXGetCurrentContext() != 0; | ||||
} | } |