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; | |||
| } | |||