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 | |||
// 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 | |||
//============================================================================== | |||
namespace | |||
{ | |||
class AlsaPortAndCallback; | |||
//============================================================================== | |||
class AlsaClient : public ReferenceCountedObject | |||
{ | |||
public: | |||
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(); | |||
} | |||
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; } | |||
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: | |||
bool input; | |||
snd_seq_t* handle; | |||
Array<AlsaPortAndCallback*> activeCallbacks; | |||
int clientId; | |||
OwnedArray<Port> ports; | |||
Atomic<int> activeCallbacks; | |||
CriticalSection callbackLock; | |||
static AlsaClient* inInstance; | |||
static AlsaClient* outInstance; | |||
static AlsaClient* instance; | |||
//============================================================================== | |||
friend class ReferenceCountedObjectPtr<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); | |||
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*& instance = (input ? inInstance : outInstance); | |||
jassert (instance != nullptr); | |||
instance = nullptr; | |||
if (handle != nullptr) | |||
{ | |||
snd_seq_close (handle); | |||
handle = nullptr; | |||
} | |||
jassert (activeCallbacks.size() == 0); | |||
jassert (activeCallbacks.get() == 0); | |||
if (inputThread) | |||
{ | |||
inputThread->stopThread (3000); | |||
inputThread = nullptr; | |||
} | |||
} | |||
//============================================================================== | |||
@@ -152,7 +310,7 @@ private: | |||
MidiInputThread (AlsaClient& c) | |||
: Thread ("Juce MIDI Input"), client (c), concatenator (2048) | |||
{ | |||
jassert (client.input && client.get() != nullptr); | |||
jassert (client.get() != nullptr); | |||
} | |||
void run() override | |||
@@ -176,8 +334,6 @@ private: | |||
if (threadShouldExit()) | |||
break; | |||
snd_seq_nonblock (seqHandle, 1); | |||
do | |||
{ | |||
snd_seq_event_t* inputEvent = nullptr; | |||
@@ -213,202 +369,91 @@ private: | |||
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; | |||
} | |||
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()) | |||
{ | |||
snd_seq_system_info_t* systemInfo = 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, | |||
deviceNamesFound, deviceIndexToOpen); | |||
snd_seq_client_info_free (clientInfo); | |||
if (port) | |||
break; | |||
} | |||
} | |||
} | |||
snd_seq_system_info_free (systemInfo); | |||
} | |||
} | |||
deviceNamesFound.appendNumbersToDuplicates (true, true); | |||
@@ -416,80 +461,6 @@ static AlsaPort iterateMidiDevices (const bool forInput, | |||
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 | |||
StringArray MidiOutput::getDevices() | |||
@@ -509,13 +480,16 @@ MidiOutput* MidiOutput::openDevice (int deviceIndex) | |||
MidiOutput* newDevice = nullptr; | |||
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; | |||
} | |||
@@ -523,17 +497,16 @@ MidiOutput* MidiOutput::openDevice (int deviceIndex) | |||
MidiOutput* MidiOutput::createNewDevice (const String& deviceName) | |||
{ | |||
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; | |||
} | |||
@@ -542,12 +515,13 @@ MidiOutput::~MidiOutput() | |||
{ | |||
stopBackgroundThread(); | |||
delete static_cast<MidiOutputDevice*> (internal); | |||
AlsaClient::Ptr client (AlsaClient::getInstance()); | |||
client->deletePort (static_cast<AlsaClient::Port*> (internal)); | |||
} | |||
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() | |||
{ | |||
stop(); | |||
delete static_cast<AlsaPortAndCallback*> (internal); | |||
AlsaClient::Ptr client (AlsaClient::getInstance()); | |||
client->deletePort (static_cast<AlsaClient::Port*> (internal)); | |||
} | |||
void MidiInput::start() | |||
{ | |||
static_cast<AlsaPortAndCallback*> (internal)->enableCallback (true); | |||
static_cast<AlsaClient::Port*> (internal)->enableCallback (true); | |||
} | |||
void MidiInput::stop() | |||
{ | |||
static_cast<AlsaPortAndCallback*> (internal)->enableCallback (false); | |||
static_cast<AlsaClient::Port*> (internal)->enableCallback (false); | |||
} | |||
int MidiInput::getDefaultDeviceIndex() | |||
@@ -589,13 +564,16 @@ MidiInput* MidiInput::openDevice (int deviceIndex, MidiInputCallback* callback) | |||
MidiInput* newDevice = nullptr; | |||
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; | |||
} | |||
@@ -603,17 +581,16 @@ MidiInput* MidiInput::openDevice (int deviceIndex, MidiInputCallback* callback) | |||
MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback) | |||
{ | |||
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; | |||
} | |||
@@ -101,10 +101,6 @@ namespace juce | |||
#endif | |||
#endif | |||
#if JUCE_LINUX | |||
extern Display* display; | |||
#endif | |||
extern JUCE_API bool handleManufacturerSpecificVST2Opcode (int32, pointer_sized_int, void*, float); | |||
} | |||
@@ -332,6 +328,10 @@ public: | |||
vstEffect.flags |= vstEffectFlagDataInChunks; | |||
#if JUCE_LINUX | |||
display = XWindowSystem::getInstance()->displayRef(); | |||
#endif | |||
activePlugins.add (this); | |||
} | |||
@@ -370,6 +370,10 @@ public: | |||
messageThreadIsDefinitelyCorrect = false; | |||
#endif | |||
} | |||
#if JUCE_LINUX | |||
display = XWindowSystem::getInstance()->displayUnref(); | |||
#endif | |||
} | |||
} | |||
@@ -1343,7 +1347,11 @@ public: | |||
Rectangle<int> childBounds (child->getWidth(), child->getHeight()); | |||
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 | |||
#if JUCE_MAC | |||
@@ -1404,6 +1412,7 @@ private: | |||
#if JUCE_MAC | |||
void* hostWindow; | |||
#elif JUCE_LINUX | |||
::Display* display; | |||
Window hostWindow; | |||
#else | |||
HWND hostWindow; | |||
@@ -224,14 +224,11 @@ static pointer_sized_int VSTINTERFACECALL audioMaster (VstEffectInterface* effec | |||
//============================================================================== | |||
#if JUCE_LINUX | |||
extern Display* display; | |||
extern XContext windowHandleXContext; | |||
namespace | |||
{ | |||
static bool xErrorTriggered = false; | |||
static int temporaryErrorHandler (Display*, XErrorEvent*) | |||
static int temporaryErrorHandler (::Display*, XErrorEvent*) | |||
{ | |||
xErrorTriggered = true; | |||
return 0; | |||
@@ -249,8 +246,13 @@ namespace | |||
unsigned char* data; | |||
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); | |||
@@ -263,7 +265,12 @@ namespace | |||
Window* childWindows; | |||
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) | |||
return childWindows [0]; | |||
@@ -2025,6 +2032,7 @@ public: | |||
#elif JUCE_LINUX | |||
pluginWindow = None; | |||
pluginProc = None; | |||
display = XWindowSystem::getInstance()->displayRef(); | |||
#elif JUCE_MAC | |||
ignoreUnused (recursiveResize, pluginRefusesToResize, alreadyInside); | |||
@@ -2053,6 +2061,8 @@ public: | |||
carbonWrapper = nullptr; | |||
#endif | |||
cocoaWrapper = nullptr; | |||
#elif JUCE_LINUX | |||
display = XWindowSystem::getInstance()->displayUnref(); | |||
#endif | |||
activeVSTWindows.removeFirstMatchingValue (this); | |||
@@ -2253,6 +2263,7 @@ private: | |||
void* originalWndProc; | |||
int sizeCheckCount; | |||
#elif JUCE_LINUX | |||
::Display* display; | |||
Window pluginWindow; | |||
EventProcPtr pluginProc; | |||
#endif | |||
@@ -810,6 +810,7 @@ public: | |||
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, | |||
always create your own sub-folder to put them in, to avoid making a mess. | |||
On GNU/Linux it is "~/.config". | |||
*/ | |||
userApplicationDataDirectory, | |||
@@ -819,6 +820,8 @@ public: | |||
On the Mac it'll be "/Library", on Windows, it could be something like | |||
"\Documents and Settings\All Users\Application Data". | |||
On GNU/Linux it is "/opt". | |||
Depending on the setup, this folder may be read-only. | |||
*/ | |||
commonApplicationDataDirectory, | |||
@@ -120,29 +120,22 @@ File File::getSpecialLocation (const SpecialLocationType type) | |||
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 userApplicationDataDirectory: return resolveXDGFolder ("XDG_CONFIG_HOME", "~"); | |||
case userApplicationDataDirectory: return resolveXDGFolder ("XDG_CONFIG_HOME", "~/.config"); | |||
case commonDocumentsDirectory: | |||
case commonApplicationDataDirectory: return File ("/var"); | |||
case commonApplicationDataDirectory: return File ("/opt"); | |||
case globalApplicationsDirectory: return File ("/usr"); | |||
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: | |||
@@ -53,10 +53,6 @@ | |||
#import <IOKit/pwr_mgt/IOPMLib.h> | |||
#elif JUCE_LINUX | |||
#include <X11/Xlib.h> | |||
#include <X11/Xresource.h> | |||
#include <X11/Xutil.h> | |||
#undef KeyPress | |||
#include <unistd.h> | |||
#endif | |||
@@ -90,7 +86,6 @@ namespace juce | |||
#include "native/juce_win32_Messaging.cpp" | |||
#elif JUCE_LINUX | |||
#include "native/juce_ScopedXLock.h" | |||
#include "native/juce_linux_Messaging.cpp" | |||
#elif JUCE_ANDROID | |||
@@ -46,7 +46,6 @@ | |||
license: ISC | |||
dependencies: juce_core | |||
linuxPackages: x11 | |||
END_JUCE_MODULE_DECLARATION | |||
@@ -80,7 +79,11 @@ namespace juce | |||
#include "interprocess/juce_InterprocessConnection.h" | |||
#include "interprocess/juce_InterprocessConnectionServer.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 | |||
#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 | |||
{ | |||
public: | |||
InternalMessageQueue() | |||
: bytesInSocket (0), | |||
totalEventCount (0) | |||
: fdCount (1), | |||
loopCount (0), | |||
bytesInSocket (0) | |||
{ | |||
int ret = ::socketpair (AF_LOCAL, SOCK_STREAM, 0, fd); | |||
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() | |||
{ | |||
close (fd[0]); | |||
close (fd[1]); | |||
close (getReadHandle()); | |||
close (getWriteHandle()); | |||
clearSingletonInstance(); | |||
} | |||
//============================================================================== | |||
void postMessage (MessageManager::MessageBase* const msg) | |||
void postMessage (MessageManager::MessageBase* const msg) noexcept | |||
{ | |||
const int maxBytesInSocketQueue = 128; | |||
ScopedLock sl (lock); | |||
queue.add (msg); | |||
const int maxBytesInSocketQueue = 128; | |||
if (bytesInSocket < maxBytesInSocketQueue) | |||
{ | |||
++bytesInSocket; | |||
bytesInSocket++; | |||
ScopedUnlock ul (lock); | |||
const unsigned char x = 0xff; | |||
ssize_t bytesWritten = write (fd[0], &x, 1); | |||
ssize_t bytesWritten = write (getWriteHandle(), &x, 1); | |||
ignoreUnused (bytesWritten); | |||
} | |||
} | |||
bool isEmpty() const | |||
void setWindowSystemFd (int _fd, LinuxEventLoop::CallbackFunctionBase* _readCallback) | |||
{ | |||
jassert (fdCount == 1); | |||
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; | |||
ReferenceCountedArray <MessageManager::MessageBase> queue; | |||
int fd[2]; | |||
pollfd pfds[FD_COUNT]; | |||
ScopedPointer<LinuxEventLoop::CallbackFunctionBase> readCallback[FD_COUNT]; | |||
int fdCount; | |||
int loopCount; | |||
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); | |||
@@ -192,27 +165,12 @@ private: | |||
const ScopedUnlock ul (lock); | |||
unsigned char x; | |||
ssize_t numBytes = read (fd[1], &x, 1); | |||
ssize_t numBytes = read (_fd, &x, 1); | |||
ignoreUnused (numBytes); | |||
} | |||
return queue.removeAndReturn (0); | |||
} | |||
bool dispatchNextInternalMessage() | |||
{ | |||
if (const MessageManager::MessageBase::Ptr msg = popNextMessage()) | |||
{ | |||
JUCE_TRY | |||
{ | |||
msg->messageCallback(); | |||
return true; | |||
} | |||
JUCE_CATCH_EXCEPTION | |||
} | |||
return false; | |||
} | |||
}; | |||
juce_ImplementSingleton_SingleThreaded (InternalMessageQueue) | |||
@@ -221,57 +179,7 @@ juce_ImplementSingleton_SingleThreaded (InternalMessageQueue) | |||
//============================================================================== | |||
namespace LinuxErrorHandling | |||
{ | |||
static bool errorOccurred = false; | |||
static bool keyboardBreakOccurred = false; | |||
static XErrorHandler oldErrorHandler = (XErrorHandler) 0; | |||
static XIOErrorHandler oldIOErrorHandler = (XIOErrorHandler) 0; | |||
//============================================================================== | |||
// Usually happens when client-server connection is broken | |||
int ioErrorHandler (Display*) | |||
{ | |||
DBG ("ERROR: connection to X server broken.. terminating."); | |||
if (JUCEApplicationBase::isStandaloneApp()) | |||
MessageManager::getInstance()->stopDispatchLoop(); | |||
errorOccurred = true; | |||
return 0; | |||
} | |||
int errorHandler (Display* display, XErrorEvent* event) | |||
{ | |||
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) | |||
@@ -297,78 +205,25 @@ void MessageManager::doPlatformSpecificInitialisation() | |||
{ | |||
if (JUCEApplicationBase::isStandaloneApp()) | |||
{ | |||
// Initialise xlib for multiple thread support | |||
static bool initThreadCalled = false; | |||
if (! initThreadCalled) | |||
{ | |||
if (! XInitThreads()) | |||
{ | |||
// This is fatal! Print error and closedown | |||
Logger::outputDebugString ("Failed to initialise xlib thread support."); | |||
Process::terminate(); | |||
return; | |||
} | |||
initThreadCalled = true; | |||
} | |||
LinuxErrorHandling::installXErrorHandlers(); | |||
LinuxErrorHandling::installKeyboardBreakHandler(); | |||
} | |||
// Create the internal message queue | |||
InternalMessageQueue::getInstance(); | |||
// Try to connect to a display | |||
String displayName (getenv ("DISPLAY")); | |||
if (displayName.isEmpty()) | |||
displayName = ":0.0"; | |||
display = XOpenDisplay (displayName.toUTF8()); | |||
if (display != nullptr) // This is not fatal! we can run headless. | |||
{ | |||
// Create a context to store user data associated with Windows we create | |||
windowHandleXContext = XUniqueContext(); | |||
// We're only interested in client messages for this window, which are always sent | |||
XSetWindowAttributes swa; | |||
swa.event_mask = NoEventMask; | |||
// Create our message window (this will never be mapped) | |||
const int screen = DefaultScreen (display); | |||
juce_messageWindowHandle = XCreateWindow (display, RootWindow (display, screen), | |||
0, 0, 1, 1, 0, 0, InputOnly, | |||
DefaultVisual (display, screen), | |||
CWEventMask, &swa); | |||
} | |||
InternalMessageQueue* queue = InternalMessageQueue::getInstance(); | |||
ignoreUnused (queue); | |||
} | |||
void MessageManager::doPlatformSpecificShutdown() | |||
{ | |||
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) | |||
{ | |||
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; | |||
@@ -382,29 +237,38 @@ void MessageManager::broadcastMessage (const String& /* value */) | |||
// this function expects that it will NEVER be called simultaneously for two concurrent threads | |||
bool MessageManager::dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages) | |||
{ | |||
while (! LinuxErrorHandling::errorOccurred) | |||
for (;;) | |||
{ | |||
if (LinuxErrorHandling::keyboardBreakOccurred) | |||
{ | |||
LinuxErrorHandling::errorOccurred = true; | |||
if (JUCEApplicationBase::isStandaloneApp()) | |||
Process::terminate(); | |||
break; | |||
} | |||
JUCEApplicationBase::getInstance()->quit(); | |||
if (InternalMessageQueue* queue = InternalMessageQueue::getInstanceWithoutCreating()) | |||
{ | |||
if (queue->dispatchNextEvent()) | |||
return true; | |||
if (returnIfNoPendingMessages) | |||
break; | |||
else if (returnIfNoPendingMessages) | |||
return false; | |||
// wait for 2000ms for next events if necessary | |||
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(); | |||
DWORD_PTR result; | |||
SendMessageTimeout (windows.getUnchecked(i), WM_COPYDATA, | |||
SendMessageTimeout (windows.getUnchecked (i), WM_COPYDATA, | |||
(WPARAM) juce_messageWindowHandle, | |||
(LPARAM) &data, | |||
SMTO_BLOCK | SMTO_ABORTIFHUNG, 8000, &result); | |||
@@ -282,8 +282,9 @@ extern bool juce_areThereAnyAlwaysOnTopWindows(); | |||
#include "native/juce_win32_FileChooser.cpp" | |||
#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" | |||
#elif JUCE_ANDROID | |||
@@ -281,6 +281,10 @@ class FlexBox; | |||
#include "lookandfeel/juce_LookAndFeel_V1.h" | |||
#include "lookandfeel/juce_LookAndFeel_V3.h" | |||
#if JUCE_LINUX | |||
#include "native/juce_linux_X11.h" | |||
#endif | |||
// these classes are C++11-only | |||
#if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS && JUCE_COMPILER_SUPPORTS_LAMBDAS | |||
#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; | |||
namespace ClipboardHelpers | |||
@@ -33,7 +32,7 @@ namespace ClipboardHelpers | |||
static Atom atom_TARGETS; | |||
//============================================================================== | |||
static void initSelectionAtoms() | |||
static void initSelectionAtoms (::Display* display) | |||
{ | |||
static bool isInitialised = false; | |||
@@ -41,16 +40,16 @@ namespace ClipboardHelpers | |||
{ | |||
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 | |||
// 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; | |||
@@ -86,7 +85,8 @@ namespace ClipboardHelpers | |||
//============================================================================== | |||
// 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); | |||
@@ -107,7 +107,7 @@ namespace ClipboardHelpers | |||
{ | |||
jassert (event.xselection.requestor == juce_messageWindowHandle); | |||
selectionContent = readWindowProperty (event.xselection.requestor, | |||
selectionContent = readWindowProperty (display, event.xselection.requestor, | |||
event.xselection.property); | |||
return true; | |||
} | |||
@@ -128,69 +128,66 @@ namespace ClipboardHelpers | |||
// Called from the event loop in juce_linux_Messaging in response to SelectionRequest events | |||
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) | |||
{ | |||
ScopedXDisplay xDisplay; | |||
::Display* display = xDisplay.get(); | |||
if (display != nullptr) | |||
{ | |||
ClipboardHelpers::initSelectionAtoms(); | |||
ClipboardHelpers::initSelectionAtoms (display); | |||
ClipboardHelpers::localClipboardContent = clipText; | |||
XSetSelectionOwner (display, XA_PRIMARY, juce_messageWindowHandle, CurrentTime); | |||
@@ -224,10 +224,12 @@ void SystemClipboard::copyTextToClipboard (const String& clipText) | |||
String SystemClipboard::getTextFromClipboard() | |||
{ | |||
String content; | |||
ScopedXDisplay xDisplay; | |||
::Display* display = xDisplay.get(); | |||
if (display != nullptr) | |||
{ | |||
ClipboardHelpers::initSelectionAtoms(); | |||
ClipboardHelpers::initSelectionAtoms (display); | |||
/* 1) try to read from the "CLIPBOARD" selection first (the "high | |||
level" clipboard that is supposed to be filled by ctrl-C | |||
@@ -256,12 +258,14 @@ String SystemClipboard::getTextFromClipboard() | |||
else | |||
{ | |||
// 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) | |||
{ | |||
// 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 | |||
#if JUCE_WEB_BROWSER | |||
#include "native/juce_linux_WebBrowserComponent.cpp" | |||
#include "native/juce_linux_X11_WebBrowserComponent.cpp" | |||
#endif | |||
#include "native/juce_linux_SystemTrayIcon.cpp" | |||
#include "native/juce_linux_X11_SystemTrayIcon.cpp" | |||
//============================================================================== | |||
#elif JUCE_ANDROID | |||
@@ -22,22 +22,23 @@ | |||
============================================================================== | |||
*/ | |||
extern ::Display* display; | |||
//============================================================================== | |||
class SystemTrayIconComponent::Pimpl | |||
{ | |||
public: | |||
Pimpl (const Image& im, Window windowH) : image (im) | |||
{ | |||
ScopedXLock xlock; | |||
ScopedXDisplay xDisplay; | |||
::Display* display = xDisplay.get(); | |||
ScopedXLock xlock (display); | |||
Screen* const screen = XDefaultScreenOfDisplay (display); | |||
const int screenNumber = XScreenNumberOfScreen (screen); | |||
String screenAtom ("_NET_SYSTEM_TRAY_S"); | |||
screenAtom << screenNumber; | |||
Atom selectionAtom = XInternAtom (display, screenAtom.toUTF8(), false); | |||
Atom selectionAtom = Atoms::getCreating (display, screenAtom.toUTF8()); | |||
XGrabServer (display); | |||
Window managerWin = XGetSelectionOwner (display, selectionAtom); | |||
@@ -53,7 +54,7 @@ public: | |||
XEvent ev = { 0 }; | |||
ev.xclient.type = ClientMessage; | |||
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.data.l[0] = CurrentTime; | |||
ev.xclient.data.l[1] = 0 /*SYSTEM_TRAY_REQUEST_DOCK*/; | |||
@@ -67,11 +68,11 @@ public: | |||
// For older KDE's ... | |||
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); | |||
// 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); | |||
// 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" | |||
#elif JUCE_LINUX | |||
#include "native/juce_OpenGL_linux.h" | |||
#include "native/juce_OpenGL_linux_X11.h" | |||
#elif JUCE_ANDROID | |||
#include "native/juce_OpenGL_android.h" | |||
@@ -22,7 +22,6 @@ | |||
============================================================================== | |||
*/ | |||
extern ::Display* display; | |||
extern XContext windowHandleXContext; | |||
//============================================================================== | |||
@@ -61,7 +60,9 @@ public: | |||
: component (comp), renderContext (0), embeddedWindow (0), swapFrames (0), bestVisual (0), | |||
contextToShareWith (shareContext), context (nullptr), dummy (*this) | |||
{ | |||
ScopedXLock xlock; | |||
display = XWindowSystem::getInstance()->displayRef(); | |||
ScopedXLock xlock (display); | |||
XSync (display, False); | |||
GLint attribs[] = | |||
@@ -125,18 +126,20 @@ public: | |||
if (embeddedWindow != 0) | |||
{ | |||
ScopedXLock xlock; | |||
ScopedXLock xlock (display); | |||
XUnmapWindow (display, embeddedWindow); | |||
XDestroyWindow (display, embeddedWindow); | |||
} | |||
if (bestVisual != nullptr) | |||
XFree (bestVisual); | |||
XWindowSystem::getInstance()->displayUnref(); | |||
} | |||
void initialiseOnRenderThread (OpenGLContext& c) | |||
{ | |||
ScopedXLock xlock; | |||
ScopedXLock xlock (display); | |||
renderContext = glXCreateContext (display, bestVisual, (GLXContext) contextToShareWith, GL_TRUE); | |||
c.makeActive(); | |||
context = &c; | |||
@@ -163,6 +166,8 @@ public: | |||
static void deactivateCurrentContext() | |||
{ | |||
ScopedXDisplay xDisplay; | |||
::Display* display = xDisplay.get(); | |||
glXMakeCurrent (display, None, 0); | |||
} | |||
@@ -178,7 +183,7 @@ public: | |||
const Rectangle<int> physicalBounds = | |||
juce_LinuxScaledToPhysicalBounds (component.getPeer(), bounds); | |||
ScopedXLock xlock; | |||
ScopedXLock xlock (display); | |||
XMoveResizeWindow (display, embeddedWindow, | |||
physicalBounds.getX(), physicalBounds.getY(), | |||
(unsigned int) jmax (1, physicalBounds.getWidth()), | |||
@@ -229,12 +234,17 @@ private: | |||
OpenGLContext* context; | |||
DummyComponent dummy; | |||
::Display* display; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeContext) | |||
}; | |||
//============================================================================== | |||
bool OpenGLHelpers::isContextActive() | |||
{ | |||
ScopedXLock xlock; | |||
ScopedXDisplay xDisplay; | |||
::Display* display = xDisplay.get(); | |||
ScopedXLock xlock (display); | |||
return glXGetCurrentContext() != 0; | |||
} |