fix macOS build (following Projucer changes made in Windows, which removed /Applications/JUCE/modules from its headers). move JUCE headers under source control, so that Windows and macOS can both build against same version of JUCE. remove AUv3 target (I think it's an iOS thing, so it will never work with this macOS fluidsynth dylib).
This commit is contained in:
92
modules/juce_events/broadcasters/juce_ActionBroadcaster.cpp
Normal file
92
modules/juce_events/broadcasters/juce_ActionBroadcaster.cpp
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class ActionBroadcaster::ActionMessage : public MessageManager::MessageBase
|
||||
{
|
||||
public:
|
||||
ActionMessage (const ActionBroadcaster* ab,
|
||||
const String& messageText, ActionListener* l) noexcept
|
||||
: broadcaster (const_cast<ActionBroadcaster*> (ab)),
|
||||
message (messageText),
|
||||
listener (l)
|
||||
{}
|
||||
|
||||
void messageCallback() override
|
||||
{
|
||||
if (const ActionBroadcaster* const b = broadcaster)
|
||||
if (b->actionListeners.contains (listener))
|
||||
listener->actionListenerCallback (message);
|
||||
}
|
||||
|
||||
private:
|
||||
WeakReference<ActionBroadcaster> broadcaster;
|
||||
const String message;
|
||||
ActionListener* const listener;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (ActionMessage)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
ActionBroadcaster::ActionBroadcaster()
|
||||
{
|
||||
// are you trying to create this object before or after juce has been intialised??
|
||||
jassert (MessageManager::getInstanceWithoutCreating() != nullptr);
|
||||
}
|
||||
|
||||
ActionBroadcaster::~ActionBroadcaster()
|
||||
{
|
||||
// all event-based objects must be deleted BEFORE juce is shut down!
|
||||
jassert (MessageManager::getInstanceWithoutCreating() != nullptr);
|
||||
}
|
||||
|
||||
void ActionBroadcaster::addActionListener (ActionListener* const listener)
|
||||
{
|
||||
const ScopedLock sl (actionListenerLock);
|
||||
|
||||
if (listener != nullptr)
|
||||
actionListeners.add (listener);
|
||||
}
|
||||
|
||||
void ActionBroadcaster::removeActionListener (ActionListener* const listener)
|
||||
{
|
||||
const ScopedLock sl (actionListenerLock);
|
||||
actionListeners.removeValue (listener);
|
||||
}
|
||||
|
||||
void ActionBroadcaster::removeAllActionListeners()
|
||||
{
|
||||
const ScopedLock sl (actionListenerLock);
|
||||
actionListeners.clear();
|
||||
}
|
||||
|
||||
void ActionBroadcaster::sendActionMessage (const String& message) const
|
||||
{
|
||||
const ScopedLock sl (actionListenerLock);
|
||||
|
||||
for (int i = actionListeners.size(); --i >= 0;)
|
||||
(new ActionMessage (this, message, actionListeners.getUnchecked(i)))->post();
|
||||
}
|
||||
|
||||
} // namespace juce
|
79
modules/juce_events/broadcasters/juce_ActionBroadcaster.h
Normal file
79
modules/juce_events/broadcasters/juce_ActionBroadcaster.h
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/** Manages a list of ActionListeners, and can send them messages.
|
||||
|
||||
To quickly add methods to your class that can add/remove action
|
||||
listeners and broadcast to them, you can derive from this.
|
||||
|
||||
@see ActionListener, ChangeListener
|
||||
|
||||
@tags{Events}
|
||||
*/
|
||||
class JUCE_API ActionBroadcaster
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an ActionBroadcaster. */
|
||||
ActionBroadcaster();
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~ActionBroadcaster();
|
||||
|
||||
//==============================================================================
|
||||
/** Adds a listener to the list.
|
||||
Trying to add a listener that's already on the list will have no effect.
|
||||
*/
|
||||
void addActionListener (ActionListener* listener);
|
||||
|
||||
/** Removes a listener from the list.
|
||||
If the listener isn't on the list, this won't have any effect.
|
||||
*/
|
||||
void removeActionListener (ActionListener* listener);
|
||||
|
||||
/** Removes all listeners from the list. */
|
||||
void removeAllActionListeners();
|
||||
|
||||
//==============================================================================
|
||||
/** Broadcasts a message to all the registered listeners.
|
||||
@see ActionListener::actionListenerCallback
|
||||
*/
|
||||
void sendActionMessage (const String& message) const;
|
||||
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
class ActionMessage;
|
||||
friend class ActionMessage;
|
||||
|
||||
SortedSet<ActionListener*> actionListeners;
|
||||
CriticalSection actionListenerLock;
|
||||
|
||||
JUCE_DECLARE_WEAK_REFERENCEABLE (ActionBroadcaster)
|
||||
JUCE_DECLARE_NON_COPYABLE (ActionBroadcaster)
|
||||
};
|
||||
|
||||
} // namespace juce
|
48
modules/juce_events/broadcasters/juce_ActionListener.h
Normal file
48
modules/juce_events/broadcasters/juce_ActionListener.h
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Interface class for delivery of events that are sent by an ActionBroadcaster.
|
||||
|
||||
@see ActionBroadcaster, ChangeListener
|
||||
|
||||
@tags{Events}
|
||||
*/
|
||||
class JUCE_API ActionListener
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~ActionListener() {}
|
||||
|
||||
/** Overridden by your subclass to receive the callback.
|
||||
|
||||
@param message the string that was specified when the event was triggered
|
||||
by a call to ActionBroadcaster::sendActionMessage()
|
||||
*/
|
||||
virtual void actionListenerCallback (const String& message) = 0;
|
||||
};
|
||||
|
||||
} // namespace juce
|
93
modules/juce_events/broadcasters/juce_AsyncUpdater.cpp
Normal file
93
modules/juce_events/broadcasters/juce_AsyncUpdater.cpp
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class AsyncUpdater::AsyncUpdaterMessage : public CallbackMessage
|
||||
{
|
||||
public:
|
||||
AsyncUpdaterMessage (AsyncUpdater& au) : owner (au) {}
|
||||
|
||||
void messageCallback() override
|
||||
{
|
||||
if (shouldDeliver.compareAndSetBool (0, 1))
|
||||
owner.handleAsyncUpdate();
|
||||
}
|
||||
|
||||
AsyncUpdater& owner;
|
||||
Atomic<int> shouldDeliver;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (AsyncUpdaterMessage)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
AsyncUpdater::AsyncUpdater()
|
||||
{
|
||||
activeMessage = new AsyncUpdaterMessage (*this);
|
||||
}
|
||||
|
||||
AsyncUpdater::~AsyncUpdater()
|
||||
{
|
||||
// You're deleting this object with a background thread while there's an update
|
||||
// pending on the main event thread - that's pretty dodgy threading, as the callback could
|
||||
// happen after this destructor has finished. You should either use a MessageManagerLock while
|
||||
// deleting this object, or find some other way to avoid such a race condition.
|
||||
jassert ((! isUpdatePending())
|
||||
|| MessageManager::getInstanceWithoutCreating() == nullptr
|
||||
|| MessageManager::getInstanceWithoutCreating()->currentThreadHasLockedMessageManager());
|
||||
|
||||
activeMessage->shouldDeliver.set (0);
|
||||
}
|
||||
|
||||
void AsyncUpdater::triggerAsyncUpdate()
|
||||
{
|
||||
// If you're calling this before (or after) the MessageManager is
|
||||
// running, then you're not going to get any callbacks!
|
||||
jassert (MessageManager::getInstanceWithoutCreating() != nullptr);
|
||||
|
||||
if (activeMessage->shouldDeliver.compareAndSetBool (1, 0))
|
||||
if (! activeMessage->post())
|
||||
cancelPendingUpdate(); // if the message queue fails, this avoids getting
|
||||
// trapped waiting for the message to arrive
|
||||
}
|
||||
|
||||
void AsyncUpdater::cancelPendingUpdate() noexcept
|
||||
{
|
||||
activeMessage->shouldDeliver.set (0);
|
||||
}
|
||||
|
||||
void AsyncUpdater::handleUpdateNowIfNeeded()
|
||||
{
|
||||
// This can only be called by the event thread.
|
||||
jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager());
|
||||
|
||||
if (activeMessage->shouldDeliver.exchange (0) != 0)
|
||||
handleAsyncUpdate();
|
||||
}
|
||||
|
||||
bool AsyncUpdater::isUpdatePending() const noexcept
|
||||
{
|
||||
return activeMessage->shouldDeliver.value != 0;
|
||||
}
|
||||
|
||||
} // namespace juce
|
110
modules/juce_events/broadcasters/juce_AsyncUpdater.h
Normal file
110
modules/juce_events/broadcasters/juce_AsyncUpdater.h
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Has a callback method that is triggered asynchronously.
|
||||
|
||||
This object allows an asynchronous callback function to be triggered, for
|
||||
tasks such as coalescing multiple updates into a single callback later on.
|
||||
|
||||
Basically, one or more calls to the triggerAsyncUpdate() will result in the
|
||||
message thread calling handleAsyncUpdate() as soon as it can.
|
||||
|
||||
@tags{Events}
|
||||
*/
|
||||
class JUCE_API AsyncUpdater
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an AsyncUpdater object. */
|
||||
AsyncUpdater();
|
||||
|
||||
/** Destructor.
|
||||
If there are any pending callbacks when the object is deleted, these are lost.
|
||||
*/
|
||||
virtual ~AsyncUpdater();
|
||||
|
||||
//==============================================================================
|
||||
/** Causes the callback to be triggered at a later time.
|
||||
|
||||
This method returns immediately, after which a callback to the
|
||||
handleAsyncUpdate() method will be made by the message thread as
|
||||
soon as possible.
|
||||
|
||||
If an update callback is already pending but hasn't happened yet, calling
|
||||
this method will have no effect.
|
||||
|
||||
It's thread-safe to call this method from any thread, BUT beware of calling
|
||||
it from a real-time (e.g. audio) thread, because it involves posting a message
|
||||
to the system queue, which means it may block (and in general will do on
|
||||
most OSes).
|
||||
*/
|
||||
void triggerAsyncUpdate();
|
||||
|
||||
/** This will stop any pending updates from happening.
|
||||
|
||||
If called after triggerAsyncUpdate() and before the handleAsyncUpdate()
|
||||
callback happens, this will cancel the handleAsyncUpdate() callback.
|
||||
|
||||
Note that this method simply cancels the next callback - if a callback is already
|
||||
in progress on a different thread, this won't block until the callback finishes, so
|
||||
there's no guarantee that the callback isn't still running when the method returns.
|
||||
*/
|
||||
void cancelPendingUpdate() noexcept;
|
||||
|
||||
/** If an update has been triggered and is pending, this will invoke it
|
||||
synchronously.
|
||||
|
||||
Use this as a kind of "flush" operation - if an update is pending, the
|
||||
handleAsyncUpdate() method will be called immediately; if no update is
|
||||
pending, then nothing will be done.
|
||||
|
||||
Because this may invoke the callback, this method must only be called on
|
||||
the main event thread.
|
||||
*/
|
||||
void handleUpdateNowIfNeeded();
|
||||
|
||||
/** Returns true if there's an update callback in the pipeline. */
|
||||
bool isUpdatePending() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Called back to do whatever your class needs to do.
|
||||
|
||||
This method is called by the message thread at the next convenient time
|
||||
after the triggerAsyncUpdate() method has been called.
|
||||
*/
|
||||
virtual void handleAsyncUpdate() = 0;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
class AsyncUpdaterMessage;
|
||||
friend class ReferenceCountedObjectPtr<AsyncUpdaterMessage>;
|
||||
ReferenceCountedObjectPtr<AsyncUpdaterMessage> activeMessage;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AsyncUpdater)
|
||||
};
|
||||
|
||||
} // namespace juce
|
99
modules/juce_events/broadcasters/juce_ChangeBroadcaster.cpp
Normal file
99
modules/juce_events/broadcasters/juce_ChangeBroadcaster.cpp
Normal file
@ -0,0 +1,99 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
ChangeBroadcaster::ChangeBroadcaster() noexcept
|
||||
{
|
||||
broadcastCallback.owner = this;
|
||||
}
|
||||
|
||||
ChangeBroadcaster::~ChangeBroadcaster()
|
||||
{
|
||||
}
|
||||
|
||||
void ChangeBroadcaster::addChangeListener (ChangeListener* const listener)
|
||||
{
|
||||
// Listeners can only be safely added when the event thread is locked
|
||||
// You can use a MessageManagerLock if you need to call this from another thread.
|
||||
jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager());
|
||||
|
||||
changeListeners.add (listener);
|
||||
}
|
||||
|
||||
void ChangeBroadcaster::removeChangeListener (ChangeListener* const listener)
|
||||
{
|
||||
// Listeners can only be safely removed when the event thread is locked
|
||||
// You can use a MessageManagerLock if you need to call this from another thread.
|
||||
jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager());
|
||||
|
||||
changeListeners.remove (listener);
|
||||
}
|
||||
|
||||
void ChangeBroadcaster::removeAllChangeListeners()
|
||||
{
|
||||
// Listeners can only be safely removed when the event thread is locked
|
||||
// You can use a MessageManagerLock if you need to call this from another thread.
|
||||
jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager());
|
||||
|
||||
changeListeners.clear();
|
||||
}
|
||||
|
||||
void ChangeBroadcaster::sendChangeMessage()
|
||||
{
|
||||
if (changeListeners.size() > 0)
|
||||
broadcastCallback.triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
void ChangeBroadcaster::sendSynchronousChangeMessage()
|
||||
{
|
||||
// This can only be called by the event thread.
|
||||
jassert (MessageManager::getInstance()->isThisTheMessageThread());
|
||||
|
||||
broadcastCallback.cancelPendingUpdate();
|
||||
callListeners();
|
||||
}
|
||||
|
||||
void ChangeBroadcaster::dispatchPendingMessages()
|
||||
{
|
||||
broadcastCallback.handleUpdateNowIfNeeded();
|
||||
}
|
||||
|
||||
void ChangeBroadcaster::callListeners()
|
||||
{
|
||||
changeListeners.call ([this] (ChangeListener& l) { l.changeListenerCallback (this); });
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
ChangeBroadcaster::ChangeBroadcasterCallback::ChangeBroadcasterCallback()
|
||||
: owner (nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void ChangeBroadcaster::ChangeBroadcasterCallback::handleAsyncUpdate()
|
||||
{
|
||||
jassert (owner != nullptr);
|
||||
owner->callListeners();
|
||||
}
|
||||
|
||||
} // namespace juce
|
103
modules/juce_events/broadcasters/juce_ChangeBroadcaster.h
Normal file
103
modules/juce_events/broadcasters/juce_ChangeBroadcaster.h
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Holds a list of ChangeListeners, and sends messages to them when instructed.
|
||||
|
||||
@see ChangeListener
|
||||
|
||||
@tags{Events}
|
||||
*/
|
||||
class JUCE_API ChangeBroadcaster
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an ChangeBroadcaster. */
|
||||
ChangeBroadcaster() noexcept;
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~ChangeBroadcaster();
|
||||
|
||||
//==============================================================================
|
||||
/** Registers a listener to receive change callbacks from this broadcaster.
|
||||
Trying to add a listener that's already on the list will have no effect.
|
||||
*/
|
||||
void addChangeListener (ChangeListener* listener);
|
||||
|
||||
/** Unregisters a listener from the list.
|
||||
If the listener isn't on the list, this won't have any effect.
|
||||
*/
|
||||
void removeChangeListener (ChangeListener* listener);
|
||||
|
||||
/** Removes all listeners from the list. */
|
||||
void removeAllChangeListeners();
|
||||
|
||||
//==============================================================================
|
||||
/** Causes an asynchronous change message to be sent to all the registered listeners.
|
||||
|
||||
The message will be delivered asynchronously by the main message thread, so this
|
||||
method will return immediately. To call the listeners synchronously use
|
||||
sendSynchronousChangeMessage().
|
||||
*/
|
||||
void sendChangeMessage();
|
||||
|
||||
/** Sends a synchronous change message to all the registered listeners.
|
||||
|
||||
This will immediately call all the listeners that are registered. For thread-safety
|
||||
reasons, you must only call this method on the main message thread.
|
||||
|
||||
@see dispatchPendingMessages
|
||||
*/
|
||||
void sendSynchronousChangeMessage();
|
||||
|
||||
/** If a change message has been sent but not yet dispatched, this will call
|
||||
sendSynchronousChangeMessage() to make the callback immediately.
|
||||
|
||||
For thread-safety reasons, you must only call this method on the main message thread.
|
||||
*/
|
||||
void dispatchPendingMessages();
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
class ChangeBroadcasterCallback : public AsyncUpdater
|
||||
{
|
||||
public:
|
||||
ChangeBroadcasterCallback();
|
||||
void handleAsyncUpdate() override;
|
||||
|
||||
ChangeBroadcaster* owner;
|
||||
};
|
||||
|
||||
friend class ChangeBroadcasterCallback;
|
||||
ChangeBroadcasterCallback broadcastCallback;
|
||||
ListenerList <ChangeListener> changeListeners;
|
||||
|
||||
void callListeners();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (ChangeBroadcaster)
|
||||
};
|
||||
|
||||
} // namespace juce
|
63
modules/juce_events/broadcasters/juce_ChangeListener.h
Normal file
63
modules/juce_events/broadcasters/juce_ChangeListener.h
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class ChangeBroadcaster;
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Receives change event callbacks that are sent out by a ChangeBroadcaster.
|
||||
|
||||
A ChangeBroadcaster keeps a set of listeners to which it broadcasts a message when
|
||||
the ChangeBroadcaster::sendChangeMessage() method is called. A subclass of
|
||||
ChangeListener is used to receive these callbacks.
|
||||
|
||||
Note that the major difference between an ActionListener and a ChangeListener
|
||||
is that for a ChangeListener, multiple changes will be coalesced into fewer
|
||||
callbacks, but ActionListeners perform one callback for every event posted.
|
||||
|
||||
@see ChangeBroadcaster, ActionListener
|
||||
|
||||
@tags{Events}
|
||||
*/
|
||||
class JUCE_API ChangeListener
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~ChangeListener() {}
|
||||
|
||||
/** Your subclass should implement this method to receive the callback.
|
||||
@param source the ChangeBroadcaster that triggered the callback.
|
||||
*/
|
||||
virtual void changeListenerCallback (ChangeBroadcaster* source) = 0;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE
|
||||
// This method's signature has changed to take a ChangeBroadcaster parameter - please update your code!
|
||||
private: virtual int changeListenerCallback (void*) { return 0; }
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace juce
|
267
modules/juce_events/interprocess/juce_ConnectedChildProcess.cpp
Normal file
267
modules/juce_events/interprocess/juce_ConnectedChildProcess.cpp
Normal file
@ -0,0 +1,267 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
enum { magicMastSlaveConnectionHeader = 0x712baf04 };
|
||||
|
||||
static const char* startMessage = "__ipc_st";
|
||||
static const char* killMessage = "__ipc_k_";
|
||||
static const char* pingMessage = "__ipc_p_";
|
||||
enum { specialMessageSize = 8, defaultTimeoutMs = 8000 };
|
||||
|
||||
static inline bool isMessageType (const MemoryBlock& mb, const char* messageType) noexcept
|
||||
{
|
||||
return mb.matches (messageType, (size_t) specialMessageSize);
|
||||
}
|
||||
|
||||
static String getCommandLinePrefix (const String& commandLineUniqueID)
|
||||
{
|
||||
return "--" + commandLineUniqueID + ":";
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// This thread sends and receives ping messages every second, so that it
|
||||
// can find out if the other process has stopped running.
|
||||
struct ChildProcessPingThread : public Thread,
|
||||
private AsyncUpdater
|
||||
{
|
||||
ChildProcessPingThread (int timeout) : Thread ("IPC ping"), timeoutMs (timeout)
|
||||
{
|
||||
pingReceived();
|
||||
}
|
||||
|
||||
void pingReceived() noexcept { countdown = timeoutMs / 1000 + 1; }
|
||||
void triggerConnectionLostMessage() { triggerAsyncUpdate(); }
|
||||
|
||||
virtual bool sendPingMessage (const MemoryBlock&) = 0;
|
||||
virtual void pingFailed() = 0;
|
||||
|
||||
int timeoutMs;
|
||||
|
||||
private:
|
||||
Atomic<int> countdown;
|
||||
|
||||
void handleAsyncUpdate() override { pingFailed(); }
|
||||
|
||||
void run() override
|
||||
{
|
||||
while (! threadShouldExit())
|
||||
{
|
||||
if (--countdown <= 0 || ! sendPingMessage ({ pingMessage, specialMessageSize }))
|
||||
{
|
||||
triggerConnectionLostMessage();
|
||||
break;
|
||||
}
|
||||
|
||||
wait (1000);
|
||||
}
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcessPingThread)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct ChildProcessMaster::Connection : public InterprocessConnection,
|
||||
private ChildProcessPingThread
|
||||
{
|
||||
Connection (ChildProcessMaster& m, const String& pipeName, int timeout)
|
||||
: InterprocessConnection (false, magicMastSlaveConnectionHeader),
|
||||
ChildProcessPingThread (timeout),
|
||||
owner (m)
|
||||
{
|
||||
if (createPipe (pipeName, timeoutMs))
|
||||
startThread (4);
|
||||
}
|
||||
|
||||
~Connection()
|
||||
{
|
||||
stopThread (10000);
|
||||
}
|
||||
|
||||
private:
|
||||
void connectionMade() override {}
|
||||
void connectionLost() override { owner.handleConnectionLost(); }
|
||||
|
||||
bool sendPingMessage (const MemoryBlock& m) override { return owner.sendMessageToSlave (m); }
|
||||
void pingFailed() override { connectionLost(); }
|
||||
|
||||
void messageReceived (const MemoryBlock& m) override
|
||||
{
|
||||
pingReceived();
|
||||
|
||||
if (m.getSize() != specialMessageSize || ! isMessageType (m, pingMessage))
|
||||
owner.handleMessageFromSlave (m);
|
||||
}
|
||||
|
||||
ChildProcessMaster& owner;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Connection)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
ChildProcessMaster::ChildProcessMaster() {}
|
||||
|
||||
ChildProcessMaster::~ChildProcessMaster()
|
||||
{
|
||||
killSlaveProcess();
|
||||
}
|
||||
|
||||
void ChildProcessMaster::handleConnectionLost() {}
|
||||
|
||||
bool ChildProcessMaster::sendMessageToSlave (const MemoryBlock& mb)
|
||||
{
|
||||
if (connection != nullptr)
|
||||
return connection->sendMessage (mb);
|
||||
|
||||
jassertfalse; // this can only be used when the connection is active!
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ChildProcessMaster::launchSlaveProcess (const File& executable, const String& commandLineUniqueID,
|
||||
int timeoutMs, int streamFlags)
|
||||
{
|
||||
killSlaveProcess();
|
||||
|
||||
auto pipeName = "p" + String::toHexString (Random().nextInt64());
|
||||
|
||||
StringArray args;
|
||||
args.add (executable.getFullPathName());
|
||||
args.add (getCommandLinePrefix (commandLineUniqueID) + pipeName);
|
||||
|
||||
childProcess.reset (new ChildProcess());
|
||||
|
||||
if (childProcess->start (args, streamFlags))
|
||||
{
|
||||
connection.reset (new Connection (*this, pipeName, timeoutMs <= 0 ? defaultTimeoutMs : timeoutMs));
|
||||
|
||||
if (connection->isConnected())
|
||||
{
|
||||
sendMessageToSlave ({ startMessage, specialMessageSize });
|
||||
return true;
|
||||
}
|
||||
|
||||
connection.reset();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ChildProcessMaster::killSlaveProcess()
|
||||
{
|
||||
if (connection != nullptr)
|
||||
{
|
||||
sendMessageToSlave ({ killMessage, specialMessageSize });
|
||||
connection->disconnect();
|
||||
connection.reset();
|
||||
}
|
||||
|
||||
childProcess.reset();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct ChildProcessSlave::Connection : public InterprocessConnection,
|
||||
private ChildProcessPingThread
|
||||
{
|
||||
Connection (ChildProcessSlave& p, const String& pipeName, int timeout)
|
||||
: InterprocessConnection (false, magicMastSlaveConnectionHeader),
|
||||
ChildProcessPingThread (timeout),
|
||||
owner (p)
|
||||
{
|
||||
connectToPipe (pipeName, timeoutMs);
|
||||
startThread (4);
|
||||
}
|
||||
|
||||
~Connection()
|
||||
{
|
||||
stopThread (10000);
|
||||
}
|
||||
|
||||
private:
|
||||
ChildProcessSlave& owner;
|
||||
|
||||
void connectionMade() override {}
|
||||
void connectionLost() override { owner.handleConnectionLost(); }
|
||||
|
||||
bool sendPingMessage (const MemoryBlock& m) override { return owner.sendMessageToMaster (m); }
|
||||
void pingFailed() override { connectionLost(); }
|
||||
|
||||
void messageReceived (const MemoryBlock& m) override
|
||||
{
|
||||
pingReceived();
|
||||
|
||||
if (isMessageType (m, pingMessage))
|
||||
return;
|
||||
|
||||
if (isMessageType (m, killMessage))
|
||||
return triggerConnectionLostMessage();
|
||||
|
||||
if (isMessageType (m, startMessage))
|
||||
return owner.handleConnectionMade();
|
||||
|
||||
owner.handleMessageFromMaster (m);
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Connection)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
ChildProcessSlave::ChildProcessSlave() {}
|
||||
ChildProcessSlave::~ChildProcessSlave() {}
|
||||
|
||||
void ChildProcessSlave::handleConnectionMade() {}
|
||||
void ChildProcessSlave::handleConnectionLost() {}
|
||||
|
||||
bool ChildProcessSlave::sendMessageToMaster (const MemoryBlock& mb)
|
||||
{
|
||||
if (connection != nullptr)
|
||||
return connection->sendMessage (mb);
|
||||
|
||||
jassertfalse; // this can only be used when the connection is active!
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ChildProcessSlave::initialiseFromCommandLine (const String& commandLine,
|
||||
const String& commandLineUniqueID,
|
||||
int timeoutMs)
|
||||
{
|
||||
auto prefix = getCommandLinePrefix (commandLineUniqueID);
|
||||
|
||||
if (commandLine.trim().startsWith (prefix))
|
||||
{
|
||||
auto pipeName = commandLine.fromFirstOccurrenceOf (prefix, false, false)
|
||||
.upToFirstOccurrenceOf (" ", false, false).trim();
|
||||
|
||||
if (pipeName.isNotEmpty())
|
||||
{
|
||||
connection.reset (new Connection (*this, pipeName, timeoutMs <= 0 ? defaultTimeoutMs : timeoutMs));
|
||||
|
||||
if (! connection->isConnected())
|
||||
connection.reset();
|
||||
}
|
||||
}
|
||||
|
||||
return connection != nullptr;
|
||||
}
|
||||
|
||||
} // namespace juce
|
204
modules/juce_events/interprocess/juce_ConnectedChildProcess.h
Normal file
204
modules/juce_events/interprocess/juce_ConnectedChildProcess.h
Normal file
@ -0,0 +1,204 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Acts as the slave end of a master/slave pair of connected processes.
|
||||
|
||||
The ChildProcessSlave and ChildProcessMaster classes make it easy for an app
|
||||
to spawn a child process, and to manage a 2-way messaging connection to control it.
|
||||
|
||||
To use the system, you need to create subclasses of both ChildProcessSlave and
|
||||
ChildProcessMaster. To instantiate the ChildProcessSlave object, you must
|
||||
add some code to your main() or JUCEApplication::initialise() function that
|
||||
calls the initialiseFromCommandLine() method to check the app's command-line
|
||||
parameters to see whether it's being launched as a child process. If this returns
|
||||
true then the slave process can be allowed to run, and its handleMessageFromMaster()
|
||||
method will be called whenever a message arrives.
|
||||
|
||||
The juce demo app has a good example of this class in action.
|
||||
|
||||
@see ChildProcessMaster, InterprocessConnection, ChildProcess
|
||||
|
||||
@tags{Events}
|
||||
*/
|
||||
class JUCE_API ChildProcessSlave
|
||||
{
|
||||
public:
|
||||
/** Creates a non-connected slave process.
|
||||
Use initialiseFromCommandLine to connect to a master process.
|
||||
*/
|
||||
ChildProcessSlave();
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~ChildProcessSlave();
|
||||
|
||||
/** This checks some command-line parameters to see whether they were generated by
|
||||
ChildProcessMaster::launchSlaveProcess(), and if so, connects to that master process.
|
||||
|
||||
In an exe that can be used as a child process, you should add some code to your
|
||||
main() or JUCEApplication::initialise() that calls this method.
|
||||
|
||||
The commandLineUniqueID should be a short alphanumeric identifier (no spaces!)
|
||||
that matches the string passed to ChildProcessMaster::launchSlaveProcess().
|
||||
|
||||
The timeoutMs parameter lets you specify how long the child process is allowed
|
||||
to run without receiving a ping from the master before the master is considered to
|
||||
have died, and handleConnectionLost() will be called. Passing <= 0 for this timeout
|
||||
makes it use a default value.
|
||||
|
||||
Returns true if the command-line matches and the connection is made successfully.
|
||||
*/
|
||||
bool initialiseFromCommandLine (const String& commandLine,
|
||||
const String& commandLineUniqueID,
|
||||
int timeoutMs = 0);
|
||||
|
||||
//==============================================================================
|
||||
/** This will be called to deliver messages from the master process.
|
||||
The call will probably be made on a background thread, so be careful with your
|
||||
thread-safety! You may want to respond by sending back a message with
|
||||
sendMessageToMaster()
|
||||
*/
|
||||
virtual void handleMessageFromMaster (const MemoryBlock&) = 0;
|
||||
|
||||
/** This will be called when the master process finishes connecting to this slave.
|
||||
The call will probably be made on a background thread, so be careful with your thread-safety!
|
||||
*/
|
||||
virtual void handleConnectionMade();
|
||||
|
||||
/** This will be called when the connection to the master process is lost.
|
||||
The call may be made from any thread (including the message thread).
|
||||
Typically, if your process only exists to act as a slave, you should probably exit
|
||||
when this happens.
|
||||
*/
|
||||
virtual void handleConnectionLost();
|
||||
|
||||
/** Tries to send a message to the master process.
|
||||
This returns true if the message was sent, but doesn't check that it actually gets
|
||||
delivered at the other end. If successful, the data will emerge in a call to your
|
||||
ChildProcessMaster::handleMessageFromSlave().
|
||||
*/
|
||||
bool sendMessageToMaster (const MemoryBlock&);
|
||||
|
||||
private:
|
||||
struct Connection;
|
||||
friend struct Connection;
|
||||
friend struct ContainerDeletePolicy<Connection>;
|
||||
std::unique_ptr<Connection> connection;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcessSlave)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Acts as the master in a master/slave pair of connected processes.
|
||||
|
||||
The ChildProcessSlave and ChildProcessMaster classes make it easy for an app
|
||||
to spawn a child process, and to manage a 2-way messaging connection to control it.
|
||||
|
||||
To use the system, you need to create subclasses of both ChildProcessSlave and
|
||||
ChildProcessMaster. When you want your master process to launch the slave, you
|
||||
just call launchSlaveProcess(), and it'll attempt to launch the executable that
|
||||
you specify (which may be the same exe), and assuming it has been set-up to
|
||||
correctly parse the command-line parameters (see ChildProcessSlave) then a
|
||||
two-way connection will be created.
|
||||
|
||||
The juce demo app has a good example of this class in action.
|
||||
|
||||
@see ChildProcessSlave, InterprocessConnection, ChildProcess
|
||||
|
||||
@tags{Events}
|
||||
*/
|
||||
class JUCE_API ChildProcessMaster
|
||||
{
|
||||
public:
|
||||
/** Creates an uninitialised master process object.
|
||||
Use launchSlaveProcess to launch and connect to a child process.
|
||||
*/
|
||||
ChildProcessMaster();
|
||||
|
||||
/** Destructor.
|
||||
Note that the destructor calls killSlaveProcess(), but doesn't wait for
|
||||
the child process to finish terminating.
|
||||
*/
|
||||
virtual ~ChildProcessMaster();
|
||||
|
||||
/** Attempts to launch and connect to a slave process.
|
||||
This will start the given executable, passing it a special command-line
|
||||
parameter based around the commandLineUniqueID string, which must be a
|
||||
short alphanumeric string (no spaces!) that identifies your app. The exe
|
||||
that gets launched must respond by calling ChildProcessSlave::initialiseFromCommandLine()
|
||||
in its startup code, and must use a matching ID to commandLineUniqueID.
|
||||
|
||||
The timeoutMs parameter lets you specify how long the child process is allowed
|
||||
to go without sending a ping before it is considered to have died and
|
||||
handleConnectionLost() will be called. Passing <= 0 for this timeout makes
|
||||
it use a default value.
|
||||
|
||||
If this all works, the method returns true, and you can begin sending and
|
||||
receiving messages with the slave process.
|
||||
|
||||
If a child process is already running, this will call killSlaveProcess() and
|
||||
start a new one.
|
||||
*/
|
||||
bool launchSlaveProcess (const File& executableToLaunch,
|
||||
const String& commandLineUniqueID,
|
||||
int timeoutMs = 0,
|
||||
int streamFlags = ChildProcess::wantStdOut | ChildProcess::wantStdErr);
|
||||
|
||||
/** Sends a kill message to the slave, and disconnects from it.
|
||||
Note that this won't wait for it to terminate.
|
||||
*/
|
||||
void killSlaveProcess();
|
||||
|
||||
/** This will be called to deliver a message from the slave process.
|
||||
The call will probably be made on a background thread, so be careful with your thread-safety!
|
||||
*/
|
||||
virtual void handleMessageFromSlave (const MemoryBlock&) = 0;
|
||||
|
||||
/** This will be called when the slave process dies or is somehow disconnected.
|
||||
The call will probably be made on a background thread, so be careful with your thread-safety!
|
||||
*/
|
||||
virtual void handleConnectionLost();
|
||||
|
||||
/** Attempts to send a message to the slave process.
|
||||
This returns true if the message was dispatched, but doesn't check that it actually
|
||||
gets delivered at the other end. If successful, the data will emerge in a call to
|
||||
your ChildProcessSlave::handleMessageFromMaster().
|
||||
*/
|
||||
bool sendMessageToSlave (const MemoryBlock&);
|
||||
|
||||
private:
|
||||
std::unique_ptr<ChildProcess> childProcess;
|
||||
|
||||
struct Connection;
|
||||
friend struct Connection;
|
||||
friend struct ContainerDeletePolicy<Connection>;
|
||||
std::unique_ptr<Connection> connection;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcessMaster)
|
||||
};
|
||||
|
||||
} // namespace juce
|
371
modules/juce_events/interprocess/juce_InterprocessConnection.cpp
Normal file
371
modules/juce_events/interprocess/juce_InterprocessConnection.cpp
Normal file
@ -0,0 +1,371 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
struct InterprocessConnection::ConnectionThread : public Thread
|
||||
{
|
||||
ConnectionThread (InterprocessConnection& c) : Thread ("JUCE IPC"), owner (c) {}
|
||||
void run() override { owner.runThread(); }
|
||||
|
||||
InterprocessConnection& owner;
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConnectionThread)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
InterprocessConnection::InterprocessConnection (bool callbacksOnMessageThread, uint32 magicMessageHeaderNumber)
|
||||
: useMessageThread (callbacksOnMessageThread),
|
||||
magicMessageHeader (magicMessageHeaderNumber)
|
||||
{
|
||||
thread.reset (new ConnectionThread (*this));
|
||||
}
|
||||
|
||||
InterprocessConnection::~InterprocessConnection()
|
||||
{
|
||||
callbackConnectionState = false;
|
||||
disconnect();
|
||||
masterReference.clear();
|
||||
thread.reset();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool InterprocessConnection::connectToSocket (const String& hostName,
|
||||
int portNumber, int timeOutMillisecs)
|
||||
{
|
||||
disconnect();
|
||||
|
||||
const ScopedLock sl (pipeAndSocketLock);
|
||||
socket.reset (new StreamingSocket());
|
||||
|
||||
if (socket->connect (hostName, portNumber, timeOutMillisecs))
|
||||
{
|
||||
connectionMadeInt();
|
||||
thread->startThread();
|
||||
return true;
|
||||
}
|
||||
|
||||
socket.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InterprocessConnection::connectToPipe (const String& pipeName, int timeoutMs)
|
||||
{
|
||||
disconnect();
|
||||
|
||||
std::unique_ptr<NamedPipe> newPipe (new NamedPipe());
|
||||
|
||||
if (newPipe->openExisting (pipeName))
|
||||
{
|
||||
const ScopedLock sl (pipeAndSocketLock);
|
||||
pipeReceiveMessageTimeout = timeoutMs;
|
||||
initialiseWithPipe (newPipe.release());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InterprocessConnection::createPipe (const String& pipeName, int timeoutMs, bool mustNotExist)
|
||||
{
|
||||
disconnect();
|
||||
|
||||
std::unique_ptr<NamedPipe> newPipe (new NamedPipe());
|
||||
|
||||
if (newPipe->createNewPipe (pipeName, mustNotExist))
|
||||
{
|
||||
const ScopedLock sl (pipeAndSocketLock);
|
||||
pipeReceiveMessageTimeout = timeoutMs;
|
||||
initialiseWithPipe (newPipe.release());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void InterprocessConnection::disconnect()
|
||||
{
|
||||
thread->signalThreadShouldExit();
|
||||
|
||||
{
|
||||
const ScopedLock sl (pipeAndSocketLock);
|
||||
if (socket != nullptr) socket->close();
|
||||
if (pipe != nullptr) pipe->close();
|
||||
}
|
||||
|
||||
thread->stopThread (4000);
|
||||
deletePipeAndSocket();
|
||||
connectionLostInt();
|
||||
}
|
||||
|
||||
void InterprocessConnection::deletePipeAndSocket()
|
||||
{
|
||||
const ScopedLock sl (pipeAndSocketLock);
|
||||
socket.reset();
|
||||
pipe.reset();
|
||||
}
|
||||
|
||||
bool InterprocessConnection::isConnected() const
|
||||
{
|
||||
const ScopedLock sl (pipeAndSocketLock);
|
||||
|
||||
return ((socket != nullptr && socket->isConnected())
|
||||
|| (pipe != nullptr && pipe->isOpen()))
|
||||
&& thread->isThreadRunning();
|
||||
}
|
||||
|
||||
String InterprocessConnection::getConnectedHostName() const
|
||||
{
|
||||
{
|
||||
const ScopedLock sl (pipeAndSocketLock);
|
||||
|
||||
if (pipe == nullptr && socket == nullptr)
|
||||
return {};
|
||||
|
||||
if (socket != nullptr && ! socket->isLocal())
|
||||
return socket->getHostName();
|
||||
}
|
||||
|
||||
return IPAddress::local().toString();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool InterprocessConnection::sendMessage (const MemoryBlock& message)
|
||||
{
|
||||
uint32 messageHeader[2] = { ByteOrder::swapIfBigEndian (magicMessageHeader),
|
||||
ByteOrder::swapIfBigEndian ((uint32) message.getSize()) };
|
||||
|
||||
MemoryBlock messageData (sizeof (messageHeader) + message.getSize());
|
||||
messageData.copyFrom (messageHeader, 0, sizeof (messageHeader));
|
||||
messageData.copyFrom (message.getData(), sizeof (messageHeader), message.getSize());
|
||||
|
||||
return writeData (messageData.getData(), (int) messageData.getSize()) == (int) messageData.getSize();
|
||||
}
|
||||
|
||||
int InterprocessConnection::writeData (void* data, int dataSize)
|
||||
{
|
||||
const ScopedLock sl (pipeAndSocketLock);
|
||||
|
||||
if (socket != nullptr)
|
||||
return socket->write (data, dataSize);
|
||||
|
||||
if (pipe != nullptr)
|
||||
return pipe->write (data, dataSize, pipeReceiveMessageTimeout);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void InterprocessConnection::initialiseWithSocket (StreamingSocket* newSocket)
|
||||
{
|
||||
jassert (socket == nullptr && pipe == nullptr);
|
||||
socket.reset (newSocket);
|
||||
connectionMadeInt();
|
||||
thread->startThread();
|
||||
}
|
||||
|
||||
void InterprocessConnection::initialiseWithPipe (NamedPipe* newPipe)
|
||||
{
|
||||
jassert (socket == nullptr && pipe == nullptr);
|
||||
pipe.reset (newPipe);
|
||||
connectionMadeInt();
|
||||
thread->startThread();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct ConnectionStateMessage : public MessageManager::MessageBase
|
||||
{
|
||||
ConnectionStateMessage (InterprocessConnection* ipc, bool connected) noexcept
|
||||
: owner (ipc), connectionMade (connected)
|
||||
{}
|
||||
|
||||
void messageCallback() override
|
||||
{
|
||||
if (auto* ipc = owner.get())
|
||||
{
|
||||
if (connectionMade)
|
||||
ipc->connectionMade();
|
||||
else
|
||||
ipc->connectionLost();
|
||||
}
|
||||
}
|
||||
|
||||
WeakReference<InterprocessConnection> owner;
|
||||
bool connectionMade;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConnectionStateMessage)
|
||||
};
|
||||
|
||||
void InterprocessConnection::connectionMadeInt()
|
||||
{
|
||||
if (! callbackConnectionState)
|
||||
{
|
||||
callbackConnectionState = true;
|
||||
|
||||
if (useMessageThread)
|
||||
(new ConnectionStateMessage (this, true))->post();
|
||||
else
|
||||
connectionMade();
|
||||
}
|
||||
}
|
||||
|
||||
void InterprocessConnection::connectionLostInt()
|
||||
{
|
||||
if (callbackConnectionState)
|
||||
{
|
||||
callbackConnectionState = false;
|
||||
|
||||
if (useMessageThread)
|
||||
(new ConnectionStateMessage (this, false))->post();
|
||||
else
|
||||
connectionLost();
|
||||
}
|
||||
}
|
||||
|
||||
struct DataDeliveryMessage : public Message
|
||||
{
|
||||
DataDeliveryMessage (InterprocessConnection* ipc, const MemoryBlock& d)
|
||||
: owner (ipc), data (d)
|
||||
{}
|
||||
|
||||
void messageCallback() override
|
||||
{
|
||||
if (auto* ipc = owner.get())
|
||||
ipc->messageReceived (data);
|
||||
}
|
||||
|
||||
WeakReference<InterprocessConnection> owner;
|
||||
MemoryBlock data;
|
||||
};
|
||||
|
||||
void InterprocessConnection::deliverDataInt (const MemoryBlock& data)
|
||||
{
|
||||
jassert (callbackConnectionState);
|
||||
|
||||
if (useMessageThread)
|
||||
(new DataDeliveryMessage (this, data))->post();
|
||||
else
|
||||
messageReceived (data);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int InterprocessConnection::readData (void* data, int num)
|
||||
{
|
||||
if (socket != nullptr)
|
||||
return socket->read (data, num, true);
|
||||
|
||||
if (pipe != nullptr)
|
||||
return pipe->read (data, num, pipeReceiveMessageTimeout);
|
||||
|
||||
jassertfalse;
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool InterprocessConnection::readNextMessage()
|
||||
{
|
||||
uint32 messageHeader[2];
|
||||
auto bytes = readData (messageHeader, sizeof (messageHeader));
|
||||
|
||||
if (bytes == sizeof (messageHeader)
|
||||
&& ByteOrder::swapIfBigEndian (messageHeader[0]) == magicMessageHeader)
|
||||
{
|
||||
auto bytesInMessage = (int) ByteOrder::swapIfBigEndian (messageHeader[1]);
|
||||
|
||||
if (bytesInMessage > 0)
|
||||
{
|
||||
MemoryBlock messageData ((size_t) bytesInMessage, true);
|
||||
int bytesRead = 0;
|
||||
|
||||
while (bytesInMessage > 0)
|
||||
{
|
||||
if (thread->threadShouldExit())
|
||||
return false;
|
||||
|
||||
auto numThisTime = jmin (bytesInMessage, 65536);
|
||||
auto bytesIn = readData (addBytesToPointer (messageData.getData(), bytesRead), numThisTime);
|
||||
|
||||
if (bytesIn <= 0)
|
||||
break;
|
||||
|
||||
bytesRead += bytesIn;
|
||||
bytesInMessage -= bytesIn;
|
||||
}
|
||||
|
||||
if (bytesRead >= 0)
|
||||
deliverDataInt (messageData);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (bytes < 0)
|
||||
{
|
||||
if (socket != nullptr)
|
||||
deletePipeAndSocket();
|
||||
|
||||
connectionLostInt();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void InterprocessConnection::runThread()
|
||||
{
|
||||
while (! thread->threadShouldExit())
|
||||
{
|
||||
if (socket != nullptr)
|
||||
{
|
||||
auto ready = socket->waitUntilReady (true, 100);
|
||||
|
||||
if (ready < 0)
|
||||
{
|
||||
deletePipeAndSocket();
|
||||
connectionLostInt();
|
||||
break;
|
||||
}
|
||||
|
||||
if (ready == 0)
|
||||
{
|
||||
thread->wait (1);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (pipe != nullptr)
|
||||
{
|
||||
if (! pipe->isOpen())
|
||||
{
|
||||
deletePipeAndSocket();
|
||||
connectionLostInt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (thread->threadShouldExit() || ! readNextMessage())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
210
modules/juce_events/interprocess/juce_InterprocessConnection.h
Normal file
210
modules/juce_events/interprocess/juce_InterprocessConnection.h
Normal file
@ -0,0 +1,210 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class InterprocessConnectionServer;
|
||||
class MemoryBlock;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Manages a simple two-way messaging connection to another process, using either
|
||||
a socket or a named pipe as the transport medium.
|
||||
|
||||
To connect to a waiting socket or an open pipe, use the connectToSocket() or
|
||||
connectToPipe() methods. If this succeeds, messages can be sent to the other end,
|
||||
and incoming messages will result in a callback via the messageReceived()
|
||||
method.
|
||||
|
||||
To open a pipe and wait for another client to connect to it, use the createPipe()
|
||||
method.
|
||||
|
||||
To act as a socket server and create connections for one or more client, see the
|
||||
InterprocessConnectionServer class.
|
||||
|
||||
@see InterprocessConnectionServer, Socket, NamedPipe
|
||||
|
||||
@tags{Events}
|
||||
*/
|
||||
class JUCE_API InterprocessConnection
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a connection.
|
||||
|
||||
Connections are created manually, connecting them with the connectToSocket()
|
||||
or connectToPipe() methods, or they are created automatically by a InterprocessConnectionServer
|
||||
when a client wants to connect.
|
||||
|
||||
@param callbacksOnMessageThread if true, callbacks to the connectionMade(),
|
||||
connectionLost() and messageReceived() methods will
|
||||
always be made using the message thread; if false,
|
||||
these will be called immediately on the connection's
|
||||
own thread.
|
||||
@param magicMessageHeaderNumber a magic number to use in the header to check the
|
||||
validity of the data blocks being sent and received. This
|
||||
can be any number, but the sender and receiver must obviously
|
||||
use matching values or they won't recognise each other.
|
||||
*/
|
||||
InterprocessConnection (bool callbacksOnMessageThread = true,
|
||||
uint32 magicMessageHeaderNumber = 0xf2b49e2c);
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~InterprocessConnection();
|
||||
|
||||
//==============================================================================
|
||||
/** Tries to connect this object to a socket.
|
||||
|
||||
For this to work, the machine on the other end needs to have a InterprocessConnectionServer
|
||||
object waiting to receive client connections on this port number.
|
||||
|
||||
@param hostName the host computer, either a network address or name
|
||||
@param portNumber the socket port number to try to connect to
|
||||
@param timeOutMillisecs how long to keep trying before giving up
|
||||
@returns true if the connection is established successfully
|
||||
@see Socket
|
||||
*/
|
||||
bool connectToSocket (const String& hostName,
|
||||
int portNumber,
|
||||
int timeOutMillisecs);
|
||||
|
||||
/** Tries to connect the object to an existing named pipe.
|
||||
|
||||
For this to work, another process on the same computer must already have opened
|
||||
an InterprocessConnection object and used createPipe() to create a pipe for this
|
||||
to connect to.
|
||||
|
||||
@param pipeName the name to use for the pipe - this should be unique to your app
|
||||
@param pipeReceiveMessageTimeoutMs a timeout length to be used when reading or writing
|
||||
to the pipe, or -1 for an infinite timeout.
|
||||
@returns true if it connects successfully.
|
||||
@see createPipe, NamedPipe
|
||||
*/
|
||||
bool connectToPipe (const String& pipeName, int pipeReceiveMessageTimeoutMs);
|
||||
|
||||
/** Tries to create a new pipe for other processes to connect to.
|
||||
|
||||
This creates a pipe with the given name, so that other processes can use
|
||||
connectToPipe() to connect to the other end.
|
||||
|
||||
@param pipeName the name to use for the pipe - this should be unique to your app
|
||||
@param pipeReceiveMessageTimeoutMs a timeout length to be used when reading or writing
|
||||
to the pipe, or -1 for an infinite timeout
|
||||
@param mustNotExist if set to true, the method will fail if the pipe already exists
|
||||
@returns true if the pipe was created, or false if it fails (e.g. if another process is
|
||||
already using using the pipe)
|
||||
*/
|
||||
bool createPipe (const String& pipeName, int pipeReceiveMessageTimeoutMs, bool mustNotExist = false);
|
||||
|
||||
/** Disconnects and closes any currently-open sockets or pipes. */
|
||||
void disconnect();
|
||||
|
||||
/** True if a socket or pipe is currently active. */
|
||||
bool isConnected() const;
|
||||
|
||||
/** Returns the socket that this connection is using (or nullptr if it uses a pipe). */
|
||||
StreamingSocket* getSocket() const noexcept { return socket.get(); }
|
||||
|
||||
/** Returns the pipe that this connection is using (or nullptr if it uses a socket). */
|
||||
NamedPipe* getPipe() const noexcept { return pipe.get(); }
|
||||
|
||||
/** Returns the name of the machine at the other end of this connection.
|
||||
This may return an empty string if the name is unknown.
|
||||
*/
|
||||
String getConnectedHostName() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Tries to send a message to the other end of this connection.
|
||||
|
||||
This will fail if it's not connected, or if there's some kind of write error. If
|
||||
it succeeds, the connection object at the other end will receive the message by
|
||||
a callback to its messageReceived() method.
|
||||
|
||||
@see messageReceived
|
||||
*/
|
||||
bool sendMessage (const MemoryBlock& message);
|
||||
|
||||
//==============================================================================
|
||||
/** Called when the connection is first connected.
|
||||
|
||||
If the connection was created with the callbacksOnMessageThread flag set, then
|
||||
this will be called on the message thread; otherwise it will be called on a server
|
||||
thread.
|
||||
*/
|
||||
virtual void connectionMade() = 0;
|
||||
|
||||
/** Called when the connection is broken.
|
||||
|
||||
If the connection was created with the callbacksOnMessageThread flag set, then
|
||||
this will be called on the message thread; otherwise it will be called on a server
|
||||
thread.
|
||||
*/
|
||||
virtual void connectionLost() = 0;
|
||||
|
||||
/** Called when a message arrives.
|
||||
|
||||
When the object at the other end of this connection sends us a message with sendMessage(),
|
||||
this callback is used to deliver it to us.
|
||||
|
||||
If the connection was created with the callbacksOnMessageThread flag set, then
|
||||
this will be called on the message thread; otherwise it will be called on a server
|
||||
thread.
|
||||
|
||||
@see sendMessage
|
||||
*/
|
||||
virtual void messageReceived (const MemoryBlock& message) = 0;
|
||||
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
CriticalSection pipeAndSocketLock;
|
||||
std::unique_ptr<StreamingSocket> socket;
|
||||
std::unique_ptr<NamedPipe> pipe;
|
||||
bool callbackConnectionState = false;
|
||||
const bool useMessageThread;
|
||||
const uint32 magicMessageHeader;
|
||||
int pipeReceiveMessageTimeout = -1;
|
||||
|
||||
friend class InterprocessConnectionServer;
|
||||
void initialiseWithSocket (StreamingSocket*);
|
||||
void initialiseWithPipe (NamedPipe*);
|
||||
void deletePipeAndSocket();
|
||||
void connectionMadeInt();
|
||||
void connectionLostInt();
|
||||
void deliverDataInt (const MemoryBlock&);
|
||||
bool readNextMessage();
|
||||
int readData (void*, int);
|
||||
|
||||
struct ConnectionThread;
|
||||
friend struct ConnectionThread;
|
||||
friend struct ContainerDeletePolicy<ConnectionThread>;
|
||||
std::unique_ptr<ConnectionThread> thread;
|
||||
void runThread();
|
||||
int writeData (void*, int);
|
||||
|
||||
JUCE_DECLARE_WEAK_REFERENCEABLE (InterprocessConnection)
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InterprocessConnection)
|
||||
};
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,80 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
InterprocessConnectionServer::InterprocessConnectionServer() : Thread ("JUCE IPC server")
|
||||
{
|
||||
}
|
||||
|
||||
InterprocessConnectionServer::~InterprocessConnectionServer()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool InterprocessConnectionServer::beginWaitingForSocket (const int portNumber, const String& bindAddress)
|
||||
{
|
||||
stop();
|
||||
|
||||
socket.reset (new StreamingSocket());
|
||||
|
||||
if (socket->createListener (portNumber, bindAddress))
|
||||
{
|
||||
startThread();
|
||||
return true;
|
||||
}
|
||||
|
||||
socket.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
void InterprocessConnectionServer::stop()
|
||||
{
|
||||
signalThreadShouldExit();
|
||||
|
||||
if (socket != nullptr)
|
||||
socket->close();
|
||||
|
||||
stopThread (4000);
|
||||
socket.reset();
|
||||
}
|
||||
|
||||
int InterprocessConnectionServer::getBoundPort() const noexcept
|
||||
{
|
||||
return (socket == nullptr) ? -1 : socket->getBoundPort();
|
||||
}
|
||||
|
||||
void InterprocessConnectionServer::run()
|
||||
{
|
||||
while ((! threadShouldExit()) && socket != nullptr)
|
||||
{
|
||||
std::unique_ptr<StreamingSocket> clientSocket (socket->waitForNextConnection());
|
||||
|
||||
if (clientSocket != nullptr)
|
||||
if (auto* newConnection = createConnectionObject())
|
||||
newConnection->initialiseWithSocket (clientSocket.release());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,106 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
An object that waits for client sockets to connect to a port on this host, and
|
||||
creates InterprocessConnection objects for each one.
|
||||
|
||||
To use this, create a class derived from it which implements the createConnectionObject()
|
||||
method, so that it creates suitable connection objects for each client that tries
|
||||
to connect.
|
||||
|
||||
@see InterprocessConnection
|
||||
|
||||
@tags{Events}
|
||||
*/
|
||||
class JUCE_API InterprocessConnectionServer : private Thread
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an uninitialised server object.
|
||||
*/
|
||||
InterprocessConnectionServer();
|
||||
|
||||
/** Destructor. */
|
||||
~InterprocessConnectionServer();
|
||||
|
||||
//==============================================================================
|
||||
/** Starts an internal thread which listens on the given port number.
|
||||
|
||||
While this is running, if another process tries to connect with the
|
||||
InterprocessConnection::connectToSocket() method, this object will call
|
||||
createConnectionObject() to create a connection to that client.
|
||||
|
||||
Use stop() to stop the thread running.
|
||||
|
||||
@param portNumber The port on which the server will receive
|
||||
connections
|
||||
@param bindAddress The address on which the server will listen
|
||||
for connections. An empty string indicates
|
||||
that it should listen on all addresses
|
||||
assigned to this machine.
|
||||
|
||||
@see createConnectionObject, stop
|
||||
*/
|
||||
bool beginWaitingForSocket (int portNumber, const String& bindAddress = String());
|
||||
|
||||
/** Terminates the listener thread, if it's active.
|
||||
|
||||
@see beginWaitingForSocket
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/** Returns the local port number to which this server is currently bound.
|
||||
|
||||
This is useful if you need to know to which port the OS has actually bound your
|
||||
socket when calling beginWaitingForSocket with a port number of zero.
|
||||
|
||||
Returns -1 if the function fails.
|
||||
*/
|
||||
int getBoundPort() const noexcept;
|
||||
|
||||
protected:
|
||||
/** Creates a suitable connection object for a client process that wants to
|
||||
connect to this one.
|
||||
|
||||
This will be called by the listener thread when a client process tries
|
||||
to connect, and must return a new InterprocessConnection object that will
|
||||
then run as this end of the connection.
|
||||
|
||||
@see InterprocessConnection
|
||||
*/
|
||||
virtual InterprocessConnection* createConnectionObject() = 0;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
std::unique_ptr<StreamingSocket> socket;
|
||||
|
||||
void run() override;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InterprocessConnectionServer)
|
||||
};
|
||||
|
||||
} // namespace juce
|
102
modules/juce_events/juce_events.cpp
Normal file
102
modules/juce_events/juce_events.cpp
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifdef JUCE_EVENTS_H_INCLUDED
|
||||
/* When you add this cpp file to your project, you mustn't include it in a file where you've
|
||||
already included any other headers - just put it inside a file on its own, possibly with your config
|
||||
flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix
|
||||
header files that the compiler may be using.
|
||||
*/
|
||||
#error "Incorrect use of JUCE cpp file"
|
||||
#endif
|
||||
|
||||
#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1
|
||||
#define JUCE_CORE_INCLUDE_JNI_HELPERS 1
|
||||
#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1
|
||||
#define JUCE_CORE_INCLUDE_COM_SMART_PTR 1
|
||||
#define JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW 1
|
||||
|
||||
#if JUCE_USE_WINRT_MIDI
|
||||
#define JUCE_EVENTS_INCLUDE_WINRT_WRAPPER 1
|
||||
#endif
|
||||
|
||||
#include "juce_events.h"
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_MAC
|
||||
#import <IOKit/IOKitLib.h>
|
||||
#import <IOKit/IOCFPlugIn.h>
|
||||
#import <IOKit/hid/IOHIDLib.h>
|
||||
#import <IOKit/hid/IOHIDKeys.h>
|
||||
#import <IOKit/pwr_mgt/IOPMLib.h>
|
||||
|
||||
#elif JUCE_LINUX
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
#include "messages/juce_ApplicationBase.cpp"
|
||||
#include "messages/juce_DeletedAtShutdown.cpp"
|
||||
#include "messages/juce_MessageListener.cpp"
|
||||
#include "messages/juce_MessageManager.cpp"
|
||||
#include "broadcasters/juce_ActionBroadcaster.cpp"
|
||||
#include "broadcasters/juce_AsyncUpdater.cpp"
|
||||
#include "broadcasters/juce_ChangeBroadcaster.cpp"
|
||||
#include "timers/juce_MultiTimer.cpp"
|
||||
#include "timers/juce_Timer.cpp"
|
||||
#include "interprocess/juce_InterprocessConnection.cpp"
|
||||
#include "interprocess/juce_InterprocessConnectionServer.cpp"
|
||||
#include "interprocess/juce_ConnectedChildProcess.cpp"
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_MAC || JUCE_IOS
|
||||
|
||||
#include "native/juce_osx_MessageQueue.h"
|
||||
|
||||
#if JUCE_CLANG
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wundeclared-selector"
|
||||
#endif
|
||||
|
||||
#if JUCE_MAC
|
||||
#include "native/juce_mac_MessageManager.mm"
|
||||
#else
|
||||
#include "native/juce_ios_MessageManager.mm"
|
||||
#endif
|
||||
|
||||
#if JUCE_CLANG
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
#elif JUCE_WINDOWS
|
||||
#include "native/juce_win32_Messaging.cpp"
|
||||
#if JUCE_EVENTS_INCLUDE_WINRT_WRAPPER
|
||||
#include "native/juce_win32_WinRTWrapper.cpp"
|
||||
#endif
|
||||
|
||||
#elif JUCE_LINUX
|
||||
#include "native/juce_linux_Messaging.cpp"
|
||||
|
||||
#elif JUCE_ANDROID
|
||||
#include "native/juce_android_Messaging.cpp"
|
||||
|
||||
#endif
|
96
modules/juce_events/juce_events.h
Normal file
96
modules/juce_events/juce_events.h
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
The block below describes the properties of this module, and is read by
|
||||
the Projucer to automatically generate project code that uses it.
|
||||
For details about the syntax and how to create or use a module, see the
|
||||
JUCE Module Format.txt file.
|
||||
|
||||
|
||||
BEGIN_JUCE_MODULE_DECLARATION
|
||||
|
||||
ID: juce_events
|
||||
vendor: juce
|
||||
version: 5.3.2
|
||||
name: JUCE message and event handling classes
|
||||
description: Classes for running an application's main event loop and sending/receiving messages, timers, etc.
|
||||
website: http://www.juce.com/juce
|
||||
license: ISC
|
||||
|
||||
dependencies: juce_core
|
||||
|
||||
END_JUCE_MODULE_DECLARATION
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
|
||||
#pragma once
|
||||
#define JUCE_EVENTS_H_INCLUDED
|
||||
|
||||
#include <juce_core/juce_core.h>
|
||||
|
||||
//==============================================================================
|
||||
/** Config: JUCE_EXECUTE_APP_SUSPEND_ON_IOS_BACKGROUND_TASK
|
||||
Will execute your application's suspend method on an iOS background task, giving
|
||||
you extra time to save your applications state.
|
||||
*/
|
||||
#ifndef JUCE_EXECUTE_APP_SUSPEND_ON_BACKGROUND_TASK
|
||||
#define JUCE_EXECUTE_APP_SUSPEND_ON_BACKGROUND_TASK 0
|
||||
#endif
|
||||
|
||||
#if JUCE_EVENTS_INCLUDE_WINRT_WRAPPER && JUCE_WINDOWS
|
||||
#include <hstring.h>
|
||||
#endif
|
||||
|
||||
#include "messages/juce_MessageManager.h"
|
||||
#include "messages/juce_Message.h"
|
||||
#include "messages/juce_MessageListener.h"
|
||||
#include "messages/juce_CallbackMessage.h"
|
||||
#include "messages/juce_DeletedAtShutdown.h"
|
||||
#include "messages/juce_NotificationType.h"
|
||||
#include "messages/juce_ApplicationBase.h"
|
||||
#include "messages/juce_Initialisation.h"
|
||||
#include "messages/juce_MountedVolumeListChangeDetector.h"
|
||||
#include "broadcasters/juce_ActionBroadcaster.h"
|
||||
#include "broadcasters/juce_ActionListener.h"
|
||||
#include "broadcasters/juce_AsyncUpdater.h"
|
||||
#include "broadcasters/juce_ChangeListener.h"
|
||||
#include "broadcasters/juce_ChangeBroadcaster.h"
|
||||
#include "timers/juce_Timer.h"
|
||||
#include "timers/juce_MultiTimer.h"
|
||||
#include "interprocess/juce_InterprocessConnection.h"
|
||||
#include "interprocess/juce_InterprocessConnectionServer.h"
|
||||
#include "interprocess/juce_ConnectedChildProcess.h"
|
||||
|
||||
#if JUCE_LINUX
|
||||
#include "native/juce_linux_EventLoop.h"
|
||||
#endif
|
||||
|
||||
#if JUCE_WINDOWS
|
||||
#if JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW
|
||||
#include "native/juce_win32_HiddenMessageWindow.h"
|
||||
#endif
|
||||
#if JUCE_EVENTS_INCLUDE_WINRT_WRAPPER
|
||||
#include "native/juce_win32_WinRTWrapper.h"
|
||||
#endif
|
||||
#endif
|
23
modules/juce_events/juce_events.mm
Normal file
23
modules/juce_events/juce_events.mm
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#include "juce_events.cpp"
|
332
modules/juce_events/messages/juce_ApplicationBase.cpp
Normal file
332
modules/juce_events/messages/juce_ApplicationBase.cpp
Normal file
@ -0,0 +1,332 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
JUCEApplicationBase::CreateInstanceFunction JUCEApplicationBase::createInstance = 0;
|
||||
JUCEApplicationBase* JUCEApplicationBase::appInstance = nullptr;
|
||||
|
||||
#if JUCE_IOS
|
||||
void* JUCEApplicationBase::iOSCustomDelegate = nullptr;
|
||||
#endif
|
||||
|
||||
JUCEApplicationBase::JUCEApplicationBase()
|
||||
{
|
||||
jassert (isStandaloneApp() && appInstance == nullptr);
|
||||
appInstance = this;
|
||||
}
|
||||
|
||||
JUCEApplicationBase::~JUCEApplicationBase()
|
||||
{
|
||||
jassert (appInstance == this);
|
||||
appInstance = nullptr;
|
||||
}
|
||||
|
||||
void JUCEApplicationBase::setApplicationReturnValue (const int newReturnValue) noexcept
|
||||
{
|
||||
appReturnValue = newReturnValue;
|
||||
}
|
||||
|
||||
// This is called on the Mac and iOS where the OS doesn't allow the stack to unwind on shutdown..
|
||||
void JUCEApplicationBase::appWillTerminateByForce()
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
{
|
||||
const std::unique_ptr<JUCEApplicationBase> app (appInstance);
|
||||
|
||||
if (app != nullptr)
|
||||
app->shutdownApp();
|
||||
}
|
||||
|
||||
DeletedAtShutdown::deleteAll();
|
||||
MessageManager::deleteInstance();
|
||||
}
|
||||
}
|
||||
|
||||
void JUCEApplicationBase::quit()
|
||||
{
|
||||
MessageManager::getInstance()->stopDispatchLoop();
|
||||
}
|
||||
|
||||
void JUCEApplicationBase::sendUnhandledException (const std::exception* const e,
|
||||
const char* const sourceFile,
|
||||
const int lineNumber)
|
||||
{
|
||||
if (auto* app = JUCEApplicationBase::getInstance())
|
||||
{
|
||||
// If you hit this assertion then the __FILE__ macro is providing a
|
||||
// relative path instead of an absolute path. On Windows this will be
|
||||
// a path relative to the build directory rather than the currently
|
||||
// running application. To fix this you must compile with the /FC flag.
|
||||
jassert (File::isAbsolutePath (sourceFile));
|
||||
|
||||
app->unhandledException (e, sourceFile, lineNumber);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
#if ! (JUCE_IOS || JUCE_ANDROID)
|
||||
#define JUCE_HANDLE_MULTIPLE_INSTANCES 1
|
||||
#endif
|
||||
|
||||
#if JUCE_HANDLE_MULTIPLE_INSTANCES
|
||||
struct JUCEApplicationBase::MultipleInstanceHandler : public ActionListener
|
||||
{
|
||||
MultipleInstanceHandler (const String& appName)
|
||||
: appLock ("juceAppLock_" + appName)
|
||||
{
|
||||
}
|
||||
|
||||
bool sendCommandLineToPreexistingInstance()
|
||||
{
|
||||
if (appLock.enter (0))
|
||||
return false;
|
||||
|
||||
if (auto* app = JUCEApplicationBase::getInstance())
|
||||
{
|
||||
MessageManager::broadcastMessage (app->getApplicationName() + "/" + app->getCommandLineParameters());
|
||||
return true;
|
||||
}
|
||||
|
||||
jassertfalse;
|
||||
return false;
|
||||
}
|
||||
|
||||
void actionListenerCallback (const String& message) override
|
||||
{
|
||||
if (auto* app = JUCEApplicationBase::getInstance())
|
||||
{
|
||||
auto appName = app->getApplicationName();
|
||||
|
||||
if (message.startsWith (appName + "/"))
|
||||
app->anotherInstanceStarted (message.substring (appName.length() + 1));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
InterProcessLock appLock;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultipleInstanceHandler)
|
||||
};
|
||||
|
||||
bool JUCEApplicationBase::sendCommandLineToPreexistingInstance()
|
||||
{
|
||||
jassert (multipleInstanceHandler == nullptr); // this must only be called once!
|
||||
|
||||
multipleInstanceHandler.reset (new MultipleInstanceHandler (getApplicationName()));
|
||||
return multipleInstanceHandler->sendCommandLineToPreexistingInstance();
|
||||
}
|
||||
|
||||
#else
|
||||
struct JUCEApplicationBase::MultipleInstanceHandler {};
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_ANDROID
|
||||
|
||||
StringArray JUCEApplicationBase::getCommandLineParameterArray() { return {}; }
|
||||
String JUCEApplicationBase::getCommandLineParameters() { return {}; }
|
||||
|
||||
#else
|
||||
|
||||
#if JUCE_WINDOWS && ! defined (_CONSOLE)
|
||||
|
||||
String JUCE_CALLTYPE JUCEApplicationBase::getCommandLineParameters()
|
||||
{
|
||||
return CharacterFunctions::findEndOfToken (CharPointer_UTF16 (GetCommandLineW()),
|
||||
CharPointer_UTF16 (L" "),
|
||||
CharPointer_UTF16 (L"\"")).findEndOfWhitespace();
|
||||
}
|
||||
|
||||
StringArray JUCE_CALLTYPE JUCEApplicationBase::getCommandLineParameterArray()
|
||||
{
|
||||
StringArray s;
|
||||
int argc = 0;
|
||||
|
||||
if (auto argv = CommandLineToArgvW (GetCommandLineW(), &argc))
|
||||
{
|
||||
s = StringArray (argv + 1, argc - 1);
|
||||
LocalFree (argv);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#if JUCE_IOS
|
||||
extern int juce_iOSMain (int argc, const char* argv[], void* classPtr);
|
||||
#endif
|
||||
|
||||
#if JUCE_MAC
|
||||
extern void initialiseNSApplication();
|
||||
#endif
|
||||
|
||||
#if JUCE_LINUX && JUCE_MODULE_AVAILABLE_juce_gui_extra && (! defined(JUCE_WEB_BROWSER) || JUCE_WEB_BROWSER)
|
||||
extern int juce_gtkWebkitMain (int argc, const char* argv[]);
|
||||
#endif
|
||||
|
||||
#if JUCE_WINDOWS
|
||||
const char* const* juce_argv = nullptr;
|
||||
int juce_argc = 0;
|
||||
#else
|
||||
extern const char* const* juce_argv; // declared in juce_core
|
||||
extern int juce_argc;
|
||||
#endif
|
||||
|
||||
String JUCEApplicationBase::getCommandLineParameters()
|
||||
{
|
||||
String argString;
|
||||
|
||||
for (int i = 1; i < juce_argc; ++i)
|
||||
{
|
||||
String arg (juce_argv[i]);
|
||||
|
||||
if (arg.containsChar (' ') && ! arg.isQuotedString())
|
||||
arg = arg.quoted ('"');
|
||||
|
||||
argString << arg << ' ';
|
||||
}
|
||||
|
||||
return argString.trim();
|
||||
}
|
||||
|
||||
StringArray JUCEApplicationBase::getCommandLineParameterArray()
|
||||
{
|
||||
return StringArray (juce_argv + 1, juce_argc - 1);
|
||||
}
|
||||
|
||||
int JUCEApplicationBase::main (int argc, const char* argv[])
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
juce_argc = argc;
|
||||
juce_argv = argv;
|
||||
|
||||
#if JUCE_MAC
|
||||
initialiseNSApplication();
|
||||
#endif
|
||||
|
||||
#if JUCE_LINUX && JUCE_MODULE_AVAILABLE_juce_gui_extra && (! defined(JUCE_WEB_BROWSER) || JUCE_WEB_BROWSER)
|
||||
if (argc >= 2 && String (argv[1]) == "--juce-gtkwebkitfork-child")
|
||||
return juce_gtkWebkitMain (argc, argv);
|
||||
#endif
|
||||
|
||||
#if JUCE_IOS
|
||||
return juce_iOSMain (argc, argv, iOSCustomDelegate);
|
||||
#else
|
||||
|
||||
return JUCEApplicationBase::main();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
int JUCEApplicationBase::main()
|
||||
{
|
||||
ScopedJuceInitialiser_GUI libraryInitialiser;
|
||||
jassert (createInstance != nullptr);
|
||||
|
||||
const std::unique_ptr<JUCEApplicationBase> app (createInstance());
|
||||
jassert (app != nullptr);
|
||||
|
||||
if (! app->initialiseApp())
|
||||
return app->shutdownApp();
|
||||
|
||||
JUCE_TRY
|
||||
{
|
||||
// loop until a quit message is received..
|
||||
MessageManager::getInstance()->runDispatchLoop();
|
||||
}
|
||||
JUCE_CATCH_EXCEPTION
|
||||
|
||||
return app->shutdownApp();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
bool JUCEApplicationBase::initialiseApp()
|
||||
{
|
||||
#if JUCE_HANDLE_MULTIPLE_INSTANCES
|
||||
if ((! moreThanOneInstanceAllowed()) && sendCommandLineToPreexistingInstance())
|
||||
{
|
||||
DBG ("Another instance is running - quitting...");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if JUCE_WINDOWS && JUCE_STANDALONE_APPLICATION && (! defined (_CONSOLE)) && (! JUCE_MINGW)
|
||||
if (AttachConsole (ATTACH_PARENT_PROCESS) != 0)
|
||||
{
|
||||
// if we've launched a GUI app from cmd.exe or PowerShell, we need this to enable printf etc.
|
||||
// However, only reassign stdout, stderr, stdin if they have not been already opened by
|
||||
// a redirect or similar.
|
||||
FILE* ignore;
|
||||
|
||||
if (_fileno(stdout) < 0) freopen_s (&ignore, "CONOUT$", "w", stdout);
|
||||
if (_fileno(stderr) < 0) freopen_s (&ignore, "CONOUT$", "w", stderr);
|
||||
if (_fileno(stdin) < 0) freopen_s (&ignore, "CONIN$", "r", stdin);
|
||||
}
|
||||
#endif
|
||||
|
||||
// let the app do its setting-up..
|
||||
initialise (getCommandLineParameters());
|
||||
|
||||
stillInitialising = false;
|
||||
|
||||
if (MessageManager::getInstance()->hasStopMessageBeenSent())
|
||||
return false;
|
||||
|
||||
#if JUCE_HANDLE_MULTIPLE_INSTANCES
|
||||
if (auto* mih = multipleInstanceHandler.get())
|
||||
MessageManager::getInstance()->registerBroadcastListener (mih);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int JUCEApplicationBase::shutdownApp()
|
||||
{
|
||||
jassert (JUCEApplicationBase::getInstance() == this);
|
||||
|
||||
#if JUCE_HANDLE_MULTIPLE_INSTANCES
|
||||
if (auto* mih = multipleInstanceHandler.get())
|
||||
MessageManager::getInstance()->deregisterBroadcastListener (mih);
|
||||
#endif
|
||||
|
||||
JUCE_TRY
|
||||
{
|
||||
// give the app a chance to clean up..
|
||||
shutdown();
|
||||
}
|
||||
JUCE_CATCH_EXCEPTION
|
||||
|
||||
multipleInstanceHandler.reset();
|
||||
return getApplicationReturnValue();
|
||||
}
|
||||
|
||||
} // namespace juce
|
323
modules/juce_events/messages/juce_ApplicationBase.h
Normal file
323
modules/juce_events/messages/juce_ApplicationBase.h
Normal file
@ -0,0 +1,323 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Abstract base class for application classes.
|
||||
|
||||
Note that in the juce_gui_basics module, there's a utility class JUCEApplication
|
||||
which derives from JUCEApplicationBase, and takes care of a few chores. Most
|
||||
of the time you'll want to derive your class from JUCEApplication rather than
|
||||
using JUCEApplicationBase directly, but if you're not using the juce_gui_basics
|
||||
module then you might need to go straight to this base class.
|
||||
|
||||
Any application that wants to run an event loop must declare a subclass of
|
||||
JUCEApplicationBase, and implement its various pure virtual methods.
|
||||
|
||||
It then needs to use the START_JUCE_APPLICATION macro somewhere in a CPP file
|
||||
to declare an instance of this class and generate suitable platform-specific
|
||||
boilerplate code to launch the app.
|
||||
|
||||
e.g. @code
|
||||
class MyJUCEApp : public JUCEApplication
|
||||
{
|
||||
public:
|
||||
MyJUCEApp() {}
|
||||
~MyJUCEApp() {}
|
||||
|
||||
void initialise (const String& commandLine) override
|
||||
{
|
||||
myMainWindow.reset (new MyApplicationWindow());
|
||||
myMainWindow->setBounds (100, 100, 400, 500);
|
||||
myMainWindow->setVisible (true);
|
||||
}
|
||||
|
||||
void shutdown() override
|
||||
{
|
||||
myMainWindow = nullptr;
|
||||
}
|
||||
|
||||
const String getApplicationName() override
|
||||
{
|
||||
return "Super JUCE-o-matic";
|
||||
}
|
||||
|
||||
const String getApplicationVersion() override
|
||||
{
|
||||
return "1.0";
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<MyApplicationWindow> myMainWindow;
|
||||
};
|
||||
|
||||
// this generates boilerplate code to launch our app class:
|
||||
START_JUCE_APPLICATION (MyJUCEApp)
|
||||
@endcode
|
||||
|
||||
@see JUCEApplication, START_JUCE_APPLICATION
|
||||
|
||||
@tags{Events}
|
||||
*/
|
||||
class JUCE_API JUCEApplicationBase
|
||||
{
|
||||
protected:
|
||||
//==============================================================================
|
||||
JUCEApplicationBase();
|
||||
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~JUCEApplicationBase();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the global instance of the application object that's running. */
|
||||
static JUCEApplicationBase* getInstance() noexcept { return appInstance; }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the application's name. */
|
||||
virtual const String getApplicationName() = 0;
|
||||
|
||||
/** Returns the application's version number. */
|
||||
virtual const String getApplicationVersion() = 0;
|
||||
|
||||
/** Checks whether multiple instances of the app are allowed.
|
||||
|
||||
If your application class returns true for this, more than one instance is
|
||||
permitted to run (except on the Mac where this isn't possible).
|
||||
|
||||
If it's false, the second instance won't start, but it you will still get a
|
||||
callback to anotherInstanceStarted() to tell you about this - which
|
||||
gives you a chance to react to what the user was trying to do.
|
||||
*/
|
||||
virtual bool moreThanOneInstanceAllowed() = 0;
|
||||
|
||||
/** Called when the application starts.
|
||||
|
||||
This will be called once to let the application do whatever initialisation
|
||||
it needs, create its windows, etc.
|
||||
|
||||
After the method returns, the normal event-dispatch loop will be run,
|
||||
until the quit() method is called, at which point the shutdown()
|
||||
method will be called to let the application clear up anything it needs
|
||||
to delete.
|
||||
|
||||
If during the initialise() method, the application decides not to start-up
|
||||
after all, it can just call the quit() method and the event loop won't be run.
|
||||
|
||||
@param commandLineParameters the line passed in does not include the name of
|
||||
the executable, just the parameter list. To get the
|
||||
parameters as an array, you can call
|
||||
JUCEApplication::getCommandLineParameters()
|
||||
@see shutdown, quit
|
||||
*/
|
||||
virtual void initialise (const String& commandLineParameters) = 0;
|
||||
|
||||
/* Called to allow the application to clear up before exiting.
|
||||
|
||||
After JUCEApplication::quit() has been called, the event-dispatch loop will
|
||||
terminate, and this method will get called to allow the app to sort itself
|
||||
out.
|
||||
|
||||
Be careful that nothing happens in this method that might rely on messages
|
||||
being sent, or any kind of window activity, because the message loop is no
|
||||
longer running at this point.
|
||||
|
||||
@see DeletedAtShutdown
|
||||
*/
|
||||
virtual void shutdown() = 0;
|
||||
|
||||
/** Indicates that the user has tried to start up another instance of the app.
|
||||
|
||||
This will get called even if moreThanOneInstanceAllowed() is false.
|
||||
*/
|
||||
virtual void anotherInstanceStarted (const String& commandLine) = 0;
|
||||
|
||||
/** Called when the operating system is trying to close the application.
|
||||
|
||||
The default implementation of this method is to call quit(), but it may
|
||||
be overloaded to ignore the request or do some other special behaviour
|
||||
instead. For example, you might want to offer the user the chance to save
|
||||
their changes before quitting, and give them the chance to cancel.
|
||||
|
||||
If you want to send a quit signal to your app, this is the correct method
|
||||
to call, because it means that requests that come from the system get handled
|
||||
in the same way as those from your own application code. So e.g. you'd
|
||||
call this method from a "quit" item on a menu bar.
|
||||
*/
|
||||
virtual void systemRequestedQuit() = 0;
|
||||
|
||||
/** This method is called when the application is being put into background mode
|
||||
by the operating system.
|
||||
*/
|
||||
virtual void suspended() = 0;
|
||||
|
||||
/** This method is called when the application is being woken from background mode
|
||||
by the operating system.
|
||||
*/
|
||||
virtual void resumed() = 0;
|
||||
|
||||
/** If any unhandled exceptions make it through to the message dispatch loop, this
|
||||
callback will be triggered, in case you want to log them or do some other
|
||||
type of error-handling.
|
||||
|
||||
If the type of exception is derived from the std::exception class, the pointer
|
||||
passed-in will be valid. If the exception is of unknown type, this pointer
|
||||
will be null.
|
||||
*/
|
||||
virtual void unhandledException (const std::exception*,
|
||||
const String& sourceFilename,
|
||||
int lineNumber) = 0;
|
||||
|
||||
/** Called by the operating system to indicate that you should reduce your memory
|
||||
footprint.
|
||||
|
||||
You should override this method to free up some memory gracefully, if possible,
|
||||
otherwise the host may forcibly kill your app.
|
||||
|
||||
At the moment this method is only called on iOS.
|
||||
*/
|
||||
virtual void memoryWarningReceived() { jassertfalse; }
|
||||
|
||||
//==============================================================================
|
||||
/** Override this method to be informed when the back button is pressed on a device.
|
||||
This is currently only implemented on Android devices.
|
||||
*/
|
||||
virtual void backButtonPressed() {}
|
||||
|
||||
//==============================================================================
|
||||
/** Signals that the main message loop should stop and the application should terminate.
|
||||
|
||||
This isn't synchronous, it just posts a quit message to the main queue, and
|
||||
when this message arrives, the message loop will stop, the shutdown() method
|
||||
will be called, and the app will exit.
|
||||
|
||||
Note that this will cause an unconditional quit to happen, so if you need an
|
||||
extra level before this, e.g. to give the user the chance to save their work
|
||||
and maybe cancel the quit, you'll need to handle this in the systemRequestedQuit()
|
||||
method - see that method's help for more info.
|
||||
|
||||
@see MessageManager
|
||||
*/
|
||||
static void quit();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the application's command line parameters as a set of strings.
|
||||
@see getCommandLineParameters
|
||||
*/
|
||||
static StringArray JUCE_CALLTYPE getCommandLineParameterArray();
|
||||
|
||||
/** Returns the application's command line parameters as a single string.
|
||||
@see getCommandLineParameterArray
|
||||
*/
|
||||
static String JUCE_CALLTYPE getCommandLineParameters();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the value that should be returned as the application's exit code when the
|
||||
app quits.
|
||||
|
||||
This is the value that's returned by the main() function. Normally you'd leave this
|
||||
as 0 unless you want to indicate an error code.
|
||||
|
||||
@see getApplicationReturnValue
|
||||
*/
|
||||
void setApplicationReturnValue (int newReturnValue) noexcept;
|
||||
|
||||
/** Returns the value that has been set as the application's exit code.
|
||||
@see setApplicationReturnValue
|
||||
*/
|
||||
int getApplicationReturnValue() const noexcept { return appReturnValue; }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if this executable is running as an app (as opposed to being a plugin
|
||||
or other kind of shared library. */
|
||||
static bool isStandaloneApp() noexcept { return createInstance != nullptr; }
|
||||
|
||||
/** Returns true if the application hasn't yet completed its initialise() method
|
||||
and entered the main event loop.
|
||||
|
||||
This is handy for things like splash screens to know when the app's up-and-running
|
||||
properly.
|
||||
*/
|
||||
bool isInitialising() const noexcept { return stillInitialising; }
|
||||
|
||||
|
||||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
// The following methods are for internal use only...
|
||||
static int main();
|
||||
static int main (int argc, const char* argv[]);
|
||||
|
||||
static void appWillTerminateByForce();
|
||||
typedef JUCEApplicationBase* (*CreateInstanceFunction)();
|
||||
static CreateInstanceFunction createInstance;
|
||||
|
||||
#if JUCE_IOS
|
||||
static void* iOSCustomDelegate;
|
||||
#endif
|
||||
|
||||
virtual bool initialiseApp();
|
||||
int shutdownApp();
|
||||
static void JUCE_CALLTYPE sendUnhandledException (const std::exception*, const char* sourceFile, int lineNumber);
|
||||
bool sendCommandLineToPreexistingInstance();
|
||||
#endif
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
static JUCEApplicationBase* appInstance;
|
||||
int appReturnValue = 0;
|
||||
bool stillInitialising = true;
|
||||
|
||||
struct MultipleInstanceHandler;
|
||||
friend struct MultipleInstanceHandler;
|
||||
friend struct ContainerDeletePolicy<MultipleInstanceHandler>;
|
||||
std::unique_ptr<MultipleInstanceHandler> multipleInstanceHandler;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (JUCEApplicationBase)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_CATCH_UNHANDLED_EXCEPTIONS || defined (DOXYGEN)
|
||||
|
||||
/** The JUCE_TRY/JUCE_CATCH_EXCEPTION wrappers can be used to pass any uncaught exceptions to
|
||||
the JUCEApplicationBase::sendUnhandledException() method.
|
||||
This functionality can be enabled with the JUCE_CATCH_UNHANDLED_EXCEPTIONS macro.
|
||||
*/
|
||||
#define JUCE_TRY try
|
||||
|
||||
/** The JUCE_TRY/JUCE_CATCH_EXCEPTION wrappers can be used to pass any uncaught exceptions to
|
||||
the JUCEApplicationBase::sendUnhandledException() method.
|
||||
This functionality can be enabled with the JUCE_CATCH_UNHANDLED_EXCEPTIONS macro.
|
||||
*/
|
||||
#define JUCE_CATCH_EXCEPTION \
|
||||
catch (const std::exception& e) { juce::JUCEApplicationBase::sendUnhandledException (&e, __FILE__, __LINE__); } \
|
||||
catch (...) { juce::JUCEApplicationBase::sendUnhandledException (nullptr, __FILE__, __LINE__); }
|
||||
|
||||
#else
|
||||
#define JUCE_TRY
|
||||
#define JUCE_CATCH_EXCEPTION
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
74
modules/juce_events/messages/juce_CallbackMessage.h
Normal file
74
modules/juce_events/messages/juce_CallbackMessage.h
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A message that invokes a callback method when it gets delivered.
|
||||
|
||||
You can use this class to fire off actions that you want to be performed later
|
||||
on the message thread.
|
||||
|
||||
To use it, create a subclass of CallbackMessage which implements the messageCallback()
|
||||
method, then call post() to dispatch it. The event thread will then invoke your
|
||||
messageCallback() method later on, and will automatically delete the message object
|
||||
afterwards.
|
||||
|
||||
Always create a new instance of a CallbackMessage on the heap, as it will be
|
||||
deleted automatically after the message has been delivered.
|
||||
|
||||
Note that this class was essential back in the days before C++11, but in modern
|
||||
times you may prefer to use MessageManager::callAsync() with a lambda.
|
||||
|
||||
@see MessageManager::callAsync, MessageListener, ActionListener, ChangeListener
|
||||
|
||||
@tags{Events}
|
||||
*/
|
||||
class JUCE_API CallbackMessage : public MessageManager::MessageBase
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
CallbackMessage() noexcept {}
|
||||
|
||||
/** Destructor. */
|
||||
~CallbackMessage() {}
|
||||
|
||||
//==============================================================================
|
||||
/** Called when the message is delivered.
|
||||
|
||||
You should implement this method and make it do whatever action you want
|
||||
to perform.
|
||||
|
||||
Note that like all other messages, this object will be deleted immediately
|
||||
after this method has been invoked.
|
||||
*/
|
||||
virtual void messageCallback() = 0;
|
||||
|
||||
private:
|
||||
// Avoid the leak-detector because for plugins, the host can unload our DLL with undelivered
|
||||
// messages still in the system event queue. These aren't harmful, but can cause annoying assertions.
|
||||
JUCE_DECLARE_NON_COPYABLE (CallbackMessage)
|
||||
};
|
||||
|
||||
} // namespace juce
|
94
modules/juce_events/messages/juce_DeletedAtShutdown.cpp
Normal file
94
modules/juce_events/messages/juce_DeletedAtShutdown.cpp
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
static SpinLock deletedAtShutdownLock; // use a spin lock because it can be statically initialised
|
||||
|
||||
static Array<DeletedAtShutdown*>& getDeletedAtShutdownObjects()
|
||||
{
|
||||
static Array<DeletedAtShutdown*> objects;
|
||||
return objects;
|
||||
}
|
||||
|
||||
DeletedAtShutdown::DeletedAtShutdown()
|
||||
{
|
||||
const SpinLock::ScopedLockType sl (deletedAtShutdownLock);
|
||||
getDeletedAtShutdownObjects().add (this);
|
||||
}
|
||||
|
||||
DeletedAtShutdown::~DeletedAtShutdown()
|
||||
{
|
||||
const SpinLock::ScopedLockType sl (deletedAtShutdownLock);
|
||||
getDeletedAtShutdownObjects().removeFirstMatchingValue (this);
|
||||
}
|
||||
|
||||
#if JUCE_MSVC
|
||||
// Disable unreachable code warning, in case the compiler manages to figure out that
|
||||
// you have no classes of DeletedAtShutdown that could throw an exception in their destructor.
|
||||
#pragma warning (push)
|
||||
#pragma warning (disable: 4702)
|
||||
#endif
|
||||
|
||||
void DeletedAtShutdown::deleteAll()
|
||||
{
|
||||
// make a local copy of the array, so it can't get into a loop if something
|
||||
// creates another DeletedAtShutdown object during its destructor.
|
||||
Array<DeletedAtShutdown*> localCopy;
|
||||
|
||||
{
|
||||
const SpinLock::ScopedLockType sl (deletedAtShutdownLock);
|
||||
localCopy = getDeletedAtShutdownObjects();
|
||||
}
|
||||
|
||||
for (int i = localCopy.size(); --i >= 0;)
|
||||
{
|
||||
JUCE_TRY
|
||||
{
|
||||
auto* deletee = localCopy.getUnchecked(i);
|
||||
|
||||
// double-check that it's not already been deleted during another object's destructor.
|
||||
{
|
||||
const SpinLock::ScopedLockType sl (deletedAtShutdownLock);
|
||||
|
||||
if (! getDeletedAtShutdownObjects().contains (deletee))
|
||||
deletee = nullptr;
|
||||
}
|
||||
|
||||
delete deletee;
|
||||
}
|
||||
JUCE_CATCH_EXCEPTION
|
||||
}
|
||||
|
||||
// if this fails, then it's likely that some new DeletedAtShutdown objects were
|
||||
// created while executing the destructors of the other ones.
|
||||
jassert (getDeletedAtShutdownObjects().isEmpty());
|
||||
|
||||
getDeletedAtShutdownObjects().clear(); // just to make sure the array doesn't have any memory still allocated
|
||||
}
|
||||
|
||||
#if JUCE_MSVC
|
||||
#pragma warning (pop)
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
65
modules/juce_events/messages/juce_DeletedAtShutdown.h
Normal file
65
modules/juce_events/messages/juce_DeletedAtShutdown.h
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Classes derived from this will be automatically deleted when the application exits.
|
||||
|
||||
After JUCEApplicationBase::shutdown() has been called, any objects derived from
|
||||
DeletedAtShutdown which are still in existence will be deleted in the reverse
|
||||
order to that in which they were created.
|
||||
|
||||
So if you've got a singleton and don't want to have to explicitly delete it, just
|
||||
inherit from this and it'll be taken care of.
|
||||
|
||||
@tags{Events}
|
||||
*/
|
||||
class JUCE_API DeletedAtShutdown
|
||||
{
|
||||
protected:
|
||||
/** Creates a DeletedAtShutdown object. */
|
||||
DeletedAtShutdown();
|
||||
|
||||
/** Destructor.
|
||||
|
||||
It's ok to delete these objects explicitly - it's only the ones left
|
||||
dangling at the end that will be deleted automatically.
|
||||
*/
|
||||
virtual ~DeletedAtShutdown();
|
||||
|
||||
|
||||
public:
|
||||
/** Deletes all extant objects.
|
||||
|
||||
This shouldn't be used by applications, as it's called automatically
|
||||
in the shutdown code of the JUCEApplicationBase class.
|
||||
*/
|
||||
static void deleteAll();
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE (DeletedAtShutdown)
|
||||
};
|
||||
|
||||
} // namespace juce
|
204
modules/juce_events/messages/juce_Initialisation.h
Normal file
204
modules/juce_events/messages/juce_Initialisation.h
Normal file
@ -0,0 +1,204 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/** Initialises JUCE's GUI classes.
|
||||
|
||||
If you're embedding JUCE into an application that uses its own event-loop rather
|
||||
than using the START_JUCE_APPLICATION macro, call this function before making any
|
||||
JUCE calls, to make sure things are initialised correctly.
|
||||
|
||||
Note that if you're creating a JUCE DLL for Windows, you may also need to call the
|
||||
Process::setCurrentModuleInstanceHandle() method.
|
||||
|
||||
@see shutdownJuce_GUI()
|
||||
*/
|
||||
JUCE_API void JUCE_CALLTYPE initialiseJuce_GUI();
|
||||
|
||||
/** Clears up any static data being used by JUCE's GUI classes.
|
||||
|
||||
If you're embedding JUCE into an application that uses its own event-loop rather
|
||||
than using the START_JUCE_APPLICATION macro, call this function in your shutdown
|
||||
code to clean up any JUCE objects that might be lying around.
|
||||
|
||||
@see initialiseJuce_GUI()
|
||||
*/
|
||||
JUCE_API void JUCE_CALLTYPE shutdownJuce_GUI();
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** A utility object that helps you initialise and shutdown JUCE correctly
|
||||
using an RAII pattern.
|
||||
|
||||
When the first instance of this class is created, it calls initialiseJuce_GUI(),
|
||||
and when the last instance is deleted, it calls shutdownJuce_GUI(), so that you
|
||||
can easily be sure that as long as at least one instance of the class exists, the
|
||||
library will be initialised.
|
||||
|
||||
This class is particularly handy to use at the beginning of a console app's
|
||||
main() function, because it'll take care of shutting down whenever you return
|
||||
from the main() call.
|
||||
|
||||
Be careful with your threading though - to be safe, you should always make sure
|
||||
that these objects are created and deleted on the message thread.
|
||||
|
||||
@tags{Events}
|
||||
*/
|
||||
class JUCE_API ScopedJuceInitialiser_GUI final
|
||||
{
|
||||
public:
|
||||
/** The constructor simply calls initialiseJuce_GUI(). */
|
||||
ScopedJuceInitialiser_GUI();
|
||||
|
||||
/** The destructor simply calls shutdownJuce_GUI(). */
|
||||
~ScopedJuceInitialiser_GUI();
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
To start a JUCE app, use this macro: START_JUCE_APPLICATION (AppSubClass) where
|
||||
AppSubClass is the name of a class derived from JUCEApplication or JUCEApplicationBase.
|
||||
|
||||
See the JUCEApplication and JUCEApplicationBase class documentation for more details.
|
||||
*/
|
||||
#ifdef DOXYGEN
|
||||
#define START_JUCE_APPLICATION(AppClass)
|
||||
#else
|
||||
#if JUCE_WINDOWS && ! defined (_CONSOLE)
|
||||
#define JUCE_MAIN_FUNCTION int __stdcall WinMain (struct HINSTANCE__*, struct HINSTANCE__*, char*, int)
|
||||
#define JUCE_MAIN_FUNCTION_ARGS
|
||||
#else
|
||||
#define JUCE_MAIN_FUNCTION int main (int argc, char* argv[])
|
||||
#define JUCE_MAIN_FUNCTION_ARGS argc, (const char**) argv
|
||||
#endif
|
||||
|
||||
#if JUCE_IOS
|
||||
|
||||
#define JUCE_CREATE_APPLICATION_DEFINE(AppClass) \
|
||||
juce::JUCEApplicationBase* juce_CreateApplication() { return new AppClass(); } \
|
||||
void* juce_GetIOSCustomDelegateClass() { return nullptr; }
|
||||
|
||||
#define JUCE_CREATE_APPLICATION_DEFINE_CUSTOM_DELEGATE(AppClass, DelegateClass) \
|
||||
juce::JUCEApplicationBase* juce_CreateApplication() { return new AppClass(); } \
|
||||
void* juce_GetIOSCustomDelegateClass() { return [DelegateClass class]; }
|
||||
|
||||
#define JUCE_MAIN_FUNCTION_DEFINITION \
|
||||
extern "C" JUCE_MAIN_FUNCTION \
|
||||
{ \
|
||||
juce::JUCEApplicationBase::createInstance = &juce_CreateApplication; \
|
||||
juce::JUCEApplicationBase::iOSCustomDelegate = juce_GetIOSCustomDelegateClass(); \
|
||||
return juce::JUCEApplicationBase::main (JUCE_MAIN_FUNCTION_ARGS); \
|
||||
}
|
||||
|
||||
#elif JUCE_ANDROID
|
||||
|
||||
#define JUCE_CREATE_APPLICATION_DEFINE(AppClass) \
|
||||
juce::JUCEApplicationBase* juce_CreateApplication() { return new AppClass(); }
|
||||
|
||||
#define JUCE_MAIN_FUNCTION_DEFINITION
|
||||
|
||||
#else
|
||||
|
||||
#define JUCE_CREATE_APPLICATION_DEFINE(AppClass) \
|
||||
juce::JUCEApplicationBase* juce_CreateApplication(); \
|
||||
juce::JUCEApplicationBase* juce_CreateApplication() { return new AppClass(); }
|
||||
|
||||
#define JUCE_MAIN_FUNCTION_DEFINITION \
|
||||
extern "C" JUCE_MAIN_FUNCTION \
|
||||
{ \
|
||||
juce::JUCEApplicationBase::createInstance = &juce_CreateApplication; \
|
||||
return juce::JUCEApplicationBase::main (JUCE_MAIN_FUNCTION_ARGS); \
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if JucePlugin_Build_Standalone
|
||||
#if JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP
|
||||
#define START_JUCE_APPLICATION(AppClass) JUCE_CREATE_APPLICATION_DEFINE(AppClass)
|
||||
#if JUCE_IOS
|
||||
#define START_JUCE_APPLICATION_WITH_CUSTOM_DELEGATE(AppClass, DelegateClass) JUCE_CREATE_APPLICATION_DEFINE_CUSTOM_DELEGATE(AppClass, DelegateClass)
|
||||
#endif
|
||||
#else
|
||||
#define START_JUCE_APPLICATION(AppClass) static_assert(false, "You are trying to use START_JUCE_APPLICATION in an audio plug-in. Define JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP=1 if you want to use a custom standalone target app.");
|
||||
#if JUCE_IOS
|
||||
#define START_JUCE_APPLICATION_WITH_CUSTOM_DELEGATE(AppClass, DelegateClass) static_assert(false, "You are trying to use START_JUCE_APPLICATION in an audio plug-in. Define JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP=1 if you want to use a custom standalone target app.");
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
|
||||
#define START_JUCE_APPLICATION(AppClass) \
|
||||
JUCE_CREATE_APPLICATION_DEFINE(AppClass) \
|
||||
JUCE_MAIN_FUNCTION_DEFINITION
|
||||
|
||||
#if JUCE_IOS
|
||||
/**
|
||||
You can instruct JUCE to use a custom iOS app delegate class instaed of JUCE's default
|
||||
app delegate. For JUCE to work you must pass all messages to JUCE's internal app delegate.
|
||||
Below is an example of minimal forwarding custom delegate. Note that you are at your own
|
||||
risk if you decide to use your own delegate and subtle, hard to debug bugs may occur.
|
||||
|
||||
@interface MyCustomDelegate : NSObject <UIApplicationDelegate> { NSObject<UIApplicationDelegate>* juceDelegate; } @end
|
||||
|
||||
@implementation MyCustomDelegate
|
||||
|
||||
-(id) init
|
||||
{
|
||||
self = [super init];
|
||||
juceDelegate = reinterpret_cast<NSObject<UIApplicationDelegate>*> ([[NSClassFromString (@"JuceAppStartupDelegate") alloc] init]);
|
||||
return self;
|
||||
}
|
||||
|
||||
-(void) dealloc
|
||||
{
|
||||
[juceDelegate release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void) forwardInvocation: (NSInvocation*) anInvocation
|
||||
{
|
||||
if (juceDelegate != nullptr && [juceDelegate respondsToSelector: [anInvocation selector]])
|
||||
[anInvocation invokeWithTarget: juceDelegate];
|
||||
else
|
||||
[super forwardInvocation: anInvocation];
|
||||
}
|
||||
|
||||
-(BOOL) respondsToSelector: (SEL) aSelector
|
||||
{
|
||||
if (juceDelegate != nullptr && [juceDelegate respondsToSelector: aSelector])
|
||||
return YES;
|
||||
|
||||
return [super respondsToSelector: aSelector];
|
||||
}
|
||||
@end
|
||||
*/
|
||||
#define START_JUCE_APPLICATION_WITH_CUSTOM_DELEGATE(AppClass, DelegateClass) \
|
||||
JUCE_CREATE_APPLICATION_DEFINE_CUSTOM_DELEGATE(AppClass, DelegateClass) \
|
||||
JUCE_MAIN_FUNCTION_DEFINITION
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
64
modules/juce_events/messages/juce_Message.h
Normal file
64
modules/juce_events/messages/juce_Message.h
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class MessageListener;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** The base class for objects that can be sent to a MessageListener.
|
||||
|
||||
If you want to send a message that carries some kind of custom data, just
|
||||
create a subclass of Message with some appropriate member variables to hold
|
||||
your data.
|
||||
|
||||
Always create a new instance of a Message object on the heap, as it will be
|
||||
deleted automatically after the message has been delivered.
|
||||
|
||||
@see MessageListener, MessageManager, ActionListener, ChangeListener
|
||||
|
||||
@tags{Events}
|
||||
*/
|
||||
class JUCE_API Message : public MessageManager::MessageBase
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an uninitialised message. */
|
||||
Message() noexcept;
|
||||
~Message();
|
||||
|
||||
using Ptr = ReferenceCountedObjectPtr<Message>;
|
||||
|
||||
//==============================================================================
|
||||
private:
|
||||
friend class MessageListener;
|
||||
WeakReference<MessageListener> recipient;
|
||||
void messageCallback() override;
|
||||
|
||||
// Avoid the leak-detector because for plugins, the host can unload our DLL with undelivered
|
||||
// messages still in the system event queue. These aren't harmful, but can cause annoying assertions.
|
||||
JUCE_DECLARE_NON_COPYABLE (Message)
|
||||
};
|
||||
|
||||
} // namespace juce
|
52
modules/juce_events/messages/juce_MessageListener.cpp
Normal file
52
modules/juce_events/messages/juce_MessageListener.cpp
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
Message::Message() noexcept {}
|
||||
Message::~Message() {}
|
||||
|
||||
void Message::messageCallback()
|
||||
{
|
||||
if (auto* r = recipient.get())
|
||||
r->handleMessage (*this);
|
||||
}
|
||||
|
||||
MessageListener::MessageListener() noexcept
|
||||
{
|
||||
// Are you trying to create a messagelistener before or after juce has been intialised??
|
||||
jassert (MessageManager::getInstanceWithoutCreating() != nullptr);
|
||||
}
|
||||
|
||||
MessageListener::~MessageListener()
|
||||
{
|
||||
masterReference.clear();
|
||||
}
|
||||
|
||||
void MessageListener::postMessage (Message* const message) const
|
||||
{
|
||||
message->recipient = const_cast<MessageListener*> (this);
|
||||
message->post();
|
||||
}
|
||||
|
||||
} // namespace juce
|
70
modules/juce_events/messages/juce_MessageListener.h
Normal file
70
modules/juce_events/messages/juce_MessageListener.h
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
MessageListener subclasses can post and receive Message objects.
|
||||
|
||||
@see Message, MessageManager, ActionListener, ChangeListener
|
||||
|
||||
@tags{Events}
|
||||
*/
|
||||
class JUCE_API MessageListener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
MessageListener() noexcept;
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~MessageListener();
|
||||
|
||||
//==============================================================================
|
||||
/** This is the callback method that receives incoming messages.
|
||||
|
||||
This is called by the MessageManager from its dispatch loop.
|
||||
|
||||
@see postMessage
|
||||
*/
|
||||
virtual void handleMessage (const Message& message) = 0;
|
||||
|
||||
//==============================================================================
|
||||
/** Sends a message to the message queue, for asynchronous delivery to this listener
|
||||
later on.
|
||||
|
||||
This method can be called safely by any thread.
|
||||
|
||||
@param message the message object to send - this will be deleted
|
||||
automatically by the message queue, so make sure it's
|
||||
allocated on the heap, not the stack!
|
||||
@see handleMessage
|
||||
*/
|
||||
void postMessage (Message* message) const;
|
||||
|
||||
private:
|
||||
WeakReference<MessageListener>::Master masterReference;
|
||||
friend class WeakReference<MessageListener>;
|
||||
};
|
||||
|
||||
} // namespace juce
|
452
modules/juce_events/messages/juce_MessageManager.cpp
Normal file
452
modules/juce_events/messages/juce_MessageManager.cpp
Normal file
@ -0,0 +1,452 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
MessageManager::MessageManager() noexcept
|
||||
: messageThreadId (Thread::getCurrentThreadId())
|
||||
{
|
||||
if (JUCEApplicationBase::isStandaloneApp())
|
||||
Thread::setCurrentThreadName ("JUCE Message Thread");
|
||||
}
|
||||
|
||||
MessageManager::~MessageManager() noexcept
|
||||
{
|
||||
broadcaster.reset();
|
||||
|
||||
doPlatformSpecificShutdown();
|
||||
|
||||
jassert (instance == this);
|
||||
instance = nullptr; // do this last in case this instance is still needed by doPlatformSpecificShutdown()
|
||||
}
|
||||
|
||||
MessageManager* MessageManager::instance = nullptr;
|
||||
|
||||
MessageManager* MessageManager::getInstance()
|
||||
{
|
||||
if (instance == nullptr)
|
||||
{
|
||||
instance = new MessageManager();
|
||||
doPlatformSpecificInitialisation();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
MessageManager* MessageManager::getInstanceWithoutCreating() noexcept
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
|
||||
void MessageManager::deleteInstance()
|
||||
{
|
||||
deleteAndZero (instance);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool MessageManager::MessageBase::post()
|
||||
{
|
||||
auto* mm = MessageManager::instance;
|
||||
|
||||
if (mm == nullptr || mm->quitMessagePosted.get() != 0 || ! postMessageToSystemQueue (this))
|
||||
{
|
||||
Ptr deleter (this); // (this will delete messages that were just created with a 0 ref count)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED && ! (JUCE_MAC || JUCE_IOS)
|
||||
bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor)
|
||||
{
|
||||
jassert (isThisTheMessageThread()); // must only be called by the message thread
|
||||
|
||||
auto endTime = Time::currentTimeMillis() + millisecondsToRunFor;
|
||||
|
||||
while (quitMessageReceived.get() == 0)
|
||||
{
|
||||
JUCE_TRY
|
||||
{
|
||||
if (! dispatchNextMessageOnSystemQueue (millisecondsToRunFor >= 0))
|
||||
Thread::sleep (1);
|
||||
}
|
||||
JUCE_CATCH_EXCEPTION
|
||||
|
||||
if (millisecondsToRunFor >= 0 && Time::currentTimeMillis() >= endTime)
|
||||
break;
|
||||
}
|
||||
|
||||
return quitMessageReceived.get() == 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ! (JUCE_MAC || JUCE_IOS || JUCE_ANDROID)
|
||||
class MessageManager::QuitMessage : public MessageManager::MessageBase
|
||||
{
|
||||
public:
|
||||
QuitMessage() {}
|
||||
|
||||
void messageCallback() override
|
||||
{
|
||||
if (auto* mm = MessageManager::instance)
|
||||
mm->quitMessageReceived = true;
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (QuitMessage)
|
||||
};
|
||||
|
||||
void MessageManager::runDispatchLoop()
|
||||
{
|
||||
jassert (isThisTheMessageThread()); // must only be called by the message thread
|
||||
|
||||
while (quitMessageReceived.get() == 0)
|
||||
{
|
||||
JUCE_TRY
|
||||
{
|
||||
if (! dispatchNextMessageOnSystemQueue (false))
|
||||
Thread::sleep (1);
|
||||
}
|
||||
JUCE_CATCH_EXCEPTION
|
||||
}
|
||||
}
|
||||
|
||||
void MessageManager::stopDispatchLoop()
|
||||
{
|
||||
(new QuitMessage())->post();
|
||||
quitMessagePosted = true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
class AsyncFunctionCallback : public MessageManager::MessageBase
|
||||
{
|
||||
public:
|
||||
AsyncFunctionCallback (MessageCallbackFunction* const f, void* const param)
|
||||
: func (f), parameter (param)
|
||||
{}
|
||||
|
||||
void messageCallback() override
|
||||
{
|
||||
result = (*func) (parameter);
|
||||
finished.signal();
|
||||
}
|
||||
|
||||
WaitableEvent finished;
|
||||
std::atomic<void*> result { nullptr };
|
||||
|
||||
private:
|
||||
MessageCallbackFunction* const func;
|
||||
void* const parameter;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (AsyncFunctionCallback)
|
||||
};
|
||||
|
||||
void* MessageManager::callFunctionOnMessageThread (MessageCallbackFunction* const func, void* const parameter)
|
||||
{
|
||||
if (isThisTheMessageThread())
|
||||
return func (parameter);
|
||||
|
||||
// If this thread has the message manager locked, then this will deadlock!
|
||||
jassert (! currentThreadHasLockedMessageManager());
|
||||
|
||||
const ReferenceCountedObjectPtr<AsyncFunctionCallback> message (new AsyncFunctionCallback (func, parameter));
|
||||
|
||||
if (message->post())
|
||||
{
|
||||
message->finished.wait();
|
||||
return message->result.load();
|
||||
}
|
||||
|
||||
jassertfalse; // the OS message queue failed to send the message!
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MessageManager::deliverBroadcastMessage (const String& value)
|
||||
{
|
||||
if (broadcaster != nullptr)
|
||||
broadcaster->sendActionMessage (value);
|
||||
}
|
||||
|
||||
void MessageManager::registerBroadcastListener (ActionListener* const listener)
|
||||
{
|
||||
if (broadcaster == nullptr)
|
||||
broadcaster.reset (new ActionBroadcaster());
|
||||
|
||||
broadcaster->addActionListener (listener);
|
||||
}
|
||||
|
||||
void MessageManager::deregisterBroadcastListener (ActionListener* const listener)
|
||||
{
|
||||
if (broadcaster != nullptr)
|
||||
broadcaster->removeActionListener (listener);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool MessageManager::isThisTheMessageThread() const noexcept
|
||||
{
|
||||
return Thread::getCurrentThreadId() == messageThreadId;
|
||||
}
|
||||
|
||||
void MessageManager::setCurrentThreadAsMessageThread()
|
||||
{
|
||||
auto thisThread = Thread::getCurrentThreadId();
|
||||
|
||||
if (messageThreadId != thisThread)
|
||||
{
|
||||
messageThreadId = thisThread;
|
||||
|
||||
// This is needed on windows to make sure the message window is created by this thread
|
||||
doPlatformSpecificShutdown();
|
||||
doPlatformSpecificInitialisation();
|
||||
}
|
||||
}
|
||||
|
||||
bool MessageManager::currentThreadHasLockedMessageManager() const noexcept
|
||||
{
|
||||
auto thisThread = Thread::getCurrentThreadId();
|
||||
return thisThread == messageThreadId || thisThread == threadWithLock.get();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
//==============================================================================
|
||||
/* The only safe way to lock the message thread while another thread does
|
||||
some work is by posting a special message, whose purpose is to tie up the event
|
||||
loop until the other thread has finished its business.
|
||||
|
||||
Any other approach can get horribly deadlocked if the OS uses its own hidden locks which
|
||||
get locked before making an event callback, because if the same OS lock gets indirectly
|
||||
accessed from another thread inside a MM lock, you're screwed. (this is exactly what happens
|
||||
in Cocoa).
|
||||
*/
|
||||
struct MessageManager::Lock::BlockingMessage : public MessageManager::MessageBase
|
||||
{
|
||||
BlockingMessage (const MessageManager::Lock* parent) noexcept
|
||||
// need a const_cast here as VS2013 doesn't like a const pointer to be in an atomic
|
||||
: owner (const_cast<MessageManager::Lock*> (parent)) {}
|
||||
|
||||
void messageCallback() override
|
||||
{
|
||||
{
|
||||
ScopedLock lock (ownerCriticalSection);
|
||||
|
||||
if (auto* o = owner.get())
|
||||
o->messageCallback();
|
||||
}
|
||||
|
||||
releaseEvent.wait();
|
||||
}
|
||||
|
||||
CriticalSection ownerCriticalSection;
|
||||
Atomic<MessageManager::Lock*> owner;
|
||||
WaitableEvent releaseEvent;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (BlockingMessage)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
MessageManager::Lock::Lock() {}
|
||||
MessageManager::Lock::~Lock() { exit(); }
|
||||
void MessageManager::Lock::enter() const noexcept { tryAcquire (true); }
|
||||
bool MessageManager::Lock::tryEnter() const noexcept { return tryAcquire (false); }
|
||||
|
||||
bool MessageManager::Lock::tryAcquire (bool lockIsMandatory) const noexcept
|
||||
{
|
||||
auto* mm = MessageManager::instance;
|
||||
|
||||
if (mm == nullptr)
|
||||
{
|
||||
jassertfalse;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! lockIsMandatory && (abortWait.get() != 0))
|
||||
{
|
||||
abortWait.set (0);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mm->currentThreadHasLockedMessageManager())
|
||||
return true;
|
||||
|
||||
try
|
||||
{
|
||||
blockingMessage = new BlockingMessage (this);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
jassert (! lockIsMandatory);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! blockingMessage->post())
|
||||
{
|
||||
// post of message failed while trying to get the lock
|
||||
jassert (! lockIsMandatory);
|
||||
blockingMessage = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
while (abortWait.get() == 0)
|
||||
lockedEvent.wait (-1);
|
||||
|
||||
abortWait.set (0);
|
||||
|
||||
if (lockGained.get() != 0)
|
||||
{
|
||||
mm->threadWithLock = Thread::getCurrentThreadId();
|
||||
return true;
|
||||
}
|
||||
|
||||
} while (lockIsMandatory);
|
||||
|
||||
// we didn't get the lock
|
||||
blockingMessage->releaseEvent.signal();
|
||||
|
||||
{
|
||||
ScopedLock lock (blockingMessage->ownerCriticalSection);
|
||||
|
||||
lockGained.set (0);
|
||||
blockingMessage->owner.set (nullptr);
|
||||
}
|
||||
|
||||
blockingMessage = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
void MessageManager::Lock::exit() const noexcept
|
||||
{
|
||||
if (lockGained.compareAndSetBool (false, true))
|
||||
{
|
||||
auto* mm = MessageManager::instance;
|
||||
|
||||
jassert (mm == nullptr || mm->currentThreadHasLockedMessageManager());
|
||||
lockGained.set (0);
|
||||
|
||||
if (mm != nullptr)
|
||||
mm->threadWithLock = 0;
|
||||
|
||||
if (blockingMessage != nullptr)
|
||||
{
|
||||
blockingMessage->releaseEvent.signal();
|
||||
blockingMessage = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MessageManager::Lock::messageCallback() const
|
||||
{
|
||||
lockGained.set (1);
|
||||
abort();
|
||||
}
|
||||
|
||||
void MessageManager::Lock::abort() const noexcept
|
||||
{
|
||||
abortWait.set (1);
|
||||
lockedEvent.signal();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
MessageManagerLock::MessageManagerLock (Thread* threadToCheck)
|
||||
: locked (attemptLock (threadToCheck, nullptr))
|
||||
{}
|
||||
|
||||
MessageManagerLock::MessageManagerLock (ThreadPoolJob* jobToCheck)
|
||||
: locked (attemptLock (nullptr, jobToCheck))
|
||||
{}
|
||||
|
||||
bool MessageManagerLock::attemptLock (Thread* threadToCheck, ThreadPoolJob* jobToCheck)
|
||||
{
|
||||
jassert (threadToCheck == nullptr || jobToCheck == nullptr);
|
||||
|
||||
if (threadToCheck != nullptr)
|
||||
threadToCheck->addListener (this);
|
||||
|
||||
if (jobToCheck != nullptr)
|
||||
jobToCheck->addListener (this);
|
||||
|
||||
// tryEnter may have a spurious abort (return false) so keep checking the condition
|
||||
while ((threadToCheck == nullptr || ! threadToCheck->threadShouldExit())
|
||||
&& (jobToCheck == nullptr || ! jobToCheck->shouldExit()))
|
||||
{
|
||||
if (mmLock.tryEnter())
|
||||
break;
|
||||
}
|
||||
|
||||
if (threadToCheck != nullptr)
|
||||
{
|
||||
threadToCheck->removeListener (this);
|
||||
|
||||
if (threadToCheck->threadShouldExit())
|
||||
return false;
|
||||
}
|
||||
|
||||
if (jobToCheck != nullptr)
|
||||
{
|
||||
jobToCheck->removeListener (this);
|
||||
|
||||
if (jobToCheck->shouldExit())
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
MessageManagerLock::~MessageManagerLock() noexcept { mmLock.exit(); }
|
||||
|
||||
void MessageManagerLock::exitSignalSent()
|
||||
{
|
||||
mmLock.abort();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_API void JUCE_CALLTYPE initialiseJuce_GUI();
|
||||
JUCE_API void JUCE_CALLTYPE initialiseJuce_GUI()
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
MessageManager::getInstance();
|
||||
}
|
||||
}
|
||||
|
||||
JUCE_API void JUCE_CALLTYPE shutdownJuce_GUI();
|
||||
JUCE_API void JUCE_CALLTYPE shutdownJuce_GUI()
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
DeletedAtShutdown::deleteAll();
|
||||
MessageManager::deleteInstance();
|
||||
}
|
||||
}
|
||||
|
||||
static int numScopedInitInstances = 0;
|
||||
|
||||
ScopedJuceInitialiser_GUI::ScopedJuceInitialiser_GUI() { if (numScopedInitInstances++ == 0) initialiseJuce_GUI(); }
|
||||
ScopedJuceInitialiser_GUI::~ScopedJuceInitialiser_GUI() { if (--numScopedInitInstances == 0) shutdownJuce_GUI(); }
|
||||
|
||||
} // namespace juce
|
465
modules/juce_events/messages/juce_MessageManager.h
Normal file
465
modules/juce_events/messages/juce_MessageManager.h
Normal file
@ -0,0 +1,465 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class MessageManagerLock;
|
||||
class ThreadPoolJob;
|
||||
class ActionListener;
|
||||
class ActionBroadcaster;
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_MODULE_AVAILABLE_juce_opengl
|
||||
class OpenGLContext;
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
/** See MessageManager::callFunctionOnMessageThread() for use of this function type. */
|
||||
typedef void* (MessageCallbackFunction) (void* userData);
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
This class is in charge of the application's event-dispatch loop.
|
||||
|
||||
@see Message, CallbackMessage, MessageManagerLock, JUCEApplication, JUCEApplicationBase
|
||||
|
||||
@tags{Events}
|
||||
*/
|
||||
class JUCE_API MessageManager final
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Returns the global instance of the MessageManager. */
|
||||
static MessageManager* getInstance();
|
||||
|
||||
/** Returns the global instance of the MessageManager, or nullptr if it doesn't exist. */
|
||||
static MessageManager* getInstanceWithoutCreating() noexcept;
|
||||
|
||||
/** Deletes the global MessageManager instance.
|
||||
Does nothing if no instance had been created.
|
||||
*/
|
||||
static void deleteInstance();
|
||||
|
||||
//==============================================================================
|
||||
/** Runs the event dispatch loop until a stop message is posted.
|
||||
|
||||
This method is only intended to be run by the application's startup routine,
|
||||
as it blocks, and will only return after the stopDispatchLoop() method has been used.
|
||||
|
||||
@see stopDispatchLoop
|
||||
*/
|
||||
void runDispatchLoop();
|
||||
|
||||
/** Sends a signal that the dispatch loop should terminate.
|
||||
|
||||
After this is called, the runDispatchLoop() or runDispatchLoopUntil() methods
|
||||
will be interrupted and will return.
|
||||
|
||||
@see runDispatchLoop
|
||||
*/
|
||||
void stopDispatchLoop();
|
||||
|
||||
/** Returns true if the stopDispatchLoop() method has been called.
|
||||
*/
|
||||
bool hasStopMessageBeenSent() const noexcept { return quitMessagePosted.get() != 0; }
|
||||
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN
|
||||
/** Synchronously dispatches messages until a given time has elapsed.
|
||||
|
||||
Returns false if a quit message has been posted by a call to stopDispatchLoop(),
|
||||
otherwise returns true.
|
||||
*/
|
||||
bool runDispatchLoopUntil (int millisecondsToRunFor);
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
/** Asynchronously invokes a function or C++11 lambda on the message thread. */
|
||||
template <typename FunctionType>
|
||||
static void callAsync (FunctionType functionToCall)
|
||||
{
|
||||
new AsyncCallInvoker<FunctionType> (functionToCall);
|
||||
}
|
||||
|
||||
/** Calls a function using the message-thread.
|
||||
|
||||
This can be used by any thread to cause this function to be called-back
|
||||
by the message thread. If it's the message-thread that's calling this method,
|
||||
then the function will just be called; if another thread is calling, a message
|
||||
will be posted to the queue, and this method will block until that message
|
||||
is delivered, the function is called, and the result is returned.
|
||||
|
||||
Be careful not to cause any deadlocks with this! It's easy to do - e.g. if the caller
|
||||
thread has a critical section locked, which an unrelated message callback then tries to lock
|
||||
before the message thread gets round to processing this callback.
|
||||
|
||||
@param callback the function to call - its signature must be @code
|
||||
void* myCallbackFunction (void*) @endcode
|
||||
@param userData a user-defined pointer that will be passed to the function that gets called
|
||||
@returns the value that the callback function returns.
|
||||
@see MessageManagerLock
|
||||
*/
|
||||
void* callFunctionOnMessageThread (MessageCallbackFunction* callback, void* userData);
|
||||
|
||||
/** Returns true if the caller-thread is the message thread. */
|
||||
bool isThisTheMessageThread() const noexcept;
|
||||
|
||||
/** Called to tell the manager that the current thread is the one that's running the dispatch loop.
|
||||
|
||||
(Best to ignore this method unless you really know what you're doing..)
|
||||
@see getCurrentMessageThread
|
||||
*/
|
||||
void setCurrentThreadAsMessageThread();
|
||||
|
||||
/** Returns the ID of the current message thread, as set by setCurrentThreadAsMessageThread().
|
||||
|
||||
(Best to ignore this method unless you really know what you're doing..)
|
||||
@see setCurrentThreadAsMessageThread
|
||||
*/
|
||||
Thread::ThreadID getCurrentMessageThread() const noexcept { return messageThreadId; }
|
||||
|
||||
/** Returns true if the caller thread has currently got the message manager locked.
|
||||
|
||||
see the MessageManagerLock class for more info about this.
|
||||
|
||||
This will be true if the caller is the message thread, because that automatically
|
||||
gains a lock while a message is being dispatched.
|
||||
*/
|
||||
bool currentThreadHasLockedMessageManager() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Sends a message to all other JUCE applications that are running.
|
||||
|
||||
@param messageText the string that will be passed to the actionListenerCallback()
|
||||
method of the broadcast listeners in the other app.
|
||||
@see registerBroadcastListener, ActionListener
|
||||
*/
|
||||
static void broadcastMessage (const String& messageText);
|
||||
|
||||
/** Registers a listener to get told about broadcast messages.
|
||||
|
||||
The actionListenerCallback() callback's string parameter
|
||||
is the message passed into broadcastMessage().
|
||||
|
||||
@see broadcastMessage
|
||||
*/
|
||||
void registerBroadcastListener (ActionListener* listener);
|
||||
|
||||
/** Deregisters a broadcast listener. */
|
||||
void deregisterBroadcastListener (ActionListener* listener);
|
||||
|
||||
//==============================================================================
|
||||
/** Internal class used as the base class for all message objects.
|
||||
You shouldn't need to use this directly - see the CallbackMessage or Message
|
||||
classes instead.
|
||||
*/
|
||||
class JUCE_API MessageBase : public ReferenceCountedObject
|
||||
{
|
||||
public:
|
||||
MessageBase() noexcept {}
|
||||
virtual ~MessageBase() {}
|
||||
|
||||
virtual void messageCallback() = 0;
|
||||
bool post();
|
||||
|
||||
using Ptr = ReferenceCountedObjectPtr<MessageBase>;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (MessageBase)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** A lock you can use to lock the message manager. You can use this class with
|
||||
the RAII-based ScopedLock classes.
|
||||
*/
|
||||
class Lock
|
||||
{
|
||||
public:
|
||||
/**
|
||||
Creates a new critical section to exclusively access methods which can
|
||||
only be called when the message manager is locked.
|
||||
|
||||
Unlike CrititcalSection, multiple instances of this lock class provide
|
||||
exclusive access to a single resource - the MessageManager.
|
||||
*/
|
||||
Lock();
|
||||
|
||||
/** Destructor. */
|
||||
~Lock();
|
||||
|
||||
/** Acquires the message manager lock.
|
||||
|
||||
If the caller thread already has exclusive access to the MessageManager, this method
|
||||
will return immediately.
|
||||
If another thread is currently using the MessageManager, this will wait until that
|
||||
thread releases the lock to the MessageManager.
|
||||
|
||||
This call will only exit if the lock was accquired by this thread. Calling abort while
|
||||
a thread is waiting for enter to finish, will have no effect.
|
||||
|
||||
@see exit, abort
|
||||
*/
|
||||
void enter() const noexcept;
|
||||
|
||||
/** Attempts to lock the meesage manager and exits if abort is called.
|
||||
|
||||
This method behaves identically to enter, except that it will abort waiting for
|
||||
the lock if the abort method is called.
|
||||
|
||||
Unlike other JUCE critical sections, this method **will** block waiting for the lock.
|
||||
|
||||
To ensure predictable behaviour, you should re-check your abort condition if tryEnter
|
||||
returns false.
|
||||
|
||||
This method can be used if you want to do some work while waiting for the
|
||||
MessageManagerLock:
|
||||
|
||||
void doWorkWhileWaitingForMessageManagerLock()
|
||||
{
|
||||
MessageManager::Lock::ScopedTryLockType mmLock (messageManagerLock);
|
||||
|
||||
while (! mmLock.isLocked())
|
||||
{
|
||||
while (workQueue.size() > 0)
|
||||
{
|
||||
auto work = workQueue.pop();
|
||||
doSomeWork (work);
|
||||
}
|
||||
|
||||
// this will block until we either have the lock or there is work
|
||||
mmLock.retryLock();
|
||||
}
|
||||
|
||||
// we have the mmlock
|
||||
// do some message manager stuff like resizing and painting components
|
||||
}
|
||||
|
||||
// called from another thread
|
||||
void addWorkToDo (Work work)
|
||||
{
|
||||
queue.push (work);
|
||||
messageManagerLock.abort();
|
||||
}
|
||||
|
||||
@returns false if waiting for a lock was aborted, true if the lock was accquired.
|
||||
@see enter, abort, ScopedTryLock
|
||||
*/
|
||||
bool tryEnter() const noexcept;
|
||||
|
||||
/** Releases the message manager lock.
|
||||
@see enter, ScopedLock
|
||||
*/
|
||||
void exit() const noexcept;
|
||||
|
||||
/** Unblocks a thread which is waiting in tryEnter
|
||||
Call this method if you want to unblock a thread which is waiting for the
|
||||
MessageManager lock in tryEnter.
|
||||
This method does not have any effetc on a thread waiting for a lock in enter.
|
||||
@see tryEnter
|
||||
*/
|
||||
void abort() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Provides the type of scoped lock to use with a CriticalSection. */
|
||||
typedef GenericScopedLock<Lock> ScopedLockType;
|
||||
|
||||
/** Provides the type of scoped unlocker to use with a CriticalSection. */
|
||||
typedef GenericScopedUnlock<Lock> ScopedUnlockType;
|
||||
|
||||
/** Provides the type of scoped try-locker to use with a CriticalSection. */
|
||||
typedef GenericScopedTryLock<Lock> ScopedTryLockType;
|
||||
|
||||
private:
|
||||
struct BlockingMessage;
|
||||
friend class ReferenceCountedObjectPtr<BlockingMessage>;
|
||||
|
||||
bool tryAcquire (bool) const noexcept;
|
||||
void messageCallback() const;
|
||||
|
||||
//==============================================================================
|
||||
mutable ReferenceCountedObjectPtr<BlockingMessage> blockingMessage;
|
||||
WaitableEvent lockedEvent;
|
||||
mutable Atomic<int> abortWait, lockGained;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
// Internal methods - do not use!
|
||||
void deliverBroadcastMessage (const String&);
|
||||
~MessageManager() noexcept;
|
||||
#endif
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
MessageManager() noexcept;
|
||||
|
||||
static MessageManager* instance;
|
||||
|
||||
friend class MessageBase;
|
||||
class QuitMessage;
|
||||
friend class QuitMessage;
|
||||
friend class MessageManagerLock;
|
||||
|
||||
std::unique_ptr<ActionBroadcaster> broadcaster;
|
||||
Atomic<int> quitMessagePosted { 0 }, quitMessageReceived { 0 };
|
||||
Thread::ThreadID messageThreadId;
|
||||
Atomic<Thread::ThreadID> threadWithLock;
|
||||
|
||||
static bool postMessageToSystemQueue (MessageBase*);
|
||||
static void* exitModalLoopCallback (void*);
|
||||
static void doPlatformSpecificInitialisation();
|
||||
static void doPlatformSpecificShutdown();
|
||||
static bool dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages);
|
||||
|
||||
template <typename FunctionType>
|
||||
struct AsyncCallInvoker : public MessageBase
|
||||
{
|
||||
AsyncCallInvoker (FunctionType f) : callback (f) { post(); }
|
||||
void messageCallback() override { callback(); }
|
||||
FunctionType callback;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AsyncCallInvoker)
|
||||
};
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MessageManager)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Used to make sure that the calling thread has exclusive access to the message loop.
|
||||
|
||||
Because it's not thread-safe to call any of the Component or other UI classes
|
||||
from threads other than the message thread, one of these objects can be used to
|
||||
lock the message loop and allow this to be done. The message thread will be
|
||||
suspended for the lifetime of the MessageManagerLock object, so create one on
|
||||
the stack like this: @code
|
||||
void MyThread::run()
|
||||
{
|
||||
someData = 1234;
|
||||
|
||||
const MessageManagerLock mmLock;
|
||||
// the event loop will now be locked so it's safe to make a few calls..
|
||||
|
||||
myComponent->setBounds (newBounds);
|
||||
myComponent->repaint();
|
||||
|
||||
// ..the event loop will now be unlocked as the MessageManagerLock goes out of scope
|
||||
}
|
||||
@endcode
|
||||
|
||||
Obviously be careful not to create one of these and leave it lying around, or
|
||||
your app will grind to a halt!
|
||||
|
||||
MessageManagerLocks are re-entrant, so can be safely nested if the current thread
|
||||
already has the lock.
|
||||
|
||||
Another caveat is that using this in conjunction with other CriticalSections
|
||||
can create lots of interesting ways of producing a deadlock! In particular, if
|
||||
your message thread calls stopThread() for a thread that uses these locks,
|
||||
you'll get an (occasional) deadlock..
|
||||
|
||||
@see MessageManager, MessageManager::currentThreadHasLockedMessageManager
|
||||
|
||||
@tags{Events}
|
||||
*/
|
||||
class JUCE_API MessageManagerLock : private Thread::Listener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Tries to acquire a lock on the message manager.
|
||||
|
||||
The constructor attempts to gain a lock on the message loop, and the lock will be
|
||||
kept for the lifetime of this object.
|
||||
|
||||
Optionally, you can pass a thread object here, and while waiting to obtain the lock,
|
||||
this method will keep checking whether the thread has been given the
|
||||
Thread::signalThreadShouldExit() signal. If this happens, then it will return
|
||||
without gaining the lock. If you pass a thread, you must check whether the lock was
|
||||
successful by calling lockWasGained(). If this is false, your thread is being told to
|
||||
die, so you should take evasive action.
|
||||
|
||||
If you pass nullptr for the thread object, it will wait indefinitely for the lock - be
|
||||
careful when doing this, because it's very easy to deadlock if your message thread
|
||||
attempts to call stopThread() on a thread just as that thread attempts to get the
|
||||
message lock.
|
||||
|
||||
If the calling thread already has the lock, nothing will be done, so it's safe and
|
||||
quick to use these locks recursively.
|
||||
|
||||
E.g.
|
||||
@code
|
||||
void run()
|
||||
{
|
||||
...
|
||||
|
||||
while (! threadShouldExit())
|
||||
{
|
||||
MessageManagerLock mml (Thread::getCurrentThread());
|
||||
|
||||
if (! mml.lockWasGained())
|
||||
return; // another thread is trying to kill us!
|
||||
|
||||
..do some locked stuff here..
|
||||
}
|
||||
|
||||
..and now the MM is now unlocked..
|
||||
}
|
||||
@endcode
|
||||
|
||||
*/
|
||||
MessageManagerLock (Thread* threadToCheckForExitSignal = nullptr);
|
||||
|
||||
//==============================================================================
|
||||
/** This has the same behaviour as the other constructor, but takes a ThreadPoolJob
|
||||
instead of a thread.
|
||||
|
||||
See the MessageManagerLock (Thread*) constructor for details on how this works.
|
||||
*/
|
||||
MessageManagerLock (ThreadPoolJob* jobToCheckForExitSignal);
|
||||
|
||||
//==============================================================================
|
||||
/** Releases the current thread's lock on the message manager.
|
||||
|
||||
Make sure this object is created and deleted by the same thread,
|
||||
otherwise there are no guarantees what will happen!
|
||||
*/
|
||||
~MessageManagerLock() noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if the lock was successfully acquired.
|
||||
(See the constructor that takes a Thread for more info).
|
||||
*/
|
||||
bool lockWasGained() const noexcept { return locked; }
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
MessageManager::Lock mmLock;
|
||||
bool locked;
|
||||
|
||||
//==============================================================================
|
||||
bool attemptLock (Thread*, ThreadPoolJob*);
|
||||
void exitSignalSent() override;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (MessageManagerLock)
|
||||
};
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_MAC || JUCE_WINDOWS || defined (DOXYGEN)
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
An instance of this class will provide callbacks when drives are
|
||||
mounted or unmounted on the system.
|
||||
|
||||
Just inherit from this class and implement the pure virtual method
|
||||
to get the callbacks, there's no need to do anything else.
|
||||
|
||||
@see File::findFileSystemRoots()
|
||||
|
||||
@tags{Events}
|
||||
*/
|
||||
class JUCE_API MountedVolumeListChangeDetector
|
||||
{
|
||||
public:
|
||||
MountedVolumeListChangeDetector();
|
||||
virtual ~MountedVolumeListChangeDetector();
|
||||
|
||||
/** This method is called when a volume is mounted or unmounted. */
|
||||
virtual void mountedVolumeListChanged() = 0;
|
||||
|
||||
private:
|
||||
JUCE_PUBLIC_IN_DLL_BUILD (struct Pimpl)
|
||||
friend struct ContainerDeletePolicy<Pimpl>;
|
||||
std::unique_ptr<Pimpl> pimpl;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MountedVolumeListChangeDetector)
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
39
modules/juce_events/messages/juce_NotificationType.h
Normal file
39
modules/juce_events/messages/juce_NotificationType.h
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
These enums are used in various classes to indicate whether a notification
|
||||
event should be sent out.
|
||||
*/
|
||||
enum NotificationType
|
||||
{
|
||||
dontSendNotification = 0, /**< No notification message should be sent. */
|
||||
sendNotification = 1, /**< Requests a notification message, either synchronous or not. */
|
||||
sendNotificationSync, /**< Requests a synchronous notification. */
|
||||
sendNotificationAsync, /**< Requests an asynchronous notification. */
|
||||
};
|
||||
|
||||
} // namespace juce
|
171
modules/juce_events/native/juce_android_Messaging.cpp
Normal file
171
modules/juce_events/native/juce_android_Messaging.cpp
Normal file
@ -0,0 +1,171 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
namespace Android
|
||||
{
|
||||
class Runnable : public juce::AndroidInterfaceImplementer
|
||||
{
|
||||
public:
|
||||
virtual void run() = 0;
|
||||
|
||||
private:
|
||||
jobject invoke (jobject proxy, jobject method, jobjectArray args) override
|
||||
{
|
||||
auto* env = getEnv();
|
||||
auto methodName = juce::juceString ((jstring) env->CallObjectMethod (method, JavaMethod.getName));
|
||||
|
||||
if (methodName == "run")
|
||||
{
|
||||
run();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// invoke base class
|
||||
return AndroidInterfaceImplementer::invoke (proxy, method, args);
|
||||
}
|
||||
};
|
||||
|
||||
struct Handler
|
||||
{
|
||||
Handler() : nativeHandler (getEnv()->NewObject (AndroidHandler, AndroidHandler.constructor)) {}
|
||||
~Handler() { clearSingletonInstance(); }
|
||||
|
||||
JUCE_DECLARE_SINGLETON (Handler, false)
|
||||
|
||||
bool post (jobject runnable)
|
||||
{
|
||||
return (getEnv()->CallBooleanMethod (nativeHandler.get(), AndroidHandler.post, runnable) != 0);
|
||||
}
|
||||
|
||||
GlobalRef nativeHandler;
|
||||
};
|
||||
|
||||
JUCE_IMPLEMENT_SINGLETON (Handler)
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct AndroidMessageQueue : private Android::Runnable
|
||||
{
|
||||
JUCE_DECLARE_SINGLETON_SINGLETHREADED (AndroidMessageQueue, true)
|
||||
|
||||
AndroidMessageQueue()
|
||||
: self (CreateJavaInterface (this, "java/lang/Runnable").get())
|
||||
{
|
||||
}
|
||||
|
||||
~AndroidMessageQueue()
|
||||
{
|
||||
jassert (MessageManager::getInstance()->isThisTheMessageThread());
|
||||
clearSingletonInstance();
|
||||
}
|
||||
|
||||
bool post (MessageManager::MessageBase::Ptr&& message)
|
||||
{
|
||||
queue.add (static_cast<MessageManager::MessageBase::Ptr&& > (message));
|
||||
|
||||
// this will call us on the message thread
|
||||
return handler.post (self.get());
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void run() override
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
MessageManager::MessageBase::Ptr message (queue.removeAndReturn (0));
|
||||
|
||||
if (message == nullptr)
|
||||
break;
|
||||
|
||||
message->messageCallback();
|
||||
}
|
||||
}
|
||||
|
||||
// the this pointer to this class in Java land
|
||||
GlobalRef self;
|
||||
|
||||
ReferenceCountedArray<MessageManager::MessageBase, CriticalSection> queue;
|
||||
Android::Handler handler;
|
||||
};
|
||||
|
||||
JUCE_IMPLEMENT_SINGLETON (AndroidMessageQueue)
|
||||
|
||||
//==============================================================================
|
||||
void MessageManager::doPlatformSpecificInitialisation() { AndroidMessageQueue::getInstance(); }
|
||||
void MessageManager::doPlatformSpecificShutdown() { AndroidMessageQueue::deleteInstance(); }
|
||||
|
||||
//==============================================================================
|
||||
bool MessageManager::dispatchNextMessageOnSystemQueue (const bool)
|
||||
{
|
||||
Logger::outputDebugString ("*** Modal loops are not possible in Android!! Exiting...");
|
||||
exit (1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message)
|
||||
{
|
||||
return AndroidMessageQueue::getInstance()->post (message);
|
||||
}
|
||||
//==============================================================================
|
||||
void MessageManager::broadcastMessage (const String&)
|
||||
{
|
||||
}
|
||||
|
||||
void MessageManager::runDispatchLoop()
|
||||
{
|
||||
}
|
||||
|
||||
void MessageManager::stopDispatchLoop()
|
||||
{
|
||||
struct QuitCallback : public CallbackMessage
|
||||
{
|
||||
QuitCallback() {}
|
||||
|
||||
void messageCallback() override
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
jmethodID quitMethod = env->GetMethodID (JuceAppActivity, "finishAndRemoveTask", "()V");
|
||||
|
||||
if (quitMethod != 0)
|
||||
{
|
||||
env->CallVoidMethod (android.activity, quitMethod);
|
||||
return;
|
||||
}
|
||||
|
||||
quitMethod = env->GetMethodID (JuceAppActivity, "finish", "()V");
|
||||
jassert (quitMethod != 0);
|
||||
env->CallVoidMethod (android.activity, quitMethod);
|
||||
}
|
||||
};
|
||||
|
||||
(new QuitCallback())->post();
|
||||
quitMessagePosted = true;
|
||||
}
|
||||
|
||||
} // namespace juce
|
103
modules/juce_events/native/juce_ios_MessageManager.mm
Normal file
103
modules/juce_events/native/juce_ios_MessageManager.mm
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
void MessageManager::runDispatchLoop()
|
||||
{
|
||||
jassert (isThisTheMessageThread()); // must only be called by the message thread
|
||||
|
||||
while (quitMessagePosted.get() == 0)
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
|
||||
beforeDate: [NSDate dateWithTimeIntervalSinceNow: 0.001]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MessageManager::stopDispatchLoop()
|
||||
{
|
||||
if (! SystemStats::isRunningInAppExtensionSandbox())
|
||||
[[[UIApplication sharedApplication] delegate] applicationWillTerminate: [UIApplication sharedApplication]];
|
||||
|
||||
exit (0); // iOS apps get no mercy..
|
||||
}
|
||||
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor)
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
jassert (isThisTheMessageThread()); // must only be called by the message thread
|
||||
|
||||
uint32 startTime = Time::getMillisecondCounter();
|
||||
NSDate* endDate = [NSDate dateWithTimeIntervalSinceNow: millisecondsToRunFor * 0.001];
|
||||
|
||||
while (quitMessagePosted.get() == 0)
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
|
||||
beforeDate: endDate];
|
||||
|
||||
if (millisecondsToRunFor >= 0
|
||||
&& Time::getMillisecondCounter() >= startTime + (uint32) millisecondsToRunFor)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return quitMessagePosted.get() == 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
static std::unique_ptr<MessageQueue> messageQueue;
|
||||
|
||||
void MessageManager::doPlatformSpecificInitialisation()
|
||||
{
|
||||
if (messageQueue == nullptr)
|
||||
messageQueue.reset (new MessageQueue());
|
||||
}
|
||||
|
||||
void MessageManager::doPlatformSpecificShutdown()
|
||||
{
|
||||
messageQueue = nullptr;
|
||||
}
|
||||
|
||||
bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message)
|
||||
{
|
||||
if (messageQueue != nullptr)
|
||||
messageQueue->post (message);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MessageManager::broadcastMessage (const String&)
|
||||
{
|
||||
// N/A on current iOS
|
||||
}
|
||||
|
||||
} // namespace juce
|
55
modules/juce_events/native/juce_linux_EventLoop.h
Normal file
55
modules/juce_events/native/juce_linux_EventLoop.h
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
} // namespace juce
|
265
modules/juce_events/native/juce_linux_Messaging.cpp
Normal file
265
modules/juce_events/native/juce_linux_Messaging.cpp
Normal file
@ -0,0 +1,265 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#include <poll.h>
|
||||
|
||||
enum FdType
|
||||
{
|
||||
INTERNAL_QUEUE_FD,
|
||||
WINDOW_SYSTEM_FD,
|
||||
FD_COUNT,
|
||||
};
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class InternalMessageQueue
|
||||
{
|
||||
public:
|
||||
InternalMessageQueue()
|
||||
{
|
||||
auto 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].reset (new LinuxEventLoop::CallbackFunction<decltype(internalQueueCb)> (internalQueueCb));
|
||||
}
|
||||
|
||||
~InternalMessageQueue()
|
||||
{
|
||||
close (getReadHandle());
|
||||
close (getWriteHandle());
|
||||
|
||||
clearSingletonInstance();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void postMessage (MessageManager::MessageBase* const msg) noexcept
|
||||
{
|
||||
ScopedLock sl (lock);
|
||||
queue.add (msg);
|
||||
|
||||
const int maxBytesInSocketQueue = 128;
|
||||
|
||||
if (bytesInSocket < maxBytesInSocketQueue)
|
||||
{
|
||||
bytesInSocket++;
|
||||
|
||||
ScopedUnlock ul (lock);
|
||||
const unsigned char x = 0xff;
|
||||
ssize_t bytesWritten = write (getWriteHandle(), &x, 1);
|
||||
ignoreUnused (bytesWritten);
|
||||
}
|
||||
}
|
||||
|
||||
void setWindowSystemFd (int _fd, LinuxEventLoop::CallbackFunctionBase* _readCallback)
|
||||
{
|
||||
jassert (fdCount == 1);
|
||||
|
||||
ScopedLock sl (lock);
|
||||
|
||||
fdCount = 2;
|
||||
pfds[WINDOW_SYSTEM_FD].fd = _fd;
|
||||
pfds[WINDOW_SYSTEM_FD].events = POLLIN;
|
||||
readCallback[WINDOW_SYSTEM_FD].reset (_readCallback);
|
||||
readCallback[WINDOW_SYSTEM_FD]->active = true;
|
||||
}
|
||||
|
||||
void removeWindowSystemFd()
|
||||
{
|
||||
jassert (fdCount == FD_COUNT);
|
||||
|
||||
ScopedLock sl (lock);
|
||||
|
||||
fdCount = 1;
|
||||
readCallback[WINDOW_SYSTEM_FD]->active = false;
|
||||
}
|
||||
|
||||
bool dispatchNextEvent() noexcept
|
||||
{
|
||||
for (int counter = 0; counter < fdCount; counter++)
|
||||
{
|
||||
const int i = loopCount++;
|
||||
loopCount %= fdCount;
|
||||
|
||||
if (readCallback[i] != nullptr && readCallback[i]->active)
|
||||
if ((*readCallback[i]) (pfds[i].fd))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool sleepUntilEvent (const int timeoutMs)
|
||||
{
|
||||
const int pnum = poll (pfds, static_cast<nfds_t> (fdCount), timeoutMs);
|
||||
return (pnum > 0);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (InternalMessageQueue)
|
||||
|
||||
private:
|
||||
CriticalSection lock;
|
||||
ReferenceCountedArray <MessageManager::MessageBase> queue;
|
||||
int fd[2];
|
||||
pollfd pfds[FD_COUNT];
|
||||
std::unique_ptr<LinuxEventLoop::CallbackFunctionBase> readCallback[FD_COUNT];
|
||||
int fdCount = 1;
|
||||
int loopCount = 0;
|
||||
int bytesInSocket = 0;
|
||||
|
||||
int getWriteHandle() const noexcept { return fd[0]; }
|
||||
int getReadHandle() const noexcept { return fd[1]; }
|
||||
|
||||
MessageManager::MessageBase::Ptr popNextMessage (int _fd) noexcept
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
if (bytesInSocket > 0)
|
||||
{
|
||||
--bytesInSocket;
|
||||
|
||||
const ScopedUnlock ul (lock);
|
||||
unsigned char x;
|
||||
ssize_t numBytes = read (_fd, &x, 1);
|
||||
ignoreUnused (numBytes);
|
||||
}
|
||||
|
||||
return queue.removeAndReturn (0);
|
||||
}
|
||||
};
|
||||
|
||||
JUCE_IMPLEMENT_SINGLETON (InternalMessageQueue)
|
||||
|
||||
|
||||
//==============================================================================
|
||||
namespace LinuxErrorHandling
|
||||
{
|
||||
static bool keyboardBreakOccurred = false;
|
||||
|
||||
//==============================================================================
|
||||
void keyboardBreakSignalHandler (int sig)
|
||||
{
|
||||
if (sig == SIGINT)
|
||||
keyboardBreakOccurred = true;
|
||||
}
|
||||
|
||||
void installKeyboardBreakHandler()
|
||||
{
|
||||
struct sigaction saction;
|
||||
sigset_t maskSet;
|
||||
sigemptyset (&maskSet);
|
||||
saction.sa_handler = keyboardBreakSignalHandler;
|
||||
saction.sa_mask = maskSet;
|
||||
saction.sa_flags = 0;
|
||||
sigaction (SIGINT, &saction, 0);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MessageManager::doPlatformSpecificInitialisation()
|
||||
{
|
||||
if (JUCEApplicationBase::isStandaloneApp())
|
||||
LinuxErrorHandling::installKeyboardBreakHandler();
|
||||
|
||||
// Create the internal message queue
|
||||
auto* queue = InternalMessageQueue::getInstance();
|
||||
ignoreUnused (queue);
|
||||
}
|
||||
|
||||
void MessageManager::doPlatformSpecificShutdown()
|
||||
{
|
||||
InternalMessageQueue::deleteInstance();
|
||||
}
|
||||
|
||||
bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message)
|
||||
{
|
||||
if (auto* queue = InternalMessageQueue::getInstanceWithoutCreating())
|
||||
{
|
||||
queue->postMessage (message);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MessageManager::broadcastMessage (const String&)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
// this function expects that it will NEVER be called simultaneously for two concurrent threads
|
||||
bool MessageManager::dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
if (LinuxErrorHandling::keyboardBreakOccurred)
|
||||
JUCEApplicationBase::getInstance()->quit();
|
||||
|
||||
if (auto* queue = InternalMessageQueue::getInstanceWithoutCreating())
|
||||
{
|
||||
if (queue->dispatchNextEvent())
|
||||
break;
|
||||
|
||||
if (returnIfNoPendingMessages)
|
||||
return false;
|
||||
|
||||
// wait for 2000ms for next events if necessary
|
||||
queue->sleepUntilEvent (2000);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void LinuxEventLoop::setWindowSystemFdInternal (int fd, LinuxEventLoop::CallbackFunctionBase* readCallback) noexcept
|
||||
{
|
||||
if (auto* queue = InternalMessageQueue::getInstanceWithoutCreating())
|
||||
queue->setWindowSystemFd (fd, readCallback);
|
||||
}
|
||||
|
||||
void LinuxEventLoop::removeWindowSystemFd() noexcept
|
||||
{
|
||||
if (auto* queue = InternalMessageQueue::getInstanceWithoutCreating())
|
||||
queue->removeWindowSystemFd();
|
||||
}
|
||||
|
||||
|
||||
} // namespace juce
|
521
modules/juce_events/native/juce_mac_MessageManager.mm
Normal file
521
modules/juce_events/native/juce_mac_MessageManager.mm
Normal file
@ -0,0 +1,521 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
typedef void (*AppFocusChangeCallback)();
|
||||
AppFocusChangeCallback appFocusChangeCallback = nullptr;
|
||||
|
||||
typedef bool (*CheckEventBlockedByModalComps) (NSEvent*);
|
||||
CheckEventBlockedByModalComps isEventBlockedByModalComps = nullptr;
|
||||
|
||||
typedef void (*MenuTrackingChangedCallback)(bool);
|
||||
MenuTrackingChangedCallback menuTrackingChangedCallback = nullptr;
|
||||
|
||||
//==============================================================================
|
||||
struct AppDelegate
|
||||
{
|
||||
public:
|
||||
AppDelegate()
|
||||
{
|
||||
static AppDelegateClass cls;
|
||||
delegate = [cls.createInstance() init];
|
||||
|
||||
NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
|
||||
|
||||
[center addObserver: delegate selector: @selector (mainMenuTrackingBegan:)
|
||||
name: NSMenuDidBeginTrackingNotification object: nil];
|
||||
[center addObserver: delegate selector: @selector (mainMenuTrackingEnded:)
|
||||
name: NSMenuDidEndTrackingNotification object: nil];
|
||||
|
||||
if (JUCEApplicationBase::isStandaloneApp())
|
||||
{
|
||||
[NSApp setDelegate: delegate];
|
||||
|
||||
[[NSDistributedNotificationCenter defaultCenter] addObserver: delegate
|
||||
selector: @selector (broadcastMessageCallback:)
|
||||
name: getBroadcastEventName()
|
||||
object: nil
|
||||
suspensionBehavior: NSNotificationSuspensionBehaviorDeliverImmediately];
|
||||
}
|
||||
else
|
||||
{
|
||||
[center addObserver: delegate selector: @selector (applicationDidResignActive:)
|
||||
name: NSApplicationDidResignActiveNotification object: NSApp];
|
||||
|
||||
[center addObserver: delegate selector: @selector (applicationDidBecomeActive:)
|
||||
name: NSApplicationDidBecomeActiveNotification object: NSApp];
|
||||
|
||||
[center addObserver: delegate selector: @selector (applicationWillUnhide:)
|
||||
name: NSApplicationWillUnhideNotification object: NSApp];
|
||||
}
|
||||
}
|
||||
|
||||
~AppDelegate()
|
||||
{
|
||||
[[NSRunLoop currentRunLoop] cancelPerformSelectorsWithTarget: delegate];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver: delegate];
|
||||
|
||||
if (JUCEApplicationBase::isStandaloneApp())
|
||||
{
|
||||
[NSApp setDelegate: nil];
|
||||
|
||||
[[NSDistributedNotificationCenter defaultCenter] removeObserver: delegate
|
||||
name: getBroadcastEventName()
|
||||
object: nil];
|
||||
}
|
||||
|
||||
[delegate release];
|
||||
}
|
||||
|
||||
static NSString* getBroadcastEventName()
|
||||
{
|
||||
return juceStringToNS ("juce_" + String::toHexString (File::getSpecialLocation (File::currentExecutableFile).hashCode64()));
|
||||
}
|
||||
|
||||
MessageQueue messageQueue;
|
||||
id delegate;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
struct AppDelegateClass : public ObjCClass<NSObject>
|
||||
{
|
||||
AppDelegateClass() : ObjCClass<NSObject> ("JUCEAppDelegate_")
|
||||
{
|
||||
addMethod (@selector (applicationWillFinishLaunching:), applicationWillFinishLaunching, "v@:@");
|
||||
addMethod (@selector (getUrl:withReplyEvent:), getUrl_withReplyEvent, "v@:@@");
|
||||
addMethod (@selector (applicationShouldTerminate:), applicationShouldTerminate, "I@:@");
|
||||
addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@");
|
||||
addMethod (@selector (application:openFile:), application_openFile, "c@:@@");
|
||||
addMethod (@selector (application:openFiles:), application_openFiles, "v@:@@");
|
||||
addMethod (@selector (applicationDidBecomeActive:), applicationDidBecomeActive, "v@:@");
|
||||
addMethod (@selector (applicationDidResignActive:), applicationDidResignActive, "v@:@");
|
||||
addMethod (@selector (applicationWillUnhide:), applicationWillUnhide, "v@:@");
|
||||
addMethod (@selector (broadcastMessageCallback:), broadcastMessageCallback, "v@:@");
|
||||
addMethod (@selector (mainMenuTrackingBegan:), mainMenuTrackingBegan, "v@:@");
|
||||
addMethod (@selector (mainMenuTrackingEnded:), mainMenuTrackingEnded, "v@:@");
|
||||
addMethod (@selector (dummyMethod), dummyMethod, "v@:");
|
||||
|
||||
#if JUCE_PUSH_NOTIFICATIONS
|
||||
//==============================================================================
|
||||
addIvar<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>*> ("pushNotificationsDelegate");
|
||||
|
||||
addMethod (@selector (applicationDidFinishLaunching:), applicationDidFinishLaunching, "v@:@");
|
||||
addMethod (@selector (setPushNotificationsDelegate:), setPushNotificationsDelegate, "v@:@");
|
||||
addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), registeredForRemoteNotifications, "v@:@@");
|
||||
addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), failedToRegisterForRemoteNotifications, "v@:@@");
|
||||
addMethod (@selector (application:didReceiveRemoteNotification:), didReceiveRemoteNotification, "v@:@@");
|
||||
#endif
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
private:
|
||||
static void applicationWillFinishLaunching (id self, SEL, NSNotification*)
|
||||
{
|
||||
[[NSAppleEventManager sharedAppleEventManager] setEventHandler: self
|
||||
andSelector: @selector (getUrl:withReplyEvent:)
|
||||
forEventClass: kInternetEventClass
|
||||
andEventID: kAEGetURL];
|
||||
}
|
||||
|
||||
#if JUCE_PUSH_NOTIFICATIONS
|
||||
static void applicationDidFinishLaunching (id self, SEL, NSNotification* notification)
|
||||
{
|
||||
if (notification.userInfo != nil)
|
||||
{
|
||||
NSUserNotification* userNotification = [notification.userInfo objectForKey: nsStringLiteral ("NSApplicationLaunchUserNotificationKey")];
|
||||
|
||||
if (userNotification != nil && userNotification.userInfo != nil)
|
||||
didReceiveRemoteNotification (self, nil, [NSApplication sharedApplication], userNotification.userInfo);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static NSApplicationTerminateReply applicationShouldTerminate (id /*self*/, SEL, NSApplication*)
|
||||
{
|
||||
if (auto* app = JUCEApplicationBase::getInstance())
|
||||
{
|
||||
app->systemRequestedQuit();
|
||||
|
||||
if (! MessageManager::getInstance()->hasStopMessageBeenSent())
|
||||
return NSTerminateCancel;
|
||||
}
|
||||
|
||||
return NSTerminateNow;
|
||||
}
|
||||
|
||||
static void applicationWillTerminate (id /*self*/, SEL, NSNotification*)
|
||||
{
|
||||
JUCEApplicationBase::appWillTerminateByForce();
|
||||
}
|
||||
|
||||
static BOOL application_openFile (id /*self*/, SEL, NSApplication*, NSString* filename)
|
||||
{
|
||||
if (auto* app = JUCEApplicationBase::getInstance())
|
||||
{
|
||||
app->anotherInstanceStarted (quotedIfContainsSpaces (filename));
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
static void application_openFiles (id /*self*/, SEL, NSApplication*, NSArray* filenames)
|
||||
{
|
||||
if (auto* app = JUCEApplicationBase::getInstance())
|
||||
{
|
||||
StringArray files;
|
||||
|
||||
for (NSString* f in filenames)
|
||||
files.add (quotedIfContainsSpaces (f));
|
||||
|
||||
if (files.size() > 0)
|
||||
app->anotherInstanceStarted (files.joinIntoString (" "));
|
||||
}
|
||||
}
|
||||
|
||||
static void applicationDidBecomeActive (id /*self*/, SEL, NSNotification*) { focusChanged(); }
|
||||
static void applicationDidResignActive (id /*self*/, SEL, NSNotification*) { focusChanged(); }
|
||||
static void applicationWillUnhide (id /*self*/, SEL, NSNotification*) { focusChanged(); }
|
||||
|
||||
static void broadcastMessageCallback (id /*self*/, SEL, NSNotification* n)
|
||||
{
|
||||
NSDictionary* dict = (NSDictionary*) [n userInfo];
|
||||
auto messageString = nsStringToJuce ((NSString*) [dict valueForKey: nsStringLiteral ("message")]);
|
||||
MessageManager::getInstance()->deliverBroadcastMessage (messageString);
|
||||
}
|
||||
|
||||
static void mainMenuTrackingBegan (id /*self*/, SEL, NSNotification*)
|
||||
{
|
||||
if (menuTrackingChangedCallback != nullptr)
|
||||
(*menuTrackingChangedCallback) (true);
|
||||
}
|
||||
|
||||
static void mainMenuTrackingEnded (id /*self*/, SEL, NSNotification*)
|
||||
{
|
||||
if (menuTrackingChangedCallback != nullptr)
|
||||
(*menuTrackingChangedCallback) (false);
|
||||
}
|
||||
|
||||
static void dummyMethod (id /*self*/, SEL) {} // (used as a way of running a dummy thread)
|
||||
|
||||
static void focusChanged()
|
||||
{
|
||||
if (appFocusChangeCallback != nullptr)
|
||||
(*appFocusChangeCallback)();
|
||||
}
|
||||
|
||||
static void getUrl_withReplyEvent (id /*self*/, SEL, NSAppleEventDescriptor* event, NSAppleEventDescriptor*)
|
||||
{
|
||||
if (auto* app = JUCEApplicationBase::getInstance())
|
||||
app->anotherInstanceStarted (quotedIfContainsSpaces ([[event paramDescriptorForKeyword: keyDirectObject] stringValue]));
|
||||
}
|
||||
|
||||
static String quotedIfContainsSpaces (NSString* file)
|
||||
{
|
||||
String s (nsStringToJuce (file));
|
||||
if (s.containsChar (' '))
|
||||
s = s.quoted ('"');
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
#if JUCE_PUSH_NOTIFICATIONS
|
||||
//==============================================================================
|
||||
static void setPushNotificationsDelegate (id self, SEL, NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>* delegate)
|
||||
{
|
||||
object_setInstanceVariable (self, "pushNotificationsDelegate", delegate);
|
||||
}
|
||||
|
||||
static NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>* getPushNotificationsDelegate (id self)
|
||||
{
|
||||
return getIvar<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>*> (self, "pushNotificationsDelegate");
|
||||
}
|
||||
|
||||
static void registeredForRemoteNotifications (id self, SEL, NSApplication* application, NSData* deviceToken)
|
||||
{
|
||||
auto* delegate = getPushNotificationsDelegate (self);
|
||||
|
||||
SEL selector = NSSelectorFromString (@"application:didRegisterForRemoteNotificationsWithDeviceToken:");
|
||||
|
||||
if (delegate != nil && [delegate respondsToSelector: selector])
|
||||
{
|
||||
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]];
|
||||
[invocation setSelector: selector];
|
||||
[invocation setTarget: delegate];
|
||||
[invocation setArgument: &application atIndex:2];
|
||||
[invocation setArgument: &deviceToken atIndex:3];
|
||||
|
||||
[invocation invoke];
|
||||
}
|
||||
}
|
||||
|
||||
static void failedToRegisterForRemoteNotifications (id self, SEL, NSApplication* application, NSError* error)
|
||||
{
|
||||
auto* delegate = getPushNotificationsDelegate (self);
|
||||
|
||||
SEL selector = NSSelectorFromString (@"application:didFailToRegisterForRemoteNotificationsWithError:");
|
||||
|
||||
if (delegate != nil && [delegate respondsToSelector: selector])
|
||||
{
|
||||
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]];
|
||||
[invocation setSelector: selector];
|
||||
[invocation setTarget: delegate];
|
||||
[invocation setArgument: &application atIndex:2];
|
||||
[invocation setArgument: &error atIndex:3];
|
||||
|
||||
[invocation invoke];
|
||||
}
|
||||
}
|
||||
|
||||
static void didReceiveRemoteNotification (id self, SEL, NSApplication* application, NSDictionary* userInfo)
|
||||
{
|
||||
auto* delegate = getPushNotificationsDelegate (self);
|
||||
|
||||
SEL selector = NSSelectorFromString (@"application:didReceiveRemoteNotification:");
|
||||
|
||||
if (delegate != nil && [delegate respondsToSelector: selector])
|
||||
{
|
||||
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]];
|
||||
[invocation setSelector: selector];
|
||||
[invocation setTarget: delegate];
|
||||
[invocation setArgument: &application atIndex:2];
|
||||
[invocation setArgument: &userInfo atIndex:3];
|
||||
|
||||
[invocation invoke];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
};
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
void MessageManager::runDispatchLoop()
|
||||
{
|
||||
if (quitMessagePosted.get() == 0) // check that the quit message wasn't already posted..
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
// must only be called by the message thread!
|
||||
jassert (isThisTheMessageThread());
|
||||
|
||||
#if JUCE_PROJUCER_LIVE_BUILD
|
||||
runDispatchLoopUntil (std::numeric_limits<int>::max());
|
||||
#else
|
||||
#if JUCE_CATCH_UNHANDLED_EXCEPTIONS
|
||||
@try
|
||||
{
|
||||
[NSApp run];
|
||||
}
|
||||
@catch (NSException* e)
|
||||
{
|
||||
// An AppKit exception will kill the app, but at least this provides a chance to log it.,
|
||||
std::runtime_error ex (std::string ("NSException: ") + [[e name] UTF8String] + ", Reason:" + [[e reason] UTF8String]);
|
||||
JUCEApplicationBase::sendUnhandledException (&ex, __FILE__, __LINE__);
|
||||
}
|
||||
@finally
|
||||
{
|
||||
}
|
||||
#else
|
||||
[NSApp run];
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void shutdownNSApp()
|
||||
{
|
||||
[NSApp stop: nil];
|
||||
[NSEvent startPeriodicEventsAfterDelay: 0 withPeriod: 0.1];
|
||||
}
|
||||
|
||||
void MessageManager::stopDispatchLoop()
|
||||
{
|
||||
#if JUCE_PROJUCER_LIVE_BUILD
|
||||
quitMessagePosted = true;
|
||||
#else
|
||||
|
||||
if (isThisTheMessageThread())
|
||||
{
|
||||
quitMessagePosted = true;
|
||||
shutdownNSApp();
|
||||
}
|
||||
else
|
||||
{
|
||||
struct QuitCallback : public CallbackMessage
|
||||
{
|
||||
QuitCallback() {}
|
||||
void messageCallback() override { MessageManager::getInstance()->stopDispatchLoop(); }
|
||||
};
|
||||
|
||||
(new QuitCallback())->post();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor)
|
||||
{
|
||||
jassert (millisecondsToRunFor >= 0);
|
||||
jassert (isThisTheMessageThread()); // must only be called by the message thread
|
||||
|
||||
uint32 endTime = Time::getMillisecondCounter() + (uint32) millisecondsToRunFor;
|
||||
|
||||
while (quitMessagePosted.get() == 0)
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
CFRunLoopRunInMode (kCFRunLoopDefaultMode, 0.001, true);
|
||||
|
||||
NSEvent* e = [NSApp nextEventMatchingMask: NSEventMaskAny
|
||||
untilDate: [NSDate dateWithTimeIntervalSinceNow: 0.001]
|
||||
inMode: NSDefaultRunLoopMode
|
||||
dequeue: YES];
|
||||
|
||||
if (e != nil && (isEventBlockedByModalComps == nullptr || ! (*isEventBlockedByModalComps) (e)))
|
||||
[NSApp sendEvent: e];
|
||||
|
||||
if (Time::getMillisecondCounter() >= endTime)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return quitMessagePosted.get() == 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
void initialiseNSApplication();
|
||||
void initialiseNSApplication()
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
[NSApplication sharedApplication];
|
||||
}
|
||||
}
|
||||
|
||||
static AppDelegate* appDelegate = nullptr;
|
||||
|
||||
void MessageManager::doPlatformSpecificInitialisation()
|
||||
{
|
||||
if (appDelegate == nil)
|
||||
appDelegate = new AppDelegate();
|
||||
}
|
||||
|
||||
void MessageManager::doPlatformSpecificShutdown()
|
||||
{
|
||||
delete appDelegate;
|
||||
appDelegate = nullptr;
|
||||
}
|
||||
|
||||
bool MessageManager::postMessageToSystemQueue (MessageBase* message)
|
||||
{
|
||||
jassert (appDelegate != nil);
|
||||
appDelegate->messageQueue.post (message);
|
||||
return true;
|
||||
}
|
||||
|
||||
void MessageManager::broadcastMessage (const String& message)
|
||||
{
|
||||
NSDictionary* info = [NSDictionary dictionaryWithObject: juceStringToNS (message)
|
||||
forKey: nsStringLiteral ("message")];
|
||||
|
||||
[[NSDistributedNotificationCenter defaultCenter] postNotificationName: AppDelegate::getBroadcastEventName()
|
||||
object: nil
|
||||
userInfo: info];
|
||||
}
|
||||
|
||||
// Special function used by some plugin classes to re-post carbon events
|
||||
void __attribute__ ((visibility("default"))) repostCurrentNSEvent();
|
||||
void __attribute__ ((visibility("default"))) repostCurrentNSEvent()
|
||||
{
|
||||
struct EventReposter : public CallbackMessage
|
||||
{
|
||||
EventReposter() : e ([[NSApp currentEvent] retain]) {}
|
||||
~EventReposter() { [e release]; }
|
||||
|
||||
void messageCallback() override
|
||||
{
|
||||
[NSApp postEvent: e atStart: YES];
|
||||
}
|
||||
|
||||
NSEvent* e;
|
||||
};
|
||||
|
||||
(new EventReposter())->post();
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_MAC
|
||||
struct MountedVolumeListChangeDetector::Pimpl
|
||||
{
|
||||
Pimpl (MountedVolumeListChangeDetector& d) : owner (d)
|
||||
{
|
||||
static ObserverClass cls;
|
||||
delegate = [cls.createInstance() init];
|
||||
ObserverClass::setOwner (delegate, this);
|
||||
|
||||
NSNotificationCenter* nc = [[NSWorkspace sharedWorkspace] notificationCenter];
|
||||
|
||||
[nc addObserver: delegate selector: @selector (changed:) name: NSWorkspaceDidMountNotification object: nil];
|
||||
[nc addObserver: delegate selector: @selector (changed:) name: NSWorkspaceDidUnmountNotification object: nil];
|
||||
}
|
||||
|
||||
~Pimpl()
|
||||
{
|
||||
[[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver: delegate];
|
||||
[delegate release];
|
||||
}
|
||||
|
||||
private:
|
||||
MountedVolumeListChangeDetector& owner;
|
||||
id delegate;
|
||||
|
||||
struct ObserverClass : public ObjCClass<NSObject>
|
||||
{
|
||||
ObserverClass() : ObjCClass<NSObject> ("JUCEDriveObserver_")
|
||||
{
|
||||
addIvar<Pimpl*> ("owner");
|
||||
addMethod (@selector (changed:), changed, "v@:@");
|
||||
addProtocol (@protocol (NSTextInput));
|
||||
registerClass();
|
||||
}
|
||||
|
||||
static Pimpl* getOwner (id self) { return getIvar<Pimpl*> (self, "owner"); }
|
||||
static void setOwner (id self, Pimpl* owner) { object_setInstanceVariable (self, "owner", owner); }
|
||||
|
||||
static void changed (id self, SEL, NSNotification*)
|
||||
{
|
||||
getOwner (self)->owner.mountedVolumeListChanged();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
MountedVolumeListChangeDetector::MountedVolumeListChangeDetector() { pimpl.reset (new Pimpl (*this)); }
|
||||
MountedVolumeListChangeDetector::~MountedVolumeListChangeDetector() {}
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
105
modules/juce_events/native/juce_osx_MessageQueue.h
Normal file
105
modules/juce_events/native/juce_osx_MessageQueue.h
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/* An internal message pump class used in OSX and iOS. */
|
||||
class MessageQueue
|
||||
{
|
||||
public:
|
||||
MessageQueue()
|
||||
{
|
||||
#if JUCE_IOS
|
||||
runLoop = CFRunLoopGetCurrent();
|
||||
#else
|
||||
runLoop = CFRunLoopGetMain();
|
||||
#endif
|
||||
|
||||
CFRunLoopSourceContext sourceContext;
|
||||
zerostruct (sourceContext); // (can't use "= { 0 }" on this object because it's typedef'ed as a C struct)
|
||||
sourceContext.info = this;
|
||||
sourceContext.perform = runLoopSourceCallback;
|
||||
runLoopSource = CFRunLoopSourceCreate (kCFAllocatorDefault, 1, &sourceContext);
|
||||
CFRunLoopAddSource (runLoop, runLoopSource, kCFRunLoopCommonModes);
|
||||
}
|
||||
|
||||
~MessageQueue() noexcept
|
||||
{
|
||||
CFRunLoopRemoveSource (runLoop, runLoopSource, kCFRunLoopCommonModes);
|
||||
CFRunLoopSourceInvalidate (runLoopSource);
|
||||
CFRelease (runLoopSource);
|
||||
}
|
||||
|
||||
void post (MessageManager::MessageBase* const message)
|
||||
{
|
||||
messages.add (message);
|
||||
wakeUp();
|
||||
}
|
||||
|
||||
private:
|
||||
ReferenceCountedArray<MessageManager::MessageBase, CriticalSection> messages;
|
||||
CFRunLoopRef runLoop;
|
||||
CFRunLoopSourceRef runLoopSource;
|
||||
|
||||
void wakeUp() noexcept
|
||||
{
|
||||
CFRunLoopSourceSignal (runLoopSource);
|
||||
CFRunLoopWakeUp (runLoop);
|
||||
}
|
||||
|
||||
bool deliverNextMessage()
|
||||
{
|
||||
const MessageManager::MessageBase::Ptr nextMessage (messages.removeAndReturn (0));
|
||||
|
||||
if (nextMessage == nullptr)
|
||||
return false;
|
||||
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
JUCE_TRY
|
||||
{
|
||||
nextMessage->messageCallback();
|
||||
}
|
||||
JUCE_CATCH_EXCEPTION
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void runLoopCallback() noexcept
|
||||
{
|
||||
for (int i = 4; --i >= 0;)
|
||||
if (! deliverNextMessage())
|
||||
return;
|
||||
|
||||
wakeUp();
|
||||
}
|
||||
|
||||
static void runLoopSourceCallback (void* info) noexcept
|
||||
{
|
||||
static_cast<MessageQueue*> (info)->runLoopCallback();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace juce
|
135
modules/juce_events/native/juce_win32_HiddenMessageWindow.h
Normal file
135
modules/juce_events/native/juce_win32_HiddenMessageWindow.h
Normal file
@ -0,0 +1,135 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class HiddenMessageWindow
|
||||
{
|
||||
public:
|
||||
HiddenMessageWindow (const TCHAR* const messageWindowName, WNDPROC wndProc)
|
||||
{
|
||||
String className ("JUCE_");
|
||||
className << String::toHexString (Time::getHighResolutionTicks());
|
||||
|
||||
HMODULE moduleHandle = (HMODULE) Process::getCurrentModuleInstanceHandle();
|
||||
|
||||
WNDCLASSEX wc = { 0 };
|
||||
wc.cbSize = sizeof (wc);
|
||||
wc.lpfnWndProc = wndProc;
|
||||
wc.cbWndExtra = 4;
|
||||
wc.hInstance = moduleHandle;
|
||||
wc.lpszClassName = className.toWideCharPointer();
|
||||
|
||||
atom = RegisterClassEx (&wc);
|
||||
jassert (atom != 0);
|
||||
|
||||
hwnd = CreateWindow (getClassNameFromAtom(), messageWindowName,
|
||||
0, 0, 0, 0, 0, 0, 0, moduleHandle, 0);
|
||||
jassert (hwnd != 0);
|
||||
}
|
||||
|
||||
~HiddenMessageWindow()
|
||||
{
|
||||
DestroyWindow (hwnd);
|
||||
UnregisterClass (getClassNameFromAtom(), 0);
|
||||
}
|
||||
|
||||
inline HWND getHWND() const noexcept { return hwnd; }
|
||||
|
||||
private:
|
||||
ATOM atom;
|
||||
HWND hwnd;
|
||||
|
||||
LPCTSTR getClassNameFromAtom() noexcept { return (LPCTSTR) (pointer_sized_uint) atom; }
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class JuceWindowIdentifier
|
||||
{
|
||||
public:
|
||||
static bool isJUCEWindow (HWND hwnd) noexcept
|
||||
{
|
||||
return GetWindowLongPtr (hwnd, GWLP_USERDATA) == getImprobableWindowNumber();
|
||||
}
|
||||
|
||||
static void setAsJUCEWindow (HWND hwnd, bool isJuceWindow) noexcept
|
||||
{
|
||||
SetWindowLongPtr (hwnd, GWLP_USERDATA, isJuceWindow ? getImprobableWindowNumber() : 0);
|
||||
}
|
||||
|
||||
private:
|
||||
static LONG_PTR getImprobableWindowNumber() noexcept
|
||||
{
|
||||
static auto number = (LONG_PTR) Random().nextInt64();
|
||||
return number;
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class DeviceChangeDetector : private Timer
|
||||
{
|
||||
public:
|
||||
DeviceChangeDetector (const wchar_t* const name)
|
||||
: messageWindow (name, (WNDPROC) deviceChangeEventCallback)
|
||||
{
|
||||
SetWindowLongPtr (messageWindow.getHWND(), GWLP_USERDATA, (LONG_PTR) this);
|
||||
}
|
||||
|
||||
virtual ~DeviceChangeDetector() {}
|
||||
|
||||
virtual void systemDeviceChanged() = 0;
|
||||
|
||||
void triggerAsyncDeviceChangeCallback()
|
||||
{
|
||||
// We'll pause before sending a message, because on device removal, the OS hasn't always updated
|
||||
// its device lists correctly at this point. This also helps avoid repeated callbacks.
|
||||
startTimer (500);
|
||||
}
|
||||
|
||||
private:
|
||||
HiddenMessageWindow messageWindow;
|
||||
|
||||
static LRESULT CALLBACK deviceChangeEventCallback (HWND h, const UINT message,
|
||||
const WPARAM wParam, const LPARAM lParam)
|
||||
{
|
||||
if (message == WM_DEVICECHANGE
|
||||
&& (wParam == 0x8000 /*DBT_DEVICEARRIVAL*/
|
||||
|| wParam == 0x8004 /*DBT_DEVICEREMOVECOMPLETE*/
|
||||
|| wParam == 0x0007 /*DBT_DEVNODES_CHANGED*/))
|
||||
{
|
||||
((DeviceChangeDetector*) GetWindowLongPtr (h, GWLP_USERDATA))
|
||||
->triggerAsyncDeviceChangeCallback();
|
||||
}
|
||||
|
||||
return DefWindowProc (h, message, wParam, lParam);
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
stopTimer();
|
||||
systemDeviceChanged();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace juce
|
240
modules/juce_events/native/juce_win32_Messaging.cpp
Normal file
240
modules/juce_events/native/juce_win32_Messaging.cpp
Normal file
@ -0,0 +1,240 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
extern HWND juce_messageWindowHandle;
|
||||
|
||||
typedef bool (*CheckEventBlockedByModalComps) (const MSG&);
|
||||
CheckEventBlockedByModalComps isEventBlockedByModalComps = nullptr;
|
||||
|
||||
typedef void (*SettingChangeCallbackFunc) (void);
|
||||
SettingChangeCallbackFunc settingChangeCallback = nullptr;
|
||||
|
||||
//==============================================================================
|
||||
namespace WindowsMessageHelpers
|
||||
{
|
||||
const unsigned int customMessageID = WM_USER + 123;
|
||||
const unsigned int broadcastMessageMagicNumber = 0xc403;
|
||||
|
||||
const TCHAR messageWindowName[] = _T("JUCEWindow");
|
||||
std::unique_ptr<HiddenMessageWindow> messageWindow;
|
||||
|
||||
void dispatchMessageFromLParam (LPARAM lParam)
|
||||
{
|
||||
if (MessageManager::MessageBase* message = reinterpret_cast<MessageManager::MessageBase*> (lParam))
|
||||
{
|
||||
JUCE_TRY
|
||||
{
|
||||
message->messageCallback();
|
||||
}
|
||||
JUCE_CATCH_EXCEPTION
|
||||
|
||||
message->decReferenceCount();
|
||||
}
|
||||
}
|
||||
|
||||
BOOL CALLBACK broadcastEnumWindowProc (HWND hwnd, LPARAM lParam)
|
||||
{
|
||||
if (hwnd != juce_messageWindowHandle)
|
||||
{
|
||||
TCHAR windowName[64] = { 0 }; // no need to read longer strings than this
|
||||
GetWindowText (hwnd, windowName, 63);
|
||||
|
||||
if (String (windowName) == messageWindowName)
|
||||
reinterpret_cast<Array<HWND>*> (lParam)->add (hwnd);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void handleBroadcastMessage (const COPYDATASTRUCT* const data)
|
||||
{
|
||||
if (data != nullptr && data->dwData == broadcastMessageMagicNumber)
|
||||
{
|
||||
struct BroadcastMessage : public CallbackMessage
|
||||
{
|
||||
BroadcastMessage (CharPointer_UTF32 text, size_t length) : message (text, length) {}
|
||||
void messageCallback() override { MessageManager::getInstance()->deliverBroadcastMessage (message); }
|
||||
|
||||
String message;
|
||||
};
|
||||
|
||||
(new BroadcastMessage (CharPointer_UTF32 ((const CharPointer_UTF32::CharType*) data->lpData),
|
||||
data->cbData / sizeof (CharPointer_UTF32::CharType)))
|
||||
->post();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
LRESULT CALLBACK messageWndProc (HWND h, const UINT message, const WPARAM wParam, const LPARAM lParam) noexcept
|
||||
{
|
||||
if (h == juce_messageWindowHandle)
|
||||
{
|
||||
if (message == customMessageID)
|
||||
{
|
||||
// (These are trapped early in our dispatch loop, but must also be checked
|
||||
// here in case some 3rd-party code is running the dispatch loop).
|
||||
dispatchMessageFromLParam (lParam);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (message == WM_COPYDATA)
|
||||
{
|
||||
handleBroadcastMessage (reinterpret_cast<const COPYDATASTRUCT*> (lParam));
|
||||
return 0;
|
||||
}
|
||||
else if (message == WM_SETTINGCHANGE)
|
||||
{
|
||||
if (settingChangeCallback != nullptr)
|
||||
settingChangeCallback();
|
||||
}
|
||||
}
|
||||
|
||||
return DefWindowProc (h, message, wParam, lParam);
|
||||
}
|
||||
}
|
||||
|
||||
#if JUCE_MODULE_AVAILABLE_juce_gui_extra
|
||||
LRESULT juce_offerEventToActiveXControl (::MSG&);
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
bool MessageManager::dispatchNextMessageOnSystemQueue (const bool returnIfNoPendingMessages)
|
||||
{
|
||||
using namespace WindowsMessageHelpers;
|
||||
MSG m;
|
||||
|
||||
if (returnIfNoPendingMessages && ! PeekMessage (&m, (HWND) 0, 0, 0, PM_NOREMOVE))
|
||||
return false;
|
||||
|
||||
if (GetMessage (&m, (HWND) 0, 0, 0) >= 0)
|
||||
{
|
||||
#if JUCE_MODULE_AVAILABLE_juce_gui_extra
|
||||
if (juce_offerEventToActiveXControl (m) != S_FALSE)
|
||||
return true;
|
||||
#endif
|
||||
|
||||
if (m.message == customMessageID && m.hwnd == juce_messageWindowHandle)
|
||||
{
|
||||
dispatchMessageFromLParam (m.lParam);
|
||||
}
|
||||
else if (m.message == WM_QUIT)
|
||||
{
|
||||
if (auto* app = JUCEApplicationBase::getInstance())
|
||||
app->systemRequestedQuit();
|
||||
}
|
||||
else if (isEventBlockedByModalComps == nullptr || ! isEventBlockedByModalComps (m))
|
||||
{
|
||||
if ((m.message == WM_LBUTTONDOWN || m.message == WM_RBUTTONDOWN)
|
||||
&& ! JuceWindowIdentifier::isJUCEWindow (m.hwnd))
|
||||
{
|
||||
// if it's someone else's window being clicked on, and the focus is
|
||||
// currently on a juce window, pass the kb focus over..
|
||||
HWND currentFocus = GetFocus();
|
||||
|
||||
if (currentFocus == 0 || JuceWindowIdentifier::isJUCEWindow (currentFocus))
|
||||
SetFocus (m.hwnd);
|
||||
}
|
||||
|
||||
TranslateMessage (&m);
|
||||
DispatchMessage (&m);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message)
|
||||
{
|
||||
message->incReferenceCount();
|
||||
return PostMessage (juce_messageWindowHandle, WindowsMessageHelpers::customMessageID, 0, (LPARAM) message) != 0;
|
||||
}
|
||||
|
||||
void MessageManager::broadcastMessage (const String& value)
|
||||
{
|
||||
const String localCopy (value);
|
||||
|
||||
Array<HWND> windows;
|
||||
EnumWindows (&WindowsMessageHelpers::broadcastEnumWindowProc, (LPARAM) &windows);
|
||||
|
||||
for (int i = windows.size(); --i >= 0;)
|
||||
{
|
||||
COPYDATASTRUCT data;
|
||||
data.dwData = WindowsMessageHelpers::broadcastMessageMagicNumber;
|
||||
data.cbData = (localCopy.length() + 1) * sizeof (CharPointer_UTF32::CharType);
|
||||
data.lpData = (void*) localCopy.toUTF32().getAddress();
|
||||
|
||||
DWORD_PTR result;
|
||||
SendMessageTimeout (windows.getUnchecked (i), WM_COPYDATA,
|
||||
(WPARAM) juce_messageWindowHandle,
|
||||
(LPARAM) &data,
|
||||
SMTO_BLOCK | SMTO_ABORTIFHUNG, 8000, &result);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MessageManager::doPlatformSpecificInitialisation()
|
||||
{
|
||||
OleInitialize (0);
|
||||
|
||||
using namespace WindowsMessageHelpers;
|
||||
messageWindow.reset (new HiddenMessageWindow (messageWindowName, (WNDPROC) messageWndProc));
|
||||
juce_messageWindowHandle = messageWindow->getHWND();
|
||||
}
|
||||
|
||||
void MessageManager::doPlatformSpecificShutdown()
|
||||
{
|
||||
WindowsMessageHelpers::messageWindow = nullptr;
|
||||
|
||||
OleUninitialize();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct MountedVolumeListChangeDetector::Pimpl : private DeviceChangeDetector
|
||||
{
|
||||
Pimpl (MountedVolumeListChangeDetector& d) : DeviceChangeDetector (L"MountedVolumeList"), owner (d)
|
||||
{
|
||||
File::findFileSystemRoots (lastVolumeList);
|
||||
}
|
||||
|
||||
void systemDeviceChanged() override
|
||||
{
|
||||
Array<File> newList;
|
||||
File::findFileSystemRoots (newList);
|
||||
|
||||
if (lastVolumeList != newList)
|
||||
{
|
||||
lastVolumeList = newList;
|
||||
owner.mountedVolumeListChanged();
|
||||
}
|
||||
}
|
||||
|
||||
MountedVolumeListChangeDetector& owner;
|
||||
Array<File> lastVolumeList;
|
||||
};
|
||||
|
||||
MountedVolumeListChangeDetector::MountedVolumeListChangeDetector() { pimpl.reset (new Pimpl (*this)); }
|
||||
MountedVolumeListChangeDetector::~MountedVolumeListChangeDetector() {}
|
||||
|
||||
} // namespace juce
|
26
modules/juce_events/native/juce_win32_WinRTWrapper.cpp
Normal file
26
modules/juce_events/native/juce_win32_WinRTWrapper.cpp
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
JUCE_IMPLEMENT_SINGLETON (WinRTWrapper)
|
||||
}
|
133
modules/juce_events/native/juce_win32_WinRTWrapper.h
Normal file
133
modules/juce_events/native/juce_win32_WinRTWrapper.h
Normal file
@ -0,0 +1,133 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class WinRTWrapper : public DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
JUCE_DECLARE_SINGLETON (WinRTWrapper, true)
|
||||
|
||||
class ScopedHString
|
||||
{
|
||||
public:
|
||||
ScopedHString (String str)
|
||||
{
|
||||
if (WinRTWrapper::getInstance()->isInitialised())
|
||||
WinRTWrapper::getInstance()->createHString (str.toWideCharPointer(),
|
||||
static_cast<uint32_t> (str.length()),
|
||||
&hstr);
|
||||
}
|
||||
|
||||
~ScopedHString()
|
||||
{
|
||||
if (WinRTWrapper::getInstance()->isInitialised() && hstr != nullptr)
|
||||
WinRTWrapper::getInstance()->deleteHString (hstr);
|
||||
}
|
||||
|
||||
HSTRING get() const noexcept
|
||||
{
|
||||
return hstr;
|
||||
}
|
||||
|
||||
private:
|
||||
HSTRING hstr = nullptr;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScopedHString)
|
||||
};
|
||||
|
||||
~WinRTWrapper()
|
||||
{
|
||||
if (winRTHandle != nullptr)
|
||||
::FreeLibrary (winRTHandle);
|
||||
|
||||
clearSingletonInstance();
|
||||
}
|
||||
|
||||
String hStringToString (HSTRING hstr)
|
||||
{
|
||||
if (isInitialised())
|
||||
if (const wchar_t* str = getHStringRawBuffer (hstr, nullptr))
|
||||
return String (str);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool isInitialised() const noexcept
|
||||
{
|
||||
return initialised;
|
||||
}
|
||||
|
||||
template <class ComClass>
|
||||
ComSmartPtr<ComClass> getWRLFactory (const wchar_t* runtimeClassID)
|
||||
{
|
||||
ComSmartPtr<ComClass> comPtr;
|
||||
|
||||
if (isInitialised())
|
||||
{
|
||||
ScopedHString classID (runtimeClassID);
|
||||
if (classID.get() != nullptr)
|
||||
roGetActivationFactory (classID.get(), __uuidof (ComClass), (void**) comPtr.resetAndGetPointerAddress());
|
||||
}
|
||||
|
||||
return comPtr;
|
||||
}
|
||||
|
||||
private:
|
||||
WinRTWrapper()
|
||||
{
|
||||
winRTHandle = ::LoadLibraryA ("api-ms-win-core-winrt-l1-1-0");
|
||||
if (winRTHandle == nullptr)
|
||||
return;
|
||||
|
||||
roInitialize = (RoInitializeFuncPtr) ::GetProcAddress (winRTHandle, "RoInitialize");
|
||||
createHString = (WindowsCreateStringFuncPtr) ::GetProcAddress (winRTHandle, "WindowsCreateString");
|
||||
deleteHString = (WindowsDeleteStringFuncPtr) ::GetProcAddress (winRTHandle, "WindowsDeleteString");
|
||||
getHStringRawBuffer = (WindowsGetStringRawBufferFuncPtr) ::GetProcAddress (winRTHandle, "WindowsGetStringRawBuffer");
|
||||
roGetActivationFactory = (RoGetActivationFactoryFuncPtr) ::GetProcAddress (winRTHandle, "RoGetActivationFactory");
|
||||
|
||||
if (roInitialize == nullptr || createHString == nullptr || deleteHString == nullptr
|
||||
|| getHStringRawBuffer == nullptr || roGetActivationFactory == nullptr)
|
||||
return;
|
||||
|
||||
HRESULT status = roInitialize (1);
|
||||
initialised = ! (status != S_OK && status != S_FALSE && status != 0x80010106L);
|
||||
}
|
||||
|
||||
HMODULE winRTHandle = nullptr;
|
||||
bool initialised = false;
|
||||
|
||||
typedef HRESULT (WINAPI* RoInitializeFuncPtr) (int);
|
||||
typedef HRESULT (WINAPI* WindowsCreateStringFuncPtr) (LPCWSTR, UINT32, HSTRING*);
|
||||
typedef HRESULT (WINAPI* WindowsDeleteStringFuncPtr) (HSTRING);
|
||||
typedef PCWSTR (WINAPI* WindowsGetStringRawBufferFuncPtr) (HSTRING, UINT32*);
|
||||
typedef HRESULT (WINAPI* RoGetActivationFactoryFuncPtr) (HSTRING, REFIID, void**);
|
||||
|
||||
RoInitializeFuncPtr roInitialize = nullptr;
|
||||
WindowsCreateStringFuncPtr createHString = nullptr;
|
||||
WindowsDeleteStringFuncPtr deleteHString = nullptr;
|
||||
WindowsGetStringRawBufferFuncPtr getHStringRawBuffer = nullptr;
|
||||
RoGetActivationFactoryFuncPtr roGetActivationFactory = nullptr;
|
||||
};
|
||||
|
||||
} // namespace juce
|
108
modules/juce_events/timers/juce_MultiTimer.cpp
Normal file
108
modules/juce_events/timers/juce_MultiTimer.cpp
Normal file
@ -0,0 +1,108 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
struct MultiTimerCallback : public Timer
|
||||
{
|
||||
MultiTimerCallback (const int tid, MultiTimer& mt) noexcept
|
||||
: owner (mt), timerID (tid)
|
||||
{
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
owner.timerCallback (timerID);
|
||||
}
|
||||
|
||||
MultiTimer& owner;
|
||||
const int timerID;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultiTimerCallback)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
MultiTimer::MultiTimer() noexcept {}
|
||||
MultiTimer::MultiTimer (const MultiTimer&) noexcept {}
|
||||
|
||||
MultiTimer::~MultiTimer()
|
||||
{
|
||||
const SpinLock::ScopedLockType sl (timerListLock);
|
||||
timers.clear();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Timer* MultiTimer::getCallback (int timerID) const noexcept
|
||||
{
|
||||
for (int i = timers.size(); --i >= 0;)
|
||||
{
|
||||
MultiTimerCallback* const t = static_cast<MultiTimerCallback*> (timers.getUnchecked(i));
|
||||
|
||||
if (t->timerID == timerID)
|
||||
return t;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void MultiTimer::startTimer (const int timerID, const int intervalInMilliseconds) noexcept
|
||||
{
|
||||
const SpinLock::ScopedLockType sl (timerListLock);
|
||||
|
||||
Timer* timer = getCallback (timerID);
|
||||
|
||||
if (timer == nullptr)
|
||||
timers.add (timer = new MultiTimerCallback (timerID, *this));
|
||||
|
||||
timer->startTimer (intervalInMilliseconds);
|
||||
}
|
||||
|
||||
void MultiTimer::stopTimer (const int timerID) noexcept
|
||||
{
|
||||
const SpinLock::ScopedLockType sl (timerListLock);
|
||||
|
||||
if (Timer* const t = getCallback (timerID))
|
||||
t->stopTimer();
|
||||
}
|
||||
|
||||
bool MultiTimer::isTimerRunning (const int timerID) const noexcept
|
||||
{
|
||||
const SpinLock::ScopedLockType sl (timerListLock);
|
||||
|
||||
if (Timer* const t = getCallback (timerID))
|
||||
return t->isTimerRunning();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int MultiTimer::getTimerInterval (const int timerID) const noexcept
|
||||
{
|
||||
const SpinLock::ScopedLockType sl (timerListLock);
|
||||
|
||||
if (Timer* const t = getCallback (timerID))
|
||||
return t->getTimerInterval();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace juce
|
125
modules/juce_events/timers/juce_MultiTimer.h
Normal file
125
modules/juce_events/timers/juce_MultiTimer.h
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A type of timer class that can run multiple timers with different frequencies,
|
||||
all of which share a single callback.
|
||||
|
||||
This class is very similar to the Timer class, but allows you run multiple
|
||||
separate timers, where each one has a unique ID number. The methods in this
|
||||
class are exactly equivalent to those in Timer, but with the addition of
|
||||
this ID number.
|
||||
|
||||
To use it, you need to create a subclass of MultiTimer, implementing the
|
||||
timerCallback() method. Then you can start timers with startTimer(), and
|
||||
each time the callback is triggered, it passes in the ID of the timer that
|
||||
caused it.
|
||||
|
||||
@see Timer
|
||||
|
||||
@tags{Events}
|
||||
*/
|
||||
class JUCE_API MultiTimer
|
||||
{
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** Creates a MultiTimer.
|
||||
|
||||
When created, no timers are running, so use startTimer() to start things off.
|
||||
*/
|
||||
MultiTimer() noexcept;
|
||||
|
||||
/** Creates a copy of another timer.
|
||||
|
||||
Note that this timer will not contain any running timers, even if the one you're
|
||||
copying from was running.
|
||||
*/
|
||||
MultiTimer (const MultiTimer&) noexcept;
|
||||
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Destructor. */
|
||||
virtual ~MultiTimer();
|
||||
|
||||
//==============================================================================
|
||||
/** The user-defined callback routine that actually gets called by each of the
|
||||
timers that are running.
|
||||
|
||||
It's perfectly ok to call startTimer() or stopTimer() from within this
|
||||
callback to change the subsequent intervals.
|
||||
*/
|
||||
virtual void timerCallback (int timerID) = 0;
|
||||
|
||||
//==============================================================================
|
||||
/** Starts a timer and sets the length of interval required.
|
||||
|
||||
If the timer is already started, this will reset it, so the
|
||||
time between calling this method and the next timer callback
|
||||
will not be less than the interval length passed in.
|
||||
|
||||
@param timerID a unique Id number that identifies the timer to
|
||||
start. This is the id that will be passed back
|
||||
to the timerCallback() method when this timer is
|
||||
triggered
|
||||
@param intervalInMilliseconds the interval to use (any values less than 1 will be
|
||||
rounded up to 1)
|
||||
*/
|
||||
void startTimer (int timerID, int intervalInMilliseconds) noexcept;
|
||||
|
||||
/** Stops a timer.
|
||||
|
||||
If a timer has been started with the given ID number, it will be cancelled.
|
||||
No more callbacks will be made for the specified timer after this method returns.
|
||||
|
||||
If this is called from a different thread, any callbacks that may
|
||||
be currently executing may be allowed to finish before the method
|
||||
returns.
|
||||
*/
|
||||
void stopTimer (int timerID) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Checks whether a timer has been started for a specified ID.
|
||||
@returns true if a timer with the given ID is running.
|
||||
*/
|
||||
bool isTimerRunning (int timerID) const noexcept;
|
||||
|
||||
/** Returns the interval for a specified timer ID.
|
||||
@returns the timer's interval in milliseconds if it's running, or 0 if no
|
||||
timer was running for the ID number specified.
|
||||
*/
|
||||
int getTimerInterval (int timerID) const noexcept;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
private:
|
||||
SpinLock timerListLock;
|
||||
OwnedArray<Timer> timers;
|
||||
|
||||
Timer* getCallback (int) const noexcept;
|
||||
MultiTimer& operator= (const MultiTimer&);
|
||||
};
|
||||
|
||||
} // namespace juce
|
389
modules/juce_events/timers/juce_Timer.cpp
Normal file
389
modules/juce_events/timers/juce_Timer.cpp
Normal file
@ -0,0 +1,389 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class Timer::TimerThread : private Thread,
|
||||
private DeletedAtShutdown,
|
||||
private AsyncUpdater
|
||||
{
|
||||
public:
|
||||
typedef CriticalSection LockType; // (mysteriously, using a SpinLock here causes problems on some XP machines..)
|
||||
|
||||
TimerThread() : Thread ("JUCE Timer")
|
||||
{
|
||||
timers.reserve (32);
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
~TimerThread() noexcept
|
||||
{
|
||||
signalThreadShouldExit();
|
||||
callbackArrived.signal();
|
||||
stopThread (4000);
|
||||
jassert (instance == this || instance == nullptr);
|
||||
|
||||
if (instance == this)
|
||||
instance = nullptr;
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
auto lastTime = Time::getMillisecondCounter();
|
||||
ReferenceCountedObjectPtr<CallTimersMessage> messageToSend (new CallTimersMessage());
|
||||
|
||||
while (! threadShouldExit())
|
||||
{
|
||||
auto now = Time::getMillisecondCounter();
|
||||
auto elapsed = (int) (now >= lastTime ? (now - lastTime)
|
||||
: (std::numeric_limits<uint32>::max() - (lastTime - now)));
|
||||
lastTime = now;
|
||||
|
||||
auto timeUntilFirstTimer = getTimeUntilFirstTimer (elapsed);
|
||||
|
||||
if (timeUntilFirstTimer <= 0)
|
||||
{
|
||||
if (callbackArrived.wait (0))
|
||||
{
|
||||
// already a message in flight - do nothing..
|
||||
}
|
||||
else
|
||||
{
|
||||
messageToSend->post();
|
||||
|
||||
if (! callbackArrived.wait (300))
|
||||
{
|
||||
// Sometimes our message can get discarded by the OS (e.g. when running as an RTAS
|
||||
// when the app has a modal loop), so this is how long to wait before assuming the
|
||||
// message has been lost and trying again.
|
||||
messageToSend->post();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// don't wait for too long because running this loop also helps keep the
|
||||
// Time::getApproximateMillisecondTimer value stay up-to-date
|
||||
wait (jlimit (1, 100, timeUntilFirstTimer));
|
||||
}
|
||||
}
|
||||
|
||||
void callTimers()
|
||||
{
|
||||
auto timeout = Time::getMillisecondCounter() + 100;
|
||||
|
||||
const LockType::ScopedLockType sl (lock);
|
||||
|
||||
while (! timers.empty())
|
||||
{
|
||||
auto& first = timers.front();
|
||||
|
||||
if (first.countdownMs > 0)
|
||||
break;
|
||||
|
||||
auto* timer = first.timer;
|
||||
first.countdownMs = timer->timerPeriodMs;
|
||||
shuffleTimerBackInQueue (0);
|
||||
notify();
|
||||
|
||||
const LockType::ScopedUnlockType ul (lock);
|
||||
|
||||
JUCE_TRY
|
||||
{
|
||||
timer->timerCallback();
|
||||
}
|
||||
JUCE_CATCH_EXCEPTION
|
||||
|
||||
// avoid getting stuck in a loop if a timer callback repeatedly takes too long
|
||||
if (Time::getMillisecondCounter() > timeout)
|
||||
break;
|
||||
}
|
||||
|
||||
callbackArrived.signal();
|
||||
}
|
||||
|
||||
void callTimersSynchronously()
|
||||
{
|
||||
if (! isThreadRunning())
|
||||
{
|
||||
// (This is relied on by some plugins in cases where the MM has
|
||||
// had to restart and the async callback never started)
|
||||
cancelPendingUpdate();
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
callTimers();
|
||||
}
|
||||
|
||||
static inline void add (Timer* tim) noexcept
|
||||
{
|
||||
if (instance == nullptr)
|
||||
instance = new TimerThread();
|
||||
|
||||
instance->addTimer (tim);
|
||||
}
|
||||
|
||||
static inline void remove (Timer* tim) noexcept
|
||||
{
|
||||
if (instance != nullptr)
|
||||
instance->removeTimer (tim);
|
||||
}
|
||||
|
||||
static inline void resetCounter (Timer* tim) noexcept
|
||||
{
|
||||
if (instance != nullptr)
|
||||
instance->resetTimerCounter (tim);
|
||||
}
|
||||
|
||||
static TimerThread* instance;
|
||||
static LockType lock;
|
||||
|
||||
private:
|
||||
struct TimerCountdown
|
||||
{
|
||||
Timer* timer;
|
||||
int countdownMs;
|
||||
};
|
||||
|
||||
std::vector<TimerCountdown> timers;
|
||||
|
||||
WaitableEvent callbackArrived;
|
||||
|
||||
struct CallTimersMessage : public MessageManager::MessageBase
|
||||
{
|
||||
CallTimersMessage() {}
|
||||
|
||||
void messageCallback() override
|
||||
{
|
||||
if (instance != nullptr)
|
||||
instance->callTimers();
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
void addTimer (Timer* t)
|
||||
{
|
||||
// Trying to add a timer that's already here - shouldn't get to this point,
|
||||
// so if you get this assertion, let me know!
|
||||
jassert (std::find_if (timers.begin(), timers.end(),
|
||||
[t](TimerCountdown i) { return i.timer == t; }) == timers.end());
|
||||
|
||||
auto pos = timers.size();
|
||||
|
||||
timers.push_back ({ t, t->timerPeriodMs });
|
||||
t->positionInQueue = pos;
|
||||
shuffleTimerForwardInQueue (pos);
|
||||
notify();
|
||||
}
|
||||
|
||||
void removeTimer (Timer* t)
|
||||
{
|
||||
auto pos = t->positionInQueue;
|
||||
auto lastIndex = timers.size() - 1;
|
||||
|
||||
jassert (pos <= lastIndex);
|
||||
jassert (timers[pos].timer == t);
|
||||
|
||||
for (auto i = pos; i < lastIndex; ++i)
|
||||
{
|
||||
timers[i] = timers[i + 1];
|
||||
timers[i].timer->positionInQueue = i;
|
||||
}
|
||||
|
||||
timers.pop_back();
|
||||
}
|
||||
|
||||
void resetTimerCounter (Timer* t) noexcept
|
||||
{
|
||||
auto pos = t->positionInQueue;
|
||||
|
||||
jassert (pos < timers.size());
|
||||
jassert (timers[pos].timer == t);
|
||||
|
||||
auto lastCountdown = timers[pos].countdownMs;
|
||||
auto newCountdown = t->timerPeriodMs;
|
||||
|
||||
if (newCountdown != lastCountdown)
|
||||
{
|
||||
timers[pos].countdownMs = newCountdown;
|
||||
|
||||
if (newCountdown > lastCountdown)
|
||||
shuffleTimerBackInQueue (pos);
|
||||
else
|
||||
shuffleTimerForwardInQueue (pos);
|
||||
|
||||
notify();
|
||||
}
|
||||
}
|
||||
|
||||
void shuffleTimerBackInQueue (size_t pos)
|
||||
{
|
||||
auto numTimers = timers.size();
|
||||
|
||||
if (pos < numTimers - 1)
|
||||
{
|
||||
auto t = timers[pos];
|
||||
|
||||
for (;;)
|
||||
{
|
||||
auto next = pos + 1;
|
||||
|
||||
if (next == numTimers || timers[next].countdownMs >= t.countdownMs)
|
||||
break;
|
||||
|
||||
timers[pos] = timers[next];
|
||||
timers[pos].timer->positionInQueue = pos;
|
||||
|
||||
++pos;
|
||||
}
|
||||
|
||||
timers[pos] = t;
|
||||
t.timer->positionInQueue = pos;
|
||||
}
|
||||
}
|
||||
|
||||
void shuffleTimerForwardInQueue (size_t pos)
|
||||
{
|
||||
if (pos > 0)
|
||||
{
|
||||
auto t = timers[pos];
|
||||
|
||||
while (pos > 0)
|
||||
{
|
||||
auto& prev = timers[(size_t) pos - 1];
|
||||
|
||||
if (prev.countdownMs <= t.countdownMs)
|
||||
break;
|
||||
|
||||
timers[pos] = prev;
|
||||
timers[pos].timer->positionInQueue = pos;
|
||||
|
||||
--pos;
|
||||
}
|
||||
|
||||
timers[pos] = t;
|
||||
t.timer->positionInQueue = pos;
|
||||
}
|
||||
}
|
||||
|
||||
int getTimeUntilFirstTimer (int numMillisecsElapsed)
|
||||
{
|
||||
const LockType::ScopedLockType sl (lock);
|
||||
|
||||
if (timers.empty())
|
||||
return 1000;
|
||||
|
||||
for (auto& t : timers)
|
||||
t.countdownMs -= numMillisecsElapsed;
|
||||
|
||||
return timers.front().countdownMs;
|
||||
}
|
||||
|
||||
void handleAsyncUpdate() override
|
||||
{
|
||||
startThread (7);
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TimerThread)
|
||||
};
|
||||
|
||||
Timer::TimerThread* Timer::TimerThread::instance = nullptr;
|
||||
Timer::TimerThread::LockType Timer::TimerThread::lock;
|
||||
|
||||
//==============================================================================
|
||||
Timer::Timer() noexcept {}
|
||||
Timer::Timer (const Timer&) noexcept {}
|
||||
|
||||
Timer::~Timer()
|
||||
{
|
||||
stopTimer();
|
||||
}
|
||||
|
||||
void Timer::startTimer (int interval) noexcept
|
||||
{
|
||||
// If you're calling this before (or after) the MessageManager is
|
||||
// running, then you're not going to get any timer callbacks!
|
||||
jassert (MessageManager::getInstanceWithoutCreating() != nullptr);
|
||||
|
||||
const TimerThread::LockType::ScopedLockType sl (TimerThread::lock);
|
||||
|
||||
bool wasStopped = (timerPeriodMs == 0);
|
||||
timerPeriodMs = jmax (1, interval);
|
||||
|
||||
if (wasStopped)
|
||||
TimerThread::add (this);
|
||||
else
|
||||
TimerThread::resetCounter (this);
|
||||
}
|
||||
|
||||
void Timer::startTimerHz (int timerFrequencyHz) noexcept
|
||||
{
|
||||
if (timerFrequencyHz > 0)
|
||||
startTimer (1000 / timerFrequencyHz);
|
||||
else
|
||||
stopTimer();
|
||||
}
|
||||
|
||||
void Timer::stopTimer() noexcept
|
||||
{
|
||||
const TimerThread::LockType::ScopedLockType sl (TimerThread::lock);
|
||||
|
||||
if (timerPeriodMs > 0)
|
||||
{
|
||||
TimerThread::remove (this);
|
||||
timerPeriodMs = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void JUCE_CALLTYPE Timer::callPendingTimersSynchronously()
|
||||
{
|
||||
if (TimerThread::instance != nullptr)
|
||||
TimerThread::instance->callTimersSynchronously();
|
||||
}
|
||||
|
||||
struct LambdaInvoker : private Timer
|
||||
{
|
||||
LambdaInvoker (int milliseconds, std::function<void()> f) : function (f)
|
||||
{
|
||||
startTimer (milliseconds);
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
auto f = function;
|
||||
delete this;
|
||||
f();
|
||||
}
|
||||
|
||||
std::function<void()> function;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (LambdaInvoker)
|
||||
};
|
||||
|
||||
void JUCE_CALLTYPE Timer::callAfterDelay (int milliseconds, std::function<void()> f)
|
||||
{
|
||||
new LambdaInvoker (milliseconds, f);
|
||||
}
|
||||
|
||||
} // namespace juce
|
137
modules/juce_events/timers/juce_Timer.h
Normal file
137
modules/juce_events/timers/juce_Timer.h
Normal file
@ -0,0 +1,137 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided 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.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Makes repeated callbacks to a virtual method at a specified time interval.
|
||||
|
||||
A Timer's timerCallback() method will be repeatedly called at a given
|
||||
interval. When you create a Timer object, it will do nothing until the
|
||||
startTimer() method is called, which will cause the message thread to
|
||||
start making callbacks at the specified interval, until stopTimer() is called
|
||||
or the object is deleted.
|
||||
|
||||
The time interval isn't guaranteed to be precise to any more than maybe
|
||||
10-20ms, and the intervals may end up being much longer than requested if the
|
||||
system is busy. Because the callbacks are made by the main message thread,
|
||||
anything that blocks the message queue for a period of time will also prevent
|
||||
any timers from running until it can carry on.
|
||||
|
||||
If you need to have a single callback that is shared by multiple timers with
|
||||
different frequencies, then the MultiTimer class allows you to do that - its
|
||||
structure is very similar to the Timer class, but contains multiple timers
|
||||
internally, each one identified by an ID number.
|
||||
|
||||
@see HighResolutionTimer, MultiTimer
|
||||
|
||||
@tags{Events}
|
||||
*/
|
||||
class JUCE_API Timer
|
||||
{
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** Creates a Timer.
|
||||
When created, the timer is stopped, so use startTimer() to get it going.
|
||||
*/
|
||||
Timer() noexcept;
|
||||
|
||||
/** Creates a copy of another timer.
|
||||
|
||||
Note that this timer won't be started, even if the one you're copying
|
||||
is running.
|
||||
*/
|
||||
Timer (const Timer&) noexcept;
|
||||
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Destructor. */
|
||||
virtual ~Timer();
|
||||
|
||||
//==============================================================================
|
||||
/** The user-defined callback routine that actually gets called periodically.
|
||||
|
||||
It's perfectly ok to call startTimer() or stopTimer() from within this
|
||||
callback to change the subsequent intervals.
|
||||
*/
|
||||
virtual void timerCallback() = 0;
|
||||
|
||||
//==============================================================================
|
||||
/** Starts the timer and sets the length of interval required.
|
||||
|
||||
If the timer is already started, this will reset it, so the
|
||||
time between calling this method and the next timer callback
|
||||
will not be less than the interval length passed in.
|
||||
|
||||
@param intervalInMilliseconds the interval to use (any value less
|
||||
than 1 will be rounded up to 1)
|
||||
*/
|
||||
void startTimer (int intervalInMilliseconds) noexcept;
|
||||
|
||||
/** Starts the timer with an interval specified in Hertz.
|
||||
This is effectively the same as calling startTimer (1000 / timerFrequencyHz).
|
||||
*/
|
||||
void startTimerHz (int timerFrequencyHz) noexcept;
|
||||
|
||||
/** Stops the timer.
|
||||
|
||||
No more timer callbacks will be triggered after this method returns.
|
||||
|
||||
Note that if you call this from a background thread while the message-thread
|
||||
is already in the middle of your callback, then this method will cancel any
|
||||
future timer callbacks, but it will return without waiting for the current one
|
||||
to finish. The current callback will continue, possibly still running some of
|
||||
your timer code after this method has returned.
|
||||
*/
|
||||
void stopTimer() noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if the timer is currently running. */
|
||||
bool isTimerRunning() const noexcept { return timerPeriodMs > 0; }
|
||||
|
||||
/** Returns the timer's interval.
|
||||
@returns the timer's interval in milliseconds if it's running, or 0 if it's not.
|
||||
*/
|
||||
int getTimerInterval() const noexcept { return timerPeriodMs; }
|
||||
|
||||
//==============================================================================
|
||||
/** Invokes a lambda after a given number of milliseconds. */
|
||||
static void JUCE_CALLTYPE callAfterDelay (int milliseconds, std::function<void()> functionToCall);
|
||||
|
||||
//==============================================================================
|
||||
/** For internal use only: invokes any timers that need callbacks.
|
||||
Don't call this unless you really know what you're doing!
|
||||
*/
|
||||
static void JUCE_CALLTYPE callPendingTimersSynchronously();
|
||||
|
||||
private:
|
||||
class TimerThread;
|
||||
friend class TimerThread;
|
||||
size_t positionInQueue = (size_t) -1;
|
||||
int timerPeriodMs = 0;
|
||||
|
||||
Timer& operator= (const Timer&) = delete;
|
||||
};
|
||||
|
||||
} // namespace juce
|
Reference in New Issue
Block a user